Skip to content

Commit c25fc05

Browse files
committed
Add descriptor integration tests
Rework integration framework: Add generic descriptor satisfaction support
1 parent ae3c98e commit c25fc05

File tree

4 files changed

+326
-8
lines changed

4 files changed

+326
-8
lines changed

integration_test/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ miniscript = {path = "../"}
88

99
# Until 0.26 support is released on rust-bitcoincore-rpc
1010
bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "bcc35944b3dd636cdff9710f90f8e0cfcab28f27"}
11-
bitcoin = "0.28.0-rc.1"
11+
bitcoin = {ver = "0.28.0-rc.1", features = ["rand"]}
1212
log = "0.4"
1313
rand = "0.8.4"

integration_test/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ extern crate miniscript;
1010

1111
use bitcoincore_rpc::{Auth, Client, RpcApi};
1212

13+
mod test_desc;
1314
mod test_cpp;
1415
mod test_util;
1516
use test_util::TestData;

integration_test/src/test_desc.rs

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
//! # rust-miniscript integration test
2+
//!
3+
//! Read Miniscripts from file and translate into miniscripts
4+
//! which we know how to satisfy
5+
//!
6+
7+
use bitcoin::blockdata::witness::Witness;
8+
use bitcoin::secp256k1;
9+
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
10+
use bitcoin::util::sighash::SigHashCache;
11+
use bitcoin::util::taproot::{LeafVersion, TapLeafHash};
12+
use bitcoin::util::{psbt, sighash};
13+
use bitcoin::{self, Amount, OutPoint, SchnorrSig, Script, Transaction, TxIn, TxOut, Txid};
14+
use bitcoincore_rpc::{json, Client, RpcApi};
15+
use miniscript::miniscript::iter;
16+
use miniscript::psbt::PbstOps;
17+
use miniscript::{Descriptor, DescriptorTrait, Miniscript, ToPublicKey};
18+
use miniscript::{MiniscriptKey, ScriptContext};
19+
use std::collections::BTreeMap;
20+
21+
use crate::test_util::{self, TestData};
22+
23+
/// Quickly create a BTC amount.
24+
fn btc<F: Into<f64>>(btc: F) -> Amount {
25+
Amount::from_btc(btc.into()).unwrap()
26+
}
27+
28+
// Find the Outpoint by spk
29+
fn get_vout(cl: &Client, txid: Txid, value: u64, spk: Script) -> (OutPoint, TxOut) {
30+
let tx = cl
31+
.get_transaction(&txid, None)
32+
.unwrap()
33+
.transaction()
34+
.unwrap();
35+
for (i, txout) in tx.output.into_iter().enumerate() {
36+
if txout.value == value && spk == txout.script_pubkey {
37+
return (OutPoint::new(txid, i as u32), txout);
38+
}
39+
}
40+
unreachable!("Only call get vout on functions which have the expected outpoint");
41+
}
42+
43+
pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witness {
44+
let secp = secp256k1::Secp256k1::new();
45+
let sks = &testdata.secretdata.sks;
46+
let xonly_keypairs = &testdata.secretdata.x_only_keypairs;
47+
let pks = &testdata.pubdata.pks;
48+
let x_only_pks = &testdata.pubdata.x_only_pks;
49+
// Generate some blocks
50+
let blocks = cl
51+
.generate_to_address(500, &cl.get_new_address(None, None).unwrap())
52+
.unwrap();
53+
assert_eq!(blocks.len(), 500);
54+
55+
let desc = test_util::parse_test_desc(&desc, &testdata.pubdata);
56+
let derived_desc = desc.derived_descriptor(&secp, 0).unwrap();
57+
// Next send some btc to each address corresponding to the miniscript
58+
let txid = cl
59+
.send_to_address(
60+
&derived_desc.address(bitcoin::Network::Regtest).unwrap(),
61+
btc(1),
62+
None,
63+
None,
64+
None,
65+
None,
66+
None,
67+
None,
68+
)
69+
.unwrap();
70+
// Wait for the funds to mature.
71+
let blocks = cl
72+
.generate_to_address(50, &cl.get_new_address(None, None).unwrap())
73+
.unwrap();
74+
assert_eq!(blocks.len(), 50);
75+
// Create a PSBT for each transaction.
76+
// Spend one input and spend one output for simplicity.
77+
let mut psbt = Psbt {
78+
unsigned_tx: Transaction {
79+
version: 2,
80+
lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC)
81+
input: vec![],
82+
output: vec![],
83+
},
84+
unknown: BTreeMap::new(),
85+
proprietary: BTreeMap::new(),
86+
xpub: BTreeMap::new(),
87+
version: 0,
88+
inputs: vec![],
89+
outputs: vec![],
90+
};
91+
// figure out the outpoint from the txid
92+
let (outpoint, witness_utxo) =
93+
get_vout(&cl, txid, btc(1.0).as_sat(), derived_desc.script_pubkey());
94+
let mut txin = TxIn::default();
95+
txin.previous_output = outpoint;
96+
// set the sequence to a non-final number for the locktime transactions to be
97+
// processed correctly.
98+
// We waited 50 blocks, keep 49 for safety
99+
txin.sequence = 49;
100+
psbt.unsigned_tx.input.push(txin);
101+
// Get a new script pubkey from the node so that
102+
// the node wallet tracks the receiving transaction
103+
// and we can check it by gettransaction RPC.
104+
let addr = cl
105+
.get_new_address(None, Some(json::AddressType::Bech32))
106+
.unwrap();
107+
psbt.unsigned_tx.output.push(TxOut {
108+
value: 99_999_000,
109+
script_pubkey: addr.script_pubkey(),
110+
});
111+
let mut input = psbt::Input::default();
112+
input.witness_utxo = Some(witness_utxo.clone());
113+
psbt.inputs.push(input);
114+
psbt.outputs.push(psbt::Output::default());
115+
psbt.update_desc(0, &desc, 0..0).unwrap();
116+
117+
// --------------------------------------------
118+
// Sign the transactions with all keys
119+
// AKA the signer role of psbt
120+
// Get all the pubkeys and the corresponding secret keys
121+
122+
let mut sighash_cache = SigHashCache::new(&psbt.unsigned_tx);
123+
match derived_desc {
124+
Descriptor::Tr(ref tr) => {
125+
// Fixme: take a parameter
126+
let hash_ty = sighash::SchnorrSigHashType::Default;
127+
128+
let internal_key_present = x_only_pks
129+
.iter()
130+
.position(|&x| x.to_public_key() == *tr.internal_key());
131+
let internal_keypair = internal_key_present.map(|idx| xonly_keypairs[idx].clone());
132+
let prevouts = [witness_utxo];
133+
let prevouts = sighash::Prevouts::All(&prevouts);
134+
135+
if let Some(mut internal_keypair) = internal_keypair {
136+
// ---------------------- Tr key spend --------------------
137+
internal_keypair.tweak_add_assign(&secp, tr.spend_info().tap_tweak().as_ref());
138+
let sighash_msg = sighash_cache
139+
.taproot_key_spend_signature_hash(0, &prevouts, hash_ty)
140+
.unwrap();
141+
let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap();
142+
let schnorr_sig = secp.sign_schnorr(&msg, &internal_keypair);
143+
psbt.inputs[0].tap_key_sig = Some(SchnorrSig {
144+
sig: schnorr_sig,
145+
hash_ty: hash_ty,
146+
});
147+
} else {
148+
// No internal key
149+
}
150+
// ------------------ script spend -------------
151+
let x_only_keypairs_reqd: Vec<(secp256k1::KeyPair, TapLeafHash)> = tr
152+
.iter_scripts()
153+
.flat_map(|(_depth, ms)| {
154+
let leaf_hash = TapLeafHash::from_script(&ms.encode(), LeafVersion::TapScript);
155+
ms.iter_pk_pkh().filter_map(move |pk_pkh| match pk_pkh {
156+
iter::PkPkh::PlainPubkey(pk) => {
157+
let i = x_only_pks.iter().position(|&x| x.to_public_key() == pk);
158+
i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash))
159+
}
160+
iter::PkPkh::HashedPubkey(hash) => {
161+
let i = x_only_pks
162+
.iter()
163+
.position(|&x| x.to_public_key().to_pubkeyhash() == hash);
164+
i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash))
165+
}
166+
})
167+
})
168+
.collect();
169+
for (keypair, leaf_hash) in x_only_keypairs_reqd {
170+
let sighash_msg = sighash_cache
171+
.taproot_script_spend_signature_hash(0, &prevouts, leaf_hash, hash_ty)
172+
.unwrap();
173+
let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap();
174+
let sig = secp.sign_schnorr(&msg, &keypair);
175+
// FIXME: uncomment when == is supported for secp256k1::KeyPair. (next major release)
176+
// let x_only_pk = pks[xonly_keypairs.iter().position(|&x| x == keypair).unwrap()];
177+
// Just recalc public key
178+
let x_only_pk = secp256k1::XOnlyPublicKey::from_keypair(&keypair);
179+
psbt.inputs[0].tap_script_sigs.insert(
180+
(x_only_pk, leaf_hash),
181+
bitcoin::SchnorrSig {
182+
sig,
183+
hash_ty: hash_ty,
184+
},
185+
);
186+
}
187+
}
188+
_ => {
189+
// Non-tr descriptors
190+
// Ecdsa sigs
191+
let sks_reqd = match derived_desc {
192+
Descriptor::Bare(bare) => find_sks_ms(&bare.as_inner(), testdata),
193+
Descriptor::Pkh(pk) => find_sk_single_key(*pk.as_inner(), testdata),
194+
Descriptor::Wpkh(pk) => find_sk_single_key(*pk.as_inner(), testdata),
195+
Descriptor::Sh(sh) => match sh.as_inner() {
196+
miniscript::descriptor::ShInner::Wsh(wsh) => match wsh.as_inner() {
197+
miniscript::descriptor::WshInner::SortedMulti(ref smv) => {
198+
let ms = Miniscript::from_ast(smv.sorted_node()).unwrap();
199+
find_sks_ms(&ms, testdata)
200+
}
201+
miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata),
202+
},
203+
miniscript::descriptor::ShInner::Wpkh(pk) => {
204+
find_sk_single_key(*pk.as_inner(), testdata)
205+
}
206+
miniscript::descriptor::ShInner::SortedMulti(smv) => {
207+
let ms = Miniscript::from_ast(smv.sorted_node()).unwrap();
208+
find_sks_ms(&ms, testdata)
209+
}
210+
miniscript::descriptor::ShInner::Ms(ms) => find_sks_ms(&ms, testdata),
211+
},
212+
Descriptor::Wsh(wsh) => match wsh.as_inner() {
213+
miniscript::descriptor::WshInner::SortedMulti(ref smv) => {
214+
let ms = Miniscript::from_ast(smv.sorted_node()).unwrap();
215+
find_sks_ms(&ms, testdata)
216+
}
217+
miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata),
218+
},
219+
Descriptor::Tr(_tr) => unreachable!("Tr checked earlier"),
220+
};
221+
let msg = psbt.sighash_msg(0, &mut sighash_cache, None).unwrap().to_secp_msg();
222+
223+
// Fixme: Take a parameter
224+
let hash_ty = bitcoin::EcdsaSigHashType::All;
225+
226+
// Finally construct the signature and add to psbt
227+
for sk in sks_reqd {
228+
let sig = secp.sign_ecdsa(&msg, &sk);
229+
let pk = pks[sks.iter().position(|&x| x == sk).unwrap()];
230+
psbt.inputs[0].partial_sigs.insert(
231+
pk.inner,
232+
bitcoin::EcdsaSig {
233+
sig,
234+
hash_ty: hash_ty,
235+
},
236+
);
237+
}
238+
}
239+
}
240+
// Add the hash preimages to the psbt
241+
psbt.inputs[0].sha256_preimages.insert(
242+
testdata.pubdata.sha256,
243+
testdata.secretdata.sha256_pre.to_vec(),
244+
);
245+
psbt.inputs[0].hash256_preimages.insert(
246+
testdata.pubdata.hash256,
247+
testdata.secretdata.hash256_pre.to_vec(),
248+
);
249+
psbt.inputs[0].hash160_preimages.insert(
250+
testdata.pubdata.hash160,
251+
testdata.secretdata.hash160_pre.to_vec(),
252+
);
253+
psbt.inputs[0].ripemd160_preimages.insert(
254+
testdata.pubdata.ripemd160,
255+
testdata.secretdata.ripemd160_pre.to_vec(),
256+
);
257+
println!("Testing descriptor: {}", desc);
258+
// Finalize the transaction using psbt
259+
// Let miniscript do it's magic!
260+
if let Err(e) = psbt.finalize(&secp) {
261+
// All miniscripts should satisfy
262+
panic!("Could not satisfy non-malleably: error{} desc:{} ", e, desc);
263+
}
264+
let tx = psbt.extract(&secp).expect("Extraction error");
265+
266+
// Send the transactions to bitcoin node for mining.
267+
// Regtest mode has standardness checks
268+
// Check whether the node accepts the transactions
269+
let txid = cl
270+
.send_raw_transaction(&tx)
271+
.expect(&format!("send tx failed for desc {}", desc));
272+
273+
// Finally mine the blocks and await confirmations
274+
let _blocks = cl
275+
.generate_to_address(10, &cl.get_new_address(None, None).unwrap())
276+
.unwrap();
277+
// Get the required transactions from the node mined in the blocks.
278+
// Check whether the transaction is mined in blocks
279+
// Assert that the confirmations are > 0.
280+
let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations;
281+
assert!(num_conf > 0);
282+
tx.input[0].witness.clone()
283+
}
284+
285+
// Find all secret corresponding to the known public keys in ms
286+
fn find_sks_ms<Ctx: ScriptContext>(
287+
ms: &Miniscript<bitcoin::PublicKey, Ctx>,
288+
testdata: &TestData,
289+
) -> Vec<secp256k1::SecretKey> {
290+
let sks = &testdata.secretdata.sks;
291+
let pks = &testdata.pubdata.pks;
292+
let sks = ms
293+
.iter_pk_pkh()
294+
.filter_map(|pk_pkh| match pk_pkh {
295+
iter::PkPkh::PlainPubkey(pk) => {
296+
let i = pks.iter().position(|&x| x.to_public_key() == pk);
297+
i.map(|idx| (sks[idx]))
298+
}
299+
iter::PkPkh::HashedPubkey(hash) => {
300+
let i = pks
301+
.iter()
302+
.position(|&x| x.to_public_key().to_pubkeyhash() == hash);
303+
i.map(|idx| (sks[idx]))
304+
}
305+
})
306+
.collect();
307+
sks
308+
}
309+
310+
fn find_sk_single_key(pk: bitcoin::PublicKey, testdata: &TestData) -> Vec<secp256k1::SecretKey> {
311+
let sks = &testdata.secretdata.sks;
312+
let pks = &testdata.pubdata.pks;
313+
let i = pks
314+
.iter()
315+
.position(|&x| x.to_public_key() == pk);
316+
i.map(|idx| vec![sks[idx]]).unwrap_or(Vec::new())
317+
}

