diff --git a/packages/wasm-utxo/src/bitgo_psbt/mod.rs b/packages/wasm-utxo/src/bitgo_psbt/mod.rs index 07f15bf..850519c 100644 --- a/packages/wasm-utxo/src/bitgo_psbt/mod.rs +++ b/packages/wasm-utxo/src/bitgo_psbt/mod.rs @@ -229,16 +229,56 @@ impl BitGoPsbt { )), } } + + /// Sign the PSBT with the provided key. + /// Wraps the underlying PSBT's sign method from miniscript::psbt::PsbtExt. + /// + /// # Type Parameters + /// - `C`: Signing context from secp256k1 + /// - `K`: Key type that implements `psbt::GetKey` trait + /// + /// # Returns + /// - `Ok(SigningKeysMap)` on success, mapping input index to keys used for signing + /// - `Err((SigningKeysMap, SigningErrors))` on failure, containing both partial success info and errors + pub fn sign( + &mut self, + k: &K, + secp: &secp256k1::Secp256k1, + ) -> Result< + miniscript::bitcoin::psbt::SigningKeysMap, + ( + miniscript::bitcoin::psbt::SigningKeysMap, + miniscript::bitcoin::psbt::SigningErrors, + ), + > + where + C: secp256k1::Signing + secp256k1::Verification, + K: miniscript::bitcoin::psbt::GetKey, + { + match self { + BitGoPsbt::BitcoinLike(ref mut psbt, _network) => psbt.sign(k, secp), + BitGoPsbt::Zcash(_zcash_psbt, _network) => { + // Return an error indicating Zcash signing is not implemented + Err(( + Default::default(), + std::collections::BTreeMap::from_iter([( + 0, + miniscript::bitcoin::psbt::SignError::KeyNotFound, + )]), + )) + } + } + } } #[cfg(test)] mod tests { use super::*; use crate::fixed_script_wallet::Chain; - use crate::fixed_script_wallet::{RootWalletKeys, WalletScripts}; + use crate::fixed_script_wallet::WalletScripts; use crate::test_utils::fixtures; + use crate::test_utils::fixtures::assert_hex_eq; use base64::engine::{general_purpose::STANDARD as BASE64_STANDARD, Engine}; - use miniscript::bitcoin::bip32::Xpub; use miniscript::bitcoin::consensus::Decodable; use miniscript::bitcoin::Transaction; @@ -386,18 +426,95 @@ mod tests { output.script_pubkey.to_hex_string() } - fn assert_matches_wallet_scripts( + type PartialSignatures = + std::collections::BTreeMap; + + fn assert_eq_partial_signatures( + actual: &PartialSignatures, + expected: &PartialSignatures, + ) -> Result<(), String> { + assert_eq!( + actual.len(), + expected.len(), + "Partial signatures should match" + ); + for (actual_sig, expected_sig) in actual.iter().zip(expected.iter()) { + assert_eq!(actual_sig.0, expected_sig.0, "Public key should match"); + assert_hex_eq( + &hex::encode(actual_sig.1.serialize()), + &hex::encode(expected_sig.1.serialize()), + "Signature", + )?; + } + Ok(()) + } + + // ensure we can put the first signature (user signature) on an unsigned PSBT + fn assert_half_sign( + script_type: fixtures::ScriptType, + unsigned_bitgo_psbt: &BitGoPsbt, + halfsigned_bitgo_psbt: &BitGoPsbt, + wallet_keys: &fixtures::XprvTriple, + input_index: usize, + ) -> Result<(), String> { + let user_key = wallet_keys.user_key(); + + // Clone the unsigned PSBT and sign with user key + let mut signed_psbt = unsigned_bitgo_psbt.clone(); + let secp = secp256k1::Secp256k1::new(); + + // Sign with user key using the new sign method + signed_psbt + .sign(user_key, &secp) + .map_err(|(_num_keys, errors)| format!("Failed to sign PSBT: {:?}", errors))?; + + // Extract partial signatures from the signed input + let signed_input = match &signed_psbt { + BitGoPsbt::BitcoinLike(psbt, _) => &psbt.inputs[input_index], + BitGoPsbt::Zcash(_, _) => { + return Err("Zcash signing not yet implemented".to_string()); + } + }; + + match script_type { + fixtures::ScriptType::P2trLegacyScriptPath + | fixtures::ScriptType::P2trMusig2ScriptPath => { + assert_eq!(signed_input.tap_script_sigs.len(), 1); + // Get expected tap script sig from halfsigned fixture + let expected_tap_script_sig = halfsigned_bitgo_psbt.clone().into_psbt().inputs + [input_index] + .tap_script_sigs + .clone(); + assert_eq!(signed_input.tap_script_sigs, expected_tap_script_sig); + } + _ => { + let actual_partial_sigs = signed_input.partial_sigs.clone(); + // Get expected partial signatures from halfsigned fixture + let expected_partial_sigs = halfsigned_bitgo_psbt.clone().into_psbt().inputs + [input_index] + .partial_sigs + .clone(); + + assert_eq!(actual_partial_sigs.len(), 1); + assert_eq_partial_signatures(&actual_partial_sigs, &expected_partial_sigs)?; + } + } + + Ok(()) + } + + fn assert_full_signed_matches_wallet_scripts( network: Network, tx_format: fixtures::TxFormat, fixture: &fixtures::PsbtFixture, - wallet_keys: &RootWalletKeys, + wallet_keys: &fixtures::XprvTriple, input_index: usize, input_fixture: &fixtures::PsbtInputFixture, ) -> Result<(), String> { let (chain, index) = parse_fixture_paths(input_fixture).expect("Failed to parse fixture paths"); let scripts = WalletScripts::from_wallet_keys( - wallet_keys, + &wallet_keys.to_root_wallet_keys(), chain, index, &network.output_script_support(), @@ -497,50 +614,51 @@ mod tests { network: Network, tx_format: fixtures::TxFormat, ) -> Result<(), String> { - let fixture = fixtures::load_psbt_fixture_with_format( - network.to_utxolib_name(), - fixtures::SignatureState::Fullsigned, - tx_format, - ) - .expect("Failed to load fixture"); - let wallet_keys = - fixtures::parse_wallet_keys(&fixture).expect("Failed to parse wallet keys"); - let secp = crate::bitcoin::secp256k1::Secp256k1::new(); - let wallet_keys = RootWalletKeys::new( - wallet_keys - .iter() - .map(|x| Xpub::from_priv(&secp, x)) - .collect::>() - .try_into() - .expect("Failed to convert to XpubTriple"), - ); + let psbt_stages = fixtures::PsbtStages::load(network, tx_format)?; + let psbt_input_stages = + fixtures::PsbtInputStages::from_psbt_stages(&psbt_stages, script_type); // Check if the script type is supported by the network let output_script_support = network.output_script_support(); - let input_fixture = fixture.find_input_with_script_type(script_type); if !script_type.is_supported_by(&output_script_support) { // Script type not supported by network - skip test (no fixture expected) assert!( - input_fixture.is_err(), + psbt_input_stages.is_err(), "Expected error for unsupported script type" ); return Ok(()); } - let (input_index, input_fixture) = input_fixture.unwrap(); + let psbt_input_stages = psbt_input_stages.unwrap(); + + if script_type != fixtures::ScriptType::P2trMusig2TaprootKeypath { + assert_half_sign( + script_type, + &psbt_stages + .unsigned + .to_bitgo_psbt(network) + .expect("Failed to convert to BitGo PSBT"), + &psbt_stages + .halfsigned + .to_bitgo_psbt(network) + .expect("Failed to convert to BitGo PSBT"), + &psbt_input_stages.wallet_keys, + psbt_input_stages.input_index, + )?; + } - assert_matches_wallet_scripts( + assert_full_signed_matches_wallet_scripts( network, tx_format, - &fixture, - &wallet_keys, - input_index, - input_fixture, + &psbt_stages.fullsigned, + &psbt_input_stages.wallet_keys, + psbt_input_stages.input_index, + &psbt_input_stages.input_fixture_fullsigned, )?; assert_finalize_input( - fixture.to_bitgo_psbt(network).unwrap(), - input_index, + psbt_stages.fullsigned.to_bitgo_psbt(network).unwrap(), + psbt_input_stages.input_index, network, tx_format, )?; @@ -548,7 +666,7 @@ mod tests { Ok(()) } - crate::test_psbt_fixtures!(test_p2sh_script_generation_from_fixture, network, format, { + crate::test_psbt_fixtures!(test_p2sh_suite, network, format, { test_wallet_script_type(fixtures::ScriptType::P2sh, network, format).unwrap(); }, ignore: [ // TODO: sighash support @@ -558,7 +676,7 @@ mod tests { ]); crate::test_psbt_fixtures!( - test_p2sh_p2wsh_script_generation_from_fixture, + test_p2sh_p2wsh_suite, network, format, { @@ -569,7 +687,7 @@ mod tests { ); crate::test_psbt_fixtures!( - test_p2wsh_script_generation_from_fixture, + test_p2wsh_suite, network, format, { @@ -579,27 +697,24 @@ mod tests { ignore: [BitcoinGold] ); - crate::test_psbt_fixtures!(test_p2tr_script_generation_from_fixture, network, format, { - test_wallet_script_type(fixtures::ScriptType::P2tr, network, format).unwrap(); + crate::test_psbt_fixtures!(test_p2tr_legacy_script_path_suite, network, format, { + test_wallet_script_type(fixtures::ScriptType::P2trLegacyScriptPath, network, format) + .unwrap(); }); - crate::test_psbt_fixtures!( - test_p2tr_musig2_script_path_generation_from_fixture, - network, - format, - { - test_wallet_script_type(fixtures::ScriptType::P2trMusig2, network, format).unwrap(); - } - ); + crate::test_psbt_fixtures!(test_p2tr_musig2_script_path_suite, network, format, { + test_wallet_script_type(fixtures::ScriptType::P2trMusig2ScriptPath, network, format) + .unwrap(); + }); - crate::test_psbt_fixtures!( - test_p2tr_musig2_key_path_spend_script_generation_from_fixture, - network, - format, - { - test_wallet_script_type(fixtures::ScriptType::TaprootKeypath, network, format).unwrap(); - } - ); + crate::test_psbt_fixtures!(test_p2tr_musig2_key_path_suite, network, format, { + test_wallet_script_type( + fixtures::ScriptType::P2trMusig2TaprootKeypath, + network, + format, + ) + .unwrap(); + }); crate::test_psbt_fixtures!(test_extract_transaction, network, format, { let fixture = fixtures::load_psbt_fixture_with_format( diff --git a/packages/wasm-utxo/src/bitgo_psbt/p2tr_musig2_input.rs b/packages/wasm-utxo/src/bitgo_psbt/p2tr_musig2_input.rs index e6931a0..83825a4 100644 --- a/packages/wasm-utxo/src/bitgo_psbt/p2tr_musig2_input.rs +++ b/packages/wasm-utxo/src/bitgo_psbt/p2tr_musig2_input.rs @@ -689,12 +689,12 @@ mod tests { .expect("Failed to load fixture"); let (input_index, input_fixture) = fixture - .find_input_with_script_type(ScriptType::TaprootKeypath) + .find_input_with_script_type(ScriptType::P2trMusig2TaprootKeypath) .expect("Failed to find taprootKeyPathSpend input"); let finalized_input_fixture = if signature_state == SignatureState::Fullsigned { let (finalized_input_index, finalized_input_fixture) = fixture - .find_finalized_input_with_script_type(ScriptType::TaprootKeypath) + .find_finalized_input_with_script_type(ScriptType::P2trMusig2TaprootKeypath) .expect("Failed to find taprootKeyPathSpend finalized input"); assert_eq!(input_index, finalized_input_index); Some(finalized_input_fixture) diff --git a/packages/wasm-utxo/src/fixed_script_wallet/test_utils/fixtures.rs b/packages/wasm-utxo/src/fixed_script_wallet/test_utils/fixtures.rs index 69c73d1..e87b2c0 100644 --- a/packages/wasm-utxo/src/fixed_script_wallet/test_utils/fixtures.rs +++ b/packages/wasm-utxo/src/fixed_script_wallet/test_utils/fixtures.rs @@ -16,7 +16,7 @@ //! .expect("Failed to decode PSBT"); //! //! // Parse wallet keys (xprv) -//! let xprvs = parse_wallet_keys(&fixture) +//! let xprvs = fixture.get_wallet_xprvs() //! .expect("Failed to parse wallet keys"); //! //! // Access fixture data @@ -38,10 +38,50 @@ //! } //! ``` +use std::str::FromStr; + +use crate::{bitcoin::bip32::Xpriv, fixed_script_wallet::RootWalletKeys}; +use miniscript::bitcoin::bip32::Xpub; use serde::{Deserialize, Serialize}; use crate::Network; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct XprvTriple([Xpriv; 3]); + +impl XprvTriple { + pub fn new(xprvs: [Xpriv; 3]) -> Self { + Self(xprvs) + } + + pub fn from_strings(strings: Vec) -> Result> { + let xprvs = strings + .iter() + .map(|s| Xpriv::from_str(s).map_err(|e| Box::new(e) as Box)) + .collect::, _>>()?; + Ok(Self::new( + xprvs.try_into().expect("Expected exactly 3 xprvs"), + )) + } + + pub fn user_key(&self) -> &Xpriv { + &self.0[0] + } + + pub fn backup_key(&self) -> &Xpriv { + &self.0[1] + } + + pub fn bitgo_key(&self) -> &Xpriv { + &self.0[2] + } + + pub fn to_root_wallet_keys(&self) -> RootWalletKeys { + let secp = crate::bitcoin::secp256k1::Secp256k1::new(); + RootWalletKeys::new(self.0.map(|x| Xpub::from_priv(&secp, &x))) + } +} + // Basic helper types (no dependencies on other types in this file) #[derive(Debug, Clone, Deserialize, Serialize)] @@ -357,6 +397,22 @@ pub enum PsbtInputFixture { P2shP2pk(P2shP2pkInput), } +impl PsbtInputFixture { + /// Get partial signatures from PSBT input fixtures that support them. + /// Returns None for input types that don't use ECDSA partial signatures (e.g., Taproot). + pub fn partial_sigs(&self) -> Option<&Vec> { + match self { + PsbtInputFixture::P2sh(fixture) => Some(&fixture.partial_sig), + PsbtInputFixture::P2shP2wsh(fixture) => Some(&fixture.partial_sig), + PsbtInputFixture::P2wsh(fixture) => Some(&fixture.partial_sig), + PsbtInputFixture::P2shP2pk(fixture) => Some(&fixture.partial_sig), + PsbtInputFixture::P2trLegacy(_) + | PsbtInputFixture::P2trMusig2ScriptPath(_) + | PsbtInputFixture::P2trMusig2KeyPath(_) => None, + } + } +} + // Finalized input type structs (depend on helper types above) #[derive(Debug, Clone, Deserialize, Serialize)] @@ -480,6 +536,100 @@ pub struct PsbtFixture { pub extracted_transaction: Option, } +// Test helper types for multi-stage PSBT testing + +pub struct PsbtStages { + pub network: Network, + pub tx_format: TxFormat, + pub wallet_keys: XprvTriple, + pub unsigned: PsbtFixture, + pub halfsigned: PsbtFixture, + pub fullsigned: PsbtFixture, +} + +impl PsbtStages { + pub fn load(network: Network, tx_format: TxFormat) -> Result { + let unsigned = load_psbt_fixture_with_format( + network.to_utxolib_name(), + SignatureState::Unsigned, + tx_format, + ) + .expect("Failed to load unsigned fixture"); + let halfsigned = load_psbt_fixture_with_format( + network.to_utxolib_name(), + SignatureState::Halfsigned, + tx_format, + ) + .expect("Failed to load halfsigned fixture"); + let fullsigned = load_psbt_fixture_with_format( + network.to_utxolib_name(), + SignatureState::Fullsigned, + tx_format, + ) + .expect("Failed to load fullsigned fixture"); + let wallet_keys_unsigned = unsigned + .get_wallet_xprvs() + .expect("Failed to parse wallet keys"); + let wallet_keys_halfsigned = halfsigned + .get_wallet_xprvs() + .expect("Failed to parse wallet keys"); + let wallet_keys_fullsigned = fullsigned + .get_wallet_xprvs() + .expect("Failed to parse wallet keys"); + assert_eq!(wallet_keys_unsigned, wallet_keys_halfsigned); + assert_eq!(wallet_keys_unsigned, wallet_keys_fullsigned); + + Ok(Self { + network, + tx_format, + wallet_keys: wallet_keys_unsigned.clone(), + unsigned, + halfsigned, + fullsigned, + }) + } +} + +pub struct PsbtInputStages { + pub network: Network, + pub tx_format: TxFormat, + pub wallet_keys: XprvTriple, + pub wallet_script_type: ScriptType, + pub input_index: usize, + pub input_fixture_unsigned: PsbtInputFixture, + pub input_fixture_halfsigned: PsbtInputFixture, + pub input_fixture_fullsigned: PsbtInputFixture, +} + +impl PsbtInputStages { + pub fn from_psbt_stages( + psbt_stages: &PsbtStages, + wallet_script_type: ScriptType, + ) -> Result { + let input_fixture_unsigned = psbt_stages + .unsigned + .find_input_with_script_type(wallet_script_type)?; + let input_fixture_halfsigned = psbt_stages + .halfsigned + .find_input_with_script_type(wallet_script_type)?; + let input_fixture_fullsigned = psbt_stages + .fullsigned + .find_input_with_script_type(wallet_script_type)?; + assert_eq!(input_fixture_unsigned.0, input_fixture_halfsigned.0); + assert_eq!(input_fixture_unsigned.0, input_fixture_fullsigned.0); + Ok(Self { + network: psbt_stages.network, + tx_format: psbt_stages.tx_format, + wallet_keys: psbt_stages.wallet_keys.clone(), + wallet_script_type, + input_index: input_fixture_unsigned.0, + input_fixture_unsigned: input_fixture_unsigned.1.clone(), + input_fixture_halfsigned: input_fixture_halfsigned.1.clone(), + input_fixture_fullsigned: input_fixture_fullsigned.1.clone(), + }) + } +} + /// Helper function to find a unique input matching a predicate fn find_unique_input<'a, T, I, F>( iter: I, @@ -514,6 +664,11 @@ impl PsbtFixture { Ok(psbt) } + /// Parse wallet keys from fixture (xprv strings) + pub fn get_wallet_xprvs(&self) -> Result> { + XprvTriple::from_strings(self.wallet_keys.clone()) + } + pub fn find_input_with_script_type( &self, script_type: ScriptType, @@ -707,19 +862,6 @@ pub fn decode_psbt_from_fixture( Ok(psbt) } -/// Parse wallet keys from fixture (xprv strings) -pub fn parse_wallet_keys( - fixture: &PsbtFixture, -) -> Result, Box> { - use std::str::FromStr; - - fixture - .wallet_keys - .iter() - .map(|key_str| crate::bitcoin::bip32::Xpriv::from_str(key_str).map_err(|e| e.into())) - .collect() -} - // Helper functions for validation /// Compares a generated hex string with an expected hex string @@ -1001,9 +1143,12 @@ pub enum ScriptType { P2sh, P2shP2wsh, P2wsh, - P2tr, - P2trMusig2, - TaprootKeypath, + // Chain 30 and 31 - we only support script path spending for these + P2trLegacyScriptPath, + // Chain 40 and 41 - script path spend + P2trMusig2ScriptPath, + // Chain 40 and 41 - keypath spend + P2trMusig2TaprootKeypath, } impl ScriptType { @@ -1013,9 +1158,9 @@ impl ScriptType { ScriptType::P2sh => "p2sh", ScriptType::P2shP2wsh => "p2shP2wsh", ScriptType::P2wsh => "p2wsh", - ScriptType::P2tr => "p2tr", - ScriptType::P2trMusig2 => "p2trMusig2", - ScriptType::TaprootKeypath => "taprootKeypath", + ScriptType::P2trLegacyScriptPath => "p2tr", + ScriptType::P2trMusig2ScriptPath => "p2trMusig2", + ScriptType::P2trMusig2TaprootKeypath => "taprootKeypath", } } @@ -1026,13 +1171,16 @@ impl ScriptType { (ScriptType::P2sh, PsbtInputFixture::P2sh(_)) | (ScriptType::P2shP2wsh, PsbtInputFixture::P2shP2wsh(_)) | (ScriptType::P2wsh, PsbtInputFixture::P2wsh(_)) - | (ScriptType::P2tr, PsbtInputFixture::P2trLegacy(_)) | ( - ScriptType::P2trMusig2, + ScriptType::P2trLegacyScriptPath, + PsbtInputFixture::P2trLegacy(_) + ) + | ( + ScriptType::P2trMusig2ScriptPath, PsbtInputFixture::P2trMusig2ScriptPath(_) ) | ( - ScriptType::TaprootKeypath, + ScriptType::P2trMusig2TaprootKeypath, PsbtInputFixture::P2trMusig2KeyPath(_) ) ) @@ -1045,13 +1193,16 @@ impl ScriptType { (ScriptType::P2sh, PsbtFinalInputFixture::P2sh(_)) | (ScriptType::P2shP2wsh, PsbtFinalInputFixture::P2shP2wsh(_)) | (ScriptType::P2wsh, PsbtFinalInputFixture::P2wsh(_)) - | (ScriptType::P2tr, PsbtFinalInputFixture::P2trLegacy(_)) | ( - ScriptType::P2trMusig2, + ScriptType::P2trLegacyScriptPath, + PsbtFinalInputFixture::P2trLegacy(_) + ) + | ( + ScriptType::P2trMusig2ScriptPath, PsbtFinalInputFixture::P2trMusig2ScriptPath(_) ) | ( - ScriptType::TaprootKeypath, + ScriptType::P2trMusig2TaprootKeypath, PsbtFinalInputFixture::P2trMusig2KeyPath(_) ) ) @@ -1064,7 +1215,9 @@ impl ScriptType { pub fn is_taproot(&self) -> bool { matches!( self, - ScriptType::P2tr | ScriptType::P2trMusig2 | ScriptType::TaprootKeypath + ScriptType::P2trLegacyScriptPath + | ScriptType::P2trMusig2ScriptPath + | ScriptType::P2trMusig2TaprootKeypath ) } @@ -1282,10 +1435,6 @@ mod tests { let psbt = decode_psbt_from_fixture(&fixture).expect("Failed to decode PSBT"); assert_eq!(psbt.inputs.len(), 7); assert_eq!(psbt.outputs.len(), 5); - - // Parse wallet keys - let xprvs = parse_wallet_keys(&fixture).expect("Failed to parse wallet keys"); - assert_eq!(xprvs.len(), 3); } #[test] @@ -1382,7 +1531,7 @@ mod tests { // Test finding taproot key path finalized input let (index, input) = fixture - .find_finalized_input_with_script_type(ScriptType::TaprootKeypath) + .find_finalized_input_with_script_type(ScriptType::P2trMusig2TaprootKeypath) .expect("Failed to find taproot key path finalized input"); assert_eq!(index, 5); assert!(matches!(input, PsbtFinalInputFixture::P2trMusig2KeyPath(_))); diff --git a/packages/wasm-utxo/src/fixed_script_wallet/wallet_keys.rs b/packages/wasm-utxo/src/fixed_script_wallet/wallet_keys.rs index ba7660a..2f2d551 100644 --- a/packages/wasm-utxo/src/fixed_script_wallet/wallet_keys.rs +++ b/packages/wasm-utxo/src/fixed_script_wallet/wallet_keys.rs @@ -32,7 +32,13 @@ pub fn to_pub_triple(xpubs: &XpubTriple) -> PubTriple { .expect("could not convert vec to array") } -#[derive(Debug)] +pub fn derivation_path(prefix: &DerivationPath, chain: u32, index: u32) -> DerivationPath { + prefix + .child(ChildNumber::Normal { index: chain }) + .child(ChildNumber::Normal { index }) +} + +#[derive(Debug, Clone)] pub struct RootWalletKeys { xpubs: XpubTriple, derivation_prefixes: [DerivationPath; 3], @@ -68,10 +74,7 @@ impl RootWalletKeys { let paths: Vec = self .derivation_prefixes .iter() - .map(|p| { - p.child(ChildNumber::Normal { index: chain }) - .child(ChildNumber::Normal { index }) - }) + .map(|p| derivation_path(p, chain, index)) .collect::>(); let ctx = Secp256k1::new(); diff --git a/packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/singlesig.rs b/packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/singlesig.rs index 95cc802..2751932 100644 --- a/packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/singlesig.rs +++ b/packages/wasm-utxo/src/fixed_script_wallet/wallet_scripts/singlesig.rs @@ -31,11 +31,11 @@ impl ScriptP2shP2pk { #[cfg(test)] mod tests { + use miniscript::bitcoin::bip32::Xpub; + use super::*; use crate::bitcoin::secp256k1::Secp256k1; - use crate::fixed_script_wallet::test_utils::fixtures::{ - load_psbt_fixture, parse_wallet_keys, SignatureState, - }; + use crate::fixed_script_wallet::test_utils::fixtures::{load_psbt_fixture, SignatureState}; #[test] fn test_p2sh_p2pk_script_generation_from_fixture() { @@ -56,33 +56,18 @@ mod tests { // Get the expected values from the fixture let expected_redeem_script = &p2shp2pk_input.redeem_script; - let expected_pubkey = p2shp2pk_input + p2shp2pk_input .partial_sig .first() .map(|sig| &sig.pubkey) .expect("No partial signature found"); // Parse the wallet keys - let xprvs = parse_wallet_keys(&fixture).expect("Failed to parse wallet keys"); + let xprvs = fixture + .get_wallet_xprvs() + .expect("Failed to parse wallet keys"); let secp = Secp256k1::new(); - - // Find which key matches the expected pubkey - let mut matching_key = None; - for xprv in &xprvs { - let xpub = crate::bitcoin::bip32::Xpub::from_priv(&secp, xprv); - // Convert secp256k1::PublicKey to bitcoin::PublicKey - let bitcoin_pubkey = crate::bitcoin::PublicKey::new(xpub.public_key); - let compressed_pubkey = CompressedPublicKey::try_from(bitcoin_pubkey) - .expect("Failed to convert to compressed pubkey"); - let pubkey_hex = hex::encode(compressed_pubkey.to_bytes()); - - if pubkey_hex == *expected_pubkey { - matching_key = Some(compressed_pubkey); - break; - } - } - - let pubkey = matching_key.expect("Could not find matching pubkey in wallet keys"); + let pubkey = Xpub::from_priv(&secp, xprvs.user_key()).to_pub(); // Build the p2sh-p2pk script let script = ScriptP2shP2pk::new(pubkey);