From 967b95e2e1ff4122c44e7b5d041cbeca5e7b897d Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 25 May 2022 19:39:59 -0700 Subject: [PATCH 1/3] Add support for running integration tests via cargo test Future commits will remove the hacky integration test setup. There are still some annoying warnings present for ununsed functions when they are really used. https://github.com/rust-lang/rust/issues/46379 Disambiguiate rand feature with dev dependancy https://users.rust-lang.org/t/features-and-dependencies-cannot-have-the-same-name/47746 --- Cargo.toml | 5 + tests/setup/mod.rs | 29 +++ tests/setup/test_util.rs | 308 ++++++++++++++++++++++++++++++ tests/test_cpp.rs | 249 ++++++++++++++++++++++++ tests/test_desc.rs | 400 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 991 insertions(+) create mode 100644 tests/setup/mod.rs create mode 100644 tests/setup/test_util.rs create mode 100644 tests/test_cpp.rs create mode 100644 tests/test_desc.rs diff --git a/Cargo.toml b/Cargo.toml index 57da5b57d..150e41d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,11 @@ rand = ["bitcoin/rand"] bitcoin = "0.28.0" serde = { version = "1.0", optional = true} +[dev-dependencies] +bitcoind = {version = "0.26.1", features=["22_0"]} +actual-rand = { package = "rand", version = "0.8.4"} +bitcoin = { version = "0.28", features = ["rand"]} + [[example]] name = "htlc" required-features = ["compiler"] diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs new file mode 100644 index 000000000..9ff0eff13 --- /dev/null +++ b/tests/setup/mod.rs @@ -0,0 +1,29 @@ +extern crate miniscript; + +use bitcoind::bitcoincore_rpc::RpcApi; +use bitcoind::BitcoinD; +use miniscript::bitcoin; + +pub mod test_util; + +// Launch an instance of bitcoind with +pub fn setup() -> BitcoinD { + let exe_path = bitcoind::exe_path().unwrap(); + let bitcoind = bitcoind::BitcoinD::new(exe_path).unwrap(); + let cl = &bitcoind.client; + // generate to an address by the wallet. And wait for funds to mature + let addr = cl.get_new_address(None, None).unwrap(); + let blks = cl.generate_to_address(101, &addr).unwrap(); + assert_eq!(blks.len(), 101); + + assert_eq!( + cl.get_balance(Some(1) /*min conf*/, None).unwrap(), + bitcoin::Amount::from_sat(100_000_000 * 50) + ); + bitcoind +} + +#[test] +fn test_setup() { + setup(); +} diff --git a/tests/setup/test_util.rs b/tests/setup/test_util.rs new file mode 100644 index 000000000..f5490d492 --- /dev/null +++ b/tests/setup/test_util.rs @@ -0,0 +1,308 @@ +//! # Miniscript integration test file format +//! +//! This file has custom parsing for miniscripts that enables satisfier to spend transaction +//! +//! K : Compressed key available +//! K!: Compressed key with corresponding secret key unknown +//! X: X-only key available +//! X!: X-only key with corresponding secret key unknown +//! +//! Example: +//! pk(K1)/pkh(X1)/multi(n,...K3,...) represents a compressed key 'K1'/(X-only key 'X1') whose private key in known by the wallet +//! pk(K2!)/pkh(K3!)/multi(n,...K5!,...) represents a key 'K' whose private key is NOT known to the test wallet +//! sha256(H)/hash256(H)/ripemd160(H)/hash160(H) is hash node whose preimage is known to wallet +//! sha256(H!)/hash256(H!)/ripemd160(H!)/hash160(H!) is hash node whose preimage is *NOT* known to wallet +//! timelocks are taken from the transaction value. +//! +//! The keys/hashes are automatically translated so that the tests knows how to satisfy things that don't end with ! +//! + +use std::str::FromStr; + +use actual_rand as rand; +use bitcoin::hashes::hex::ToHex; +use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; +use bitcoin::secp256k1; +use miniscript::descriptor::{SinglePub, SinglePubKey}; +use miniscript::{Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk}; +use rand::RngCore; + +#[derive(Clone, Debug)] +pub struct PubData { + pub pks: Vec, + pub x_only_pks: Vec, + pub sha256: sha256::Hash, + pub hash256: sha256d::Hash, + pub ripemd160: ripemd160::Hash, + pub hash160: hash160::Hash, +} + +#[derive(Debug, Clone)] +pub struct SecretData { + pub sks: Vec, + pub x_only_keypairs: Vec, + pub sha256_pre: [u8; 32], + pub hash256_pre: [u8; 32], + pub ripemd160_pre: [u8; 32], + pub hash160_pre: [u8; 32], +} +#[derive(Debug, Clone)] +pub struct TestData { + pub pubdata: PubData, + pub secretdata: SecretData, +} + +// Setup (sk, pk) pairs +fn setup_keys( + n: usize, +) -> ( + Vec, + Vec, + Vec, + Vec, +) { + let secp_sign = secp256k1::Secp256k1::signing_only(); + let mut sk = [0; 32]; + let mut sks = vec![]; + let mut pks = vec![]; + for i in 1..n + 1 { + sk[0] = i as u8; + sk[1] = (i >> 8) as u8; + sk[2] = (i >> 16) as u8; + + let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"); + let pk = miniscript::bitcoin::PublicKey { + inner: secp256k1::PublicKey::from_secret_key(&secp_sign, &sk), + compressed: true, + }; + pks.push(pk); + sks.push(sk); + } + + let mut x_only_keypairs = vec![]; + let mut x_only_pks = vec![]; + + for i in 0..n { + let keypair = bitcoin::KeyPair::from_secret_key(&secp_sign, sks[i]); + let xpk = bitcoin::XOnlyPublicKey::from_keypair(&keypair); + x_only_keypairs.push(keypair); + x_only_pks.push(xpk); + } + (sks, pks, x_only_keypairs, x_only_pks) +} + +impl TestData { + // generate a fixed data for n keys + pub(crate) fn new_fixed_data(n: usize) -> Self { + let (sks, pks, x_only_keypairs, x_only_pks) = setup_keys(n); + let sha256_pre = [0x12 as u8; 32]; + let sha256 = sha256::Hash::hash(&sha256_pre); + let hash256_pre = [0x34 as u8; 32]; + let hash256 = sha256d::Hash::hash(&hash256_pre); + let hash160_pre = [0x56 as u8; 32]; + let hash160 = hash160::Hash::hash(&hash160_pre); + let ripemd160_pre = [0x78 as u8; 32]; + let ripemd160 = ripemd160::Hash::hash(&ripemd160_pre); + + let pubdata = PubData { + pks, + sha256, + hash256, + ripemd160, + hash160, + x_only_pks, + }; + let secretdata = SecretData { + sks, + sha256_pre, + hash256_pre, + ripemd160_pre, + hash160_pre, + x_only_keypairs, + }; + Self { + pubdata, + secretdata, + } + } +} + +/// Obtain an insecure random public key with unknown secret key for testing +pub fn random_pk(mut seed: u8) -> bitcoin::PublicKey { + loop { + let mut data = [0; 33]; + for byte in &mut data[..] { + *byte = seed; + // totally a rng + seed = seed.wrapping_mul(41).wrapping_add(53); + } + data[0] = 2 + (data[0] >> 7); + if let Ok(key) = bitcoin::PublicKey::from_slice(&data[..33]) { + return key; + } + } +} + +/// Parse an insane miniscript into a miniscript with the format described above at file header +pub fn parse_insane_ms( + ms: &str, + pubdata: &PubData, +) -> Miniscript { + let ms = subs_hash_frag(ms, pubdata); + let ms = + Miniscript::::from_str_insane(&ms).expect("only parsing valid minsicripts"); + let mut i = 0; + let mut j = pubdata.pks.len(); + let ms = ms.translate_pk_infallible( + &mut |pk_str: &String| { + let avail = !pk_str.ends_with("!"); + if avail { + i = i + 1; + if pk_str.starts_with("K") { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + }) + } else if pk_str.starts_with("X") { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), + }) + } else { + // Parse any other keys as known to allow compatibility with existing tests + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + }) + } + } else { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(random_pk(59)), + }) + } + }, + &mut |pk_str: &String| { + let avail = !pk_str.ends_with("!"); + if avail { + j = j - 1; + if pk_str.starts_with("K") { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + }) + } else if pk_str.starts_with("X") { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), + }) + } else { + // Parse any other keys as known to allow compatibility with existing tests + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + }) + } + } else { + DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(random_pk(59)), + }) + } + }, + ); + ms +} + +pub fn parse_test_desc(desc: &str, pubdata: &PubData) -> Descriptor { + let desc = subs_hash_frag(desc, pubdata); + let desc = + Descriptor::::from_str(&desc).expect("only parsing valid and sane descriptors"); + let mut i = 0; + let mut j = pubdata.pks.len(); + let desc: Result<_, ()> = desc.translate_pk( + &mut |pk_str: &'_ String| { + let avail = !pk_str.ends_with("!"); + if avail { + i = i + 1; + if pk_str.starts_with("K") { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + })) + } else if pk_str.starts_with("X") { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), + })) + } else { + panic!("Key must start with either K or X") + } + } else { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(random_pk(59)), + })) + } + }, + &mut |pkh_str: &'_ String| { + let avail = !pkh_str.ends_with("!"); + if avail { + j = j - 1; + if pkh_str.starts_with("K") { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + })) + } else if pkh_str.starts_with("X") { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), + })) + } else { + panic!("Key must start with either K or X") + } + } else { + Ok(DescriptorPublicKey::Single(SinglePub { + origin: None, + key: SinglePubKey::FullKey(random_pk(61)), + })) + } + }, + ); + desc.expect("Translate must succeed") +} + +// substitute hash fragments in the string as the per rules +fn subs_hash_frag(ms: &str, pubdata: &PubData) -> String { + let ms = ms.replace( + "sha256(H)", + &format!("sha256({})", &pubdata.sha256.to_hex()), + ); + let ms = ms.replace( + "hash256(H)", + &format!("hash256({})", &pubdata.hash256.into_inner().to_hex()), + ); + let ms = ms.replace( + "ripemd160(H)", + &format!("ripemd160({})", &pubdata.ripemd160.to_hex()), + ); + let ms = ms.replace( + "hash160(H)", + &format!("hash160({})", &pubdata.hash160.to_hex()), + ); + + let mut rand_hash32 = [0u8; 32]; + rand::thread_rng().fill_bytes(&mut rand_hash32); + + let mut rand_hash20 = [0u8; 20]; + rand::thread_rng().fill_bytes(&mut rand_hash20); + let ms = ms.replace("sha256(H!)", &format!("sha256({})", rand_hash32.to_hex())); + let ms = ms.replace("hash256(H!)", &format!("hash256({})", rand_hash32.to_hex())); + let ms = ms.replace( + "ripemd160(H!)", + &format!("ripemd160({})", rand_hash20.to_hex()), + ); + let ms = ms.replace("hash160(H!)", &format!("hash160({})", rand_hash20.to_hex())); + ms +} diff --git a/tests/test_cpp.rs b/tests/test_cpp.rs new file mode 100644 index 000000000..70e7f0daa --- /dev/null +++ b/tests/test_cpp.rs @@ -0,0 +1,249 @@ +//! # rust-miniscript integration test +//! +//! Read Miniscripts from file and translate into miniscripts +//! which we know how to satisfy +//! + +use std::collections::BTreeMap; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +use bitcoin::secp256k1::{self, Secp256k1}; +use bitcoin::util::psbt; +use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; +use bitcoin::{self, Amount, OutPoint, Transaction, TxIn, TxOut, Txid}; +use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; +use miniscript::miniscript::iter; +use miniscript::psbt::PsbtExt; +use miniscript::{Descriptor, Miniscript, MiniscriptKey, Segwitv0}; + +mod setup; +use setup::test_util::{self, PubData, TestData}; + +// parse ~30 miniscripts from file +pub(crate) fn parse_miniscripts( + secp: &Secp256k1, + pubdata: &PubData, +) -> Vec> { + // File must exist in current path before this produces output + let mut desc_vec = vec![]; + if let Ok(lines) = read_lines("./random_ms.txt") { + // Consumes the iterator, returns an (Optional) String + for line in lines { + let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata); + let wsh = Descriptor::new_wsh(ms).unwrap(); + desc_vec.push(wsh.derived_descriptor(secp, 0).unwrap()); + } + } + desc_vec +} + +// The output is wrapped in a Result to allow matching on errors +// Returns an Iterator to the Reader of the lines of the file. +fn read_lines

