Skip to content

Commit 871e953

Browse files
evanlinjinclaude
andcommitted
bitcoind-tests: Add integration test for Plan::satisfy() with P2WSH
Add test_plan_satisfy_wsh() and test_plan_satisfy_sh_wsh() which verify that Plan::satisfy() correctly constructs witness and script_sig for P2WSH descriptors by calling plan.satisfy() directly and broadcasting the resulting transaction to Bitcoin Core. This is a regression test for #896. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3a7398e commit 871e953

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

bitcoind-tests/tests/test_desc.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//!
66
77
use std::collections::BTreeMap;
8+
use std::str::FromStr;
89
use std::{error, fmt};
910

1011
use actual_rand as rand;
@@ -423,3 +424,141 @@ fn test_satisfy() {
423424
let cl = &setup::setup().client;
424425
test_descs(cl, &testdata);
425426
}
427+
428+
fn test_plan_satisfy(
429+
cl: &Client,
430+
testdata: &TestData,
431+
descriptor: &str,
432+
) -> Result<Witness, DescError> {
433+
use std::collections::BTreeMap;
434+
435+
use miniscript::plan::Assets;
436+
use miniscript::DefiniteDescriptorKey;
437+
438+
let secp = secp256k1::Secp256k1::new();
439+
let sks = &testdata.secretdata.sks;
440+
let pks = &testdata.pubdata.pks;
441+
442+
let blocks = cl
443+
.generate_to_address(1, &cl.new_address().unwrap())
444+
.unwrap();
445+
assert_eq!(blocks.0.len(), 1);
446+
447+
let definite_desc = test_util::parse_test_desc(descriptor, &testdata.pubdata)
448+
.map_err(|_| DescError::DescParseError)?
449+
.at_derivation_index(0)
450+
.unwrap();
451+
452+
let derived_desc = definite_desc.derived_descriptor(&secp);
453+
let desc_address = derived_desc
454+
.address(bitcoin::Network::Regtest)
455+
.map_err(|_| DescError::AddressComputationError)?;
456+
457+
let txid = cl
458+
.send_to_address(&desc_address, btc(1))
459+
.expect("rpc call failed")
460+
.txid()
461+
.expect("conversion to model failed");
462+
463+
let blocks = cl
464+
.generate_to_address(2, &cl.new_address().unwrap())
465+
.unwrap();
466+
assert_eq!(blocks.0.len(), 2);
467+
468+
let (outpoint, witness_utxo) = get_vout(cl, txid, btc(1.0), derived_desc.script_pubkey());
469+
470+
let mut assets = Assets::new();
471+
for pk in pks.iter() {
472+
let dpk = miniscript::DescriptorPublicKey::Single(miniscript::descriptor::SinglePub {
473+
origin: None,
474+
key: miniscript::descriptor::SinglePubKey::FullKey(*pk),
475+
});
476+
assets = assets.add(dpk);
477+
}
478+
479+
let plan = definite_desc
480+
.clone()
481+
.plan(&assets)
482+
.expect("plan creation failed");
483+
484+
let mut unsigned_tx = Transaction {
485+
version: transaction::Version::TWO,
486+
lock_time: absolute::LockTime::from_time(1_603_866_330).expect("valid timestamp"),
487+
input: vec![TxIn {
488+
previous_output: outpoint,
489+
sequence: Sequence::from_height(1),
490+
..Default::default()
491+
}],
492+
output: vec![TxOut {
493+
value: Amount::from_sat(99_997_000),
494+
script_pubkey: cl
495+
.new_address_with_type(AddressType::Bech32)
496+
.unwrap()
497+
.script_pubkey(),
498+
}],
499+
};
500+
501+
let mut sighash_cache = SighashCache::new(&unsigned_tx);
502+
503+
use miniscript::descriptor::DescriptorType;
504+
let sighash_type = sighash::EcdsaSighashType::All;
505+
let desc_type = derived_desc.desc_type();
506+
let sighash_msg = match desc_type {
507+
DescriptorType::Wsh
508+
| DescriptorType::WshSortedMulti
509+
| DescriptorType::ShWsh
510+
| DescriptorType::ShWshSortedMulti => {
511+
let script_code = derived_desc.script_code().expect("has script_code");
512+
sighash_cache
513+
.p2wsh_signature_hash(0, &script_code, witness_utxo.value, sighash_type)
514+
.expect("sighash")
515+
}
516+
_ => panic!("test is only for wsh descriptors, got {:?}", desc_type),
517+
};
518+
519+
let msg = secp256k1::Message::from_digest(sighash_msg.to_byte_array());
520+
521+
let mut sig_map: BTreeMap<DefiniteDescriptorKey, ecdsa::Signature> = BTreeMap::new();
522+
for (i, pk) in pks.iter().enumerate() {
523+
let signature = secp.sign_ecdsa(&msg, &sks[i]);
524+
let dpk = DefiniteDescriptorKey::from_str(&pk.to_string()).unwrap();
525+
sig_map.insert(dpk, ecdsa::Signature { signature, sighash_type });
526+
}
527+
528+
let (witness_stack, script_sig) = plan.satisfy(&sig_map).expect("satisfaction failed");
529+
530+
unsigned_tx.input[0].witness = Witness::from_slice(&witness_stack);
531+
unsigned_tx.input[0].script_sig = script_sig;
532+
533+
let txid = cl
534+
.send_raw_transaction(&unsigned_tx)
535+
.unwrap_or_else(|e| panic!("send tx failed for desc {}: {:?}", definite_desc, e))
536+
.txid()
537+
.expect("conversion to model failed");
538+
539+
let _blocks = cl
540+
.generate_to_address(1, &cl.new_address().unwrap())
541+
.unwrap();
542+
let num_conf = cl.get_transaction(txid).unwrap().confirmations;
543+
assert!(num_conf > 0);
544+
545+
Ok(unsigned_tx.input[0].witness.clone())
546+
}
547+
548+
#[test]
549+
fn test_plan_satisfy_wsh() {
550+
let testdata = TestData::new_fixed_data(50);
551+
let cl = &setup::setup().client;
552+
553+
test_plan_satisfy(cl, &testdata, "wsh(pk(K))").unwrap();
554+
test_plan_satisfy(cl, &testdata, "wsh(multi(2,K1,K2,K3))").unwrap();
555+
}
556+
557+
#[test]
558+
fn test_plan_satisfy_sh_wsh() {
559+
let testdata = TestData::new_fixed_data(50);
560+
let cl = &setup::setup().client;
561+
562+
test_plan_satisfy(cl, &testdata, "sh(wsh(pk(K)))").unwrap();
563+
test_plan_satisfy(cl, &testdata, "sh(wsh(multi(2,K1,K2,K3)))").unwrap();
564+
}

0 commit comments

Comments
 (0)