diff --git a/bitcoin/src/blockdata/witness.rs b/bitcoin/src/blockdata/witness.rs index 20efc0b5f..d0ac6f526 100644 --- a/bitcoin/src/blockdata/witness.rs +++ b/bitcoin/src/blockdata/witness.rs @@ -13,7 +13,7 @@ use crate::crypto::ecdsa; use crate::prelude::Vec; #[cfg(doc)] use crate::script::ScriptExt as _; -use crate::taproot::{self, TAPROOT_ANNEX_PREFIX}; +use crate::taproot::{self, LeafScript, LeafVersion, TAPROOT_ANNEX_PREFIX, TAPROOT_CONTROL_BASE_SIZE, TAPROOT_LEAF_MASK}; use crate::Script; #[rustfmt::skip] // Keep public re-exports separate. @@ -156,6 +156,22 @@ crate::internal_macros::define_extension_trait! { } } + /// Returns the leaf script with its version but without the merkle proof. + /// + /// This does not guarantee that this represents a P2TR [`Witness`]. It + /// merely gets the second to last or third to last element depending on + /// the first byte of the last element being equal to 0x50 and the associated + /// version. + fn taproot_leaf_script(&self) -> Option> { + match P2TrSpend::from_witness(self) { + Some(P2TrSpend::Script { leaf_script, control_block, .. }) if control_block.len() >= TAPROOT_CONTROL_BASE_SIZE => { + let version = LeafVersion::from_consensus(control_block[0] & TAPROOT_LEAF_MASK).ok()?; + Some(LeafScript { version, script: leaf_script, }) + }, + _ => None, + } + } + /// Get the taproot control block following BIP341 rules. /// /// This does not guarantee that this represents a P2TR [`Witness`]. It @@ -371,6 +387,32 @@ mod test { assert_eq!(witness_annex.tapscript(), Some(Script::from_bytes(&tapscript[..]))); } + #[test] + fn get_taproot_leaf_script() { + let tapscript = hex!("deadbeef"); + let control_block = hex!("c0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + // annex starting with 0x50 causes the branching logic. + let annex = hex!("50"); + + let witness_vec = vec![tapscript.clone(), control_block.clone()]; + let witness_vec_annex = vec![tapscript.clone(), control_block, annex]; + + let witness_serialized: Vec = serialize(&witness_vec); + let witness_serialized_annex: Vec = serialize(&witness_vec_annex); + + let witness = deserialize::(&witness_serialized[..]).unwrap(); + let witness_annex = deserialize::(&witness_serialized_annex[..]).unwrap(); + + let expected_leaf_script = LeafScript { + version: LeafVersion::TapScript, + script: Script::from_bytes(&tapscript), + }; + + // With or without annex, the tapscript should be returned. + assert_eq!(witness.taproot_leaf_script().unwrap(), expected_leaf_script); + assert_eq!(witness_annex.taproot_leaf_script().unwrap(), expected_leaf_script); + } + #[test] fn get_tapscript_from_keypath() { let signature = hex!("deadbeef"); diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs index 775357f07..69746a1a5 100644 --- a/bitcoin/src/taproot/mod.rs +++ b/bitcoin/src/taproot/mod.rs @@ -142,6 +142,15 @@ pub const TAPROOT_CONTROL_BASE_SIZE: usize = 33; pub const TAPROOT_CONTROL_MAX_SIZE: usize = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT; +/// The leaf script with its version. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LeafScript { + /// The version of the script. + pub version: LeafVersion, + /// The script, usually `ScriptBuf` or `&Script`. + pub script: S, +} + // type alias for versioned tap script corresponding Merkle proof type ScriptMerkleProofMap = BTreeMap<(ScriptBuf, LeafVersion), BTreeSet>;