Skip to content

Commit

Permalink
tests: test wsh(miniscript) registration & signing with simulator
Browse files Browse the repository at this point in the history
Using rust-miniscript lib to help with constructing the tx and witness.
  • Loading branch information
benma committed Jul 5, 2024
1 parent 2f43278 commit c8c0cb1
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 29 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ wasm-bindgen-test = "0.3.42"
tokio = { version = "1", features = ["time", "macros", "rt", "fs"] }
reqwest = "0.12"
url = "2.5"
# Enable this to be able to get coverage using `cargo tarpaulin --features=simulator,tokio --out=Html` without compilation error.
# See https://github.com/rust-bitcoin/rust-bitcoinconsensus/pull/94
# bitcoinconsensus = { git = "https://github.com/benma/rust-bitcoinconsensus.git", rev = "d132eec12cf86038838c45185b083f834dfb7b26", default-features = false }
bitcoinconsensus = { version = "0.106.0", default-features = false }
miniscript = "12.0.0"

[build-dependencies]
prost-build = { version = "0.11" }
Expand Down
271 changes: 242 additions & 29 deletions tests/subtests/test_btc_psbt.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use super::PairedBitBox;

use bitbox_api::pb;
use std::collections::HashMap;

use bitbox_api::{btc::Xpub, pb, Keypath};

use bitcoin::bip32::DerivationPath;
use bitcoin::psbt::Psbt;
use bitcoin::secp256k1;
use bitcoin::{
transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness,
};
use miniscript::psbt::PsbtExt;
use miniscript::TranslatePk;

// Checks that the psbt is fully signed and valid (all scripts execute correctly).
fn verify_transaction(psbt: Psbt) {
Expand Down Expand Up @@ -46,11 +51,12 @@ fn verify_transaction(psbt: Psbt) {
pub async fn test(bitbox: &PairedBitBox) {
test_taproot_key_spend(bitbox).await;
test_mixed_spend(bitbox).await;
test_policy_wsh(bitbox).await;
}

// Test signing; all inputs are BIP86 Taproot keyspends.
async fn test_taproot_key_spend(bitbox: &PairedBitBox) {
let secp = bitcoin::secp256k1::Secp256k1::new();
let secp = secp256k1::Secp256k1::new();

let fingerprint = super::simulator_xprv().fingerprint(&secp);

Expand Down Expand Up @@ -164,19 +170,15 @@ async fn test_taproot_key_spend(bitbox: &PairedBitBox) {
.unwrap();

// Finalize, add witnesses.
psbt.inputs.iter_mut().for_each(|input| {
let mut script_witness = Witness::new();
script_witness.push(input.tap_key_sig.unwrap().to_vec());
input.final_script_witness = Some(script_witness);
});
psbt.finalize_mut(&secp).unwrap();

// Verify the signed tx, including that all sigs/witnesses are correct.
verify_transaction(psbt);
}

// Test signing; mixed input types (p2wpkh, p2wpkh-p2sh, p2tr)
async fn test_mixed_spend(bitbox: &PairedBitBox) {
let secp = bitcoin::secp256k1::Secp256k1::new();
let secp = secp256k1::Secp256k1::new();

let fingerprint = super::simulator_xprv().fingerprint(&secp);

Expand Down Expand Up @@ -313,28 +315,239 @@ async fn test_mixed_spend(bitbox: &PairedBitBox) {
.unwrap();

// Finalize, add witnesses.
psbt.finalize_mut(&secp).unwrap();

// // p2tr
// psbt.inputs[0].final_script_witness = Some(Witness::p2tr_key_spend(
// psbt.inputs[0].tap_key_sig.as_ref().unwrap(),
// ));
// // p2wpkh
// psbt.inputs[1].final_script_witness = Some({
// let (pubkey, sig) = psbt.inputs[1].partial_sigs.first_key_value().unwrap();
// Witness::p2wpkh(sig, &pubkey.inner)
// });
// // p2wpkh-p2sh needs a witness (for the p2wpkh part) and a script_sig (for the p2sh part).
// psbt.inputs[2].final_script_sig = Some({
// let redeemscript: &bitcoin::script::PushBytes =
// input2_redeemscript.as_bytes().try_into().unwrap();
// let mut script = ScriptBuf::new();
// script.push_slice(redeemscript);
// script
// });
// psbt.inputs[2].final_script_witness = Some({
// let (pubkey, sig) = psbt.inputs[2].partial_sigs.first_key_value().unwrap();
// Witness::p2wpkh(sig, &pubkey.inner)
// });

// Verify the signed tx, including that all sigs/witnesses are correct.
verify_transaction(psbt);
}

struct StrPkTranslator {
pk_map: HashMap<&'static str, bitcoin::PublicKey>,
}

impl miniscript::Translator<String, bitcoin::PublicKey, ()> for StrPkTranslator {
fn pk(&mut self, pk: &String) -> Result<bitcoin::PublicKey, ()> {
self.pk_map.get(pk.as_str()).copied().ok_or(())
}

// We don't need to implement these methods as we are not using them in the policy.
// Fail if we encounter any hash fragments. See also translate_hash_clone! macro.
miniscript::translate_hash_fail!(String, bitcoin::PublicKey, ());
}

async fn test_policy_wsh(bitbox: &PairedBitBox) {
let secp = secp256k1::Secp256k1::new();

// p2tr
psbt.inputs[0].final_script_witness = Some(Witness::p2tr_key_spend(
psbt.inputs[0].tap_key_sig.as_ref().unwrap(),
));
// p2wpkh
psbt.inputs[1].final_script_witness = Some({
let (pubkey, sig) = psbt.inputs[1].partial_sigs.first_key_value().unwrap();
Witness::p2wpkh(sig, &pubkey.inner)
});
// p2wpkh-p2sh needs a witness (for the p2wpkh part) and a script_sig (for the p2sh part).
psbt.inputs[2].final_script_sig = Some({
let redeemscript: &bitcoin::script::PushBytes =
input2_redeemscript.as_bytes().try_into().unwrap();
let mut script = ScriptBuf::new();
script.push_slice(redeemscript);
script
});
psbt.inputs[2].final_script_witness = Some({
let (pubkey, sig) = psbt.inputs[2].partial_sigs.first_key_value().unwrap();
Witness::p2wpkh(sig, &pubkey.inner)
});
let coin = pb::BtcCoin::Tbtc;
let policy = "wsh(or_b(pk(@0/**),s:pk(@1/**)))";
let our_root_fingerprint = super::simulator_xprv().fingerprint(&secp);

let keypath_account: Keypath = "m/48'/1'/0'/3'".try_into().unwrap();

let our_xpub_str = bitbox
.btc_xpub(
coin,
&keypath_account,
pb::btc_pub_request::XPubType::Tpub,
false,
)
.await
.unwrap();

let our_xpub: Xpub = our_xpub_str.parse().unwrap();
let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap();

let keys = &[
// Our key: root fingerprint and keypath are required.
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: Some(our_root_fingerprint),
keypath: Some(keypath_account.clone()),
xpub: our_xpub,
},
// Foreign key: root fingerprint and keypath are optional.
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: None,
keypath: None,
xpub: some_xpub,
},
];
let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys);

// Register policy if not already registered. This must be done before any receive address is
// created or any transaction is signed.
let is_registered = bitbox
.btc_is_script_config_registered(coin, &policy_config, None)
.await
.unwrap();

if !is_registered {
bitbox
.btc_register_script_config(
coin,
&policy_config,
None,
pb::btc_register_script_config_request::XPubType::AutoXpubTpub,
Some("test wsh policy"),
)
.await
.unwrap();
}

let descriptor: miniscript::Descriptor<String> = policy.parse().unwrap();
assert!(descriptor.sanity_check().is_ok());

let input_pubkey: bitcoin::PublicKey = our_xpub
.derive_pub(&secp, &[0.into(), 0.into()])
.unwrap()
.to_pub()
.into();

let change_pubkey: bitcoin::PublicKey = some_xpub
.derive_pub(&secp, &[0.into(), 0.into()])
.unwrap()
.to_pub()
.into();

let input_descriptor = descriptor
.translate_pk(&mut StrPkTranslator {
pk_map: {
let mut pk_map = HashMap::new();
pk_map.insert("@0/**", input_pubkey.clone());
pk_map.insert("@1/**", change_pubkey.clone());
pk_map
},
})
.unwrap();

let change_descriptor = descriptor
.translate_pk(&mut StrPkTranslator {
pk_map: {
let mut pk_map = HashMap::new();
pk_map.insert(
"@0/**",
our_xpub
.derive_pub(&secp, &[1.into(), 0.into()])
.unwrap()
.to_pub()
.into(),
);
pk_map.insert(
"@1/**",
some_xpub
.derive_pub(&secp, &[1.into(), 0.into()])
.unwrap()
.to_pub()
.into(),
);
pk_map
},
})
.unwrap();

// A previous tx which creates some UTXOs we can reference later.
let prev_tx = Transaction {
version: transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: "3131313131313131313131313131313131313131313131313131313131313131:0"
.parse()
.unwrap(),
script_sig: ScriptBuf::new(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::default(),
}],
output: vec![TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: input_descriptor.script_pubkey(),
}],
};

