From 77e38c4d721a0034b7527df36f389df3aee733ff Mon Sep 17 00:00:00 2001 From: qdee Date: Thu, 9 Apr 2026 20:37:59 +0100 Subject: [PATCH 1/3] feat(bitcoin): add PSBT signing via existing sign_transaction interface Detects PSBT format by magic bytes inside BitcoinSigner::sign_transaction, keeping the ChainSigner trait chain-agnostic with no new methods. Supports P2WPKH inputs, skips inputs not owned by the signing key. Signed PSBT bytes are returned in SignOutput::signature. --- ows/crates/ows-signer/Cargo.toml | 1 + ows/crates/ows-signer/src/chains/bitcoin.rs | 178 +++++++++++++++++++- 2 files changed, 177 insertions(+), 2 deletions(-) diff --git a/ows/crates/ows-signer/Cargo.toml b/ows/crates/ows-signer/Cargo.toml index ae07ac12..2d3ea3fb 100644 --- a/ows/crates/ows-signer/Cargo.toml +++ b/ows/crates/ows-signer/Cargo.toml @@ -13,6 +13,7 @@ fast-kdf = [] [dependencies] ows-core = { path = "../ows-core", version = "=1.3.0" } +bitcoin = { version = "0.32", features = ["base64"] } xrpl-rust = { version = "1.1.0", default-features = false, features = ["core"] } k256 = { version = "0.13", features = ["ecdsa", "arithmetic"] } ed25519-dalek = { version = "2", features = ["hazmat"] } diff --git a/ows/crates/ows-signer/src/chains/bitcoin.rs b/ows/crates/ows-signer/src/chains/bitcoin.rs index 6e38693d..85a96bcf 100644 --- a/ows/crates/ows-signer/src/chains/bitcoin.rs +++ b/ows/crates/ows-signer/src/chains/bitcoin.rs @@ -1,9 +1,17 @@ use crate::curve::Curve; use crate::traits::{ChainSigner, SignOutput, SignerError}; +use bitcoin::psbt::Psbt; +use bitcoin::sighash::{EcdsaSighashType, SighashCache}; +use bitcoin::{Network, PrivateKey, PublicKey, ScriptBuf}; use k256::ecdsa::SigningKey; use ows_core::ChainType; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; +use bitcoin::base64::Engine; +use std::str::FromStr; + +/// PSBT magic bytes: "psbt\xff" +const PSBT_MAGIC: &[u8] = &[0x70, 0x73, 0x62, 0x74, 0xff]; /// Bitcoin chain signer (BIP-84 native segwit / bech32). pub struct BitcoinSigner { @@ -28,7 +36,7 @@ impl BitcoinSigner { fn signing_key(private_key: &[u8]) -> Result { SigningKey::from_slice(private_key) - .map_err(|_| SignerError::InvalidPrivateKey("key parsing failed".into())) + .map_err(|e| SignerError::InvalidPrivateKey(e.to_string())) } /// Hash160: SHA256 then RIPEMD160 of the compressed public key. @@ -37,6 +45,135 @@ impl BitcoinSigner { let ripemd = Ripemd160::digest(sha256); ripemd.to_vec() } + + fn bitcoin_private_key(private_key: &[u8]) -> Result { + let signing_key = Self::signing_key(private_key)?; + let secret_key = bitcoin::secp256k1::SecretKey::from_slice(&signing_key.to_bytes()) + .map_err(|e| SignerError::InvalidPrivateKey(e.to_string()))?; + Ok(PrivateKey::new(secret_key, Network::Bitcoin)) + } + + fn public_keys(private_key: &[u8]) -> Result<(PrivateKey, PublicKey), SignerError> { + let private_key = Self::bitcoin_private_key(private_key)?; + let secp = bitcoin::secp256k1::Secp256k1::new(); + let public_key = private_key.public_key(&secp); + Ok((private_key, public_key)) + } + + fn p2wpkh_script_pubkey(public_key: &PublicKey) -> Result { + let wpkh = public_key.wpubkey_hash().map_err(|_| { + SignerError::AddressDerivationFailed("bitcoin public key must be compressed".into()) + })?; + Ok(ScriptBuf::new_p2wpkh(&wpkh)) + } + + fn previous_output(psbt: &Psbt, index: usize) -> Result { + let input = psbt.inputs.get(index).ok_or_else(|| { + SignerError::InvalidTransaction(format!("missing PSBT input {index}")) + })?; + + if let Some(witness_utxo) = &input.witness_utxo { + return Ok(witness_utxo.clone()); + } + + if let Some(non_witness_utxo) = &input.non_witness_utxo { + let prevout = psbt + .unsigned_tx + .input + .get(index) + .ok_or_else(|| { + SignerError::InvalidTransaction(format!( + "missing unsigned transaction input {index}" + )) + })? + .previous_output; + + if non_witness_utxo.compute_txid() != prevout.txid { + return Err(SignerError::InvalidTransaction(format!( + "non_witness_utxo txid mismatch for input {index}" + ))); + } + + return non_witness_utxo + .output + .get(prevout.vout as usize) + .cloned() + .ok_or_else(|| { + SignerError::InvalidTransaction(format!( + "missing prevout {} for input {index}", + prevout.vout + )) + }); + } + + Err(SignerError::InvalidTransaction(format!( + "PSBT input {index} is missing witness_utxo/non_witness_utxo" + ))) + } + + /// Sign a PSBT, adding partial signatures for inputs owned by this key. + /// Returns the serialized signed PSBT. + fn sign_psbt(private_key: &[u8], psbt_bytes: &[u8]) -> Result, SignerError> { + let psbt_base64 = + bitcoin::base64::engine::general_purpose::STANDARD.encode(psbt_bytes); + let mut psbt = Psbt::from_str(&psbt_base64) + .map_err(|e| SignerError::InvalidTransaction(format!("invalid PSBT: {e}")))?; + + let (priv_key, pub_key) = Self::public_keys(private_key)?; + let expected_script = Self::p2wpkh_script_pubkey(&pub_key)?; + let secp = bitcoin::secp256k1::Secp256k1::new(); + + for index in 0..psbt.inputs.len() { + let prevout = Self::previous_output(&psbt, index)?; + if prevout.script_pubkey != expected_script { + continue; + } + + let sighash_type = psbt.inputs[index] + .sighash_type + .map(|ty| { + ty.ecdsa_hash_ty().map_err(|e| { + SignerError::InvalidTransaction(format!( + "unsupported sighash type for input {index}: {e}" + )) + }) + }) + .transpose()? + .unwrap_or(EcdsaSighashType::All); + + let sighash = SighashCache::new(&psbt.unsigned_tx) + .p2wpkh_signature_hash( + index, + &prevout.script_pubkey, + prevout.value, + sighash_type, + ) + .map_err(|e| { + SignerError::SigningFailed(format!( + "failed to compute sighash for input {index}: {e}" + )) + })?; + + let msg = bitcoin::secp256k1::Message::from_digest_slice(sighash.as_ref()) + .map_err(|e| { + SignerError::SigningFailed(format!( + "invalid sighash digest for input {index}: {e}" + )) + })?; + + let signature = secp.sign_ecdsa(&msg, &priv_key.inner); + + psbt.inputs[index].partial_sigs.insert( + pub_key, + bitcoin::ecdsa::Signature { + signature, + sighash_type, + }, + ); + } + + Ok(psbt.serialize()) + } } /// Encode an integer as a Bitcoin CompactSize (varint). @@ -117,7 +254,17 @@ impl ChainSigner for BitcoinSigner { private_key: &[u8], tx_bytes: &[u8], ) -> Result { - // Bitcoin transaction signing: double SHA256 of the sighash preimage + // Detect PSBT by magic bytes and handle natively + if tx_bytes.starts_with(PSBT_MAGIC) { + let signed_psbt = Self::sign_psbt(private_key, tx_bytes)?; + return Ok(SignOutput { + signature: signed_psbt, + recovery_id: None, + public_key: None, + }); + } + + // Standard Bitcoin transaction signing: double SHA256 of the sighash preimage let hash = Sha256::digest(Sha256::digest(tx_bytes)); self.sign(private_key, &hash) } @@ -255,6 +402,33 @@ mod tests { .expect("signature should verify at varint boundary (253 bytes)"); } + #[test] + fn test_sign_transaction_rejects_invalid_psbt() { + let signer = BitcoinSigner::mainnet(); + let mut privkey = vec![0u8; 31]; + privkey.push(1u8); + + // Valid PSBT magic but truncated body + let mut bad_psbt = PSBT_MAGIC.to_vec(); + bad_psbt.extend_from_slice(b"truncated"); + + let result = signer.sign_transaction(&privkey, &bad_psbt); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("invalid PSBT")); + } + + #[test] + fn test_sign_transaction_non_psbt_still_works() { + let signer = BitcoinSigner::mainnet(); + let mut privkey = vec![0u8; 31]; + privkey.push(1u8); + + // Non-PSBT bytes should go through the normal double-SHA256 path + let tx_bytes = b"some raw tx bytes"; + let result = signer.sign_transaction(&privkey, tx_bytes); + assert!(result.is_ok()); + } + #[test] fn test_sign_message_short_message_still_works() { use k256::ecdsa::signature::hazmat::PrehashVerifier; From b81eeb9556cb385806dc93b95b87ca0a69d8a774 Mon Sep 17 00:00:00 2001 From: qdee Date: Fri, 17 Apr 2026 11:08:26 +0100 Subject: [PATCH 2/3] Update Cargo lockfiles for bitcoin dependency --- bindings/node/Cargo.lock | 87 +++++++++++++++++++++++++++++++++++++- bindings/python/Cargo.lock | 87 +++++++++++++++++++++++++++++++++++++- ows/Cargo.lock | 87 +++++++++++++++++++++++++++++++++++++- 3 files changed, 258 insertions(+), 3 deletions(-) diff --git a/bindings/node/Cargo.lock b/bindings/node/Cargo.lock index b39d983f..0815278d 100644 --- a/bindings/node/Cargo.lock +++ b/bindings/node/Cargo.lock @@ -171,6 +171,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.21.7" @@ -216,6 +226,55 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "base64 0.21.7", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1 0.29.1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.11.0" @@ -926,6 +985,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hkdf" version = "0.12.4" @@ -1495,6 +1569,7 @@ dependencies = [ "aes-gcm", "base64 0.22.1", "bech32 0.11.1", + "bitcoin", "blake2", "bs58", "coins-bip32", @@ -1940,6 +2015,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", +] + [[package]] name = "secp256k1" version = "0.30.0" @@ -2964,7 +3049,7 @@ dependencies = [ "regex", "ripemd", "rust_decimal", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_repr", diff --git a/bindings/python/Cargo.lock b/bindings/python/Cargo.lock index 266f17f5..9cd1a1a5 100644 --- a/bindings/python/Cargo.lock +++ b/bindings/python/Cargo.lock @@ -162,6 +162,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.21.7" @@ -207,6 +217,55 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "base64 0.21.7", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1 0.29.1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.11.0" @@ -898,6 +957,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hkdf" version = "0.12.4" @@ -1414,6 +1488,7 @@ dependencies = [ "aes-gcm", "base64 0.22.1", "bech32 0.11.1", + "bitcoin", "blake2", "bs58", "coins-bip32", @@ -1924,6 +1999,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", +] + [[package]] name = "secp256k1" version = "0.30.0" @@ -2954,7 +3039,7 @@ dependencies = [ "regex", "ripemd", "rust_decimal", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_repr", diff --git a/ows/Cargo.lock b/ows/Cargo.lock index 691ff155..561ace42 100644 --- a/ows/Cargo.lock +++ b/ows/Cargo.lock @@ -212,6 +212,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + [[package]] name = "base64" version = "0.21.7" @@ -257,6 +267,55 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "base64 0.21.7", + "bech32 0.11.1", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1 0.29.1", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.11.0" @@ -1010,6 +1069,21 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hkdf" version = "0.12.4" @@ -1602,6 +1676,7 @@ dependencies = [ "aes-gcm", "base64 0.22.1", "bech32 0.11.1", + "bitcoin", "blake2", "bs58", "coins-bip32", @@ -2231,6 +2306,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "secp256k1-sys", +] + [[package]] name = "secp256k1" version = "0.30.0" @@ -3441,7 +3526,7 @@ dependencies = [ "regex", "ripemd", "rust_decimal", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_repr", From a49e5463026bcc2ec688596bbc8b05ef6e5d3b24 Mon Sep 17 00:00:00 2001 From: qdee Date: Fri, 17 Apr 2026 11:11:16 +0100 Subject: [PATCH 3/3] Format bitcoin PSBT signing code --- ows/crates/ows-signer/src/chains/bitcoin.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ows/crates/ows-signer/src/chains/bitcoin.rs b/ows/crates/ows-signer/src/chains/bitcoin.rs index 85a96bcf..df9f3c26 100644 --- a/ows/crates/ows-signer/src/chains/bitcoin.rs +++ b/ows/crates/ows-signer/src/chains/bitcoin.rs @@ -1,5 +1,6 @@ use crate::curve::Curve; use crate::traits::{ChainSigner, SignOutput, SignerError}; +use bitcoin::base64::Engine; use bitcoin::psbt::Psbt; use bitcoin::sighash::{EcdsaSighashType, SighashCache}; use bitcoin::{Network, PrivateKey, PublicKey, ScriptBuf}; @@ -7,7 +8,6 @@ use k256::ecdsa::SigningKey; use ows_core::ChainType; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; -use bitcoin::base64::Engine; use std::str::FromStr; /// PSBT magic bytes: "psbt\xff" @@ -114,8 +114,7 @@ impl BitcoinSigner { /// Sign a PSBT, adding partial signatures for inputs owned by this key. /// Returns the serialized signed PSBT. fn sign_psbt(private_key: &[u8], psbt_bytes: &[u8]) -> Result, SignerError> { - let psbt_base64 = - bitcoin::base64::engine::general_purpose::STANDARD.encode(psbt_bytes); + let psbt_base64 = bitcoin::base64::engine::general_purpose::STANDARD.encode(psbt_bytes); let mut psbt = Psbt::from_str(&psbt_base64) .map_err(|e| SignerError::InvalidTransaction(format!("invalid PSBT: {e}")))?; @@ -142,20 +141,15 @@ impl BitcoinSigner { .unwrap_or(EcdsaSighashType::All); let sighash = SighashCache::new(&psbt.unsigned_tx) - .p2wpkh_signature_hash( - index, - &prevout.script_pubkey, - prevout.value, - sighash_type, - ) + .p2wpkh_signature_hash(index, &prevout.script_pubkey, prevout.value, sighash_type) .map_err(|e| { SignerError::SigningFailed(format!( "failed to compute sighash for input {index}: {e}" )) })?; - let msg = bitcoin::secp256k1::Message::from_digest_slice(sighash.as_ref()) - .map_err(|e| { + let msg = + bitcoin::secp256k1::Message::from_digest_slice(sighash.as_ref()).map_err(|e| { SignerError::SigningFailed(format!( "invalid sighash digest for input {index}: {e}" ))