Skip to content

Commit f8349f4

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 383bca0 commit f8349f4

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
@@ -79,7 +79,7 @@ required-features = ["usb", "tokio/rt", "tokio/macros"]
7979

8080
[[example]]
8181
name = "btc_miniscript"
82-
required-features = ["usb", "tokio/rt", "tokio/macros"]
82+
required-features = ["simulator", "tokio/rt", "tokio/macros"]
8383

8484
[[example]]
8585
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
@@ -215,13 +215,19 @@ pub enum PsbtError {
215215
enum OurKey {
216216
Segwit(bitcoin::secp256k1::PublicKey, Keypath),
217217
TaprootInternal(Keypath),
218+
TaprootScript(
219+
bitcoin::secp256k1::XOnlyPublicKey,
220+
bitcoin::taproot::TapLeafHash,
221+
Keypath,
222+
),
218223
}
219224

220225
impl OurKey {
221226
fn keypath(&self) -> Keypath {
222227
match self {
223228
OurKey::Segwit(_, kp) => kp.clone(),
224229
OurKey::TaprootInternal(kp) => kp.clone(),
230+
OurKey::TaprootScript(_, _, kp) => kp.clone(),
225231
}
226232
}
227233
}
@@ -295,19 +301,34 @@ fn find_our_key<T: PsbtOutputInfo>(
295301
our_root_fingerprint: &[u8],
296302
output_info: T,
297303
) -> Result<OurKey, PsbtError> {
298-
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
299-
let (leaf_hashes, (fingerprint, derivation_path)) = output_info
300-
.get_tap_key_origins()
301-
.get(tap_internal_key)
302-
.ok_or(PsbtError::KeyNotFound)?;
303-
if !leaf_hashes.is_empty() {
304-
return Err(PsbtError::UnsupportedTapScript);
305-
}
304+
for (xonly, (leaf_hashes, (fingerprint, derivation_path))) in
305+
output_info.get_tap_key_origins().iter()
306+
{
306307
if &fingerprint[..] == our_root_fingerprint {
307308
// TODO: check for fingerprint collision
308-
return Ok(OurKey::TaprootInternal(derivation_path.into()));
309+
310+
if let Some(tap_internal_key) = output_info.get_tap_internal_key() {
311+
if tap_internal_key == xonly {
312+
if !leaf_hashes.is_empty() {
313+
// TODO change err msg, we don't support the
314+
// same key as internal key and also in a leaf
315+
// script.
316+
return Err(PsbtError::UnsupportedTapScript);
317+
}
318+
return Ok(OurKey::TaprootInternal(derivation_path.into()));
319+
}
320+
}
321+
if leaf_hashes.len() != 1 {
322+
// TODO change err msg, per BIP-388 all pubkeys are
323+
// unique, so it can't be in multiple leafs.
324+
return Err(PsbtError::UnsupportedTapScript);
325+
}
326+
return Ok(OurKey::TaprootScript(
327+
*xonly,
328+
leaf_hashes[0],
329+
derivation_path.into(),
330+
));
309331
}
310-
return Err(PsbtError::KeyNotFound);
311332
}
312333
for (pubkey, (fingerprint, derivation_path)) in output_info.get_bip32_derivation().iter() {
313334
if &fingerprint[..] == our_root_fingerprint {
@@ -516,15 +537,26 @@ pub fn make_script_config_policy(policy: &str, keys: &[KeyOriginInfo]) -> pb::Bt
516537
}
517538
}
518539

519-
fn is_taproot(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
520-
matches!(script_config,
521-
pb::BtcScriptConfigWithKeypath {
522-
script_config:
523-
Some(pb::BtcScriptConfig {
524-
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
525-
}),
526-
..
527-
} if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32)
540+
fn is_taproot_simple(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
541+
matches!(
542+
script_config.script_config.as_ref(),
543+
Some(pb::BtcScriptConfig {
544+
config: Some(pb::btc_script_config::Config::SimpleType(simple_type)),
545+
}) if *simple_type == pb::btc_script_config::SimpleType::P2tr as i32
546+
)
547+
}
548+
549+
fn is_taproot_policy(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
550+
matches!(
551+
script_config.script_config.as_ref(),
552+
Some(pb::BtcScriptConfig {
553+
config: Some(pb::btc_script_config::Config::Policy(policy)),
554+
}) if policy.policy.as_str().starts_with("tr("),
555+
)
556+
}
557+
558+
fn is_schnorr(script_config: &pb::BtcScriptConfigWithKeypath) -> bool {
559+
is_taproot_simple(script_config) | is_taproot_policy(script_config)
528560
}
529561

530562
impl<R: Runtime> PairedBitBox<R> {
@@ -621,7 +653,7 @@ impl<R: Runtime> PairedBitBox<R> {
621653
format_unit: pb::btc_sign_init_request::FormatUnit,
622654
) -> Result<Vec<Vec<u8>>, Error> {
623655
self.validate_version(">=9.4.0")?; // anti-klepto since 9.4.0
624-
if transaction.script_configs.iter().any(is_taproot) {
656+
if transaction.script_configs.iter().any(is_taproot_simple) {
625657
self.validate_version(">=9.10.0")?; // taproot since 9.10.0
626658
}
627659

@@ -648,7 +680,7 @@ impl<R: Runtime> PairedBitBox<R> {
648680
let input_index: usize = next_response.index as _;
649681
let tx_input: &TxInput = &transaction.inputs[input_index];
650682

651-
let input_is_schnorr = is_taproot(
683+
let input_is_schnorr = is_schnorr(
652684
&transaction.script_configs[tx_input.script_config_index as usize],
653685
);
654686
let perform_antiklepto = is_inputs_pass2 && !input_is_schnorr;
@@ -844,7 +876,12 @@ impl<R: Runtime> PairedBitBox<R> {
844876
psbt_input.tap_key_sig = Some(
845877
bitcoin::taproot::Signature::from_slice(signature)
846878
.map_err(|_| Error::InvalidSignature)?,
847-
)
879+
);
880+
}
881+
OurKey::TaprootScript(xonly, leaf_hash, _) => {
882+
let sig = bitcoin::taproot::Signature::from_slice(signature)
883+
.map_err(|_| Error::InvalidSignature)?;
884+
psbt_input.tap_script_sigs.insert((xonly, leaf_hash), sig);
848885
}
849886
}
850887
}

0 commit comments

Comments
 (0)