let tx = Transaction {
version: transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: prev_tx.compute_txid(),
vout: 0,
},
script_sig: ScriptBuf::new(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::default(),
}],
output: vec![
TxOut {
value: Amount::from_sat(70_000_000),
script_pubkey: change_descriptor.script_pubkey(),
},
TxOut {
value: Amount::from_sat(20_000_000),
script_pubkey: ScriptBuf::new_p2tr(
&secp,
// random private key:
// 9dbb534622a6100a39b73dece43c6d4db14b9a612eb46a6c64c2bb849e283ce8
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};

let input_path: DerivationPath = "m/48'/1'/0'/3'/0/0".parse().unwrap();
let change_path: DerivationPath = "m/48'/1'/0'/3'/1/0".parse().unwrap();

let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();

// Add input and change infos.
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
psbt.inputs[0]
.bip32_derivation
.insert(input_pubkey.inner, (our_root_fingerprint, input_path));
psbt.inputs[0].witness_script = Some(input_descriptor.explicit_script().unwrap());

psbt.outputs[0]
.bip32_derivation
.insert(change_pubkey.inner, (our_root_fingerprint, change_path));

// Sign.
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
Some(pb::BtcScriptConfigWithKeypath {
script_config: Some(policy_config),
keypath: keypath_account.to_vec(),
}),
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();

// Finalize, add witnesses.
psbt.finalize_mut(&secp).unwrap();

// Verify the signed tx, including that all sigs/witnesses are correct.
verify_transaction(psbt);
Expand Down

0 comments on commit c8c0cb1

Please sign in to comment.