|
| 1 | +//! # rust-miniscript integration test |
| 2 | +//! |
| 3 | +//! Arith expression fragment integration tests |
| 4 | +//! |
| 5 | +
|
| 6 | +use elements::pset::PartiallySignedTransaction as Psbt; |
| 7 | +use elements::sighash::SigHashCache; |
| 8 | +use elements::taproot::{LeafVersion, TapLeafHash}; |
| 9 | +use elements::{ |
| 10 | + self, confidential, pset as psbt, secp256k1_zkp as secp256k1, sighash, OutPoint, Script, TxIn, |
| 11 | + TxOut, Txid, |
| 12 | +}; |
| 13 | +use elementsd::ElementsD; |
| 14 | +use miniscript::miniscript::iter; |
| 15 | +use miniscript::psbt::{PsbtExt, PsbtInputExt}; |
| 16 | +use miniscript::{Descriptor, MiniscriptKey, ToPublicKey}; |
| 17 | +use rand::RngCore; |
| 18 | +mod setup; |
| 19 | +use setup::test_util::{self, TestData, PARAMS}; |
| 20 | +use setup::Call; |
| 21 | +use {actual_rand as rand, elements_miniscript as miniscript}; |
| 22 | + |
| 23 | +// Find the Outpoint by value. |
| 24 | +// Ideally, we should find by scriptPubkey, but this |
| 25 | +// works for temp test case |
| 26 | +fn get_vout(cl: &ElementsD, txid: Txid, value: u64, spk: Script) -> (OutPoint, TxOut) { |
| 27 | + let tx = cl.get_transaction(&txid); |
| 28 | + for (i, txout) in tx.output.into_iter().enumerate() { |
| 29 | + if txout.value == confidential::Value::Explicit(value) && txout.script_pubkey == spk { |
| 30 | + return (OutPoint::new(txid, i as u32), txout); |
| 31 | + } |
| 32 | + } |
| 33 | + unreachable!("Only call get vout on functions which have the expected outpoint"); |
| 34 | +} |
| 35 | + |
| 36 | +pub fn test_desc_satisfy(cl: &ElementsD, testdata: &TestData, desc: &str) -> Vec<Vec<u8>> { |
| 37 | + /* Convert desc into elements one by adding a prefix*/ |
| 38 | + let desc = format!("el{}", desc); |
| 39 | + // |
| 40 | + let secp = secp256k1::Secp256k1::new(); |
| 41 | + let xonly_keypairs = &testdata.secretdata.x_only_keypairs; |
| 42 | + let x_only_pks = &testdata.pubdata.x_only_pks; |
| 43 | + // Generate some blocks |
| 44 | + cl.generate(1); |
| 45 | + |
| 46 | + let desc = test_util::parse_test_desc(&desc, &testdata.pubdata); |
| 47 | + let derived_desc = desc.derived_descriptor(&secp, 0).unwrap(); |
| 48 | + // Next send some btc to each address corresponding to the miniscript |
| 49 | + let txid = cl.send_to_address( |
| 50 | + &derived_desc.address(&PARAMS).unwrap(), // No blinding |
| 51 | + "1", // 1 BTC |
| 52 | + ); |
| 53 | + // Wait for the funds to mature. |
| 54 | + cl.generate(2); |
| 55 | + // Create a PSBT for each transaction. |
| 56 | + // Spend one input and spend one output for simplicity. |
| 57 | + let mut psbt = Psbt::new_v2(); |
| 58 | + // figure out the outpoint from the txid |
| 59 | + let (outpoint, witness_utxo) = get_vout(&cl, txid, 100_000_000, derived_desc.script_pubkey()); |
| 60 | + let txin = TxIn { |
| 61 | + previous_output: outpoint, |
| 62 | + is_pegin: false, |
| 63 | + has_issuance: false, |
| 64 | + script_sig: Script::new(), |
| 65 | + sequence: 1, |
| 66 | + asset_issuance: Default::default(), |
| 67 | + witness: Default::default(), |
| 68 | + }; |
| 69 | + psbt.add_input(psbt::Input::from_txin(txin)); |
| 70 | + // Get a new script pubkey from the node so that |
| 71 | + // the node wallet tracks the receiving transaction |
| 72 | + // and we can check it by gettransaction RPC. |
| 73 | + let addr = cl.get_new_address(); |
| 74 | + let out = TxOut { |
| 75 | + // Had to decrease 'value', so that fees can be increased |
| 76 | + // (Was getting insufficient fees error, for deep script trees) |
| 77 | + value: confidential::Value::Explicit(99_997_000), |
| 78 | + script_pubkey: addr.script_pubkey(), |
| 79 | + asset: witness_utxo.asset, |
| 80 | + nonce: confidential::Nonce::Null, |
| 81 | + witness: Default::default(), |
| 82 | + }; |
| 83 | + psbt.add_output(psbt::Output::from_txout(out)); |
| 84 | + // ELEMENTS: Add fee output |
| 85 | + let fee_out = TxOut::new_fee(3_000, witness_utxo.asset.explicit().unwrap()); |
| 86 | + psbt.add_output(psbt::Output::from_txout(fee_out)); |
| 87 | + |
| 88 | + psbt.inputs_mut()[0] |
| 89 | + .update_with_descriptor_unchecked(&desc) |
| 90 | + .unwrap(); |
| 91 | + psbt.inputs_mut()[0].witness_utxo = Some(witness_utxo.clone()); |
| 92 | + |
| 93 | + // -------------------------------------------- |
| 94 | + // Sign the transactions with all keys |
| 95 | + // AKA the signer role of psbt |
| 96 | + // Get all the pubkeys and the corresponding secret keys |
| 97 | + |
| 98 | + let unsigned_tx = &psbt.extract_tx().unwrap(); |
| 99 | + let mut sighash_cache = SigHashCache::new(unsigned_tx); |
| 100 | + match derived_desc { |
| 101 | + Descriptor::TrExt(ref tr) => { |
| 102 | + let hash_ty = sighash::SchnorrSigHashType::Default; |
| 103 | + |
| 104 | + let prevouts = [witness_utxo.clone()]; |
| 105 | + let prevouts = sighash::Prevouts::All(&prevouts); |
| 106 | + // ------------------ script spend ------------- |
| 107 | + let x_only_keypairs_reqd: Vec<(secp256k1::KeyPair, TapLeafHash)> = tr |
| 108 | + .iter_scripts() |
| 109 | + .flat_map(|(_depth, ms)| { |
| 110 | + let leaf_hash = TapLeafHash::from_script(&ms.encode(), LeafVersion::default()); |
| 111 | + ms.iter_pk_pkh().filter_map(move |pk_pkh| match pk_pkh { |
| 112 | + iter::PkPkh::PlainPubkey(pk) => { |
| 113 | + let i = x_only_pks.iter().position(|&x| x.to_public_key() == pk); |
| 114 | + i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) |
| 115 | + } |
| 116 | + iter::PkPkh::HashedPubkey(hash) => { |
| 117 | + let i = x_only_pks |
| 118 | + .iter() |
| 119 | + .position(|&x| x.to_public_key().to_pubkeyhash() == hash); |
| 120 | + i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) |
| 121 | + } |
| 122 | + }) |
| 123 | + }) |
| 124 | + .collect(); |
| 125 | + for (keypair, leaf_hash) in x_only_keypairs_reqd { |
| 126 | + let sighash_msg = sighash_cache |
| 127 | + .taproot_script_spend_signature_hash( |
| 128 | + 0, |
| 129 | + &prevouts, |
| 130 | + leaf_hash, |
| 131 | + hash_ty, |
| 132 | + testdata.pubdata.genesis_hash, |
| 133 | + ) |
| 134 | + .unwrap(); |
| 135 | + let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap(); |
| 136 | + let mut aux_rand = [0u8; 32]; |
| 137 | + rand::thread_rng().fill_bytes(&mut aux_rand); |
| 138 | + let sig = secp.sign_schnorr_with_aux_rand(&msg, &keypair, &aux_rand); |
| 139 | + // FIXME: uncomment when == is supported for secp256k1::KeyPair. (next major release) |
| 140 | + // let x_only_pk = pks[xonly_keypairs.iter().position(|&x| x == keypair).unwrap()]; |
| 141 | + // Just recalc public key |
| 142 | + let x_only_pk = secp256k1::XOnlyPublicKey::from_keypair(&keypair); |
| 143 | + psbt.inputs_mut()[0].tap_script_sigs.insert( |
| 144 | + (x_only_pk, leaf_hash), |
| 145 | + elements::SchnorrSig { |
| 146 | + sig, |
| 147 | + hash_ty: hash_ty, |
| 148 | + }, |
| 149 | + ); |
| 150 | + } |
| 151 | + } |
| 152 | + _ => { |
| 153 | + // Non-tr descriptors |
| 154 | + panic!("Only testing Tr covenant descriptor") |
| 155 | + } |
| 156 | + } |
| 157 | + // Add the hash preimages to the psbt |
| 158 | + psbt.inputs_mut()[0].sha256_preimages.insert( |
| 159 | + testdata.pubdata.sha256, |
| 160 | + testdata.secretdata.sha256_pre.to_vec(), |
| 161 | + ); |
| 162 | + println!("Testing descriptor: {}", desc); |
| 163 | + // Finalize the transaction using psbt |
| 164 | + // Let miniscript do it's magic! |
| 165 | + if let Err(e) = psbt.finalize_mall_mut(&secp, testdata.pubdata.genesis_hash) { |
| 166 | + // All miniscripts should satisfy |
| 167 | + panic!( |
| 168 | + "Could not satisfy non-malleably: error{} desc:{} ", |
| 169 | + e[0], desc |
| 170 | + ); |
| 171 | + } |
| 172 | + let tx = psbt |
| 173 | + .extract(&secp, testdata.pubdata.genesis_hash) |
| 174 | + .expect("Extraction error"); |
| 175 | + |
| 176 | + // Send the transactions to bitcoin node for mining. |
| 177 | + // Regtest mode has standardness checks |
| 178 | + // Check whether the node accepts the transactions |
| 179 | + let txid = cl.send_raw_transaction(&tx); |
| 180 | + |
| 181 | + // Finally mine the blocks and await confirmations |
| 182 | + let _blocks = cl.generate(1); |
| 183 | + // Get the required transactions from the node mined in the blocks. |
| 184 | + // Check whether the transaction is mined in blocks |
| 185 | + // Assert that the confirmations are > 0. |
| 186 | + let num_conf = cl.call("gettransaction", &[txid.to_string().into()])["confirmations"] |
| 187 | + .as_u64() |
| 188 | + .unwrap(); |
| 189 | + assert!(num_conf > 0); |
| 190 | + tx.input[0].witness.script_witness.clone() |
| 191 | +} |
| 192 | + |
| 193 | +#[rustfmt::skip] |
| 194 | +fn test_descs(cl: &ElementsD, testdata: &mut TestData) { |
| 195 | + // K : Compressed key available |
| 196 | + // K!: Compressed key with corresponding secret key unknown |
| 197 | + // X: X-only key available |
| 198 | + // X!: X-only key with corresponding secret key unknown |
| 199 | + |
| 200 | + // Test 1: Simple spend with internal key |
| 201 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_asset(exp_asset)))"); |
| 202 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_asset(curr_inp_asset)))"); |
| 203 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_asset(inp_asset(0))))"); |
| 204 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_asset(out_asset(1))))"); |
| 205 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),asset_eq(curr_inp_asset,inp_asset(0))))"); |
| 206 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),asset_eq(curr_inp_asset,out_asset(0))))"); |
| 207 | + |
| 208 | + // same tests for values |
| 209 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(exp_value)))"); |
| 210 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(curr_inp_value)))"); |
| 211 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(inp_value(0))))"); |
| 212 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),is_exp_value(out_value(1))))"); |
| 213 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(curr_inp_value,inp_value(0))))"); |
| 214 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(out_value(0),out_value(0))))"); |
| 215 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),value_eq(out_value(1),out_value(1))))"); |
| 216 | + |
| 217 | + // same tests for spks |
| 218 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(out_spk(1),out_spk(1))))"); |
| 219 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(spk_v1,spk_v1)))"); |
| 220 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),spk_eq(curr_inp_spk,inp_spk(0))))"); |
| 221 | + |
| 222 | + // Testing the current input index |
| 223 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X2),curr_idx_eq(0)))"); |
| 224 | + |
| 225 | + // test some misc combinations with other miniscript fragments |
| 226 | + test_desc_satisfy(cl, testdata, |
| 227 | + "tr(X!,and_v(v:pk(X1),and_v(v:is_exp_value(out_value(0)),is_exp_asset(out_asset(0)))))", |
| 228 | + ); |
| 229 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X1),and_v(v:value_eq(conf_value,conf_value),spk_eq(spk,spk))))"); |
| 230 | + test_desc_satisfy(cl, testdata,"tr(X!,and_v(v:pk(X1),and_v(v:value_eq(conf_value,conf_value),and_v(v:spk_eq(spk,spk),curr_idx_eq(0)))))"); |
| 231 | +} |
| 232 | + |
| 233 | +#[test] |
| 234 | +fn test_introspect() { |
| 235 | + let (cl, _, genesis_hash) = &setup::setup(false); |
| 236 | + let mut testdata = TestData::new_fixed_data(50, *genesis_hash); |
| 237 | + test_descs(cl, &mut testdata); |
| 238 | +} |
0 commit comments