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