Skip to content

Commit 861af3f

Browse files
committed
Merge branch 'taproot-policies'
2 parents 14d4468 + 9e14c69 commit 861af3f

File tree

5 files changed

+387
-25
lines changed

5 files changed

+387
-25
lines changed

CHANGELOG-npm.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
## 0.7.0
66
- btc: handle error when an input's previous transaction is required but missing
77
- btc: add support for regtest
8+
- btc: add support for Taproot wallet policies
89

910
## 0.6.0
1011

CHANGELOG-rust.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
## 0.6.0
66
- btc: handle error when an input's previous transaction is required but missing
77
- btc: add support for regtest
8+
- btc: add support for Taproot wallet policies
89
- cardano: added support for vote delegation
910

1011
## 0.5.0

src/btc.rs

+62-25
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ pub enum PsbtError {
242242
#[error("{0}")]
243243
#[cfg_attr(feature = "wasm", assoc(js_code = "sign-error"))]
244244
SignError(#[from] bitcoin::psbt::SignError),
245-
#[error("The BitBox does not support Taproot script path spending.")]
246-
#[cfg_attr(feature = "wasm", assoc(js_code = "unsupported-tap-script"))]
247-
UnsupportedTapScript,
245+
#[error("Taproot pubkeys must be unique across the internal key and all leaf scripts.")]
246+
#[cfg_attr(feature = "wasm", assoc(js_code = "key-not-unique"))]
247+
KeyNotUnique,
248248
#[error("Could not find our key in an input.")]
249249
#[cfg_attr(feature = "wasm", assoc(js_code = "key-not-found"))]
250250
KeyNotFound,
@@ -256,13 +256,19 @@ pub enum PsbtError {
256256
enum OurKey {
257257
Segwit(bitcoin::secp256k1::PublicKey, Keypath),
258258
TaprootInternal(Keypath),
259+
TaprootScript(
260+
bitcoin::secp256k1::XOnlyPublicKey,
261+
bitcoin::taproot::TapLeafHash,
262+
Keypath,
263+
),
259264
}
260265

261266
impl OurKey {
262267
fn keypath(&self) -> Keypath {
263268
match self {
264269
OurKey::Segwit(_, kp) => kp.clone(),
265270
OurKey::TaprootInternal(kp) => kp.clone(),
271+
OurKey::TaprootScript(_, _, kp) => kp.clone(),
266272
}
267273
}
268274
}
@@ -336,19 +342,34 @@ fn find_our_key<T: PsbtOutputInfo>(
336342
our_root_fingerprint: &[u8],
337343
output_info: T,
338344
) -> Result<OurKey, PsbtError> {
339-
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
340-
let (leaf_hashes, (fingerprint, derivation_path)) = output_info
341-
.get_tap_key_origins()
342-
.get(tap_internal_key)
343-
.ok_or(PsbtError::KeyNotFound)?;
344-
if !leaf_hashes.is_empty() {
345-
return Err(PsbtError::UnsupportedTapScript);
346-
}
345+
for (xonly, (leaf_hashes, (fingerprint, derivation_path))) in
346+
output_info.get_tap_key_origins().iter()
347+
{
347348
if &fingerprint[..] == our_root_fingerprint {
348349
// TODO: check for fingerprint collision
349-
return Ok(OurKey::TaprootInternal(derivation_path.into()));
350+
351+
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
352+
if tap_internal_key == xonly {
353+
if !leaf_hashes.is_empty() {
354+
// TODO change err msg, we don't support the
355+
// same key as internal key and also in a leaf
356+
// script.
357+
return Err(PsbtError::KeyNotUnique);
358+
}
359+
return Ok(OurKey::TaprootInternal(derivation_path.into()));
360+
}
361+
}
362+
if leaf_hashes.len() != 1 {
363+
// TODO change err msg, per BIP-388 all pubkeys are
364+
// unique, so it can't be in multiple leafs.
365+
return Err(PsbtError::KeyNotUnique);
366+
}
367+
return Ok(OurKey::TaprootScript(
368+
*xonly,
369+
leaf_hashes[0],
370+
derivation_path.into(),
371+
));
350372
}
351-
return Err(PsbtError::KeyNotFound);
352373
}
353374
for (pubkey, (fingerprint, derivation_path)) in output_info.get_bip32_derivation().iter() {
354375
if &fingerprint[..] == our_root_fingerprint {
@@ -576,15 +597,26 @@ pub fn make_script_config_policy(policy: &str, keys: &[KeyOriginInfo]) -> pb::Bt
576597
}
577598
}
578599

579-
fn is_taproot(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
580-
matches!(script_config,
581-
pb::BtcScriptConfigWithKeypath {
582-
script_config:
583-
Some(pb::BtcScriptConfig {
584-
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
585-
}),
586-
..
587-
} if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32)
600+
fn is_taproot_simple(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
601+
matches!(
602+
script_config.script_config.as_ref(),
603+
Some(pb::BtcScriptConfig {
604+
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
605+
}) if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32
606+
)
607+
}
608+
609+
fn is_taproot_policy(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
610+
matches!(
611+
script_config.script_config.as_ref(),
612+
Some(pb::BtcScriptConfig {
613+
config: Some(pb::btc_script_config::Config::Policy(policy)),
614+
}) if policy.policy.as_str().starts_with("tr("),
615+
)
616+
}
617+
618+
fn is_schnorr(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
619+
is_taproot_simple(script_config) | is_taproot_policy(script_config)
588620
}
589621

590622
impl<R: Runtime> PairedBitBox<R> {
@@ -681,7 +713,7 @@ impl<R: Runtime> PairedBitBox<R> {
681713
format_unit: pb::btc_sign_init_request::FormatUnit,
682714
) -> Result<Vec<Vec<u8>>, Error> {
683715
self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0
684-
if transaction.script_configs.iter().any(is_taproot) {
716+
if transaction.script_configs.iter().any(is_taproot_simple) {
685717
self.validate_version(">=9.10.0")?; // taproot since 9.10.0
686718
}
687719

@@ -709,7 +741,7 @@ impl<R: Runtime> PairedBitBox<R> {
709741
let input_index: usize = next_response.index as _;
710742
let tx_input: &TxInput = &transaction.inputs[input_index];
711743

712-
let input_is_schnorr = is_taproot(
744+
let input_is_schnorr = is_schnorr(
713745
&transaction.script_configs[tx_input.script_config_index as usize],
714746
);
715747
let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
@@ -897,7 +929,12 @@ impl<R: Runtime> PairedBitBox<R> {
897929
psbt_input.tap_key_sig = Some(
898930
bitcoin::taproot::Signature::from_slice(signature)
899931
.map_err(|_| Error::InvalidSignature)?,
900-
)
932+
);
933+
}
934+
OurKey::TaprootScript(xonly, leaf_hash, _) => {
935+
let sig = bitcoin::taproot::Signature::from_slice(signature)
936+
.map_err(|_| Error::InvalidSignature)?;
937+
psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
901938
}
902939
}
903940
}

tests/simulators.json

+8
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,13 @@
22
{
33
"url": "https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.19.0/bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64",
44
"sha256": "e28be3fd6c7777624ad2574546ba125b7f134f095fa951acc8fb7295f3d33931"
5+
},
6+
{
7+
"url": "https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.20.0/bitbox02-multi-v9.20.0-simulator1.0.0-linux-amd64",
8+
"sha256": "ac32c1a71bd0a3a934bc7b94268f651c655f2e3afbb954811a256e551a420b3d"
9+
},
10+
{
11+
"url": "https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.21.0/bitbox02-multi-v9.21.0-simulator1.0.0-linux-amd64",
12+
"sha256": "72031b226ea344970a6a1506893838a63b075e0bad726557ab9d941b42c534f5"
513
}
614
]

0 commit comments

Comments
 (0)