(filename: P) -> io::Result>> +where + P: AsRef, +{ + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} + +/// Quickly create a BTC amount. +fn btc>(btc: F) -> Amount { + Amount::from_btc(btc.into()).unwrap() +} + +// Find the Outpoint by value. +// Ideally, we should find by scriptPubkey, but this +// works for temp test case +fn get_vout(cl: &Client, txid: Txid, value: u64) -> (OutPoint, TxOut) { + let tx = cl + .get_transaction(&txid, None) + .unwrap() + .transaction() + .unwrap(); + for (i, txout) in tx.output.into_iter().enumerate() { + if txout.value == value { + return (OutPoint::new(txid, i as u32), txout); + } + } + unreachable!("Only call get vout on functions which have the expected outpoint"); +} + +pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { + let secp = secp256k1::Secp256k1::new(); + let desc_vec = parse_miniscripts(&secp, &testdata.pubdata); + let sks = &testdata.secretdata.sks; + let pks = &testdata.pubdata.pks; + // Generate some blocks + let blocks = cl + .generate_to_address(500, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + assert_eq!(blocks.len(), 500); + + // Next send some btc to each address corresponding to the miniscript + let mut txids = vec![]; + for wsh in desc_vec.iter() { + let txid = cl + .send_to_address( + &wsh.address(bitcoin::Network::Regtest).unwrap(), + btc(1), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + txids.push(txid); + } + // Wait for the funds to mature. + let blocks = cl + .generate_to_address(50, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + assert_eq!(blocks.len(), 50); + // Create a PSBT for each transaction. + // Spend one input and spend one output for simplicity. + let mut psbts = vec![]; + for (desc, txid) in desc_vec.iter().zip(txids) { + let mut psbt = Psbt { + unsigned_tx: Transaction { + version: 2, + lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) + input: vec![], + output: vec![], + }, + unknown: BTreeMap::new(), + proprietary: BTreeMap::new(), + xpub: BTreeMap::new(), + version: 0, + inputs: vec![], + outputs: vec![], + }; + // figure out the outpoint from the txid + let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).as_sat()); + let mut txin = TxIn::default(); + txin.previous_output = outpoint; + // set the sequence to a non-final number for the locktime transactions to be + // processed correctly. + // We waited 50 blocks, keep 49 for safety + txin.sequence = 49; + psbt.unsigned_tx.input.push(txin); + // Get a new script pubkey from the node so that + // the node wallet tracks the receiving transaction + // and we can check it by gettransaction RPC. + let addr = cl + .get_new_address(None, Some(json::AddressType::Bech32)) + .unwrap(); + psbt.unsigned_tx.output.push(TxOut { + value: 99_999_000, + script_pubkey: addr.script_pubkey(), + }); + let mut input = psbt::Input::default(); + input.witness_utxo = Some(witness_utxo); + input.witness_script = Some(desc.explicit_script().unwrap()); + psbt.inputs.push(input); + psbt.outputs.push(psbt::Output::default()); + psbts.push(psbt); + } + + let mut spend_txids = vec![]; + // Sign the transactions with all keys + // AKA the signer role of psbt + for i in 0..psbts.len() { + // Get all the pubkeys and the corresponding secret keys + let ms: Miniscript = + Miniscript::parse_insane(psbts[i].inputs[0].witness_script.as_ref().unwrap()).unwrap(); + + let sks_reqd: Vec<_> = ms + .iter_pk_pkh() + .map(|pk_pkh| match pk_pkh { + iter::PkPkh::PlainPubkey(pk) => sks[pks.iter().position(|&x| x == pk).unwrap()], + iter::PkPkh::HashedPubkey(hash) => { + sks[pks + .iter() + .position(|&pk| pk.to_pubkeyhash() == hash) + .unwrap()] + } + }) + .collect(); + // Get the required sighash message + let amt = btc(1).as_sat(); + let mut sighash_cache = bitcoin::util::sighash::SighashCache::new(&psbts[i].unsigned_tx); + let sighash_ty = bitcoin::EcdsaSighashType::All; + let sighash = sighash_cache + .segwit_signature_hash(0, &ms.encode(), amt, sighash_ty) + .unwrap(); + + // requires both signing and verification because we check the tx + // after we psbt extract it + let msg = secp256k1::Message::from_slice(&sighash[..]).unwrap(); + + // Finally construct the signature and add to psbt + for sk in sks_reqd { + let sig = secp.sign_ecdsa(&msg, &sk); + let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; + psbts[i].inputs[0].partial_sigs.insert( + pk, + bitcoin::EcdsaSig { + sig, + hash_ty: sighash_ty, + }, + ); + } + // Add the hash preimages to the psbt + psbts[i].inputs[0].sha256_preimages.insert( + testdata.pubdata.sha256, + testdata.secretdata.sha256_pre.to_vec(), + ); + psbts[i].inputs[0].hash256_preimages.insert( + testdata.pubdata.hash256, + testdata.secretdata.hash256_pre.to_vec(), + ); + println!("{}", ms); + psbts[i].inputs[0].hash160_preimages.insert( + testdata.pubdata.hash160, + testdata.secretdata.hash160_pre.to_vec(), + ); + psbts[i].inputs[0].ripemd160_preimages.insert( + testdata.pubdata.ripemd160, + testdata.secretdata.ripemd160_pre.to_vec(), + ); + // Finalize the transaction using psbt + // Let miniscript do it's magic! + if let Err(e) = psbts[i].finalize_mall_mut(&secp) { + // All miniscripts should satisfy + panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i); + } else { + let tx = psbts[i].extract(&secp).unwrap(); + + // Send the transactions to bitcoin node for mining. + // Regtest mode has standardness checks + // Check whether the node accepts the transactions + let txid = cl + .send_raw_transaction(&tx) + .expect(&format!("{} send tx failed for ms {}", i, ms)); + spend_txids.push(txid); + } + } + // Finally mine the blocks and await confirmations + let _blocks = cl + .generate_to_address(10, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + // Get the required transactions from the node mined in the blocks. + for txid in spend_txids { + // Check whether the transaction is mined in blocks + // Assert that the confirmations are > 0. + let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations; + assert!(num_conf > 0); + } +} + +#[test] +fn tests_from_cpp() { + let cl = &setup::setup().client; + let testdata = TestData::new_fixed_data(50); + test_from_cpp_ms(cl, &testdata); +} diff --git a/tests/test_desc.rs b/tests/test_desc.rs new file mode 100644 index 000000000..d522c335f --- /dev/null +++ b/tests/test_desc.rs @@ -0,0 +1,400 @@ +//! # rust-miniscript integration test +//! +//! Read Miniscripts from file and translate into miniscripts +//! which we know how to satisfy +//! + +use std::collections::BTreeMap; + +use bitcoin::blockdata::witness::Witness; +use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; +use bitcoin::util::sighash::SighashCache; +use bitcoin::util::taproot::{LeafVersion, TapLeafHash}; +use bitcoin::util::{psbt, sighash}; +use bitcoin::{ + self, secp256k1, Amount, OutPoint, SchnorrSig, Script, Transaction, TxIn, TxOut, Txid, +}; +use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; +use miniscript::miniscript::iter; +use miniscript::psbt::{PsbtExt, PsbtInputExt}; +use miniscript::{Descriptor, Miniscript, MiniscriptKey, ScriptContext, ToPublicKey}; + +mod setup; + +use setup::test_util::{self, TestData}; +/// Quickly create a BTC amount. +fn btc>(btc: F) -> Amount { + Amount::from_btc(btc.into()).unwrap() +} + +// Find the Outpoint by spk +fn get_vout(cl: &Client, txid: Txid, value: u64, spk: Script) -> (OutPoint, TxOut) { + let tx = cl + .get_transaction(&txid, None) + .unwrap() + .transaction() + .unwrap(); + for (i, txout) in tx.output.into_iter().enumerate() { + if txout.value == value && spk == txout.script_pubkey { + return (OutPoint::new(txid, i as u32), txout); + } + } + unreachable!("Only call get vout on functions which have the expected outpoint"); +} + +pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: &str) -> Witness { + let secp = secp256k1::Secp256k1::new(); + let sks = &testdata.secretdata.sks; + let xonly_keypairs = &testdata.secretdata.x_only_keypairs; + let pks = &testdata.pubdata.pks; + let x_only_pks = &testdata.pubdata.x_only_pks; + // Generate some blocks + let blocks = cl + .generate_to_address(1, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + assert_eq!(blocks.len(), 1); + + let desc = test_util::parse_test_desc(&desc, &testdata.pubdata); + let derived_desc = desc.derived_descriptor(&secp, 0).unwrap(); + // Next send some btc to each address corresponding to the miniscript + let txid = cl + .send_to_address( + &derived_desc.address(bitcoin::Network::Regtest).unwrap(), + btc(1), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + // Wait for the funds to mature. + let blocks = cl + .generate_to_address(2, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + assert_eq!(blocks.len(), 2); + // Create a PSBT for each transaction. + // Spend one input and spend one output for simplicity. + let mut psbt = Psbt { + unsigned_tx: Transaction { + version: 2, + lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) + input: vec![], + output: vec![], + }, + unknown: BTreeMap::new(), + proprietary: BTreeMap::new(), + xpub: BTreeMap::new(), + version: 0, + inputs: vec![], + outputs: vec![], + }; + // figure out the outpoint from the txid + let (outpoint, witness_utxo) = + get_vout(&cl, txid, btc(1.0).as_sat(), derived_desc.script_pubkey()); + let mut txin = TxIn::default(); + txin.previous_output = outpoint; + // set the sequence to a non-final number for the locktime transactions to be + // processed correctly. + // We waited 2 blocks, keep 1 for safety + txin.sequence = 1; + psbt.unsigned_tx.input.push(txin); + // Get a new script pubkey from the node so that + // the node wallet tracks the receiving transaction + // and we can check it by gettransaction RPC. + let addr = cl + .get_new_address(None, Some(json::AddressType::Bech32)) + .unwrap(); + // Had to decrease 'value', so that fees can be increased + // (Was getting insufficient fees error, for deep script trees) + psbt.unsigned_tx.output.push(TxOut { + value: 99_997_000, + script_pubkey: addr.script_pubkey(), + }); + let mut input = psbt::Input::default(); + input.update_with_descriptor_unchecked(&desc).unwrap(); + input.witness_utxo = Some(witness_utxo.clone()); + psbt.inputs.push(input); + psbt.outputs.push(psbt::Output::default()); + + // -------------------------------------------- + // Sign the transactions with all keys + // AKA the signer role of psbt + // Get all the pubkeys and the corresponding secret keys + + let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); + match derived_desc { + Descriptor::Tr(ref tr) => { + // Fixme: take a parameter + let hash_ty = sighash::SchnorrSighashType::Default; + + let internal_key_present = x_only_pks + .iter() + .position(|&x| x.to_public_key() == *tr.internal_key()); + let internal_keypair = internal_key_present.map(|idx| xonly_keypairs[idx].clone()); + let prevouts = [witness_utxo]; + let prevouts = sighash::Prevouts::All(&prevouts); + + if let Some(mut internal_keypair) = internal_keypair { + // ---------------------- Tr key spend -------------------- + internal_keypair + .tweak_add_assign(&secp, tr.spend_info().tap_tweak().as_ref()) + .expect("Tweaking failed"); + let sighash_msg = sighash_cache + .taproot_key_spend_signature_hash(0, &prevouts, hash_ty) + .unwrap(); + let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap(); + let schnorr_sig = secp.sign_schnorr(&msg, &internal_keypair); + psbt.inputs[0].tap_key_sig = Some(SchnorrSig { + sig: schnorr_sig, + hash_ty: hash_ty, + }); + } else { + // No internal key + } + // ------------------ script spend ------------- + let x_only_keypairs_reqd: Vec<(secp256k1::KeyPair, TapLeafHash)> = tr + .iter_scripts() + .flat_map(|(_depth, ms)| { + let leaf_hash = TapLeafHash::from_script(&ms.encode(), LeafVersion::TapScript); + ms.iter_pk_pkh().filter_map(move |pk_pkh| match pk_pkh { + iter::PkPkh::PlainPubkey(pk) => { + let i = x_only_pks.iter().position(|&x| x.to_public_key() == pk); + i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) + } + iter::PkPkh::HashedPubkey(hash) => { + let i = x_only_pks + .iter() + .position(|&x| x.to_public_key().to_pubkeyhash() == hash); + i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) + } + }) + }) + .collect(); + for (keypair, leaf_hash) in x_only_keypairs_reqd { + let sighash_msg = sighash_cache + .taproot_script_spend_signature_hash(0, &prevouts, leaf_hash, hash_ty) + .unwrap(); + let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap(); + let sig = secp.sign_schnorr(&msg, &keypair); + // FIXME: uncomment when == is supported for secp256k1::KeyPair. (next major release) + // let x_only_pk = pks[xonly_keypairs.iter().position(|&x| x == keypair).unwrap()]; + // Just recalc public key + let x_only_pk = secp256k1::XOnlyPublicKey::from_keypair(&keypair); + psbt.inputs[0].tap_script_sigs.insert( + (x_only_pk, leaf_hash), + bitcoin::SchnorrSig { + sig, + hash_ty: hash_ty, + }, + ); + } + } + _ => { + // Non-tr descriptors + // Ecdsa sigs + let sks_reqd = match derived_desc { + Descriptor::Bare(bare) => find_sks_ms(&bare.as_inner(), testdata), + Descriptor::Pkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), + Descriptor::Wpkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), + Descriptor::Sh(sh) => match sh.as_inner() { + miniscript::descriptor::ShInner::Wsh(wsh) => match wsh.as_inner() { + miniscript::descriptor::WshInner::SortedMulti(ref smv) => { + let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); + find_sks_ms(&ms, testdata) + } + miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata), + }, + miniscript::descriptor::ShInner::Wpkh(pk) => { + find_sk_single_key(*pk.as_inner(), testdata) + } + miniscript::descriptor::ShInner::SortedMulti(smv) => { + let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); + find_sks_ms(&ms, testdata) + } + miniscript::descriptor::ShInner::Ms(ms) => find_sks_ms(&ms, testdata), + }, + Descriptor::Wsh(wsh) => match wsh.as_inner() { + miniscript::descriptor::WshInner::SortedMulti(ref smv) => { + let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); + find_sks_ms(&ms, testdata) + } + miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata), + }, + Descriptor::Tr(_tr) => unreachable!("Tr checked earlier"), + }; + let msg = psbt + .sighash_msg(0, &mut sighash_cache, None) + .unwrap() + .to_secp_msg(); + + // Fixme: Take a parameter + let hash_ty = bitcoin::EcdsaSighashType::All; + + // Finally construct the signature and add to psbt + for sk in sks_reqd { + let sig = secp.sign_ecdsa(&msg, &sk); + let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; + assert!(secp.verify_ecdsa(&msg, &sig, &pk.inner).is_ok()); + psbt.inputs[0].partial_sigs.insert( + pk, + bitcoin::EcdsaSig { + sig, + hash_ty: hash_ty, + }, + ); + } + } + } + // Add the hash preimages to the psbt + psbt.inputs[0].sha256_preimages.insert( + testdata.pubdata.sha256, + testdata.secretdata.sha256_pre.to_vec(), + ); + psbt.inputs[0].hash256_preimages.insert( + testdata.pubdata.hash256, + testdata.secretdata.hash256_pre.to_vec(), + ); + psbt.inputs[0].hash160_preimages.insert( + testdata.pubdata.hash160, + testdata.secretdata.hash160_pre.to_vec(), + ); + psbt.inputs[0].ripemd160_preimages.insert( + testdata.pubdata.ripemd160, + testdata.secretdata.ripemd160_pre.to_vec(), + ); + println!("Testing descriptor: {}", desc); + // Finalize the transaction using psbt + // Let miniscript do it's magic! + if let Err(e) = psbt.finalize_mut(&secp) { + // All miniscripts should satisfy + panic!( + "Could not satisfy non-malleably: error{} desc:{} ", + e[0], desc + ); + } + let tx = psbt.extract(&secp).expect("Extraction error"); + + // Send the transactions to bitcoin node for mining. + // Regtest mode has standardness checks + // Check whether the node accepts the transactions + let txid = cl + .send_raw_transaction(&tx) + .expect(&format!("send tx failed for desc {}", desc)); + + // Finally mine the blocks and await confirmations + let _blocks = cl + .generate_to_address(1, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + // Get the required transactions from the node mined in the blocks. + // Check whether the transaction is mined in blocks + // Assert that the confirmations are > 0. + let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations; + assert!(num_conf > 0); + tx.input[0].witness.clone() +} + +// Find all secret corresponding to the known public keys in ms +fn find_sks_ms( + ms: &Miniscript, + testdata: &TestData, +) -> Vec { + let sks = &testdata.secretdata.sks; + let pks = &testdata.pubdata.pks; + let sks = ms + .iter_pk_pkh() + .filter_map(|pk_pkh| match pk_pkh { + iter::PkPkh::PlainPubkey(pk) => { + let i = pks.iter().position(|&x| x.to_public_key() == pk); + i.map(|idx| (sks[idx])) + } + iter::PkPkh::HashedPubkey(hash) => { + let i = pks + .iter() + .position(|&x| x.to_public_key().to_pubkeyhash() == hash); + i.map(|idx| (sks[idx])) + } + }) + .collect(); + sks +} + +fn find_sk_single_key(pk: bitcoin::PublicKey, testdata: &TestData) -> Vec { + let sks = &testdata.secretdata.sks; + let pks = &testdata.pubdata.pks; + let i = pks.iter().position(|&x| x.to_public_key() == pk); + i.map(|idx| vec![sks[idx]]).unwrap_or(Vec::new()) +} + +fn test_descs(cl: &Client, testdata: &TestData) { + // K : Compressed key available + // K!: Compressed key with corresponding secret key unknown + // X: X-only key available + // X!: X-only key with corresponding secret key unknown + + // Test 1: Simple spend with internal key + let wit = test_desc_satisfy(cl, testdata, "tr(X)"); + assert!(wit.len() == 1); + + // Test 2: Same as above, but with leaves + let wit = test_desc_satisfy(cl, testdata, "tr(X,{pk(X1!),pk(X2!)})"); + assert!(wit.len() == 1); + + // Test 3: Force to spend with script spend. Unknown internal key and only one known script path + // X! -> Internal key unknown + // Leaf 1 -> pk(X1) with X1 known + // Leaf 2-> and_v(v:pk(X2),pk(X3!)) with partial witness only to X2 known + let wit = test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3!))})"); + assert!(wit.len() == 3); // control block, script and signature + + // Test 4: Force to spend with script spend. Unknown internal key and multiple script paths + // Should select the one with minimum weight + // X! -> Internal key unknown + // Leaf 1 -> pk(X1!) with X1 unknown + // Leaf 2-> and_v(v:pk(X2),pk(X3)) X2 and X3 known + let wit = test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3))})"); + assert!(wit.len() == 3); // control block, script and one signatures + + // Test 5: When everything is available, we should select the key spend path + let wit = test_desc_satisfy(cl, testdata, "tr(X,{pk(X1),and_v(v:pk(X2),pk(X3!))})"); + assert!(wit.len() == 1); // control block, script and signature + + // Test 6: Test the new multi_a opcodes + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(1,X2,X3!,X4!,X5!)})"); + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(2,X2,X3,X4!,X5!)})"); + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(3,X2,X3,X4,X5!)})"); + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(4,X2,X3,X4,X5)})"); + + // Test 7: Test script tree of depth 127 is valid, only X128 is known + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),{pk(X2!),{pk(X3!),{pk(X4!),{pk(X5!),{pk(X6!),{pk(X7!),{pk(X8!),{pk(X9!),{pk(X10!),{pk(X11!),{pk(X12!),{pk(X13!),{pk(X14!),{pk(X15!),{pk(X16!),{pk(X17!),{pk(X18!),{pk(X19!),{pk(X20!),{pk(X21!),{pk(X22!),{pk(X23!),{pk(X24!),{pk(X25!),{pk(X26!),{pk(X27!),{pk(X28!),{pk(X29!),{pk(X30!),{pk(X31!),{pk(X32!),{pk(X33!),{pk(X34!),{pk(X35!),{pk(X36!),{pk(X37!),{pk(X38!),{pk(X39!),{pk(X40!),{pk(X41!),{pk(X42!),{pk(X43!),{pk(X44!),{pk(X45!),{pk(X46!),{pk(X47!),{pk(X48!),{pk(X49!),{pk(X50!),{pk(X51!),{pk(X52!),{pk(X53!),{pk(X54!),{pk(X55!),{pk(X56!),{pk(X57!),{pk(X58!),{pk(X59!),{pk(X60!),{pk(X61!),{pk(X62!),{pk(X63!),{pk(X64!),{pk(X65!),{pk(X66!),{pk(X67!),{pk(X68!),{pk(X69!),{pk(X70!),{pk(X71!),{pk(X72!),{pk(X73!),{pk(X74!),{pk(X75!),{pk(X76!),{pk(X77!),{pk(X78!),{pk(X79!),{pk(X80!),{pk(X81!),{pk(X82!),{pk(X83!),{pk(X84!),{pk(X85!),{pk(X86!),{pk(X87!),{pk(X88!),{pk(X89!),{pk(X90!),{pk(X91!),{pk(X92!),{pk(X93!),{pk(X94!),{pk(X95!),{pk(X96!),{pk(X97!),{pk(X98!),{pk(X99!),{pk(X100!),{pk(X101!),{pk(X102!),{pk(X103!),{pk(X104!),{pk(X105!),{pk(X106!),{pk(X107!),{pk(X108!),{pk(X109!),{pk(X110!),{pk(X111!),{pk(X112!),{pk(X113!),{pk(X114!),{pk(X115!),{pk(X116!),{pk(X117!),{pk(X118!),{pk(X119!),{pk(X120!),{pk(X121!),{pk(X122!),{pk(X123!),{pk(X124!),{pk(X125!),{pk(X126!),{pk(X127!),pk(X128)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})"); + + // Test 8: Test script tree of depth 128 is valid, only X129 is known + test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),{pk(X2!),{pk(X3!),{pk(X4!),{pk(X5!),{pk(X6!),{pk(X7!),{pk(X8!),{pk(X9!),{pk(X10!),{pk(X11!),{pk(X12!),{pk(X13!),{pk(X14!),{pk(X15!),{pk(X16!),{pk(X17!),{pk(X18!),{pk(X19!),{pk(X20!),{pk(X21!),{pk(X22!),{pk(X23!),{pk(X24!),{pk(X25!),{pk(X26!),{pk(X27!),{pk(X28!),{pk(X29!),{pk(X30!),{pk(X31!),{pk(X32!),{pk(X33!),{pk(X34!),{pk(X35!),{pk(X36!),{pk(X37!),{pk(X38!),{pk(X39!),{pk(X40!),{pk(X41!),{pk(X42!),{pk(X43!),{pk(X44!),{pk(X45!),{pk(X46!),{pk(X47!),{pk(X48!),{pk(X49!),{pk(X50!),{pk(X51!),{pk(X52!),{pk(X53!),{pk(X54!),{pk(X55!),{pk(X56!),{pk(X57!),{pk(X58!),{pk(X59!),{pk(X60!),{pk(X61!),{pk(X62!),{pk(X63!),{pk(X64!),{pk(X65!),{pk(X66!),{pk(X67!),{pk(X68!),{pk(X69!),{pk(X70!),{pk(X71!),{pk(X72!),{pk(X73!),{pk(X74!),{pk(X75!),{pk(X76!),{pk(X77!),{pk(X78!),{pk(X79!),{pk(X80!),{pk(X81!),{pk(X82!),{pk(X83!),{pk(X84!),{pk(X85!),{pk(X86!),{pk(X87!),{pk(X88!),{pk(X89!),{pk(X90!),{pk(X91!),{pk(X92!),{pk(X93!),{pk(X94!),{pk(X95!),{pk(X96!),{pk(X97!),{pk(X98!),{pk(X99!),{pk(X100!),{pk(X101!),{pk(X102!),{pk(X103!),{pk(X104!),{pk(X105!),{pk(X106!),{pk(X107!),{pk(X108!),{pk(X109!),{pk(X110!),{pk(X111!),{pk(X112!),{pk(X113!),{pk(X114!),{pk(X115!),{pk(X116!),{pk(X117!),{pk(X118!),{pk(X119!),{pk(X120!),{pk(X121!),{pk(X122!),{pk(X123!),{pk(X124!),{pk(X125!),{pk(X126!),{pk(X127!),{pk(X128!),pk(X129)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})"); + + // Test 9: Test script complete tree having 128 leaves with depth log(128), only X1 is known + test_desc_satisfy(cl, testdata, "tr(X!,{{{{{{{pk(X1),pk(X2!)},{pk(X3!),pk(X4!)}},{{pk(X5!),pk(X6!)},{pk(X7!),pk(X8!)}}},{{{pk(X9!),pk(X10!)},{pk(X11!),pk(X12!)}},{{pk(X13!),pk(X14!)},{pk(X15!),pk(X16!)}}}},{{{{pk(X17!),pk(X18!)},{pk(X19!),pk(X20!)}},{{pk(X21!),pk(X22!)},{pk(X23!),pk(X24!)}}},{{{pk(X25!),pk(X26!)},{pk(X27!),pk(X28!)}},{{pk(X29!),pk(X30!)},{pk(X31!),pk(X32!)}}}}},{{{{{pk(X33!),pk(X34!)},{pk(X35!),pk(X36!)}},{{pk(X37!),pk(X38!)},{pk(X39!),pk(X40!)}}},{{{pk(X41!),pk(X42!)},{pk(X43!),pk(X44!)}},{{pk(X45!),pk(X46!)},{pk(X47!),pk(X48!)}}}},{{{{pk(X49!),pk(X50!)},{pk(X51!),pk(X52!)}},{{pk(X53!),pk(X54!)},{pk(X55!),pk(X56!)}}},{{{pk(X57!),pk(X58!)},{pk(X59!),pk(X60!)}},{{pk(X61!),pk(X62!)},{pk(X63!),pk(X64!)}}}}}},{{{{{{pk(X65!),pk(X66!)},{pk(X67!),pk(X68!)}},{{pk(X69!),pk(X70!)},{pk(X71!),pk(X72!)}}},{{{pk(X73!),pk(X74!)},{pk(X75!),pk(X76!)}},{{pk(X77!),pk(X78!)},{pk(X79!),pk(X80!)}}}},{{{{pk(X81!),pk(X82!)},{pk(X83!),pk(X84!)}},{{pk(X85!),pk(X86!)},{pk(X87!),pk(X88!)}}},{{{pk(X89!),pk(X90!)},{pk(X91!),pk(X92!)}},{{pk(X93!),pk(X94!)},{pk(X95!),pk(X96!)}}}}},{{{{{pk(X97!),pk(X98!)},{pk(X99!),pk(X100!)}},{{pk(X101!),pk(X102!)},{pk(X103!),pk(X104!)}}},{{{pk(X105!),pk(X106!)},{pk(X107!),pk(X108!)}},{{pk(X109!),pk(X110!)},{pk(X111!),pk(X112!)}}}},{{{{pk(X113!),pk(X114!)},{pk(X115!),pk(X116!)}},{{pk(X117!),pk(X118!)},{pk(X119!),pk(X120!)}}},{{{pk(X121!),pk(X122!)},{pk(X123!),pk(X124!)}},{{pk(X125!),pk(X126!)},{pk(X127!),pk(X128!)}}}}}}})"); + + // Misc tests for other descriptors that we support + // Keys + test_desc_satisfy(cl, testdata, "wpkh(K)"); + test_desc_satisfy(cl, testdata, "pkh(K)"); + test_desc_satisfy(cl, testdata, "sh(wpkh(K))"); + + // sorted multi + test_desc_satisfy(cl, testdata, "sh(sortedmulti(2,K1,K2,K3))"); + test_desc_satisfy(cl, testdata, "wsh(sortedmulti(2,K1,K2,K3))"); + test_desc_satisfy(cl, testdata, "sh(wsh(sortedmulti(2,K1,K2,K3)))"); + + // Miniscripts + test_desc_satisfy(cl, testdata, "sh(and_v(v:pk(K1),pk(K2)))"); + test_desc_satisfy(cl, testdata, "wsh(and_v(v:pk(K1),pk(K2)))"); + test_desc_satisfy(cl, testdata, "sh(wsh(and_v(v:pk(K1),pk(K2))))"); +} + +#[test] +fn test_satisfy() { + let testdata = TestData::new_fixed_data(50); + let cl = &setup::setup().client; + test_descs(cl, &testdata); +} From 90b5f10d4343c61c9c3630770bba7d6f21143406 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 25 May 2022 21:08:05 -0700 Subject: [PATCH 2/3] Remove warnings --- tests/setup/test_util.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/setup/test_util.rs b/tests/setup/test_util.rs index f5490d492..019b7c179 100644 --- a/tests/setup/test_util.rs +++ b/tests/setup/test_util.rs @@ -143,6 +143,8 @@ pub fn random_pk(mut seed: u8) -> bitcoin::PublicKey { } } +#[allow(dead_code)] +// https://github.com/rust-lang/rust/issues/46379. The code is pub fn and integration test, but still shows warnings /// Parse an insane miniscript into a miniscript with the format described above at file header pub fn parse_insane_ms( ms: &str, @@ -214,6 +216,8 @@ pub fn parse_insane_ms( ms } +#[allow(dead_code)] +// https://github.com/rust-lang/rust/issues/46379. The code is pub fn and integration test, but still shows warnings pub fn parse_test_desc(desc: &str, pubdata: &PubData) -> Descriptor { let desc = subs_hash_frag(desc, pubdata); let desc = From 139324a641d364e6c5f5c3659d81c937da7a935c Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 25 May 2022 21:09:03 -0700 Subject: [PATCH 3/3] Remove integration test code --- .github/workflows/rust.yml | 19 +- contrib/test.sh | 13 - integration_test/Cargo.toml | 14 - .../bitcoin-core-0.21.0.sha256sum | 1 - integration_test/bitcoin-core-22.0.sha256sum | 1 - integration_test/random_ms.txt | 34 -- integration_test/run.sh | 40 --- integration_test/src/main.rs | 132 ------- integration_test/src/test_cpp.rs | 243 ------------- integration_test/src/test_desc.rs | 326 ------------------ integration_test/src/test_util.rs | 307 ----------------- 11 files changed, 1 insertion(+), 1129 deletions(-) delete mode 100644 integration_test/Cargo.toml delete mode 100644 integration_test/bitcoin-core-0.21.0.sha256sum delete mode 100644 integration_test/bitcoin-core-22.0.sha256sum delete mode 100644 integration_test/random_ms.txt delete mode 100755 integration_test/run.sh delete mode 100644 integration_test/src/main.rs delete mode 100644 integration_test/src/test_cpp.rs delete mode 100644 integration_test/src/test_desc.rs delete mode 100644 integration_test/src/test_util.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4680dd209..94f70af99 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -69,21 +69,4 @@ jobs: - name: Running cargo env: DO_FEATURE_MATRIX: true - run: ./contrib/test.sh - - IntTests: - name: Integration tests - runs-on: ubuntu-latest - steps: - - name: Checkout Crate - uses: actions/checkout@v2 - - name: Checkout Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - name: Running cargo - env: - BITCOINVERSION: '22.0' - run: ./contrib/test.sh + run: ./contrib/test.sh \ No newline at end of file diff --git a/contrib/test.sh b/contrib/test.sh index 706ac51ca..34b4109b2 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -66,17 +66,4 @@ if [ "$DO_DOCS" = true ]; then RUSTDOCFLAGS="--cfg docsrs" cargo doc --all --features="$FEATURES" fi -# Run Integration tests if told so -if [ -n "$BITCOINVERSION" ]; then - set -e - cd integration_test - curl https://bitcoincore.org/bin/bitcoin-core-$BITCOINVERSION/bitcoin-$BITCOINVERSION-x86_64-linux-gnu.tar.gz | tar xvzf - bitcoin-$BITCOINVERSION/bin/bitcoind # will abort if the check fails. - sha256sum --check bitcoin-core-$BITCOINVERSION.sha256sum - export PATH=$PATH:$(pwd)/bitcoin-$BITCOINVERSION/bin - ./run.sh - # Cleanups - rm -rf bitcoin-$BITCOINVERSION - exit 0 -fi - exit 0 diff --git a/integration_test/Cargo.toml b/integration_test/Cargo.toml deleted file mode 100644 index 6bcba6538..000000000 --- a/integration_test/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "integration_tests_miniscript" -version = "0.1.0" -authors = ["Steven Roose ", "Sanket K "] -edition = "2018" - -[dependencies] -miniscript = {path = "../"} - -# Until 0.26 support is released on rust-bitcoincore-rpc -bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "1ee9a3e808815702ac1a4b974689fcb33b5648c3"} -bitcoin = { version = "0.28", features = ["rand"]} -log = "0.4" -rand = "0.8.4" \ No newline at end of file diff --git a/integration_test/bitcoin-core-0.21.0.sha256sum b/integration_test/bitcoin-core-0.21.0.sha256sum deleted file mode 100644 index cd92870b5..000000000 --- a/integration_test/bitcoin-core-0.21.0.sha256sum +++ /dev/null @@ -1 +0,0 @@ -7955542df199c6ce4ca0bb3966dcf9cc71199c592fec38508dad58301a3298d0 ./bitcoin-0.21.0/bin/bitcoind diff --git a/integration_test/bitcoin-core-22.0.sha256sum b/integration_test/bitcoin-core-22.0.sha256sum deleted file mode 100644 index c222da3f6..000000000 --- a/integration_test/bitcoin-core-22.0.sha256sum +++ /dev/null @@ -1 +0,0 @@ -4aebceb3caf83f27bd3dd9030da637bacc836912a65a896860dc0252d0ebfad9 ./bitcoin-22.0/bin/bitcoind diff --git a/integration_test/random_ms.txt b/integration_test/random_ms.txt deleted file mode 100644 index 4aa5d4fc8..000000000 --- a/integration_test/random_ms.txt +++ /dev/null @@ -1,34 +0,0 @@ -and_b(lltvln:after(1231488000),s:pk(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)) -uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000)) -or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16)) -j:and_v(vdv:after(1567547623),older(16)) -t:and_v(vu:hash256(H),v:sha256(H)) -t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(H)) -or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000))) -or_d(sha256(H),and_n(un:after(499999999),older(4194305))) -and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(H)) -j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898))) -and_b(older(16),s:or_d(sha256(H),n:after(1567547623))) -j:and_v(v:ripemd160(H),or_d(sha256(H),older(16))) -and_b(hash256(H),a:and_b(hash256(H),a:older(1))) -thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01)) -and_n(sha256(H),t:or_i(v:older(4252898),v:older(16))) -or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(H)) -c:and_v(or_c(sha256(H),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe)) -c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(H)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)) -and_v(andor(hash256(H),v:hash256(H),v:older(50000)),after(1231488000)) -andor(hash256(H),j:and_v(v:ripemd160(H),older(4194305)),ripemd160(H)) -or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(H)) -thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(H),a:ripemd160(H)) -and_n(sha256(H),uc:and_v(v:older(16),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce))) -and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(15),a:older(16))) -c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4)) -or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623))) -c:andor(ripemd160(H),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(H),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))) -c:andor(u:ripemd160(H),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))) -c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)) -multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00) -multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00) -thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00)) -thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01)) -c:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01) \ No newline at end of file diff --git a/integration_test/run.sh b/integration_test/run.sh deleted file mode 100755 index 5d8fb64e6..000000000 --- a/integration_test/run.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/sh - -RAND_DIR=$(head -c 4 /dev/null - -BLOCKFILTERARG="" -if bitcoind -version | grep -q "v0\.\(19\|2\)"; then - BLOCKFILTERARG="-blockfilterindex=1" -fi - -FALLBACKFEEARG="" -if bitcoind -version | grep -q -e "v0\.2" -e "v2[2-9]"; then - FALLBACKFEEARG="-fallbackfee=0.00001000" -fi - -bitcoind -regtest $FALLBACKFEEARG \ - -datadir=${TESTDIR}/1 \ - -rpcport=12348 \ - -server=1 \ - -printtoconsole=0 & -PID1=$! - -# Make sure it's listening. -sleep 3 - -RPC_URL=http://localhost:12348 \ - RPC_COOKIE=${TESTDIR}/1/regtest/.cookie \ - cargo run - -RESULT=$? - -kill -9 $PID1 -rm -rf ${TESTDIR} -exit $RESULT diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs deleted file mode 100644 index 28a6c39dd..000000000 --- a/integration_test/src/main.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! # rust-miniscript integration test -//! -//! This is how some external user would use rust-miniscript - -use bitcoincore_rpc::{Auth, Client, RpcApi}; - -mod test_cpp; -mod test_desc; -mod test_util; -use crate::test_util::TestData; - -struct StdLogger; - -impl log::Log for StdLogger { - fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { - metadata.target().contains("jsonrpc") || metadata.target().contains("bitcoincore_rpc") - } - - fn log(&self, record: &log::Record<'_>) { - if self.enabled(record.metadata()) { - println!( - "[{}][{}]: {}", - record.level(), - record.metadata().target(), - record.args() - ); - } - } - - fn flush(&self) {} -} - -static LOGGER: StdLogger = StdLogger; - -fn get_rpc_url() -> String { - return std::env::var("RPC_URL").expect("RPC_URL must be set"); -} - -fn get_auth() -> bitcoincore_rpc::Auth { - if let Ok(cookie) = std::env::var("RPC_COOKIE") { - return Auth::CookieFile(cookie.into()); - } else if let Ok(user) = std::env::var("RPC_USER") { - return Auth::UserPass(user, std::env::var("RPC_PASS").unwrap_or_default()); - } else { - panic!("Either RPC_COOKIE or RPC_USER + RPC_PASS must be set."); - }; -} - -fn main() { - log::set_logger(&LOGGER) - .map(|()| log::set_max_level(log::LevelFilter::max())) - .unwrap(); - - let rpc_url = format!("{}/wallet/testwallet", get_rpc_url()); - let auth = get_auth(); - - let cl = Client::new(&rpc_url, auth).unwrap(); - - // 0.21 does not create default wallet.. - cl.create_wallet("testwallet", None, None, None, None) - .unwrap(); - - let testdata = TestData::new_fixed_data(50); - test_cpp::test_from_cpp_ms(&cl, &testdata); - - test_descs(&cl, &testdata); -} - -fn test_descs(cl: &Client, testdata: &TestData) { - // K : Compressed key available - // K!: Compressed key with corresponding secret key unknown - // X: X-only key available - // X!: X-only key with corresponding secret key unknown - - // Test 1: Simple spend with internal key - let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X)"); - assert!(wit.len() == 1); - - // Test 2: Same as above, but with leaves - let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X,{pk(X1!),pk(X2!)})"); - assert!(wit.len() == 1); - - // Test 3: Force to spend with script spend. Unknown internal key and only one known script path - // X! -> Internal key unknown - // Leaf 1 -> pk(X1) with X1 known - // Leaf 2-> and_v(v:pk(X2),pk(X3!)) with partial witness only to X2 known - let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3!))})"); - assert!(wit.len() == 3); // control block, script and signature - - // Test 4: Force to spend with script spend. Unknown internal key and multiple script paths - // Should select the one with minimum weight - // X! -> Internal key unknown - // Leaf 1 -> pk(X1!) with X1 unknown - // Leaf 2-> and_v(v:pk(X2),pk(X3)) X2 and X3 known - let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1),and_v(v:pk(X2),pk(X3))})"); - assert!(wit.len() == 3); // control block, script and one signatures - - // Test 5: When everything is available, we should select the key spend path - let wit = test_desc::test_desc_satisfy(cl, testdata, "tr(X,{pk(X1),and_v(v:pk(X2),pk(X3!))})"); - assert!(wit.len() == 1); // control block, script and signature - - // Test 6: Test the new multi_a opcodes - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(1,X2,X3!,X4!,X5!)})"); - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(2,X2,X3,X4!,X5!)})"); - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(3,X2,X3,X4,X5!)})"); - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),multi_a(4,X2,X3,X4,X5)})"); - - // Test 7: Test script tree of depth 127 is valid, only X128 is known - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),{pk(X2!),{pk(X3!),{pk(X4!),{pk(X5!),{pk(X6!),{pk(X7!),{pk(X8!),{pk(X9!),{pk(X10!),{pk(X11!),{pk(X12!),{pk(X13!),{pk(X14!),{pk(X15!),{pk(X16!),{pk(X17!),{pk(X18!),{pk(X19!),{pk(X20!),{pk(X21!),{pk(X22!),{pk(X23!),{pk(X24!),{pk(X25!),{pk(X26!),{pk(X27!),{pk(X28!),{pk(X29!),{pk(X30!),{pk(X31!),{pk(X32!),{pk(X33!),{pk(X34!),{pk(X35!),{pk(X36!),{pk(X37!),{pk(X38!),{pk(X39!),{pk(X40!),{pk(X41!),{pk(X42!),{pk(X43!),{pk(X44!),{pk(X45!),{pk(X46!),{pk(X47!),{pk(X48!),{pk(X49!),{pk(X50!),{pk(X51!),{pk(X52!),{pk(X53!),{pk(X54!),{pk(X55!),{pk(X56!),{pk(X57!),{pk(X58!),{pk(X59!),{pk(X60!),{pk(X61!),{pk(X62!),{pk(X63!),{pk(X64!),{pk(X65!),{pk(X66!),{pk(X67!),{pk(X68!),{pk(X69!),{pk(X70!),{pk(X71!),{pk(X72!),{pk(X73!),{pk(X74!),{pk(X75!),{pk(X76!),{pk(X77!),{pk(X78!),{pk(X79!),{pk(X80!),{pk(X81!),{pk(X82!),{pk(X83!),{pk(X84!),{pk(X85!),{pk(X86!),{pk(X87!),{pk(X88!),{pk(X89!),{pk(X90!),{pk(X91!),{pk(X92!),{pk(X93!),{pk(X94!),{pk(X95!),{pk(X96!),{pk(X97!),{pk(X98!),{pk(X99!),{pk(X100!),{pk(X101!),{pk(X102!),{pk(X103!),{pk(X104!),{pk(X105!),{pk(X106!),{pk(X107!),{pk(X108!),{pk(X109!),{pk(X110!),{pk(X111!),{pk(X112!),{pk(X113!),{pk(X114!),{pk(X115!),{pk(X116!),{pk(X117!),{pk(X118!),{pk(X119!),{pk(X120!),{pk(X121!),{pk(X122!),{pk(X123!),{pk(X124!),{pk(X125!),{pk(X126!),{pk(X127!),pk(X128)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})"); - - // Test 8: Test script tree of depth 128 is valid, only X129 is known - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{pk(X1!),{pk(X2!),{pk(X3!),{pk(X4!),{pk(X5!),{pk(X6!),{pk(X7!),{pk(X8!),{pk(X9!),{pk(X10!),{pk(X11!),{pk(X12!),{pk(X13!),{pk(X14!),{pk(X15!),{pk(X16!),{pk(X17!),{pk(X18!),{pk(X19!),{pk(X20!),{pk(X21!),{pk(X22!),{pk(X23!),{pk(X24!),{pk(X25!),{pk(X26!),{pk(X27!),{pk(X28!),{pk(X29!),{pk(X30!),{pk(X31!),{pk(X32!),{pk(X33!),{pk(X34!),{pk(X35!),{pk(X36!),{pk(X37!),{pk(X38!),{pk(X39!),{pk(X40!),{pk(X41!),{pk(X42!),{pk(X43!),{pk(X44!),{pk(X45!),{pk(X46!),{pk(X47!),{pk(X48!),{pk(X49!),{pk(X50!),{pk(X51!),{pk(X52!),{pk(X53!),{pk(X54!),{pk(X55!),{pk(X56!),{pk(X57!),{pk(X58!),{pk(X59!),{pk(X60!),{pk(X61!),{pk(X62!),{pk(X63!),{pk(X64!),{pk(X65!),{pk(X66!),{pk(X67!),{pk(X68!),{pk(X69!),{pk(X70!),{pk(X71!),{pk(X72!),{pk(X73!),{pk(X74!),{pk(X75!),{pk(X76!),{pk(X77!),{pk(X78!),{pk(X79!),{pk(X80!),{pk(X81!),{pk(X82!),{pk(X83!),{pk(X84!),{pk(X85!),{pk(X86!),{pk(X87!),{pk(X88!),{pk(X89!),{pk(X90!),{pk(X91!),{pk(X92!),{pk(X93!),{pk(X94!),{pk(X95!),{pk(X96!),{pk(X97!),{pk(X98!),{pk(X99!),{pk(X100!),{pk(X101!),{pk(X102!),{pk(X103!),{pk(X104!),{pk(X105!),{pk(X106!),{pk(X107!),{pk(X108!),{pk(X109!),{pk(X110!),{pk(X111!),{pk(X112!),{pk(X113!),{pk(X114!),{pk(X115!),{pk(X116!),{pk(X117!),{pk(X118!),{pk(X119!),{pk(X120!),{pk(X121!),{pk(X122!),{pk(X123!),{pk(X124!),{pk(X125!),{pk(X126!),{pk(X127!),{pk(X128!),pk(X129)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}})"); - - // Test 9: Test script complete tree having 128 leaves with depth log(128), only X1 is known - test_desc::test_desc_satisfy(cl, testdata, "tr(X!,{{{{{{{pk(X1),pk(X2!)},{pk(X3!),pk(X4!)}},{{pk(X5!),pk(X6!)},{pk(X7!),pk(X8!)}}},{{{pk(X9!),pk(X10!)},{pk(X11!),pk(X12!)}},{{pk(X13!),pk(X14!)},{pk(X15!),pk(X16!)}}}},{{{{pk(X17!),pk(X18!)},{pk(X19!),pk(X20!)}},{{pk(X21!),pk(X22!)},{pk(X23!),pk(X24!)}}},{{{pk(X25!),pk(X26!)},{pk(X27!),pk(X28!)}},{{pk(X29!),pk(X30!)},{pk(X31!),pk(X32!)}}}}},{{{{{pk(X33!),pk(X34!)},{pk(X35!),pk(X36!)}},{{pk(X37!),pk(X38!)},{pk(X39!),pk(X40!)}}},{{{pk(X41!),pk(X42!)},{pk(X43!),pk(X44!)}},{{pk(X45!),pk(X46!)},{pk(X47!),pk(X48!)}}}},{{{{pk(X49!),pk(X50!)},{pk(X51!),pk(X52!)}},{{pk(X53!),pk(X54!)},{pk(X55!),pk(X56!)}}},{{{pk(X57!),pk(X58!)},{pk(X59!),pk(X60!)}},{{pk(X61!),pk(X62!)},{pk(X63!),pk(X64!)}}}}}},{{{{{{pk(X65!),pk(X66!)},{pk(X67!),pk(X68!)}},{{pk(X69!),pk(X70!)},{pk(X71!),pk(X72!)}}},{{{pk(X73!),pk(X74!)},{pk(X75!),pk(X76!)}},{{pk(X77!),pk(X78!)},{pk(X79!),pk(X80!)}}}},{{{{pk(X81!),pk(X82!)},{pk(X83!),pk(X84!)}},{{pk(X85!),pk(X86!)},{pk(X87!),pk(X88!)}}},{{{pk(X89!),pk(X90!)},{pk(X91!),pk(X92!)}},{{pk(X93!),pk(X94!)},{pk(X95!),pk(X96!)}}}}},{{{{{pk(X97!),pk(X98!)},{pk(X99!),pk(X100!)}},{{pk(X101!),pk(X102!)},{pk(X103!),pk(X104!)}}},{{{pk(X105!),pk(X106!)},{pk(X107!),pk(X108!)}},{{pk(X109!),pk(X110!)},{pk(X111!),pk(X112!)}}}},{{{{pk(X113!),pk(X114!)},{pk(X115!),pk(X116!)}},{{pk(X117!),pk(X118!)},{pk(X119!),pk(X120!)}}},{{{pk(X121!),pk(X122!)},{pk(X123!),pk(X124!)}},{{pk(X125!),pk(X126!)},{pk(X127!),pk(X128!)}}}}}}})"); - - // Misc tests for other descriptors that we support - // Keys - test_desc::test_desc_satisfy(cl, testdata, "wpkh(K)"); - test_desc::test_desc_satisfy(cl, testdata, "pkh(K)"); - test_desc::test_desc_satisfy(cl, testdata, "sh(wpkh(K))"); - - // sorted multi - test_desc::test_desc_satisfy(cl, testdata, "sh(sortedmulti(2,K1,K2,K3))"); - test_desc::test_desc_satisfy(cl, testdata, "wsh(sortedmulti(2,K1,K2,K3))"); - test_desc::test_desc_satisfy(cl, testdata, "sh(wsh(sortedmulti(2,K1,K2,K3)))"); - - // Miniscripts - test_desc::test_desc_satisfy(cl, testdata, "sh(and_v(v:pk(K1),pk(K2)))"); - test_desc::test_desc_satisfy(cl, testdata, "wsh(and_v(v:pk(K1),pk(K2)))"); - test_desc::test_desc_satisfy(cl, testdata, "sh(wsh(and_v(v:pk(K1),pk(K2))))"); -} diff --git a/integration_test/src/test_cpp.rs b/integration_test/src/test_cpp.rs deleted file mode 100644 index 2e6e8ab46..000000000 --- a/integration_test/src/test_cpp.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! # rust-miniscript integration test -//! -//! Read Miniscripts from file and translate into miniscripts -//! which we know how to satisfy -//! - -use bitcoin::secp256k1::{self, Secp256k1}; -use bitcoin::util::psbt; -use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; -use bitcoin::{self, Amount, OutPoint, Transaction, TxIn, TxOut, Txid}; -use bitcoincore_rpc::{json, Client, RpcApi}; -use miniscript::miniscript::iter; -use miniscript::psbt::PsbtExt; -use miniscript::MiniscriptKey; -use miniscript::Segwitv0; -use miniscript::{Descriptor, Miniscript}; -use std::collections::BTreeMap; -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; - -use super::test_util::PubData; -use crate::test_util::{self, TestData}; - -// parse ~30 miniscripts from file -pub(crate) fn parse_miniscripts( - secp: &Secp256k1, - pubdata: &PubData, -) -> Vec> { - // File must exist in current path before this produces output - let mut desc_vec = vec![]; - if let Ok(lines) = read_lines("./random_ms.txt") { - // Consumes the iterator, returns an (Optional) String - for line in lines { - let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata); - let wsh = Descriptor::new_wsh(ms).unwrap(); - desc_vec.push(wsh.derived_descriptor(secp, 0).unwrap()); - } - } - desc_vec -} - -// The output is wrapped in a Result to allow matching on errors -// Returns an Iterator to the Reader of the lines of the file. -fn read_lines

