#![cfg(feature = "simulator")]
#![cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[cfg(not(feature = "tokio"))]
compile_error!("Enable the tokio feature to run simulator tests");
mod util;
use util::test_initialized_simulators;
use bitbox_api::{btc::Xpub, pb};
use bitcoin::bip32::DerivationPath;
use bitcoin::opcodes::all;
use bitcoin::psbt::Psbt;
use bitcoin::secp256k1;
use bitcoin::{
blockdata::script::Builder, transaction, Amount, OutPoint, ScriptBuf, Sequence, Transaction,
TxIn, TxOut, Witness,
};
use miniscript::psbt::PsbtExt;
fn verify_transaction(psbt: Psbt) {
let utxos: Vec<TxOut> = psbt
.iter_funding_utxos()
.map(|utxo| utxo.unwrap())
.cloned()
.collect();
let tx = psbt.extract_tx_unchecked_fee_rate();
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
let flags = bitcoinconsensus::VERIFY_ALL_PRE_TAPROOT | bitcoinconsensus::VERIFY_TAPROOT;
let utxos_converted: Vec<bitcoinconsensus::Utxo> = utxos
.iter()
.map(|output| bitcoinconsensus::Utxo {
script_pubkey: output.script_pubkey.as_bytes().as_ptr(),
script_pubkey_len: output.script_pubkey.as_bytes().len() as u32,
value: output.value.to_sat() as i64,
})
.collect();
for (idx, output) in utxos.iter().enumerate() {
bitcoinconsensus::verify_with_flags(
output.script_pubkey.as_bytes(),
output.value.to_sat(),
serialized_tx.as_slice(),
Some(&utxos_converted),
idx,
flags,
)
.unwrap();
}
}
#[tokio::test]
async fn test_btc_psbt_taproot_key_spend() {
test_initialized_simulators(async |bitbox| {
let secp = secp256k1::Secp256k1::new();
let fingerprint = util::simulator_xprv().fingerprint(&secp);
let change_path: DerivationPath = "m/86'/1'/0'/1/0".parse().unwrap();
let change_xpub = util::simulator_xpub_at(&secp, &change_path);
let input0_path: DerivationPath = "m/86'/1'/0'/0/0".parse().unwrap();
let input0_xpub = util::simulator_xpub_at(&secp, &input0_path);
let input1_path: DerivationPath = "m/86'/1'/0'/0/1".parse().unwrap();
let input1_xpub = util::simulator_xpub_at(&secp, &input1_path);
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: ScriptBuf::new_p2tr(&secp, input0_xpub.to_x_only_pub(), None),
},
TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: ScriptBuf::new_p2tr(&secp, input1_xpub.to_x_only_pub(), None),
},
],
};
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(),
},
TxIn {
previous_output: OutPoint {
txid: prev_tx.compute_txid(),
vout: 1,
},
script_sig: ScriptBuf::new(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::default(),
},
],
output: vec![
TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: ScriptBuf::new_p2tr(&secp, change_xpub.to_x_only_pub(), None),
},
TxOut {
value: Amount::from_sat(20_000_000),
script_pubkey: ScriptBuf::new_p2tr(
&secp,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone());
psbt.inputs[0].tap_internal_key = Some(input0_xpub.to_x_only_pub());
psbt.inputs[0].tap_key_origins.insert(
input0_xpub.to_x_only_pub(),
(vec![], (fingerprint, input0_path.clone())),
);
psbt.inputs[1].witness_utxo = Some(prev_tx.output[1].clone());
psbt.inputs[1].tap_internal_key = Some(input1_xpub.to_x_only_pub());
psbt.inputs[1].tap_key_origins.insert(
input1_xpub.to_x_only_pub(),
(vec![], (fingerprint, input1_path.clone())),
);
psbt.outputs[0].tap_internal_key = Some(change_xpub.to_x_only_pub());
psbt.outputs[0].tap_key_origins.insert(
change_xpub.to_x_only_pub(),
(vec![], (fingerprint, change_path.clone())),
);
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
None,
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
})
.await
}
#[tokio::test]
async fn test_btc_psbt_mixed_spend() {
test_initialized_simulators(async |bitbox| {
let secp = secp256k1::Secp256k1::new();
let fingerprint = util::simulator_xprv().fingerprint(&secp);
let change_path: DerivationPath = "m/86'/1'/0'/1/0".parse().unwrap();
let change_xpub = util::simulator_xpub_at(&secp, &change_path);
let input0_path: DerivationPath = "m/86'/1'/0'/0/0".parse().unwrap();
let input0_xpub = util::simulator_xpub_at(&secp, &input0_path);
let input1_path: DerivationPath = "m/84'/1'/0'/0/0".parse().unwrap();
let input1_xpub = util::simulator_xpub_at(&secp, &input1_path);
let input2_path: DerivationPath = "m/49'/1'/0'/0/0".parse().unwrap();
let input2_xpub = util::simulator_xpub_at(&secp, &input2_path);
let input2_redeemscript = ScriptBuf::new_p2wpkh(&input2_xpub.to_pub().wpubkey_hash());
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: ScriptBuf::new_p2tr(&secp, input0_xpub.to_x_only_pub(), None),
},
TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: ScriptBuf::new_p2wpkh(&input1_xpub.to_pub().wpubkey_hash()),
},
TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: ScriptBuf::new_p2sh(&input2_redeemscript.clone().into()),
},
],
};
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(),
},
TxIn {
previous_output: OutPoint {
txid: prev_tx.compute_txid(),
vout: 1,
},
script_sig: ScriptBuf::new(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::default(),
},
TxIn {
previous_output: OutPoint {
txid: prev_tx.compute_txid(),
vout: 2,
},
script_sig: ScriptBuf::new(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::default(),
},
],
output: vec![
TxOut {
value: Amount::from_sat(100_000_000),
script_pubkey: ScriptBuf::new_p2tr(&secp, change_xpub.to_x_only_pub(), None),
},
TxOut {
value: Amount::from_sat(20_000_000),
script_pubkey: ScriptBuf::new_p2tr(
&secp,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
psbt.inputs[0].tap_internal_key = Some(input0_xpub.to_x_only_pub());
psbt.inputs[0].tap_key_origins.insert(
input0_xpub.to_x_only_pub(),
(vec![], (fingerprint, input0_path.clone())),
);
psbt.inputs[1].non_witness_utxo = Some(prev_tx.clone());
psbt.inputs[1]
.bip32_derivation
.insert(input1_xpub.to_pub().0, (fingerprint, input1_path.clone()));
psbt.inputs[2].non_witness_utxo = Some(prev_tx.clone());
psbt.inputs[2].redeem_script = Some(input2_redeemscript.clone());
psbt.inputs[2]
.bip32_derivation
.insert(input2_xpub.to_pub().0, (fingerprint, input2_path.clone()));
psbt.outputs[0].tap_internal_key = Some(change_xpub.to_x_only_pub());
psbt.outputs[0].tap_key_origins.insert(
change_xpub.to_x_only_pub(),
(vec![], (fingerprint, change_path.clone())),
);
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
None,
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
})
.await
}
#[tokio::test]
async fn test_btc_psbt_op_return() {
test_initialized_simulators(async |bitbox| {
if !semver::VersionReq::parse(">=9.24.0")
.unwrap()
.matches(bitbox.version())
{
return;
}
let secp = secp256k1::Secp256k1::new();
let fingerprint = util::simulator_xprv().fingerprint(&secp);
let input_path: DerivationPath = "m/84'/1'/0'/0/5".parse().unwrap();
let change_path: DerivationPath = "m/84'/1'/0'/1/0".parse().unwrap();
let input_pub = util::simulator_xpub_at(&secp, &input_path).to_pub();
let change_pub = util::simulator_xpub_at(&secp, &change_path).to_pub();
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(50_000_000),
script_pubkey: ScriptBuf::new_p2wpkh(&input_pub.wpubkey_hash()),
}],
};
let op_return_data = b"hello world";
let op_return_script = Builder::new()
.push_opcode(all::OP_RETURN)
.push_slice(op_return_data)
.into_script();
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(49_000_000),
script_pubkey: ScriptBuf::new_p2wpkh(&change_pub.wpubkey_hash()),
},
TxOut {
value: Amount::from_sat(0),
script_pubkey: op_return_script.clone(),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone());
psbt.inputs[0]
.bip32_derivation
.insert(input_pub.0, (fingerprint, input_path.clone()));
psbt.outputs[0]
.bip32_derivation
.insert(change_pub.0, (fingerprint, change_path.clone()));
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
None,
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
let final_tx = psbt.clone().extract_tx_unchecked_fee_rate();
assert_eq!(final_tx.output.len(), 2);
assert_eq!(final_tx.output[1].value, Amount::from_sat(0));
assert_eq!(final_tx.output[1].script_pubkey, op_return_script);
verify_transaction(psbt);
})
.await
}
#[tokio::test]
async fn test_btc_psbt_multisig_p2wsh() {
test_initialized_simulators(async |bitbox| {
let secp = secp256k1::Secp256k1::new();
let coin = pb::BtcCoin::Tbtc;
let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp);
let threshold: u32 = 1;
let keypath_account: DerivationPath = "m/48'/1'/0'/2'".parse().unwrap();
let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account);
let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap();
let multi_descriptor: miniscript::Descriptor<miniscript::DescriptorPublicKey> = format!(
"wsh(sortedmulti({},[{}/48'/1'/0'/2']{}/<0;1>/*,{}/<0;1>/*))",
threshold, our_root_fingerprint, &our_xpub, &some_xpub
)
.parse::<miniscript::Descriptor<miniscript::DescriptorPublicKey>>()
.unwrap();
assert!(multi_descriptor.sanity_check().is_ok());
let [descriptor_receive, descriptor_change] = multi_descriptor
.into_single_descriptors()
.unwrap()
.try_into()
.unwrap();
let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap();
let change_descriptor = descriptor_change.at_derivation_index(0).unwrap();
let multisig_config = bitbox_api::btc::make_script_config_multisig(
threshold,
&[our_xpub, some_xpub],
0,
pb::btc_script_config::multisig::ScriptType::P2wsh,
);
let is_registered = bitbox
.btc_is_script_config_registered(coin, &multisig_config, None)
.await
.unwrap();
if !is_registered {
bitbox
.btc_register_script_config(
coin,
&multisig_config,
Some(&(&keypath_account).into()),
pb::btc_register_script_config_request::XPubType::AutoXpubTpub,
Some("test wsh multisig"),
)
.await
.unwrap();
}
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,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
psbt.update_input_with_descriptor(0, &input_descriptor)
.unwrap();
psbt.update_output_with_descriptor(0, &change_descriptor)
.unwrap();
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
Some(pb::BtcScriptConfigWithKeypath {
script_config: Some(multisig_config),
keypath: keypath_account.to_u32_vec(),
}),
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
}).await
}
#[tokio::test]
async fn test_btc_psbt_policy_wsh() {
test_initialized_simulators(async |bitbox| {
let secp = secp256k1::Secp256k1::new();
let coin = pb::BtcCoin::Tbtc;
let policy = "wsh(or_b(pk(@0/<0;1>/*),s:pk(@1/<0;1>/*)))";
let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp);
let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap();
let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account);
let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap();
let multi_descriptor: miniscript::Descriptor<miniscript::DescriptorPublicKey> = policy
.replace(
"@0",
&format!("[{}/48'/1'/0'/3']{}", &our_root_fingerprint, &our_xpub),
)
.replace("@1", &some_xpub.to_string())
.parse::<miniscript::Descriptor<miniscript::DescriptorPublicKey>>()
.unwrap();
assert!(multi_descriptor.sanity_check().is_ok());
let [descriptor_receive, descriptor_change] = multi_descriptor
.into_single_descriptors()
.unwrap()
.try_into()
.unwrap();
let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap();
let change_descriptor = descriptor_change.at_derivation_index(0).unwrap();
let keys = &[
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: Some(our_root_fingerprint),
keypath: Some((&keypath_account).into()),
xpub: our_xpub,
},
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: None,
keypath: None,
xpub: some_xpub,
},
];
let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys);
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 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,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].non_witness_utxo = Some(prev_tx.clone());
psbt.update_input_with_descriptor(0, &input_descriptor)
.unwrap();
psbt.update_output_with_descriptor(0, &change_descriptor)
.unwrap();
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
Some(pb::BtcScriptConfigWithKeypath {
script_config: Some(policy_config),
keypath: keypath_account.to_u32_vec(),
}),
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
}).await
}
#[tokio::test]
async fn test_btc_psbt_policy_tr_keyspend() {
test_initialized_simulators(async |bitbox| {
if !semver::VersionReq::parse(">=9.21.0")
.unwrap()
.matches(bitbox.version())
{
return;
}
let secp = secp256k1::Secp256k1::new();
let coin = pb::BtcCoin::Tbtc;
let policy = "tr(@0/<0;1>/*)";
let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp);
let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap();
let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account);
let multi_descriptor: miniscript::Descriptor<miniscript::DescriptorPublicKey> = policy
.replace(
"@0",
&format!("[{}/48'/1'/0'/3']{}", &our_root_fingerprint, &our_xpub),
)
.parse::<miniscript::Descriptor<miniscript::DescriptorPublicKey>>()
.unwrap();
assert!(multi_descriptor.sanity_check().is_ok());
let [descriptor_receive, descriptor_change] = multi_descriptor
.into_single_descriptors()
.unwrap()
.try_into()
.unwrap();
let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap();
let change_descriptor = descriptor_change.at_derivation_index(0).unwrap();
let keys = &[
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: Some(our_root_fingerprint),
keypath: Some((&keypath_account).into()),
xpub: our_xpub,
},
];
let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys);
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 tr keyspend policy"),
)
.await
.unwrap();
}
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,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone());
psbt.update_input_with_descriptor(0, &input_descriptor)
.unwrap();
psbt.update_output_with_descriptor(0, &change_descriptor)
.unwrap();
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
Some(pb::BtcScriptConfigWithKeypath {
script_config: Some(policy_config),
keypath: keypath_account.to_u32_vec(),
}),
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
})
.await
}
#[tokio::test]
async fn test_btc_psbt_policy_tr_scriptspend() {
test_initialized_simulators(async |bitbox| {
if !semver::VersionReq::parse(">=9.21.0")
.unwrap()
.matches(bitbox.version())
{
return;
}
let secp = secp256k1::Secp256k1::new();
let coin = pb::BtcCoin::Tbtc;
let policy = "tr(@0/<0;1>/*,pk(@1/<0;1>/*))";
let our_root_fingerprint = util::simulator_xprv().fingerprint(&secp);
let keypath_account: DerivationPath = "m/48'/1'/0'/3'".parse().unwrap();
let our_xpub: Xpub = util::simulator_xpub_at(&secp, &keypath_account);
let some_xpub: Xpub = "tpubDFgycCkexSxkdZfeyaasDHityE97kiYM1BeCNoivDHvydGugKtoNobt4vEX6YSHNPy2cqmWQHKjKxciJuocepsGPGxcDZVmiMBnxgA1JKQk".parse().unwrap();
let multi_descriptor: miniscript::Descriptor<miniscript::DescriptorPublicKey> = policy
.replace(
"@1",
&format!("[{}/48'/1'/0'/3']{}", &our_root_fingerprint, &our_xpub),
)
.replace("@0", &some_xpub.to_string())
.parse::<miniscript::Descriptor<miniscript::DescriptorPublicKey>>()
.unwrap();
assert!(multi_descriptor.sanity_check().is_ok());
let [descriptor_receive, descriptor_change] = multi_descriptor
.into_single_descriptors()
.unwrap()
.try_into()
.unwrap();
let input_descriptor = descriptor_receive.at_derivation_index(0).unwrap();
let change_descriptor = descriptor_change.at_derivation_index(0).unwrap();
let keys = &[
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: None,
keypath: None,
xpub: some_xpub,
},
bitbox_api::btc::KeyOriginInfo {
root_fingerprint: Some(our_root_fingerprint),
keypath: Some((&keypath_account).into()),
xpub: our_xpub,
},
];
let policy_config = bitbox_api::btc::make_script_config_policy(policy, keys);
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 tr scriptspend policy"),
)
.await
.unwrap();
}
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,
"e4adbb12c3426ec71ebb10688d8ae69d531ca822a2b790acee216a7f1b95b576"
.parse()
.unwrap(),
None,
),
},
],
};
let mut psbt = Psbt::from_unsigned_tx(tx).unwrap();
psbt.inputs[0].witness_utxo = Some(prev_tx.output[0].clone());
psbt.update_input_with_descriptor(0, &input_descriptor)
.unwrap();
psbt.update_output_with_descriptor(0, &change_descriptor)
.unwrap();
bitbox
.btc_sign_psbt(
pb::BtcCoin::Tbtc,
&mut psbt,
Some(pb::BtcScriptConfigWithKeypath {
script_config: Some(policy_config),
keypath: keypath_account.to_u32_vec(),
}),
pb::btc_sign_init_request::FormatUnit::Default,
)
.await
.unwrap();
psbt.finalize_mut(&secp).unwrap();
verify_transaction(psbt);
}).await
}