integration_test/src/test_util.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use self::rand::RngCore;
2323
use bitcoin::hashes::{hex::ToHex, Hash};
2424
use miniscript::{
2525
descriptor::{DescriptorSinglePub, SinglePubKey},
26-
Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk, TranslatePk2,
26+
Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk,
2727
};
2828
use std::str::FromStr;
2929

@@ -34,7 +34,7 @@ use bitcoin::secp256k1;
3434
#[derive(Clone, Debug)]
3535
pub struct PubData {
3636
pub pks: Vec<bitcoin::PublicKey>,
37-
pub x_only_pks: Vec<bitcoin::schnorr::XOnlyPublicKey>,
37+
pub x_only_pks: Vec<bitcoin::XOnlyPublicKey>,
3838
pub sha256: sha256::Hash,
3939
pub hash256: sha256d::Hash,
4040
pub ripemd160: ripemd160::Hash,
@@ -44,7 +44,7 @@ pub struct PubData {
4444
#[derive(Debug, Clone)]
4545
pub struct SecretData {
4646
pub sks: Vec<bitcoin::secp256k1::SecretKey>,
47-
pub x_only_keypairs: Vec<bitcoin::schnorr::KeyPair>,
47+
pub x_only_keypairs: Vec<bitcoin::KeyPair>,
4848
pub sha256_pre: [u8; 32],
4949
pub hash256_pre: [u8; 32],
5050
pub ripemd160_pre: [u8; 32],
@@ -62,8 +62,8 @@ fn setup_keys(
6262
) -> (
6363
Vec<bitcoin::secp256k1::SecretKey>,
6464
Vec<miniscript::bitcoin::PublicKey>,
65-
Vec<bitcoin::schnorr::KeyPair>,
66-
Vec<bitcoin::schnorr::XOnlyPublicKey>,
65+
Vec<bitcoin::KeyPair>,
66+
Vec<bitcoin::XOnlyPublicKey>,
6767
) {
6868
let secp_sign = secp256k1::Secp256k1::signing_only();
6969
let mut sk = [0; 32];
@@ -87,8 +87,8 @@ fn setup_keys(
8787
let mut x_only_pks = vec![];
8888

8989
for i in 0..n {
90-
let keypair = bitcoin::schnorr::KeyPair::from_secret_key(&secp_sign, sks[i]);
91-
let xpk = bitcoin::schnorr::XOnlyPublicKey::from_keypair(&keypair);
90+
let keypair = bitcoin::KeyPair::from_secret_key(&secp_sign, sks[i]);
91+
let xpk = bitcoin::XOnlyPublicKey::from_keypair(&keypair);
9292
x_only_keypairs.push(keypair);
9393
x_only_pks.push(xpk);
9494
}

0 commit comments

Comments
 (0)