use std::collections::BTreeMap;
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::secp256k1::{self, Secp256k1};
use bitcoin::util::psbt;
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::{self, Amount, LockTime, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid};
use bitcoind::bitcoincore_rpc::{json, Client, RpcApi};
use miniscript::psbt::PsbtExt;
use miniscript::Descriptor;
mod setup;
use setup::test_util::{self, PubData, TestData};
pub(crate) fn parse_miniscripts(
secp: &Secp256k1<secp256k1::All>,
pubdata: &PubData,
) -> Vec<Descriptor<bitcoin::PublicKey>> {
let mut desc_vec = vec![];
for line in read_lines("tests/data/random_ms.txt") {
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
}
fn read_lines<P>(filename: P) -> io::Lines<io::BufReader<File>>
where
P: AsRef<Path>,
{
let file = File::open(filename).expect("File not found");
io::BufReader::new(file).lines()
}
fn btc<F: Into<f64>>(btc: F) -> Amount {
Amount::from_btc(btc.into()).unwrap()
}
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;
let blocks = cl
.generate_to_address(500, &cl.get_new_address(None, None).unwrap())
.unwrap();
assert_eq!(blocks.len(), 500);
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);
}
let blocks = cl
.generate_to_address(50, &cl.get_new_address(None, None).unwrap())
.unwrap();
assert_eq!(blocks.len(), 50);
let mut psbts = vec![];
for (desc, txid) in desc_vec.iter().zip(txids) {
let mut psbt = Psbt {
unsigned_tx: Transaction {
version: 2,
lock_time: LockTime::from_time(1_603_866_330)
.expect("valid timestamp")
.into(), input: vec![],
output: vec![],
},
unknown: BTreeMap::new(),
proprietary: BTreeMap::new(),
xpub: BTreeMap::new(),
version: 0,
inputs: vec![],
outputs: vec![],
};
let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0).to_sat());
let mut txin = TxIn::default();
txin.previous_output = outpoint;
txin.sequence = Sequence::from_height(49);
psbt.unsigned_tx.input.push(txin);
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![];
for i in 0..psbts.len() {
let ms = if let Descriptor::Wsh(wsh) = &desc_vec[i] {
match wsh.as_inner() {
miniscript::descriptor::WshInner::Ms(ms) => ms,
_ => unreachable!(),
}
} else {
unreachable!("Only Wsh descriptors are supported");
};
let sks_reqd: Vec<_> = ms
.iter_pk()
.map(|pk| sks[pks.iter().position(|&x| x == pk).unwrap()])
.collect();
let amt = btc(1).to_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();
let msg = secp256k1::Message::from_slice(&sighash[..]).unwrap();
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,
},
);
}
psbts[i].inputs[0].sha256_preimages.insert(
testdata.pubdata.sha256,
testdata.secretdata.sha256_pre.to_vec(),
);
psbts[i].inputs[0].hash256_preimages.insert(
sha256d::Hash::from_inner(testdata.pubdata.hash256.into_inner()),
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(),
);
if let Err(e) = psbts[i].finalize_mall_mut(&secp) {
panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i);
} else {
let tx = psbts[i].extract(&secp).unwrap();
let txid = cl
.send_raw_transaction(&tx)
.expect(&format!("{} send tx failed for ms {}", i, ms));
spend_txids.push(txid);
}
}
let _blocks = cl
.generate_to_address(10, &cl.get_new_address(None, None).unwrap())
.unwrap();
for txid in spend_txids {
let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations;
assert!(num_conf > 0);
}
}
#[test]
fn test_setup() {
setup::setup();
}
#[test]
fn tests_from_cpp() {
let cl = &setup::setup().client;
let testdata = TestData::new_fixed_data(50);
test_from_cpp_ms(cl, &testdata);
}