From ae3c98e3564cde6a91fff120dc99c40b25f93cff Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 14 Feb 2022 02:43:32 -0800 Subject: [PATCH 1/4] Move only: Rework Integration Test and makes things more modular Easier for writing future tests. There are no new tests added in this commit. This only moves cpp tests into test_cpp, deletes read_file.rs and moves common utilities to test_util.rs --- .gitignore | 3 + integration_test/Cargo.toml | 1 + integration_test/src/main.rs | 209 +------------------- integration_test/src/read_file.rs | 159 --------------- integration_test/src/test_cpp.rs | 243 +++++++++++++++++++++++ integration_test/src/test_util.rs | 312 ++++++++++++++++++++++++++++++ 6 files changed, 565 insertions(+), 362 deletions(-) delete mode 100644 integration_test/src/read_file.rs create mode 100644 integration_test/src/test_cpp.rs create mode 100644 integration_test/src/test_util.rs diff --git a/.gitignore b/.gitignore index a44fb8010..ab3c075f3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ fuzz/hfuzz_workspace #Vscode project files .vscode + +#Intergration test files +integration_test/bitcoin-* \ No newline at end of file diff --git a/integration_test/Cargo.toml b/integration_test/Cargo.toml index d9f1cab10..e2fc8823e 100644 --- a/integration_test/Cargo.toml +++ b/integration_test/Cargo.toml @@ -10,3 +10,4 @@ miniscript = {path = "../"} bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "bcc35944b3dd636cdff9710f90f8e0cfcab28f27"} bitcoin = "0.28.0-rc.1" log = "0.4" +rand = "0.8.4" \ No newline at end of file diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 26c5bffa0..bbfe4de2e 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -8,19 +8,11 @@ extern crate log; extern crate bitcoin; extern crate miniscript; -use bitcoincore_rpc::{json, Auth, Client, RpcApi}; +use bitcoincore_rpc::{Auth, Client, RpcApi}; -use bitcoin::secp256k1; -use bitcoin::util::psbt; -use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; -use bitcoin::{Amount, OutPoint, Transaction, TxIn, TxOut, Txid}; -mod read_file; -use miniscript::miniscript::iter; -use miniscript::psbt::PsbtExt; -use miniscript::DescriptorTrait; -use miniscript::MiniscriptKey; -use miniscript::{Miniscript, Segwitv0}; -use std::collections::BTreeMap; +mod test_cpp; +mod test_util; +use test_util::TestData; struct StdLogger; @@ -45,11 +37,6 @@ impl log::Log for StdLogger { static LOGGER: StdLogger = StdLogger; -/// Quickly create a BTC amount. -fn btc>(btc: F) -> Amount { - Amount::from_btc(btc.into()).unwrap() -} - fn get_rpc_url() -> String { return std::env::var("RPC_URL").expect("RPC_URL must be set"); } @@ -64,23 +51,6 @@ fn get_auth() -> bitcoincore_rpc::Auth { }; } -// 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"); -} - fn main() { log::set_logger(&LOGGER) .map(|()| log::set_max_level(log::LevelFilter::max())) @@ -95,173 +65,6 @@ fn main() { cl.create_wallet("testwallet", None, None, None, None) .unwrap(); - let testdata = read_file::TestData::new_fixed_data(50); - let ms_vec = read_file::parse_miniscripts(&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 ms in ms_vec.iter() { - let wsh = miniscript::Descriptor::new_wsh(ms.clone()).unwrap(); - 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 (ms, txid) in ms_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(ms.encode()); - 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 secp = secp256k1::Secp256k1::new(); - 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.inner, - 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); - } + let testdata = TestData::new_fixed_data(50); + test_cpp::test_from_cpp_ms(&cl, &testdata); } diff --git a/integration_test/src/read_file.rs b/integration_test/src/read_file.rs deleted file mode 100644 index c21227a79..000000000 --- a/integration_test/src/read_file.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! # rust-miniscript integration test -//! -//! Read Miniscripts from file and translate into miniscripts -//! which we know how to satisfy -//! - -use bitcoin; -use bitcoin::hashes::hex::ToHex; -use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash}; -use bitcoin::secp256k1; -use miniscript::Miniscript; -use miniscript::MiniscriptKey; -use miniscript::Segwitv0; -use miniscript::TranslatePk; -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; - -type MsString = Miniscript; -type Ms = Miniscript; - -#[derive(Clone, Debug)] -pub(crate) struct PubData { - pub(crate) pks: Vec, - pub(crate) sha256: sha256::Hash, - pub(crate) hash256: sha256d::Hash, - pub(crate) ripemd160: ripemd160::Hash, - pub(crate) hash160: hash160::Hash, -} - -#[derive(Debug, Clone)] -pub(crate) struct SecretData { - pub(crate) sks: Vec, - pub(crate) sha256_pre: [u8; 32], - pub(crate) hash256_pre: [u8; 32], - pub(crate) ripemd160_pre: [u8; 32], - pub(crate) hash160_pre: [u8; 32], -} -#[derive(Debug, Clone)] -pub(crate) struct TestData { - pub(crate) pubdata: PubData, - pub(crate) secretdata: SecretData, -} - -// Setup (sk, pk) pairs -fn setup_keys( - n: usize, -) -> ( - 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); - } - (sks, pks) -} -impl TestData { - // generate a fixed data for n keys - pub(crate) fn new_fixed_data(n: usize) -> Self { - let (sks, 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, - }; - let secretdata = SecretData { - sks, - sha256_pre, - hash256_pre, - ripemd160_pre, - hash160_pre, - }; - Self { - pubdata, - secretdata, - } - } -} - -// parse ~30 miniscripts from file -pub(crate) fn parse_miniscripts(pubdata: &PubData) -> Vec { - let pks = &pubdata.pks; - // File must exist in current path before this produces output - let mut ms_vec = vec![]; - if let Ok(lines) = read_lines("./random_ms.txt") { - // Consumes the iterator, returns an (Optional) String - for line in lines { - if let Ok(ms) = line { - 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 ms = MsString::from_str_insane(&ms).expect("only parsing valid minsicripts"); - let mut i = 0; - let mut j = pks.len(); - let ms: Result<_, ()> = ms.translate_pk( - &mut |_c: &'_ _| { - i = i + 1; - Ok(pks[i]) - }, - &mut |_pkh: &'_ _| { - j = j - 1; - Ok(pks[j].to_pubkeyhash()) - }, - ); - ms_vec.push(ms.expect("translation cannot fail")); - } - } - } - ms_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()) -} diff --git a/integration_test/src/test_cpp.rs b/integration_test/src/test_cpp.rs new file mode 100644 index 000000000..2a25862d9 --- /dev/null +++ b/integration_test/src/test_cpp.rs @@ -0,0 +1,243 @@ +//! # 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, DescriptorTrait, 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.inner, + 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_util.rs b/integration_test/src/test_util.rs new file mode 100644 index 000000000..2477db2cb --- /dev/null +++ b/integration_test/src/test_util.rs @@ -0,0 +1,312 @@ +//! # 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 ! +//! + +extern crate rand; + +use self::rand::RngCore; +use bitcoin::hashes::{hex::ToHex, Hash}; +use miniscript::{ + descriptor::{DescriptorSinglePub, SinglePubKey}, + Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk, TranslatePk2, +}; +use std::str::FromStr; + +use bitcoin; +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::schnorr::KeyPair::from_secret_key(&secp_sign, sks[i]); + let xpk = bitcoin::schnorr::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::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + }) + } else if pk_str.starts_with("X") { + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), + }) + } else { + // Parse any other keys as known to allow compatibility with existing tests + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + }) + } + } else { + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + 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::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + }) + } else if pk_str.starts_with("X") { + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), + }) + } else { + // Parse any other keys as known to allow compatibility with existing tests + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + }) + } + } else { + DescriptorPublicKey::SinglePub(DescriptorSinglePub { + 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::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[i]), + })) + } else if pk_str.starts_with("X") { + Ok(DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[i]), + })) + } else { + panic!("Key must start with either K or X") + } + } else { + Ok(DescriptorPublicKey::SinglePub(DescriptorSinglePub { + 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::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::FullKey(pubdata.pks[j]), + })) + } else if pkh_str.starts_with("X") { + Ok(DescriptorPublicKey::SinglePub(DescriptorSinglePub { + origin: None, + key: SinglePubKey::XOnly(pubdata.x_only_pks[j]), + })) + } else { + panic!("Key must start with either K or X") + } + } else { + Ok(DescriptorPublicKey::SinglePub(DescriptorSinglePub { + 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 +} From c25fc05286d00c3a094a4203a637a359e615f131 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Tue, 8 Mar 2022 08:15:46 -0800 Subject: [PATCH 2/4] Add descriptor integration tests Rework integration framework: Add generic descriptor satisfaction support --- integration_test/Cargo.toml | 2 +- integration_test/src/main.rs | 1 + integration_test/src/test_desc.rs | 317 ++++++++++++++++++++++++++++++ integration_test/src/test_util.rs | 14 +- 4 files changed, 326 insertions(+), 8 deletions(-) create mode 100644 integration_test/src/test_desc.rs diff --git a/integration_test/Cargo.toml b/integration_test/Cargo.toml index e2fc8823e..4c733402b 100644 --- a/integration_test/Cargo.toml +++ b/integration_test/Cargo.toml @@ -8,6 +8,6 @@ miniscript = {path = "../"} # Until 0.26 support is released on rust-bitcoincore-rpc bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "bcc35944b3dd636cdff9710f90f8e0cfcab28f27"} -bitcoin = "0.28.0-rc.1" +bitcoin = {ver = "0.28.0-rc.1", features = ["rand"]} log = "0.4" rand = "0.8.4" \ No newline at end of file diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index bbfe4de2e..b75988674 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -10,6 +10,7 @@ extern crate miniscript; use bitcoincore_rpc::{Auth, Client, RpcApi}; +mod test_desc; mod test_cpp; mod test_util; use test_util::TestData; diff --git a/integration_test/src/test_desc.rs b/integration_test/src/test_desc.rs new file mode 100644 index 000000000..042d6b95d --- /dev/null +++ b/integration_test/src/test_desc.rs @@ -0,0 +1,317 @@ +//! # 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::PbstOps; +use miniscript::{Descriptor, DescriptorTrait, 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: String) -> 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(500, &cl.get_new_address(None, None).unwrap()) + .unwrap(); + assert_eq!(blocks.len(), 500); + + 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(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 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 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.clone()); + psbt.inputs.push(input); + psbt.outputs.push(psbt::Output::default()); + psbt.update_desc(0, &desc, 0..0).unwrap(); + + // -------------------------------------------- + // 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()); + 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()]; + psbt.inputs[0].partial_sigs.insert( + pk.inner, + 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(&secp) { + // All miniscripts should satisfy + panic!("Could not satisfy non-malleably: error{} desc:{} ", e, 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(10, &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 index 2477db2cb..9c320caf4 100644 --- a/integration_test/src/test_util.rs +++ b/integration_test/src/test_util.rs @@ -23,7 +23,7 @@ use self::rand::RngCore; use bitcoin::hashes::{hex::ToHex, Hash}; use miniscript::{ descriptor::{DescriptorSinglePub, SinglePubKey}, - Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk, TranslatePk2, + Descriptor, DescriptorPublicKey, Miniscript, ScriptContext, TranslatePk, }; use std::str::FromStr; @@ -34,7 +34,7 @@ use bitcoin::secp256k1; #[derive(Clone, Debug)] pub struct PubData { pub pks: Vec, - pub x_only_pks: Vec, + pub x_only_pks: Vec, pub sha256: sha256::Hash, pub hash256: sha256d::Hash, pub ripemd160: ripemd160::Hash, @@ -44,7 +44,7 @@ pub struct PubData { #[derive(Debug, Clone)] pub struct SecretData { pub sks: Vec, - pub x_only_keypairs: Vec, + pub x_only_keypairs: Vec, pub sha256_pre: [u8; 32], pub hash256_pre: [u8; 32], pub ripemd160_pre: [u8; 32], @@ -62,8 +62,8 @@ fn setup_keys( ) -> ( Vec, Vec, - Vec, - Vec, + Vec, + Vec, ) { let secp_sign = secp256k1::Secp256k1::signing_only(); let mut sk = [0; 32]; @@ -87,8 +87,8 @@ fn setup_keys( let mut x_only_pks = vec![]; for i in 0..n { - let keypair = bitcoin::schnorr::KeyPair::from_secret_key(&secp_sign, sks[i]); - let xpk = bitcoin::schnorr::XOnlyPublicKey::from_keypair(&keypair); + 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); } From 51126d3ed45516ab7b5a8894b5ddbc7b469f5887 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 14 Mar 2022 08:05:23 -0700 Subject: [PATCH 3/4] Add taproot tests Also update block count in generatetoaddress for fast execution of integration tests --- integration_test/src/main.rs | 60 ++++++++++++++++++++++++++++++- integration_test/src/test_desc.rs | 41 ++++++++++++--------- 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index b75988674..0b873211c 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -10,8 +10,8 @@ extern crate miniscript; use bitcoincore_rpc::{Auth, Client, RpcApi}; -mod test_desc; mod test_cpp; +mod test_desc; mod test_util; use test_util::TestData; @@ -68,4 +68,62 @@ fn main() { 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)})"); + + // 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_desc.rs b/integration_test/src/test_desc.rs index 042d6b95d..26fda3d76 100644 --- a/integration_test/src/test_desc.rs +++ b/integration_test/src/test_desc.rs @@ -13,7 +13,7 @@ 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::PbstOps; +use miniscript::psbt::PsbtExt; use miniscript::{Descriptor, DescriptorTrait, Miniscript, ToPublicKey}; use miniscript::{MiniscriptKey, ScriptContext}; use std::collections::BTreeMap; @@ -40,7 +40,7 @@ fn get_vout(cl: &Client, txid: Txid, value: u64, spk: Script) -> (OutPoint, TxOu unreachable!("Only call get vout on functions which have the expected outpoint"); } -pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witness { +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; @@ -48,9 +48,9 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn let x_only_pks = &testdata.pubdata.x_only_pks; // Generate some blocks let blocks = cl - .generate_to_address(500, &cl.get_new_address(None, None).unwrap()) + .generate_to_address(1, &cl.get_new_address(None, None).unwrap()) .unwrap(); - assert_eq!(blocks.len(), 500); + assert_eq!(blocks.len(), 1); let desc = test_util::parse_test_desc(&desc, &testdata.pubdata); let derived_desc = desc.derived_descriptor(&secp, 0).unwrap(); @@ -69,9 +69,9 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn .unwrap(); // Wait for the funds to mature. let blocks = cl - .generate_to_address(50, &cl.get_new_address(None, None).unwrap()) + .generate_to_address(2, &cl.get_new_address(None, None).unwrap()) .unwrap(); - assert_eq!(blocks.len(), 50); + assert_eq!(blocks.len(), 2); // Create a PSBT for each transaction. // Spend one input and spend one output for simplicity. let mut psbt = Psbt { @@ -95,8 +95,8 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn 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; + // 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 @@ -112,7 +112,7 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn input.witness_utxo = Some(witness_utxo.clone()); psbt.inputs.push(input); psbt.outputs.push(psbt::Output::default()); - psbt.update_desc(0, &desc, 0..0).unwrap(); + psbt.update_desc(0, &desc, None).unwrap(); // -------------------------------------------- // Sign the transactions with all keys @@ -134,7 +134,9 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn if let Some(mut internal_keypair) = internal_keypair { // ---------------------- Tr key spend -------------------- - internal_keypair.tweak_add_assign(&secp, tr.spend_info().tap_tweak().as_ref()); + 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(); @@ -218,7 +220,10 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn }, Descriptor::Tr(_tr) => unreachable!("Tr checked earlier"), }; - let msg = psbt.sighash_msg(0, &mut sighash_cache, None).unwrap().to_secp_msg(); + let msg = psbt + .sighash_msg(0, &mut sighash_cache, None) + .unwrap() + .to_secp_msg(); // Fixme: Take a parameter let hash_ty = bitcoin::EcdsaSigHashType::All; @@ -227,6 +232,7 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn 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.inner, bitcoin::EcdsaSig { @@ -257,9 +263,12 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn println!("Testing descriptor: {}", desc); // Finalize the transaction using psbt // Let miniscript do it's magic! - if let Err(e) = psbt.finalize(&secp) { + if let Err(e) = psbt.finalize_mut(&secp) { // All miniscripts should satisfy - panic!("Could not satisfy non-malleably: error{} desc:{} ", e, desc); + panic!( + "Could not satisfy non-malleably: error{} desc:{} ", + e[0], desc + ); } let tx = psbt.extract(&secp).expect("Extraction error"); @@ -272,7 +281,7 @@ pub fn test_desc_satisfy(cl: &Client, testdata: &TestData, desc: String) -> Witn // Finally mine the blocks and await confirmations let _blocks = cl - .generate_to_address(10, &cl.get_new_address(None, None).unwrap()) + .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 @@ -310,8 +319,6 @@ fn find_sks_ms( 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); + let i = pks.iter().position(|&x| x.to_public_key() == pk); i.map(|idx| vec![sks[idx]]).unwrap_or(Vec::new()) } From 13c47464c1fde4e5e3af0785e45f8b01885c0dd2 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Mon, 14 Mar 2022 09:48:41 -0700 Subject: [PATCH 4/4] Update CI to test on 22.0 instead of 0.21 0.21 does not have support for bech32m --- .github/workflows/rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index faf3f3668..7d163918a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -93,5 +93,5 @@ jobs: override: true - name: Running cargo env: - BITCOINVERSION: 0.21.0 + BITCOINVERSION: '22.0' run: ./contrib/test.sh