(filename: P) -> io::Result>> -where - P: AsRef, -{ - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) -} - -/// Quickly create a BTC amount. -fn btc>(btc: F) -> Amount { - Amount::from_btc(btc.into()).unwrap() -} - -// Find the Outpoint by value. -// Ideally, we should find by scriptPubkey, but this -// works for temp test case -fn get_vout(cl: &Client, txid: Txid, value: u64) -> (OutPoint, TxOut) { - let tx = cl - .get_transaction(&txid, None) - .unwrap() - .transaction() - .unwrap(); - for (i, txout) in tx.output.into_iter().enumerate() { - if txout.value == value { - return (OutPoint::new(txid, i as u32), txout); - } - } - unreachable!("Only call get vout on functions which have the expected outpoint"); -} - -pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { - let secp = secp256k1::Secp256k1::new(); - let desc_vec = parse_miniscripts(&secp, &testdata.pubdata); - let sks = &testdata.secretdata.sks; - let pks = &testdata.pubdata.pks; - // Generate some blocks - let blocks = cl - .generate_to_address(500, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - assert_eq!(blocks.len(), 500); - - // Next send some btc to each address corresponding to the miniscript - let mut txids = vec![]; - for wsh in desc_vec.iter() { - let txid = cl - .send_to_address( - &wsh.address(bitcoin::Network::Regtest).unwrap(), - btc(1), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - txids.push(txid); - } - // Wait for the funds to mature. - let blocks = cl - .generate_to_address(50, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - assert_eq!(blocks.len(), 50); - // Create a PSBT for each transaction. - // Spend one input and spend one output for simplicity. - let mut psbts = vec![]; - for (desc, txid) in desc_vec.iter().zip(txids) { - let mut psbt = Psbt { - unsigned_tx: Transaction { - version: 2, - lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) - input: vec![], - output: vec![], - }, - unknown: BTreeMap::new(), - proprietary: BTreeMap::new(), - xpub: BTreeMap::new(), - version: 0, - inputs: vec![], - outputs: vec![], - }; - // figure out the outpoint from the txid - let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).as_sat()); - let mut txin = TxIn::default(); - txin.previous_output = outpoint; - // set the sequence to a non-final number for the locktime transactions to be - // processed correctly. - // We waited 50 blocks, keep 49 for safety - txin.sequence = 49; - psbt.unsigned_tx.input.push(txin); - // Get a new script pubkey from the node so that - // the node wallet tracks the receiving transaction - // and we can check it by gettransaction RPC. - let addr = cl - .get_new_address(None, Some(json::AddressType::Bech32)) - .unwrap(); - psbt.unsigned_tx.output.push(TxOut { - value: 99_999_000, - script_pubkey: addr.script_pubkey(), - }); - let mut input = psbt::Input::default(); - input.witness_utxo = Some(witness_utxo); - input.witness_script = Some(desc.explicit_script().unwrap()); - psbt.inputs.push(input); - psbt.outputs.push(psbt::Output::default()); - psbts.push(psbt); - } - - let mut spend_txids = vec![]; - // Sign the transactions with all keys - // AKA the signer role of psbt - for i in 0..psbts.len() { - // Get all the pubkeys and the corresponding secret keys - let ms: Miniscript = - Miniscript::parse_insane(psbts[i].inputs[0].witness_script.as_ref().unwrap()).unwrap(); - - let sks_reqd: Vec<_> = ms - .iter_pk_pkh() - .map(|pk_pkh| match pk_pkh { - iter::PkPkh::PlainPubkey(pk) => sks[pks.iter().position(|&x| x == pk).unwrap()], - iter::PkPkh::HashedPubkey(hash) => { - sks[pks - .iter() - .position(|&pk| pk.to_pubkeyhash() == hash) - .unwrap()] - } - }) - .collect(); - // Get the required sighash message - let amt = btc(1).as_sat(); - let mut sighash_cache = bitcoin::util::sighash::SighashCache::new(&psbts[i].unsigned_tx); - let sighash_ty = bitcoin::EcdsaSighashType::All; - let sighash = sighash_cache - .segwit_signature_hash(0, &ms.encode(), amt, sighash_ty) - .unwrap(); - - // requires both signing and verification because we check the tx - // after we psbt extract it - let msg = secp256k1::Message::from_slice(&sighash[..]).unwrap(); - - // Finally construct the signature and add to psbt - for sk in sks_reqd { - let sig = secp.sign_ecdsa(&msg, &sk); - let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; - psbts[i].inputs[0].partial_sigs.insert( - pk, - bitcoin::EcdsaSig { - sig, - hash_ty: sighash_ty, - }, - ); - } - // Add the hash preimages to the psbt - psbts[i].inputs[0].sha256_preimages.insert( - testdata.pubdata.sha256, - testdata.secretdata.sha256_pre.to_vec(), - ); - psbts[i].inputs[0].hash256_preimages.insert( - testdata.pubdata.hash256, - testdata.secretdata.hash256_pre.to_vec(), - ); - println!("{}", ms); - psbts[i].inputs[0].hash160_preimages.insert( - testdata.pubdata.hash160, - testdata.secretdata.hash160_pre.to_vec(), - ); - psbts[i].inputs[0].ripemd160_preimages.insert( - testdata.pubdata.ripemd160, - testdata.secretdata.ripemd160_pre.to_vec(), - ); - // Finalize the transaction using psbt - // Let miniscript do it's magic! - if let Err(e) = psbts[i].finalize_mall_mut(&secp) { - // All miniscripts should satisfy - panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i); - } else { - let tx = psbts[i].extract(&secp).unwrap(); - - // Send the transactions to bitcoin node for mining. - // Regtest mode has standardness checks - // Check whether the node accepts the transactions - let txid = cl - .send_raw_transaction(&tx) - .expect(&format!("{} send tx failed for ms {}", i, ms)); - spend_txids.push(txid); - } - } - // Finally mine the blocks and await confirmations - let _blocks = cl - .generate_to_address(10, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - // Get the required transactions from the node mined in the blocks. - for txid in spend_txids { - // Check whether the transaction is mined in blocks - // Assert that the confirmations are > 0. - let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations; - assert!(num_conf > 0); - } -} diff --git a/integration_test/src/test_desc.rs b/integration_test/src/test_desc.rs deleted file mode 100644 index 789a34647..000000000 --- a/integration_test/src/test_desc.rs +++ /dev/null @@ -1,326 +0,0 @@ -//! # rust-miniscript integration test -//! -//! Read Miniscripts from file and translate into miniscripts -//! which we know how to satisfy -//! - -use bitcoin::blockdata::witness::Witness; -use bitcoin::secp256k1; -use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; -use bitcoin::util::sighash::SighashCache; -use bitcoin::util::taproot::{LeafVersion, TapLeafHash}; -use bitcoin::util::{psbt, sighash}; -use bitcoin::{self, Amount, OutPoint, SchnorrSig, Script, Transaction, TxIn, TxOut, Txid}; -use bitcoincore_rpc::{json, Client, RpcApi}; -use miniscript::miniscript::iter; -use miniscript::psbt::{PsbtExt, PsbtInputExt}; -use miniscript::{Descriptor, Miniscript, ToPublicKey}; -use miniscript::{MiniscriptKey, ScriptContext}; -use std::collections::BTreeMap; - -use crate::test_util::{self, TestData}; - -/// Quickly create a BTC amount. -fn btc>(btc: F) -> Amount { - Amount::from_btc(btc.into()).unwrap() -} - -// Find the Outpoint by spk -fn get_vout(cl: &Client, txid: Txid, value: u64, spk: Script) -> (OutPoint, TxOut) { - let tx = cl - .get_transaction(&txid, None) - .unwrap() - .transaction() - .unwrap(); - for (i, txout) in tx.output.into_iter().enumerate() { - if txout.value == value && spk == txout.script_pubkey { - return (OutPoint::new(txid, i as u32), txout); - } - } - unreachable!("Only call get vout on functions which have the expected outpoint"); -} - -pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: &str) -> Witness { - let secp = secp256k1::Secp256k1::new(); - let sks = &testdata.secretdata.sks; - let xonly_keypairs = &testdata.secretdata.x_only_keypairs; - let pks = &testdata.pubdata.pks; - let x_only_pks = &testdata.pubdata.x_only_pks; - // Generate some blocks - let blocks = cl - .generate_to_address(1, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - assert_eq!(blocks.len(), 1); - - let desc = test_util::parse_test_desc(&desc, &testdata.pubdata); - let derived_desc = desc.derived_descriptor(&secp, 0).unwrap(); - // Next send some btc to each address corresponding to the miniscript - let txid = cl - .send_to_address( - &derived_desc.address(bitcoin::Network::Regtest).unwrap(), - btc(1), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - // Wait for the funds to mature. - let blocks = cl - .generate_to_address(2, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - assert_eq!(blocks.len(), 2); - // Create a PSBT for each transaction. - // Spend one input and spend one output for simplicity. - let mut psbt = Psbt { - unsigned_tx: Transaction { - version: 2, - lock_time: 1_603_866_330, // time at 10/28/2020 @ 6:25am (UTC) - input: vec![], - output: vec![], - }, - unknown: BTreeMap::new(), - proprietary: BTreeMap::new(), - xpub: BTreeMap::new(), - version: 0, - inputs: vec![], - outputs: vec![], - }; - // figure out the outpoint from the txid - let (outpoint, witness_utxo) = - get_vout(&cl, txid, btc(1.0).as_sat(), derived_desc.script_pubkey()); - let mut txin = TxIn::default(); - txin.previous_output = outpoint; - // set the sequence to a non-final number for the locktime transactions to be - // processed correctly. - // We waited 2 blocks, keep 1 for safety - txin.sequence = 1; - psbt.unsigned_tx.input.push(txin); - // Get a new script pubkey from the node so that - // the node wallet tracks the receiving transaction - // and we can check it by gettransaction RPC. - let addr = cl - .get_new_address(None, Some(json::AddressType::Bech32)) - .unwrap(); - // Had to decrease 'value', so that fees can be increased - // (Was getting insufficient fees error, for deep script trees) - psbt.unsigned_tx.output.push(TxOut { - value: 99_997_000, - script_pubkey: addr.script_pubkey(), - }); - let mut input = psbt::Input::default(); - input.update_with_descriptor_unchecked(&desc).unwrap(); - input.witness_utxo = Some(witness_utxo.clone()); - psbt.inputs.push(input); - psbt.outputs.push(psbt::Output::default()); - - // -------------------------------------------- - // Sign the transactions with all keys - // AKA the signer role of psbt - // Get all the pubkeys and the corresponding secret keys - - let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); - match derived_desc { - Descriptor::Tr(ref tr) => { - // Fixme: take a parameter - let hash_ty = sighash::SchnorrSighashType::Default; - - let internal_key_present = x_only_pks - .iter() - .position(|&x| x.to_public_key() == *tr.internal_key()); - let internal_keypair = internal_key_present.map(|idx| xonly_keypairs[idx].clone()); - let prevouts = [witness_utxo]; - let prevouts = sighash::Prevouts::All(&prevouts); - - if let Some(mut internal_keypair) = internal_keypair { - // ---------------------- Tr key spend -------------------- - internal_keypair - .tweak_add_assign(&secp, tr.spend_info().tap_tweak().as_ref()) - .expect("Tweaking failed"); - let sighash_msg = sighash_cache - .taproot_key_spend_signature_hash(0, &prevouts, hash_ty) - .unwrap(); - let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap(); - let schnorr_sig = secp.sign_schnorr(&msg, &internal_keypair); - psbt.inputs[0].tap_key_sig = Some(SchnorrSig { - sig: schnorr_sig, - hash_ty: hash_ty, - }); - } else { - // No internal key - } - // ------------------ script spend ------------- - let x_only_keypairs_reqd: Vec<(secp256k1::KeyPair, TapLeafHash)> = tr - .iter_scripts() - .flat_map(|(_depth, ms)| { - let leaf_hash = TapLeafHash::from_script(&ms.encode(), LeafVersion::TapScript); - ms.iter_pk_pkh().filter_map(move |pk_pkh| match pk_pkh { - iter::PkPkh::PlainPubkey(pk) => { - let i = x_only_pks.iter().position(|&x| x.to_public_key() == pk); - i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) - } - iter::PkPkh::HashedPubkey(hash) => { - let i = x_only_pks - .iter() - .position(|&x| x.to_public_key().to_pubkeyhash() == hash); - i.map(|idx| (xonly_keypairs[idx].clone(), leaf_hash)) - } - }) - }) - .collect(); - for (keypair, leaf_hash) in x_only_keypairs_reqd { - let sighash_msg = sighash_cache - .taproot_script_spend_signature_hash(0, &prevouts, leaf_hash, hash_ty) - .unwrap(); - let msg = secp256k1::Message::from_slice(&sighash_msg[..]).unwrap(); - let sig = secp.sign_schnorr(&msg, &keypair); - // FIXME: uncomment when == is supported for secp256k1::KeyPair. (next major release) - // let x_only_pk = pks[xonly_keypairs.iter().position(|&x| x == keypair).unwrap()]; - // Just recalc public key - let x_only_pk = secp256k1::XOnlyPublicKey::from_keypair(&keypair); - psbt.inputs[0].tap_script_sigs.insert( - (x_only_pk, leaf_hash), - bitcoin::SchnorrSig { - sig, - hash_ty: hash_ty, - }, - ); - } - } - _ => { - // Non-tr descriptors - // Ecdsa sigs - let sks_reqd = match derived_desc { - Descriptor::Bare(bare) => find_sks_ms(&bare.as_inner(), testdata), - Descriptor::Pkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), - Descriptor::Wpkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), - Descriptor::Sh(sh) => match sh.as_inner() { - miniscript::descriptor::ShInner::Wsh(wsh) => match wsh.as_inner() { - miniscript::descriptor::WshInner::SortedMulti(ref smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } - miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata), - }, - miniscript::descriptor::ShInner::Wpkh(pk) => { - find_sk_single_key(*pk.as_inner(), testdata) - } - miniscript::descriptor::ShInner::SortedMulti(smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } - miniscript::descriptor::ShInner::Ms(ms) => find_sks_ms(&ms, testdata), - }, - Descriptor::Wsh(wsh) => match wsh.as_inner() { - miniscript::descriptor::WshInner::SortedMulti(ref smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } - miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(&ms, testdata), - }, - Descriptor::Tr(_tr) => unreachable!("Tr checked earlier"), - }; - let msg = psbt - .sighash_msg(0, &mut sighash_cache, None) - .unwrap() - .to_secp_msg(); - - // Fixme: Take a parameter - let hash_ty = bitcoin::EcdsaSighashType::All; - - // Finally construct the signature and add to psbt - for sk in sks_reqd { - let sig = secp.sign_ecdsa(&msg, &sk); - let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; - assert!(secp.verify_ecdsa(&msg, &sig, &pk.inner).is_ok()); - psbt.inputs[0].partial_sigs.insert( - pk, - bitcoin::EcdsaSig { - sig, - hash_ty: hash_ty, - }, - ); - } - } - } - // Add the hash preimages to the psbt - psbt.inputs[0].sha256_preimages.insert( - testdata.pubdata.sha256, - testdata.secretdata.sha256_pre.to_vec(), - ); - psbt.inputs[0].hash256_preimages.insert( - testdata.pubdata.hash256, - testdata.secretdata.hash256_pre.to_vec(), - ); - psbt.inputs[0].hash160_preimages.insert( - testdata.pubdata.hash160, - testdata.secretdata.hash160_pre.to_vec(), - ); - psbt.inputs[0].ripemd160_preimages.insert( - testdata.pubdata.ripemd160, - testdata.secretdata.ripemd160_pre.to_vec(), - ); - println!("Testing descriptor: {}", desc); - // Finalize the transaction using psbt - // Let miniscript do it's magic! - if let Err(e) = psbt.finalize_mut(&secp) { - // All miniscripts should satisfy - panic!( - "Could not satisfy non-malleably: error{} desc:{} ", - e[0], desc - ); - } - let tx = psbt.extract(&secp).expect("Extraction error"); - - // Send the transactions to bitcoin node for mining. - // Regtest mode has standardness checks - // Check whether the node accepts the transactions - let txid = cl - .send_raw_transaction(&tx) - .expect(&format!("send tx failed for desc {}", desc)); - - // Finally mine the blocks and await confirmations - let _blocks = cl - .generate_to_address(1, &cl.get_new_address(None, None).unwrap()) - .unwrap(); - // Get the required transactions from the node mined in the blocks. - // Check whether the transaction is mined in blocks - // Assert that the confirmations are > 0. - let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations; - assert!(num_conf > 0); - tx.input[0].witness.clone() -} - -// Find all secret corresponding to the known public keys in ms -fn find_sks_ms( - ms: &Miniscript, - testdata: &TestData, -) -> Vec { - let sks = &testdata.secretdata.sks; - let pks = &testdata.pubdata.pks; - let sks = ms - .iter_pk_pkh() - .filter_map(|pk_pkh| match pk_pkh { - iter::PkPkh::PlainPubkey(pk) => { - let i = pks.iter().position(|&x| x.to_public_key() == pk); - i.map(|idx| (sks[idx])) - } - iter::PkPkh::HashedPubkey(hash) => { - let i = pks - .iter() - .position(|&x| x.to_public_key().to_pubkeyhash() == hash); - i.map(|idx| (sks[idx])) - } - }) - .collect(); - sks -} - -fn find_sk_single_key(pk: bitcoin::PublicKey, testdata: &TestData) -> Vec { - let sks = &testdata.secretdata.sks; - let pks = &testdata.pubdata.pks; - let i = pks.iter().position(|&x| x.to_public_key() == pk); - i.map(|idx| vec![sks[idx]]).unwrap_or(Vec::new()) -} diff --git a/integration_test/src/test_util.rs b/integration_test/src/test_util.rs deleted file mode 100644 index 145a98f16..000000000 --- a/integration_test/src/test_util.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! # Miniscript integration test file format -//! -//! This file has custom parsing for miniscripts that enables satisfier to spend transaction -//! -//! K : Compressed key available -//! K!: Compressed key with corresponding secret key unknown -//! X: X-only key available -//! X!: X-only key with corresponding secret key unknown -//! -//! Example: -//! pk(K1)/pkh(X1)/multi(n,...K3,...) represents a compressed key 'K1'/(X-only key 'X1') whose private key in known by the wallet -//! pk(K2!)/pkh(K3!)/multi(n,...K5!,...) represents a key 'K' whose private key is NOT known to the test wallet -//! sha256(H)/hash256(H)/ripemd160(H)/hash160(H) is hash node whose preimage is known to wallet -//! sha256(H!)/hash256(H!)/ripemd160(H!)/hash160(H!) is hash node whose preimage is *NOT* known to wallet -//! timelocks are taken from the transaction value. -//! -//! The keys/hashes are automatically translated so that the tests knows how to satisfy things that don't end with ! -//! - -use bitcoin::hashes::{hex::ToHex, Hash}; -use miniscript::descriptor::{SinglePub, SinglePubKey}; -use miniscript::{Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk}; -use rand::RngCore; -use std::str::FromStr; - -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d}; -use bitcoin::secp256k1; - -#[derive(Clone, Debug)] -pub struct PubData { - pub pks: Vec, - pub x_only_pks: Vec, - pub sha256: sha256::Hash, - pub hash256: sha256d::Hash, - pub ripemd160: ripemd160::Hash, - pub hash160: hash160::Hash, -} - -#[derive(Debug, Clone)] -pub struct SecretData { - pub sks: Vec, - pub x_only_keypairs: Vec, - pub sha256_pre: [u8; 32], - pub hash256_pre: [u8; 32], - pub ripemd160_pre: [u8; 32], - pub hash160_pre: [u8; 32], -} -#[derive(Debug, Clone)] -pub struct TestData { - pub pubdata: PubData, - pub secretdata: SecretData, -} - -// Setup (sk, pk) pairs -fn setup_keys( - n: usize, -) -> ( - Vec, - Vec, - Vec, - Vec, -) { - let secp_sign = secp256k1::Secp256k1::signing_only(); - let mut sk = [0; 32]; - let mut sks = vec![]; - let mut pks = vec![]; - for i in 1..n + 1 { - sk[0] = i as u8; - sk[1] = (i >> 8) as u8; - sk[2] = (i >> 16) as u8; - - let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"); - let pk = miniscript::bitcoin::PublicKey { - inner: secp256k1::PublicKey::from_secret_key(&secp_sign, &sk), - compressed: true, - }; - pks.push(pk); - sks.push(sk); - } - - let mut x_only_keypairs = vec![]; - let mut x_only_pks = vec![]; - - for i in 0..n { - let keypair = bitcoin::KeyPair::from_secret_key(&secp_sign, sks[i]); - let xpk = bitcoin::XOnlyPublicKey::from_keypair(&keypair); - x_only_keypairs.push(keypair); - x_only_pks.push(xpk); - } - (sks, pks, x_only_keypairs, x_only_pks) -} - -impl TestData { - // generate a fixed data for n keys - pub(crate) fn new_fixed_data(n: usize) -> Self { - let (sks, pks, x_only_keypairs, x_only_pks) = setup_keys(n); - let sha256_pre = [0x12 as u8; 32]; - let sha256 = sha256::Hash::hash(&sha256_pre); - let hash256_pre = [0x34 as u8; 32]; - let hash256 = sha256d::Hash::hash(&hash256_pre); - let hash160_pre = [0x56 as u8; 32]; - let hash160 = hash160::Hash::hash(&hash160_pre); - let ripemd160_pre = [0x78 as u8; 32]; - let ripemd160 = ripemd160::Hash::hash(&ripemd160_pre); - - let pubdata = PubData { - pks, - sha256, - hash256, - ripemd160, - hash160, - x_only_pks, - }; - let secretdata = SecretData { - sks, - sha256_pre, - hash256_pre, - ripemd160_pre, - hash160_pre, - x_only_keypairs, - }; - Self { - pubdata, - secretdata, - } - } -} - -/// Obtain an insecure random public key with unknown secret key for testing -pub fn random_pk(mut seed: u8) -> bitcoin::PublicKey { - loop { - let mut data = [0; 33]; - for byte in &mut data[..] { - *byte = seed; - // totally a rng - seed = seed.wrapping_mul(41).wrapping_add(53); - } - data[0] = 2 + (data[0] >> 7); - if let Ok(key) = bitcoin::PublicKey::from_slice(&data[..33]) { - return key; - } - } -} - -/// Parse an insane miniscript into a miniscript with the format described above at file header -pub fn parse_insane_ms( - ms: &str, - pubdata: &PubData, -) -> Miniscript { - let ms = subs_hash_frag(ms, pubdata); - let ms = - Miniscript::::from_str_insane(&ms).expect("only parsing valid minsicripts"); - let mut i = 0; - let mut j = pubdata.pks.len(); - let ms = ms.translate_pk_infallible( - &mut |pk_str: &String| { - let avail = !pk_str.ends_with("!"); - if avail { - i = i + 1; - if pk_str.starts_with("K") { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[i]), - }) - } else if pk_str.starts_with("X") { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), - }) - } else { - // Parse any other keys as known to allow compatibility with existing tests - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[i]), - }) - } - } else { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(random_pk(59)), - }) - } - }, - &mut |pk_str: &String| { - let avail = !pk_str.ends_with("!"); - if avail { - j = j - 1; - if pk_str.starts_with("K") { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[j]), - }) - } else if pk_str.starts_with("X") { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), - }) - } else { - // Parse any other keys as known to allow compatibility with existing tests - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[j]), - }) - } - } else { - DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(random_pk(59)), - }) - } - }, - ); - ms -} - -pub fn parse_test_desc(desc: &str, pubdata: &PubData) -> Descriptor { - let desc = subs_hash_frag(desc, pubdata); - let desc = - Descriptor::::from_str(&desc).expect("only parsing valid and sane descriptors"); - let mut i = 0; - let mut j = pubdata.pks.len(); - let desc: Result<_, ()> = desc.translate_pk( - &mut |pk_str: &'_ String| { - let avail = !pk_str.ends_with("!"); - if avail { - i = i + 1; - if pk_str.starts_with("K") { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[i]), - })) - } else if pk_str.starts_with("X") { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), - })) - } else { - panic!("Key must start with either K or X") - } - } else { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(random_pk(59)), - })) - } - }, - &mut |pkh_str: &'_ String| { - let avail = !pkh_str.ends_with("!"); - if avail { - j = j - 1; - if pkh_str.starts_with("K") { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(pubdata.pks[j]), - })) - } else if pkh_str.starts_with("X") { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), - })) - } else { - panic!("Key must start with either K or X") - } - } else { - Ok(DescriptorPublicKey::Single(SinglePub { - origin: None, - key: SinglePubKey::FullKey(random_pk(61)), - })) - } - }, - ); - desc.expect("Translate must succeed") -} - -// substitute hash fragments in the string as the per rules -fn subs_hash_frag(ms: &str, pubdata: &PubData) -> String { - let ms = ms.replace( - "sha256(H)", - &format!("sha256({})", &pubdata.sha256.to_hex()), - ); - let ms = ms.replace( - "hash256(H)", - &format!("hash256({})", &pubdata.hash256.into_inner().to_hex()), - ); - let ms = ms.replace( - "ripemd160(H)", - &format!("ripemd160({})", &pubdata.ripemd160.to_hex()), - ); - let ms = ms.replace( - "hash160(H)", - &format!("hash160({})", &pubdata.hash160.to_hex()), - ); - - let mut rand_hash32 = [0u8; 32]; - rand::thread_rng().fill_bytes(&mut rand_hash32); - - let mut rand_hash20 = [0u8; 20]; - rand::thread_rng().fill_bytes(&mut rand_hash20); - let ms = ms.replace("sha256(H!)", &format!("sha256({})", rand_hash32.to_hex())); - let ms = ms.replace("hash256(H!)", &format!("hash256({})", rand_hash32.to_hex())); - let ms = ms.replace( - "ripemd160(H!)", - &format!("ripemd160({})", rand_hash20.to_hex()), - ); - let ms = ms.replace("hash160(H!)", &format!("hash160({})", rand_hash20.to_hex())); - ms -}