|
| 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 | +} |
0 commit comments