Skip to content

Commit ef09a97

Browse files
committed
btc: support Taproot policies
This mostly involves fixing PSBT signing to be able to handle Taproot leaf script spends. Integration tests with the simulator are added. The signed PSBTs are checked to be correctly signed by rust-miniscript and rust-bitcoinconsensus.
1 parent 7abaa32 commit ef09a97

File tree

6 files changed

+391
-49
lines changed

6 files changed

+391
-49
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ required-features = ["usb", "tokio/rt", "tokio/macros"]
8181

8282
[[example]]
8383
name = "btc_miniscript"
84-
required-features = ["usb", "tokio/rt", "tokio/macros"]
84+
required-features = ["simulator", "tokio/rt", "tokio/macros"]
8585

8686
[[example]]
8787
name = "eth"

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ example-btc-psbt:
1313
example-btc-sign-msg:
1414
cargo run --example btc_sign_msg --features=usb,tokio/rt,tokio/macros
1515
example-btc-miniscript:
16-
cargo run --example btc_miniscript --features=usb,tokio/rt,tokio/macros
16+
cargo run --example btc_miniscript --features=tokio/rt,tokio/macros,simulator
1717
example-eth:
1818
cargo run --example eth --features=usb,tokio/rt,tokio/macros,rlp
1919
example-cardano:

examples/btc_miniscript.rs

+27-22
Large diffs are not rendered by default.

examples/btc_sign_psbt.rs

+2-3
Large diffs are not rendered by default.

src/btc.rs

+59-22
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,19 @@ pub enum PsbtError {
248248
enum OurKey {
249249
Segwit(bitcoin::secp256k1::PublicKey, Keypath),
250250
TaprootInternal(Keypath),
251+
TaprootScript(
252+
bitcoin::secp256k1::XOnlyPublicKey,
253+
bitcoin::taproot::TapLeafHash,
254+
Keypath,
255+
),
251256
}
252257

253258
impl OurKey {
254259
fn keypath(&self) -> Keypath {
255260
match self {
256261
OurKey::Segwit(_, kp) => kp.clone(),
257262
OurKey::TaprootInternal(kp) => kp.clone(),
263+
OurKey::TaprootScript(_, _, kp) => kp.clone(),
258264
}
259265
}
260266
}
@@ -328,19 +334,34 @@ fn find_our_key<T: PsbtOutputInfo>(
328334
our_root_fingerprint: &[u8],
329335
output_info: T,
330336
) -> Result<OurKey, PsbtError> {
331-
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
332-
let (leaf_hashes, (fingerprint, derivation_path)) = output_info
333-
.get_tap_key_origins()
334-
.get(tap_internal_key)
335-
.ok_or(PsbtError::KeyNotFound)?;
336-
if !leaf_hashes.is_empty() {
337-
return Err(PsbtError::UnsupportedTapScript);
338-
}
337+
for (xonly, (leaf_hashes, (fingerprint, derivation_path))) in
338+
output_info.get_tap_key_origins().iter()
339+
{
339340
if &fingerprint[..] == our_root_fingerprint {
340341
// TODO: check for fingerprint collision
341-
return Ok(OurKey::TaprootInternal(derivation_path.into()));
342+
343+
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
344+
if tap_internal_key == xonly {
345+
if !leaf_hashes.is_empty() {
346+
// TODO change err msg, we don't support the
347+
// same key as internal key and also in a leaf
348+
// script.
349+
return Err(PsbtError::UnsupportedTapScript);
350+
}
351+
return Ok(OurKey::TaprootInternal(derivation_path.into()));
352+
}
353+
}
354+
if leaf_hashes.len() != 1 {
355+
// TODO change err msg, per BIP-388 all pubkeys are
356+
// unique, so it can't be in multiple leafs.
357+
return Err(PsbtError::UnsupportedTapScript);
358+
}
359+
return Ok(OurKey::TaprootScript(
360+
*xonly,
361+
leaf_hashes[0],
362+
derivation_path.into(),
363+
));
342364
}
343-
return Err(PsbtError::KeyNotFound);
344365
}
345366
for (pubkey, (fingerprint, derivation_path)) in output_info.get_bip32_derivation().iter() {
346367
if &fingerprint[..] == our_root_fingerprint {
@@ -568,15 +589,26 @@ pub fn make_script_config_policy(policy: &str, keys: &[KeyOriginInfo]) -> pb::Bt
568589
}
569590
}
570591

571-
fn is_taproot(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
572-
matches!(script_config,
573-
pb::BtcScriptConfigWithKeypath {
574-
script_config:
575-
Some(pb::BtcScriptConfig {
576-
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
577-
}),
578-
..
579-
} if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32)
592+
fn is_taproot_simple(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
593+
matches!(
594+
script_config.script_config.as_ref(),
595+
Some(pb::BtcScriptConfig {
596+
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
597+
}) if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32
598+
)
599+
}
600+
601+
fn is_taproot_policy(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
602+
matches!(
603+
script_config.script_config.as_ref(),
604+
Some(pb::BtcScriptConfig {
605+
config: Some(pb::btc_script_config::Config::Policy(policy)),
606+
}) if policy.policy.as_str().starts_with("tr("),
607+
)
608+
}
609+
610+
fn is_schnorr(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
611+
is_taproot_simple(script_config) | is_taproot_policy(script_config)
580612
}
581613

582614
impl<R: Runtime> PairedBitBox<R> {
@@ -673,7 +705,7 @@ impl<R: Runtime> PairedBitBox<R> {
673705
format_unit: pb::btc_sign_init_request::FormatUnit,
674706
) -> Result<Vec<Vec<u8>>, Error> {
675707
self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0
676-
if transaction.script_configs.iter().any(is_taproot) {
708+
if transaction.script_configs.iter().any(is_taproot_simple) {
677709
self.validate_version(">=9.10.0")?; // taproot since 9.10.0
678710
}
679711

@@ -700,7 +732,7 @@ impl<R: Runtime> PairedBitBox<R> {
700732
let input_index: usize = next_response.index as _;
701733
let tx_input: &TxInput = &transaction.inputs[input_index];
702734

703-
let input_is_schnorr = is_taproot(
735+
let input_is_schnorr = is_schnorr(
704736
&transaction.script_configs[tx_input.script_config_index as usize],
705737
);
706738
let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
@@ -896,7 +928,12 @@ impl<R: Runtime> PairedBitBox<R> {
896928
psbt_input.tap_key_sig = Some(
897929
bitcoin::taproot::Signature::from_slice(signature)
898930
.map_err(|_| Error::InvalidSignature)?,
899-
)
931+
);
932+
}
933+
OurKey::TaprootScript(xonly, leaf_hash, _) => {
934+
let sig = bitcoin::taproot::Signature::from_slice(signature)
935+
.map_err(|_| Error::InvalidSignature)?;
936+
psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
900937
}
901938
}
902939
}

0 commit comments

Comments
 (0)