Skip to content

Commit 026943a

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 e7c4d1d commit 026943a

File tree

1 file changed

+139
-0
lines changed

1 file changed

+139
-0
lines changed

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

0 commit comments

Comments
 (0)