55//!
66
77use std:: collections:: BTreeMap ;
8+ use std:: str:: FromStr ;
89use std:: { error, fmt} ;
910
1011use actual_rand as rand;
@@ -168,16 +169,19 @@ pub fn test_desc_satisfy(
168169
169170 if let Some ( internal_keypair) = internal_keypair {
170171 // ---------------------- Tr key spend --------------------
171- let internal_keypair = internal_keypair
172- . tap_tweak ( & secp, tr. spend_info ( ) . merkle_root ( ) ) ;
172+ let internal_keypair =
173+ internal_keypair . tap_tweak ( & secp, tr. spend_info ( ) . merkle_root ( ) ) ;
173174 let sighash_msg = sighash_cache
174175 . taproot_key_spend_signature_hash ( 0 , & prevouts, sighash_type)
175176 . unwrap ( ) ;
176177 let msg = secp256k1:: Message :: from_digest ( sighash_msg. to_byte_array ( ) ) ;
177178 let mut aux_rand = [ 0u8 ; 32 ] ;
178179 rand:: thread_rng ( ) . fill_bytes ( & mut aux_rand) ;
179- let schnorr_sig =
180- secp. sign_schnorr_with_aux_rand ( & msg, & internal_keypair. to_keypair ( ) , & aux_rand) ;
180+ let schnorr_sig = secp. sign_schnorr_with_aux_rand (
181+ & msg,
182+ & internal_keypair. to_keypair ( ) ,
183+ & aux_rand,
184+ ) ;
181185 psbt. inputs [ 0 ] . tap_key_sig =
182186 Some ( taproot:: Signature { signature : schnorr_sig, sighash_type } ) ;
183187 } else {
@@ -187,7 +191,8 @@ pub fn test_desc_satisfy(
187191 let x_only_keypairs_reqd: Vec < ( secp256k1:: Keypair , TapLeafHash ) > = tr
188192 . leaves ( )
189193 . flat_map ( |leaf| {
190- let leaf_hash = TapLeafHash :: from_script ( & leaf. compute_script ( ) , LeafVersion :: TapScript ) ;
194+ let leaf_hash =
195+ TapLeafHash :: from_script ( & leaf. compute_script ( ) , LeafVersion :: TapScript ) ;
191196 leaf. miniscript ( ) . iter_pk ( ) . filter_map ( move |pk| {
192197 let i = x_only_pks. iter ( ) . position ( |& x| x. to_public_key ( ) == pk) ;
193198 i. map ( |idx| ( xonly_keypairs[ idx] , leaf_hash) )
@@ -419,3 +424,160 @@ fn test_satisfy() {
419424 let cl = & setup:: setup ( ) . client ;
420425 test_descs ( cl, & testdata) ;
421426}
427+
428+ /// Test that Plan::satisfy() correctly constructs witness for P2WSH descriptors.
429+ /// This is a regression test for https://github.com/rust-bitcoin/rust-miniscript/issues/896
430+ pub fn test_plan_satisfy_wsh (
431+ cl : & Client ,
432+ testdata : & TestData ,
433+ descriptor : & str ,
434+ ) -> Result < Witness , DescError > {
435+ use std:: collections:: BTreeMap ;
436+
437+ use miniscript:: plan:: Assets ;
438+ use miniscript:: DefiniteDescriptorKey ;
439+
440+ let secp = secp256k1:: Secp256k1 :: new ( ) ;
441+ let sks = & testdata. secretdata . sks ;
442+ let pks = & testdata. pubdata . pks ;
443+
444+ // Generate some blocks
445+ let blocks = cl
446+ . generate_to_address ( 1 , & cl. new_address ( ) . unwrap ( ) )
447+ . unwrap ( ) ;
448+ assert_eq ! ( blocks. 0 . len( ) , 1 ) ;
449+
450+ let definite_desc = test_util:: parse_test_desc ( descriptor, & testdata. pubdata )
451+ . map_err ( |_| DescError :: DescParseError ) ?
452+ . at_derivation_index ( 0 )
453+ . unwrap ( ) ;
454+
455+ let derived_desc = definite_desc. derived_descriptor ( & secp) ;
456+ let desc_address = derived_desc
457+ . address ( bitcoin:: Network :: Regtest )
458+ . map_err ( |_| DescError :: AddressComputationError ) ?;
459+
460+ // Send some btc to the descriptor address
461+ let txid = cl
462+ . send_to_address ( & desc_address, btc ( 1 ) )
463+ . expect ( "rpc call failed" )
464+ . txid ( )
465+ . expect ( "conversion to model failed" ) ;
466+
467+ // Wait for the funds to mature
468+ let blocks = cl
469+ . generate_to_address ( 2 , & cl. new_address ( ) . unwrap ( ) )
470+ . unwrap ( ) ;
471+ assert_eq ! ( blocks. 0 . len( ) , 2 ) ;
472+
473+ // Get the UTXO
474+ let ( outpoint, witness_utxo) = get_vout ( cl, txid, btc ( 1.0 ) , derived_desc. script_pubkey ( ) ) ;
475+
476+ // Build assets from known keys
477+ let mut assets = Assets :: new ( ) ;
478+ for pk in pks. iter ( ) {
479+ let dpk = miniscript:: DescriptorPublicKey :: Single ( miniscript:: descriptor:: SinglePub {
480+ origin : None ,
481+ key : miniscript:: descriptor:: SinglePubKey :: FullKey ( * pk) ,
482+ } ) ;
483+ assets = assets. add ( dpk) ;
484+ }
485+
486+ // Get a plan (clone since plan() takes ownership)
487+ let plan = definite_desc
488+ . clone ( )
489+ . plan ( & assets)
490+ . expect ( "Failed to create plan" ) ;
491+
492+ // Create the sighash for signing
493+ let mut unsigned_tx = Transaction {
494+ version : transaction:: Version :: TWO ,
495+ lock_time : absolute:: LockTime :: from_time ( 1_603_866_330 ) . expect ( "valid timestamp" ) ,
496+ input : vec ! [ TxIn {
497+ previous_output: outpoint,
498+ sequence: Sequence :: from_height( 1 ) ,
499+ ..Default :: default ( )
500+ } ] ,
501+ output : vec ! [ TxOut {
502+ value: Amount :: from_sat( 99_997_000 ) ,
503+ script_pubkey: cl
504+ . new_address_with_type( AddressType :: Bech32 )
505+ . unwrap( )
506+ . script_pubkey( ) ,
507+ } ] ,
508+ } ;
509+
510+ let mut sighash_cache = SighashCache :: new ( & unsigned_tx) ;
511+
512+ // Compute the sighash based on descriptor type
513+ use miniscript:: descriptor:: DescriptorType ;
514+ let sighash_type = sighash:: EcdsaSighashType :: All ;
515+ let desc_type = derived_desc. desc_type ( ) ;
516+ let sighash_msg = match desc_type {
517+ DescriptorType :: Wsh
518+ | DescriptorType :: WshSortedMulti
519+ | DescriptorType :: ShWsh
520+ | DescriptorType :: ShWshSortedMulti => {
521+ let script_code = derived_desc. script_code ( ) . expect ( "has script_code" ) ;
522+ sighash_cache
523+ . p2wsh_signature_hash ( 0 , & script_code, witness_utxo. value , sighash_type)
524+ . expect ( "sighash" )
525+ }
526+ _ => panic ! ( "This test is only for WSH descriptors, got {:?}" , desc_type) ,
527+ } ;
528+
529+ let msg = secp256k1:: Message :: from_digest ( sighash_msg. to_byte_array ( ) ) ;
530+
531+ // Sign with all required keys and build a satisfier map
532+ let mut sig_map: BTreeMap < DefiniteDescriptorKey , ecdsa:: Signature > = BTreeMap :: new ( ) ;
533+ for ( i, pk) in pks. iter ( ) . enumerate ( ) {
534+ let signature = secp. sign_ecdsa ( & msg, & sks[ i] ) ;
535+ let dpk = DefiniteDescriptorKey :: from_str ( & pk. to_string ( ) ) . unwrap ( ) ;
536+ sig_map. insert ( dpk, ecdsa:: Signature { signature, sighash_type } ) ;
537+ }
538+
539+ // Use Plan::satisfy() to construct witness and script_sig
540+ let ( witness_stack, script_sig) = plan
541+ . satisfy ( & sig_map)
542+ . expect ( "Plan::satisfy() should succeed" ) ;
543+
544+ // Set the witness and script_sig on the transaction
545+ unsigned_tx. input [ 0 ] . witness = Witness :: from_slice ( & witness_stack) ;
546+ unsigned_tx. input [ 0 ] . script_sig = script_sig;
547+
548+ // Broadcast the transaction
549+ let txid = cl
550+ . send_raw_transaction ( & unsigned_tx)
551+ . unwrap_or_else ( |e| panic ! ( "send tx failed for desc {}: {:?}" , definite_desc, e) )
552+ . txid ( )
553+ . expect ( "conversion to model failed" ) ;
554+
555+ // Mine a block and verify confirmation
556+ let _blocks = cl
557+ . generate_to_address ( 1 , & cl. new_address ( ) . unwrap ( ) )
558+ . unwrap ( ) ;
559+ let num_conf = cl. get_transaction ( txid) . unwrap ( ) . confirmations ;
560+ assert ! ( num_conf > 0 ) ;
561+
562+ Ok ( unsigned_tx. input [ 0 ] . witness . clone ( ) )
563+ }
564+
565+ #[ test]
566+ fn test_plan_satisfy ( ) {
567+ let testdata = TestData :: new_fixed_data ( 50 ) ;
568+ let cl = & setup:: setup ( ) . client ;
569+
570+ // Test native P2WSH with Plan::satisfy()
571+ println ! ( "Testing wsh(pk(K)) with Plan::satisfy()" ) ;
572+ test_plan_satisfy_wsh ( cl, & testdata, "wsh(pk(K))" ) . unwrap ( ) ;
573+
574+ println ! ( "Testing wsh(multi(2,K1,K2,K3)) with Plan::satisfy()" ) ;
575+ test_plan_satisfy_wsh ( cl, & testdata, "wsh(multi(2,K1,K2,K3))" ) . unwrap ( ) ;
576+
577+ // Test P2SH-wrapped P2WSH with Plan::satisfy()
578+ println ! ( "Testing sh(wsh(pk(K))) with Plan::satisfy()" ) ;
579+ test_plan_satisfy_wsh ( cl, & testdata, "sh(wsh(pk(K)))" ) . unwrap ( ) ;
580+
581+ println ! ( "Testing sh(wsh(multi(2,K1,K2,K3))) with Plan::satisfy()" ) ;
582+ test_plan_satisfy_wsh ( cl, & testdata, "sh(wsh(multi(2,K1,K2,K3)))" ) . unwrap ( ) ;
583+ }
0 commit comments