Skip to main content

taproot_assets_core/verify/
taproot_proof.rs

1//! Taproot proof verification helpers.
2
3use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5
6use bitcoin::hashes::{Hash, HashEngine, sha256::Hash as Sha256Hash};
7use bitcoin::secp256k1::{PublicKey as SecpPublicKey, XOnlyPublicKey};
8use bitcoin::taproot::{LeafVersion, TapNodeHash};
9use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, Witness};
10use serde::{Deserialize, Serialize};
11use taproot_assets_types::asset::{
12    Asset, AssetType, AssetVersion, GenesisInfo, PrevId, PrevWitness, SerializedKey,
13    SplitCommitment,
14};
15use taproot_assets_types::commitment::{
16    TapCommitmentVersion, TapscriptPreimage, TapscriptPreimageType,
17};
18use taproot_assets_types::mssmt::{MssmtNode, MssmtProof};
19use taproot_assets_types::proof::{CommitmentProof, TaprootProof, TapscriptProof};
20
21use crate::{OpsError, TaprootOps};
22
23/// Errors returned by taproot proof verification.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Error {
26    /// Taproot output index is invalid.
27    InvalidTaprootOutputIndex {
28        /// Index requested in the anchor transaction outputs.
29        output_index: u32,
30        /// Total number of outputs in the anchor transaction.
31        output_count: usize,
32    },
33    /// Script pubkey is not a Taproot v1 witness program.
34    InvalidTaprootWitnessProgram,
35    /// Taproot output key bytes are invalid.
36    InvalidTaprootOutputKey,
37    /// Taproot proof does not include a supported method.
38    MissingTaprootProofMethod,
39    /// Taproot proof commitment data is invalid or missing.
40    InvalidCommitmentProof,
41    /// Taproot proof is missing a commitment proof.
42    MissingCommitmentProof,
43    /// Taproot proof is missing an asset proof.
44    MissingAssetProof,
45    /// Taproot proof is invalid for tapscript verification.
46    InvalidTapscriptProof,
47    /// Taproot proof derived key does not match the anchor output.
48    InvalidTaprootProof,
49    /// Tapscript preimage is empty.
50    EmptyTapscriptPreimage,
51    /// Tapscript preimage length is invalid.
52    InvalidTapscriptPreimageLength {
53        /// Expected length in bytes.
54        expected: usize,
55        /// Actual length in bytes.
56        actual: usize,
57    },
58    /// Tapleaf script version is not supported.
59    InvalidTapLeafScriptVersion,
60    /// Tapleaf script length is invalid.
61    InvalidTapLeafScriptLength,
62    /// Tapscript preimage is a Taproot Asset commitment leaf.
63    TapscriptPreimageIsTapCommitment,
64    /// Asset genesis information is missing.
65    MissingAssetGenesis,
66    /// Asset script key length is invalid.
67    InvalidAssetScriptKeyLength {
68        /// Expected length in bytes.
69        expected: usize,
70        /// Actual length in bytes.
71        actual: usize,
72    },
73    /// Asset script key is invalid.
74    InvalidAssetScriptKey,
75    /// Asset group key is invalid.
76    InvalidAssetGroupKey,
77    /// Asset script version is invalid.
78    InvalidAssetScriptVersion,
79    /// Asset group key length is invalid.
80    InvalidGroupKeyLength {
81        /// Expected length in bytes.
82        expected: usize,
83        /// Actual length in bytes.
84        actual: usize,
85    },
86    /// MS-SMT proof length is invalid.
87    InvalidMssmtProofLength {
88        /// Expected proof length.
89        expected: usize,
90        /// Actual proof length.
91        actual: usize,
92    },
93    /// MS-SMT sum overflowed while hashing.
94    MssmtSumOverflow,
95    /// Taproot operation failed.
96    Ops(OpsError),
97}
98
99impl From<OpsError> for Error {
100    /// Converts an ops error into a taproot proof error.
101    fn from(err: OpsError) -> Self {
102        Self::Ops(err)
103    }
104}
105
106impl core::fmt::Display for Error {
107    /// Formats the error for display.
108    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        match self {
110            Error::InvalidTaprootOutputIndex {
111                output_index,
112                output_count,
113            } => write!(
114                f,
115                "invalid taproot output index {} for {} outputs",
116                output_index, output_count
117            ),
118            Error::InvalidTaprootWitnessProgram => {
119                write!(f, "script pubkey is not a Taproot v1 witness program")
120            }
121            Error::InvalidTaprootOutputKey => write!(f, "invalid taproot output key"),
122            Error::MissingTaprootProofMethod => {
123                write!(f, "taproot proof missing commitment or tapscript data")
124            }
125            Error::InvalidCommitmentProof => write!(f, "invalid commitment proof"),
126            Error::MissingCommitmentProof => write!(f, "missing commitment proof"),
127            Error::MissingAssetProof => write!(f, "missing asset proof"),
128            Error::InvalidTapscriptProof => write!(f, "invalid tapscript proof"),
129            Error::InvalidTaprootProof => write!(f, "invalid taproot proof"),
130            Error::EmptyTapscriptPreimage => write!(f, "empty tapscript preimage"),
131            Error::InvalidTapscriptPreimageLength { expected, actual } => write!(
132                f,
133                "invalid tapscript preimage length {}, expected {}",
134                actual, expected
135            ),
136            Error::InvalidTapLeafScriptVersion => {
137                write!(f, "invalid tapleaf script version")
138            }
139            Error::InvalidTapLeafScriptLength => write!(f, "invalid tapleaf script length"),
140            Error::TapscriptPreimageIsTapCommitment => {
141                write!(f, "tapscript preimage is a taproot asset commitment")
142            }
143            Error::MissingAssetGenesis => write!(f, "missing asset genesis"),
144            Error::InvalidAssetScriptKeyLength { expected, actual } => write!(
145                f,
146                "asset script key length {}, expected {}",
147                actual, expected
148            ),
149            Error::InvalidAssetScriptKey => write!(f, "invalid asset script key"),
150            Error::InvalidAssetGroupKey => write!(f, "invalid asset group key"),
151            Error::InvalidAssetScriptVersion => write!(f, "invalid asset script version"),
152            Error::InvalidGroupKeyLength { expected, actual } => write!(
153                f,
154                "asset group key length must be {}, got {}",
155                expected, actual
156            ),
157            Error::InvalidMssmtProofLength { expected, actual } => write!(
158                f,
159                "invalid mssmt proof length {}, expected {}",
160                actual, expected
161            ),
162            Error::MssmtSumOverflow => write!(f, "mssmt sum overflow"),
163            Error::Ops(err) => core::fmt::Display::fmt(err, f),
164        }
165    }
166}
167
168/// Number of levels in an MS-SMT.
169const MSSMT_TREE_LEVELS: usize = 256;
170/// Length in bytes of a Taproot Asset commitment leaf script.
171const TAPROOT_ASSET_COMMITMENT_SCRIPT_SIZE: usize = 1 + 32 + 32 + 8;
172/// Marker tag for legacy Taproot Asset commitment leaves.
173const TAPROOT_ASSETS_MARKER_TAG: &str = "taproot-assets";
174/// Marker tag for V2 Taproot Asset commitment leaves.
175const TAPROOT_ASSETS_V2_TAG: &str = "taproot-assets:194243";
176/// Length in bytes of a TapBranch preimage without tag.
177const TAP_BRANCH_PREIMAGE_LEN: usize = 64;
178/// Maximum tapscript size accepted for leaf preimages.
179const MAX_TAPLEAF_SCRIPT_SIZE: usize = 4_000_000;
180
181/// Map of derived taproot output keys to their commitments.
182type ProofCommitmentKeys = BTreeMap<SerializedKey, TapCommitment>;
183
184/// Minimal TapCommitment representation derived during verification.
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct TapCommitment {
187    /// Taproot Asset commitment version.
188    pub version: TapCommitmentVersion,
189    /// Root hash of the TapCommitment MS-SMT.
190    pub root_hash: [u8; 32],
191    /// Root sum of the TapCommitment MS-SMT.
192    pub root_sum: u64,
193}
194
195impl TapCommitment {
196    /// Returns the tapscript leaf script for this commitment.
197    fn tap_leaf_script(&self) -> Vec<u8> {
198        let mut script = Vec::with_capacity(TAPROOT_ASSET_COMMITMENT_SCRIPT_SIZE);
199        match self.version {
200            TapCommitmentVersion::V0 | TapCommitmentVersion::V1 => {
201                script.push(self.version as u8);
202                script.extend_from_slice(&taproot_assets_marker());
203                script.extend_from_slice(&self.root_hash);
204                script.extend_from_slice(&self.root_sum.to_be_bytes());
205            }
206            TapCommitmentVersion::V2 => {
207                script.extend_from_slice(&taproot_assets_v2_tag());
208                script.push(self.version as u8);
209                script.extend_from_slice(&self.root_hash);
210                script.extend_from_slice(&self.root_sum.to_be_bytes());
211            }
212        }
213
214        script
215    }
216
217    /// Returns the TapNodeHash for this commitment leaf.
218    fn tap_leaf_hash(&self) -> TapNodeHash {
219        let script = ScriptBuf::from_bytes(self.tap_leaf_script());
220        TapNodeHash::from_script(script.as_script(), LeafVersion::TapScript)
221    }
222
223    /// Returns the tapscript root for this commitment and optional sibling.
224    fn tapscript_root(&self, sibling: Option<&TapscriptPreimage>) -> Result<TapNodeHash, Error> {
225        let commitment_hash = self.tap_leaf_hash();
226        let sibling_hash = match sibling {
227            Some(preimage) => Some(tapscript_preimage_hash(preimage)?),
228            None => None,
229        };
230
231        Ok(match sibling_hash {
232            Some(hash) => TapNodeHash::from_node_hashes(commitment_hash, hash),
233            None => commitment_hash,
234        })
235    }
236
237    /// Returns a downgraded TapCommitment version with the same root.
238    fn downgrade(&self) -> TapCommitment {
239        TapCommitment {
240            version: TapCommitmentVersion::V0,
241            root_hash: self.root_hash,
242            root_sum: self.root_sum,
243        }
244    }
245}
246
247/// MS-SMT root data along with its immediate children.
248#[derive(Debug, Clone, PartialEq, Eq)]
249struct MssmtRoot {
250    /// Root hash of the MS-SMT.
251    root_hash: [u8; 32],
252    /// Root sum of the MS-SMT.
253    root_sum: u64,
254    /// Root left child hash.
255    left_hash: [u8; 32],
256    /// Root right child hash.
257    right_hash: [u8; 32],
258}
259
260/// Extracts the taproot output key from an anchor transaction output.
261pub fn extract_taproot_key(
262    anchor_tx: &Transaction,
263    output_index: u32,
264) -> Result<XOnlyPublicKey, Error> {
265    let output_count = anchor_tx.output.len();
266    let output =
267        anchor_tx
268            .output
269            .get(output_index as usize)
270            .ok_or(Error::InvalidTaprootOutputIndex {
271                output_index,
272                output_count,
273            })?;
274
275    extract_taproot_key_from_script(output.script_pubkey.as_script())
276}
277
278/// Extracts the taproot output key from a script pubkey.
279pub fn extract_taproot_key_from_script(script: &Script) -> Result<XOnlyPublicKey, Error> {
280    if !script.is_p2tr() {
281        return Err(Error::InvalidTaprootWitnessProgram);
282    }
283
284    let bytes = script.as_bytes();
285    let mut key_bytes = [0u8; 32];
286    key_bytes.copy_from_slice(&bytes[2..34]);
287
288    XOnlyPublicKey::from_slice(&key_bytes).map_err(|_| Error::InvalidTaprootOutputKey)
289}
290
291/// Verifies a taproot proof against the anchor transaction output.
292pub fn verify_taproot_proof<O: TaprootOps>(
293    ops: &O,
294    anchor_tx: &Transaction,
295    proof: &TaprootProof,
296    asset: &Asset,
297    inclusion: bool,
298) -> Result<(), Error> {
299    verify_taproot_proof_with_commitment(ops, anchor_tx, proof, asset, inclusion).map(|_| ())
300}
301
302/// Verifies a taproot proof and returns the matched TapCommitment, if any.
303pub fn verify_taproot_proof_with_commitment<O: TaprootOps>(
304    ops: &O,
305    anchor_tx: &Transaction,
306    proof: &TaprootProof,
307    asset: &Asset,
308    inclusion: bool,
309) -> Result<Option<TapCommitment>, Error> {
310    let expected_key = extract_taproot_key(anchor_tx, proof.output_index)?;
311    verify_taproot_proof_with_commitment_and_key(ops, expected_key, proof, asset, inclusion)
312}
313
314/// Verifies a taproot proof against an expected key and returns the matched TapCommitment, if any.
315pub fn verify_taproot_proof_with_commitment_and_key<O: TaprootOps>(
316    ops: &O,
317    expected_key: XOnlyPublicKey,
318    proof: &TaprootProof,
319    asset: &Asset,
320    inclusion: bool,
321) -> Result<Option<TapCommitment>, Error> {
322    if inclusion {
323        let derived = derive_by_asset_inclusion(ops, proof, asset)?;
324        return verify_expected_key_with_commitment(&expected_key, &derived);
325    }
326
327    if proof.commitment_proof.is_some() {
328        let derived = derive_by_asset_exclusion(ops, proof, asset)?;
329        return verify_expected_key_with_commitment(&expected_key, &derived);
330    }
331
332    if proof.tapscript_proof.is_some() {
333        let derived = derive_by_tapscript_proof(ops, proof)?;
334        let expected = expected_key.serialize();
335        let derived_xonly = xonly_from_serialized_key(&derived)?;
336        return if expected == derived_xonly {
337            Ok(None)
338        } else {
339            Err(Error::InvalidTaprootProof)
340        };
341    }
342
343    Err(Error::MissingTaprootProofMethod)
344}
345
346/// Verifies the derived key set and returns the matching commitment, if any.
347fn verify_expected_key_with_commitment(
348    expected_key: &XOnlyPublicKey,
349    derived: &ProofCommitmentKeys,
350) -> Result<Option<TapCommitment>, Error> {
351    let expected = expected_key.serialize();
352    for (key, commitment) in derived {
353        let derived_xonly = xonly_from_serialized_key(key)?;
354        if derived_xonly == expected {
355            return Ok(Some(commitment.clone()));
356        }
357    }
358
359    Err(Error::InvalidTaprootProof)
360}
361
362/// Derives commitment keys for an inclusion proof.
363fn derive_by_asset_inclusion<O: TaprootOps>(
364    ops: &O,
365    proof: &TaprootProof,
366    asset: &Asset,
367) -> Result<ProofCommitmentKeys, Error> {
368    let commitment_proof = proof
369        .commitment_proof
370        .as_ref()
371        .ok_or(Error::MissingCommitmentProof)?;
372    if proof.tapscript_proof.is_some() {
373        return Err(Error::InvalidCommitmentProof);
374    }
375
376    let mut asset = asset.clone();
377    if asset_has_split_commitment_witness(&asset) {
378        asset = asset_without_split_commitment(&asset);
379    }
380
381    let tap_commitment = derive_commitment_by_asset_inclusion(commitment_proof, &asset)?;
382    let internal_key = serialized_key_from_pubkey(&proof.internal_key);
383    derive_commitment_keys(
384        ops,
385        &tap_commitment,
386        &internal_key,
387        commitment_proof.tap_sibling_preimage.as_ref(),
388        true,
389    )
390}
391
392/// Derives commitment keys for an exclusion proof.
393fn derive_by_asset_exclusion<O: TaprootOps>(
394    ops: &O,
395    proof: &TaprootProof,
396    asset: &Asset,
397) -> Result<ProofCommitmentKeys, Error> {
398    let commitment_proof = proof
399        .commitment_proof
400        .as_ref()
401        .ok_or(Error::MissingCommitmentProof)?;
402    if proof.tapscript_proof.is_some() {
403        return Err(Error::InvalidCommitmentProof);
404    }
405
406    let asset_commitment_key = asset_commitment_key(asset)?;
407    let tap_commitment_key = tap_commitment_key(asset)?;
408    let tap_commitment = if commitment_proof.proof.asset_proof.is_none() {
409        derive_commitment_by_asset_commitment_exclusion(commitment_proof, tap_commitment_key)?
410    } else {
411        derive_commitment_by_asset_exclusion(commitment_proof, asset_commitment_key)?
412    };
413
414    let internal_key = serialized_key_from_pubkey(&proof.internal_key);
415    derive_commitment_keys(
416        ops,
417        &tap_commitment,
418        &internal_key,
419        commitment_proof.tap_sibling_preimage.as_ref(),
420        true,
421    )
422}
423
424/// Derives the taproot output key for a tapscript proof.
425fn derive_by_tapscript_proof<O: TaprootOps>(
426    ops: &O,
427    proof: &TaprootProof,
428) -> Result<SerializedKey, Error> {
429    let tapscript_proof = proof
430        .tapscript_proof
431        .as_ref()
432        .ok_or(Error::InvalidTapscriptProof)?;
433    if proof.commitment_proof.is_some() {
434        return Err(Error::InvalidTapscriptProof);
435    }
436
437    let internal_key = serialized_key_from_pubkey(&proof.internal_key);
438    derive_taproot_key_from_tapscript(ops, &internal_key, tapscript_proof)
439}
440
441/// Derives all taproot output keys from a TapCommitment.
442fn derive_commitment_keys<O: TaprootOps>(
443    ops: &O,
444    commitment: &TapCommitment,
445    internal_key: &SerializedKey,
446    sibling: Option<&TapscriptPreimage>,
447    downgrade: bool,
448) -> Result<ProofCommitmentKeys, Error> {
449    let mut keys = ProofCommitmentKeys::new();
450    let key_v2 = derive_taproot_key_from_commitment(ops, commitment, internal_key, sibling)?;
451    keys.insert(key_v2, commitment.clone());
452
453    if downgrade {
454        let downgraded = commitment.downgrade();
455        let key_v0 = derive_taproot_key_from_commitment(ops, &downgraded, internal_key, sibling)?;
456        keys.insert(key_v0, downgraded);
457    }
458
459    Ok(keys)
460}
461
462/// Derives a taproot output key from a commitment and sibling preimage.
463fn derive_taproot_key_from_commitment<O: TaprootOps>(
464    ops: &O,
465    commitment: &TapCommitment,
466    internal_key: &SerializedKey,
467    sibling: Option<&TapscriptPreimage>,
468) -> Result<SerializedKey, Error> {
469    let internal_pubkey = ops.parse_internal_key(internal_key)?;
470    let tapscript_root = commitment.tapscript_root(sibling)?;
471    let output_key =
472        ops.taproot_output_key(&internal_pubkey, Some(tapscript_root.to_byte_array()))?;
473    Ok(output_key)
474}
475
476/// Derives a taproot output key from a tapscript proof.
477fn derive_taproot_key_from_tapscript<O: TaprootOps>(
478    ops: &O,
479    internal_key: &SerializedKey,
480    proof: &TapscriptProof,
481) -> Result<SerializedKey, Error> {
482    let internal_pubkey = ops.parse_internal_key(internal_key)?;
483    let preimage1 = proof.tap_preimage1.as_ref();
484    let preimage2 = proof.tap_preimage2.as_ref();
485    let preimage1_empty = preimage1.map_or(true, |p| p.sibling_preimage.is_empty());
486    let preimage2_empty = preimage2.map_or(true, |p| p.sibling_preimage.is_empty());
487
488    let tapscript_root = if !preimage1_empty
489        && !preimage2_empty
490        && preimage1.unwrap().sibling_type == TapscriptPreimageType::LeafPreimage
491        && preimage2.unwrap().sibling_type == TapscriptPreimageType::LeafPreimage
492    {
493        let left = tapscript_preimage_hash(preimage1.unwrap())?;
494        let right = tapscript_preimage_hash(preimage2.unwrap())?;
495        Some(TapNodeHash::from_node_hashes(left, right).to_byte_array())
496    } else if !preimage1_empty
497        && !preimage2_empty
498        && preimage1.unwrap().sibling_type == TapscriptPreimageType::BranchPreimage
499        && preimage2.unwrap().sibling_type == TapscriptPreimageType::BranchPreimage
500    {
501        let left = tapscript_preimage_hash(preimage1.unwrap())?;
502        let right = tapscript_preimage_hash(preimage2.unwrap())?;
503        Some(TapNodeHash::from_node_hashes(left, right).to_byte_array())
504    } else if !preimage1_empty
505        && !preimage2_empty
506        && preimage1.unwrap().sibling_type == TapscriptPreimageType::LeafPreimage
507        && preimage2.unwrap().sibling_type == TapscriptPreimageType::BranchPreimage
508    {
509        let left = tapscript_preimage_hash(preimage1.unwrap())?;
510        let right = tapscript_preimage_hash(preimage2.unwrap())?;
511        Some(TapNodeHash::from_node_hashes(left, right).to_byte_array())
512    } else if !preimage1_empty
513        && preimage2_empty
514        && preimage1.unwrap().sibling_type == TapscriptPreimageType::LeafPreimage
515    {
516        let leaf = tapscript_preimage_hash(preimage1.unwrap())?;
517        Some(leaf.to_byte_array())
518    } else if proof.bip86 {
519        None
520    } else {
521        return Err(Error::InvalidTapscriptProof);
522    };
523
524    let output_key = ops.taproot_output_key(&internal_pubkey, tapscript_root)?;
525    Ok(output_key)
526}
527
528/// Computes the tap hash for a tapscript preimage.
529fn tapscript_preimage_hash(preimage: &TapscriptPreimage) -> Result<TapNodeHash, Error> {
530    if preimage.sibling_preimage.is_empty() {
531        return Err(Error::EmptyTapscriptPreimage);
532    }
533
534    match preimage.sibling_type {
535        TapscriptPreimageType::LeafPreimage => {
536            let (leaf_version, script) = decode_tapleaf_preimage(&preimage.sibling_preimage)?;
537            if is_taproot_asset_commitment_script(&script) {
538                return Err(Error::TapscriptPreimageIsTapCommitment);
539            }
540            let script = ScriptBuf::from_bytes(script);
541            Ok(TapNodeHash::from_script(script.as_script(), leaf_version))
542        }
543        TapscriptPreimageType::BranchPreimage => {
544            let actual = preimage.sibling_preimage.len();
545            if actual != TAP_BRANCH_PREIMAGE_LEN {
546                return Err(Error::InvalidTapscriptPreimageLength {
547                    expected: TAP_BRANCH_PREIMAGE_LEN,
548                    actual,
549                });
550            }
551
552            let mut left = [0u8; 32];
553            left.copy_from_slice(&preimage.sibling_preimage[..32]);
554            let mut right = [0u8; 32];
555            right.copy_from_slice(&preimage.sibling_preimage[32..]);
556
557            Ok(TapNodeHash::from_node_hashes(
558                TapNodeHash::from_byte_array(left),
559                TapNodeHash::from_byte_array(right),
560            ))
561        }
562    }
563}
564
565/// Decodes a tapleaf preimage into a leaf version and script.
566fn decode_tapleaf_preimage(preimage: &[u8]) -> Result<(LeafVersion, Vec<u8>), Error> {
567    if preimage.len() < 2 {
568        return Err(Error::InvalidTapLeafScriptLength);
569    }
570
571    let leaf_version =
572        LeafVersion::from_consensus(preimage[0]).map_err(|_| Error::InvalidTapLeafScriptVersion)?;
573    if leaf_version != LeafVersion::TapScript {
574        return Err(Error::InvalidTapLeafScriptVersion);
575    }
576    let (script_len, len_len) = decode_compact_size(&preimage[1..])?;
577    let script_start = 1 + len_len;
578    let script_len = usize::try_from(script_len).map_err(|_| Error::InvalidTapLeafScriptLength)?;
579    let script_end = script_start
580        .checked_add(script_len)
581        .ok_or(Error::InvalidTapLeafScriptLength)?;
582    if script_end != preimage.len() {
583        return Err(Error::InvalidTapLeafScriptLength);
584    }
585
586    let script = preimage[script_start..script_end].to_vec();
587    if script.is_empty() || script.len() >= MAX_TAPLEAF_SCRIPT_SIZE {
588        return Err(Error::InvalidTapLeafScriptLength);
589    }
590
591    Ok((leaf_version, script))
592}
593
594/// Decodes a Bitcoin compact size integer from a byte slice.
595fn decode_compact_size(bytes: &[u8]) -> Result<(u64, usize), Error> {
596    let first = *bytes.first().ok_or(Error::InvalidTapLeafScriptLength)?;
597    match first {
598        0..=0xFC => Ok((first as u64, 1)),
599        0xFD => {
600            if bytes.len() < 3 {
601                return Err(Error::InvalidTapLeafScriptLength);
602            }
603            let val = u16::from_le_bytes([bytes[1], bytes[2]]) as u64;
604            Ok((val, 3))
605        }
606        0xFE => {
607            if bytes.len() < 5 {
608                return Err(Error::InvalidTapLeafScriptLength);
609            }
610            let val = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as u64;
611            Ok((val, 5))
612        }
613        0xFF => {
614            if bytes.len() < 9 {
615                return Err(Error::InvalidTapLeafScriptLength);
616            }
617            let val = u64::from_le_bytes([
618                bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
619            ]);
620            Ok((val, 9))
621        }
622    }
623}
624
625/// Returns true if the script matches the Taproot Asset commitment pattern.
626fn is_taproot_asset_commitment_script(script: &[u8]) -> bool {
627    if script.len() != TAPROOT_ASSET_COMMITMENT_SCRIPT_SIZE {
628        return false;
629    }
630
631    match script[0] {
632        v if v == TapCommitmentVersion::V0 as u8 || v == TapCommitmentVersion::V1 as u8 => {
633            script[1..33] == taproot_assets_marker()
634        }
635        _ => script[..32] == taproot_assets_v2_tag(),
636    }
637}
638
639/// Returns the Taproot Asset marker for V0 and V1 commitments.
640fn taproot_assets_marker() -> [u8; 32] {
641    Sha256Hash::hash(TAPROOT_ASSETS_MARKER_TAG.as_bytes()).to_byte_array()
642}
643
644/// Returns the Taproot Asset marker for V2 commitments.
645fn taproot_assets_v2_tag() -> [u8; 32] {
646    Sha256Hash::hash(TAPROOT_ASSETS_V2_TAG.as_bytes()).to_byte_array()
647}
648
649/// Builds a TapCommitment from an inclusion proof.
650fn derive_commitment_by_asset_inclusion(
651    commitment_proof: &CommitmentProof,
652    asset: &Asset,
653) -> Result<TapCommitment, Error> {
654    let asset_proof = commitment_proof
655        .proof
656        .asset_proof
657        .as_ref()
658        .ok_or(Error::MissingAssetProof)?;
659    let taproot_asset_proof = &commitment_proof.proof.taproot_asset_proof;
660
661    let asset_key = asset_commitment_key(asset)?;
662    let asset_leaf = asset_leaf(asset)?;
663    let asset_root = mssmt_root(asset_key, asset_leaf, &asset_proof.proof)?;
664    let asset_commitment_root = asset_commitment_root(asset_proof.tap_key, &asset_root);
665    let asset_commitment_leaf = asset_commitment_leaf(
666        asset_proof.version,
667        asset_commitment_root,
668        asset_root.root_sum,
669    );
670    let asset_commitment_leaf_node = mssmt_leaf(&asset_commitment_leaf, asset_root.root_sum);
671    let taproot_root = mssmt_root(
672        asset_proof.tap_key,
673        asset_commitment_leaf_node,
674        &taproot_asset_proof.proof,
675    )?;
676
677    Ok(TapCommitment {
678        version: taproot_asset_proof.version,
679        root_hash: taproot_root.root_hash,
680        root_sum: taproot_root.root_sum,
681    })
682}
683
684/// Builds a TapCommitment from an asset exclusion proof.
685fn derive_commitment_by_asset_exclusion(
686    commitment_proof: &CommitmentProof,
687    asset_commitment_key: [u8; 32],
688) -> Result<TapCommitment, Error> {
689    let asset_proof = commitment_proof
690        .proof
691        .asset_proof
692        .as_ref()
693        .ok_or(Error::MissingAssetProof)?;
694    let taproot_asset_proof = &commitment_proof.proof.taproot_asset_proof;
695    let empty_leaf = mssmt_leaf(&[], 0);
696    let asset_root = mssmt_root(asset_commitment_key, empty_leaf, &asset_proof.proof)?;
697    let asset_commitment_root = asset_commitment_root(asset_proof.tap_key, &asset_root);
698    let asset_commitment_leaf = asset_commitment_leaf(
699        asset_proof.version,
700        asset_commitment_root,
701        asset_root.root_sum,
702    );
703    let asset_commitment_leaf_node = mssmt_leaf(&asset_commitment_leaf, asset_root.root_sum);
704    let taproot_root = mssmt_root(
705        asset_proof.tap_key,
706        asset_commitment_leaf_node,
707        &taproot_asset_proof.proof,
708    )?;
709
710    Ok(TapCommitment {
711        version: taproot_asset_proof.version,
712        root_hash: taproot_root.root_hash,
713        root_sum: taproot_root.root_sum,
714    })
715}
716
717/// Builds a TapCommitment from an asset commitment exclusion proof.
718fn derive_commitment_by_asset_commitment_exclusion(
719    commitment_proof: &CommitmentProof,
720    tap_commitment_key: [u8; 32],
721) -> Result<TapCommitment, Error> {
722    if commitment_proof.proof.asset_proof.is_some() {
723        return Err(Error::InvalidCommitmentProof);
724    }
725    let taproot_asset_proof = &commitment_proof.proof.taproot_asset_proof;
726    let empty_leaf = mssmt_leaf(&[], 0);
727    let taproot_root = mssmt_root(tap_commitment_key, empty_leaf, &taproot_asset_proof.proof)?;
728
729    Ok(TapCommitment {
730        version: taproot_asset_proof.version,
731        root_hash: taproot_root.root_hash,
732        root_sum: taproot_root.root_sum,
733    })
734}
735
736/// Computes the asset commitment root hash from MS-SMT root data.
737fn asset_commitment_root(tap_key: [u8; 32], root: &MssmtRoot) -> [u8; 32] {
738    let mut engine = Sha256Hash::engine();
739    engine.input(&tap_key);
740    engine.input(&root.left_hash);
741    engine.input(&root.right_hash);
742    engine.input(&root.root_sum.to_be_bytes());
743    Sha256Hash::from_engine(engine).to_byte_array()
744}
745
746/// Encodes an asset commitment leaf for insertion into the TapCommitment tree.
747fn asset_commitment_leaf(version: AssetVersion, root_hash: [u8; 32], sum: u64) -> Vec<u8> {
748    let mut leaf = Vec::with_capacity(1 + 32 + 8);
749    leaf.push(version as u8);
750    leaf.extend_from_slice(&root_hash);
751    leaf.extend_from_slice(&sum.to_be_bytes());
752    leaf
753}
754
755/// Computes the taproot asset commitment key for an asset.
756fn tap_commitment_key(asset: &Asset) -> Result<[u8; 32], Error> {
757    if let Some(group) = &asset.asset_group {
758        let key_bytes = if group.tweaked_group_key.is_empty() {
759            &group.raw_group_key
760        } else {
761            &group.tweaked_group_key
762        };
763        if key_bytes.len() != 33 {
764            return Err(Error::InvalidGroupKeyLength {
765                expected: 33,
766                actual: key_bytes.len(),
767            });
768        }
769
770        let pubkey =
771            SecpPublicKey::from_slice(key_bytes).map_err(|_| Error::InvalidAssetGroupKey)?;
772        let (xonly, _) = pubkey.x_only_public_key();
773        let hash = Sha256Hash::hash(&xonly.serialize());
774        return Ok(hash.to_byte_array());
775    }
776
777    let genesis = asset
778        .asset_genesis
779        .as_ref()
780        .ok_or(Error::MissingAssetGenesis)?;
781    Ok(genesis.asset_id.to_byte_array())
782}
783
784/// Computes the asset commitment key for an asset.
785fn asset_commitment_key(asset: &Asset) -> Result<[u8; 32], Error> {
786    let genesis = asset
787        .asset_genesis
788        .as_ref()
789        .ok_or(Error::MissingAssetGenesis)?;
790    if asset.script_key.len() != 33 {
791        return Err(Error::InvalidAssetScriptKeyLength {
792            expected: 33,
793            actual: asset.script_key.len(),
794        });
795    }
796    let script_key =
797        SecpPublicKey::from_slice(&asset.script_key).map_err(|_| Error::InvalidAssetScriptKey)?;
798    let (xonly, _) = script_key.x_only_public_key();
799
800    let issuance_disabled = asset.asset_group.is_none();
801    if issuance_disabled {
802        return Ok(Sha256Hash::hash(&xonly.serialize()).to_byte_array());
803    }
804
805    let mut engine = Sha256Hash::engine();
806    engine.input(&genesis.asset_id.to_byte_array());
807    engine.input(&xonly.serialize());
808    Ok(Sha256Hash::from_engine(engine).to_byte_array())
809}
810
811/// Returns true if the asset contains a split commitment witness.
812fn asset_has_split_commitment_witness(asset: &Asset) -> bool {
813    if asset.prev_witnesses.len() != 1 {
814        return false;
815    }
816
817    let witness = &asset.prev_witnesses[0];
818    witness.prev_id.is_some() && witness.tx_witness.is_empty() && witness.split_commitment.is_some()
819}
820
821/// Returns a copy of the asset without any split commitment witness.
822fn asset_without_split_commitment(asset: &Asset) -> Asset {
823    let mut asset = asset.clone();
824    if asset_has_split_commitment_witness(&asset) {
825        asset.prev_witnesses[0].split_commitment = None;
826    }
827    asset
828}
829
830/// Builds an MS-SMT leaf from bytes and a sum.
831fn mssmt_leaf(value: &[u8], sum: u64) -> MssmtNode {
832    let mut engine = Sha256Hash::engine();
833    engine.input(value);
834    engine.input(&sum.to_be_bytes());
835    let hash = Sha256Hash::from_engine(engine);
836    MssmtNode { hash, sum }
837}
838
839/// Builds an MS-SMT branch from two child nodes.
840fn mssmt_branch(left: &MssmtNode, right: &MssmtNode) -> Result<MssmtNode, Error> {
841    let sum = left
842        .sum
843        .checked_add(right.sum)
844        .ok_or(Error::MssmtSumOverflow)?;
845
846    let mut engine = Sha256Hash::engine();
847    engine.input(&left.hash.to_byte_array());
848    engine.input(&right.hash.to_byte_array());
849    engine.input(&sum.to_be_bytes());
850    let hash = Sha256Hash::from_engine(engine);
851    Ok(MssmtNode { hash, sum })
852}
853
854/// Computes the MS-SMT root for a leaf and proof.
855fn mssmt_root(key: [u8; 32], leaf: MssmtNode, proof: &MssmtProof) -> Result<MssmtRoot, Error> {
856    let nodes = normalize_mssmt_nodes(&proof.nodes)?;
857    let mut current = leaf;
858    let mut root_left = [0u8; 32];
859    let mut root_right = [0u8; 32];
860
861    for i in (0..MSSMT_TREE_LEVELS).rev() {
862        let sibling = &nodes[MSSMT_TREE_LEVELS - 1 - i];
863        let bit = mssmt_bit_index(i as u8, &key);
864        let (left, right) = if bit == 0 {
865            (&current, sibling)
866        } else {
867            (sibling, &current)
868        };
869
870        if i == 0 {
871            root_left = left.hash.to_byte_array();
872            root_right = right.hash.to_byte_array();
873        }
874
875        current = mssmt_branch(left, right)?;
876    }
877
878    Ok(MssmtRoot {
879        root_hash: current.hash.to_byte_array(),
880        root_sum: current.sum,
881        left_hash: root_left,
882        right_hash: root_right,
883    })
884}
885
886/// Returns the bit at an index for an MS-SMT key.
887fn mssmt_bit_index(idx: u8, key: &[u8; 32]) -> u8 {
888    let byte_val = key[(idx / 8) as usize];
889    (byte_val >> (idx % 8)) & 1
890}
891
892/// Normalizes MS-SMT proof nodes, expanding empty nodes as needed.
893fn normalize_mssmt_nodes(nodes: &[MssmtNode]) -> Result<Vec<MssmtNode>, Error> {
894    if nodes.len() != MSSMT_TREE_LEVELS {
895        return Err(Error::InvalidMssmtProofLength {
896            expected: MSSMT_TREE_LEVELS,
897            actual: nodes.len(),
898        });
899    }
900
901    let empty_nodes = mssmt_empty_nodes();
902    let mut normalized = Vec::with_capacity(MSSMT_TREE_LEVELS);
903    for (idx, node) in nodes.iter().enumerate() {
904        let height = MSSMT_TREE_LEVELS - idx;
905        if is_zero_mssmt_node(node) {
906            normalized.push(empty_nodes[height].clone());
907        } else {
908            normalized.push(node.clone());
909        }
910    }
911
912    Ok(normalized)
913}
914
915/// Returns true if an MS-SMT node is the zero placeholder.
916fn is_zero_mssmt_node(node: &MssmtNode) -> bool {
917    node.hash == Sha256Hash::all_zeros() && node.sum == 0
918}
919
920/// Returns the empty MS-SMT nodes from root to leaf.
921fn mssmt_empty_nodes() -> Vec<MssmtNode> {
922    let mut nodes = Vec::with_capacity(MSSMT_TREE_LEVELS + 1);
923    nodes.resize_with(MSSMT_TREE_LEVELS + 1, || MssmtNode {
924        hash: Sha256Hash::all_zeros(),
925        sum: 0,
926    });
927
928    let leaf = mssmt_leaf(&[], 0);
929    nodes[MSSMT_TREE_LEVELS] = leaf.clone();
930    for i in (0..MSSMT_TREE_LEVELS).rev() {
931        let parent = mssmt_branch(&nodes[i + 1], &nodes[i + 1]).unwrap_or(leaf.clone());
932        nodes[i] = parent;
933    }
934
935    nodes
936}
937
938/// Encodes an asset into a leaf node.
939fn asset_leaf(asset: &Asset) -> Result<MssmtNode, Error> {
940    let include_witness = asset.version == AssetVersion::V0;
941    let bytes = encode_asset(asset, include_witness)?;
942    Ok(mssmt_leaf(&bytes, asset.amount))
943}
944
945/// Encodes an asset into TLV bytes.
946fn encode_asset(asset: &Asset, include_tx_witness: bool) -> Result<Vec<u8>, Error> {
947    let genesis = asset
948        .asset_genesis
949        .as_ref()
950        .ok_or(Error::MissingAssetGenesis)?;
951    let mut out = Vec::new();
952
953    encode_record(ASSET_LEAF_VERSION, &[asset.version as u8], &mut out);
954    let genesis_bytes = encode_genesis_info(genesis)?;
955    encode_record(ASSET_LEAF_GENESIS, &genesis_bytes, &mut out);
956    encode_record(
957        ASSET_LEAF_TYPE,
958        &[asset_type_byte(genesis.asset_type)],
959        &mut out,
960    );
961    let amount_bytes = encode_bigsize_to_vec(asset.amount);
962    encode_record(ASSET_LEAF_AMOUNT, &amount_bytes, &mut out);
963    if asset.lock_time > 0 {
964        let bytes = encode_bigsize_to_vec(asset.lock_time as u64);
965        encode_record(ASSET_LEAF_LOCK_TIME, &bytes, &mut out);
966    }
967    if asset.relative_lock_time > 0 {
968        let bytes = encode_bigsize_to_vec(asset.relative_lock_time as u64);
969        encode_record(ASSET_LEAF_RELATIVE_LOCK_TIME, &bytes, &mut out);
970    }
971    if !asset.prev_witnesses.is_empty() {
972        let witnesses = encode_prev_witnesses(&asset.prev_witnesses, include_tx_witness)?;
973        encode_record(ASSET_LEAF_PREV_WITNESS, &witnesses, &mut out);
974    }
975    if let Some(root) = asset.split_commitment_root.as_ref() {
976        let bytes = encode_split_commitment_root(root);
977        encode_record(ASSET_LEAF_SPLIT_COMMITMENT_ROOT, &bytes, &mut out);
978    }
979    let script_version =
980        u16::try_from(asset.script_version).map_err(|_| Error::InvalidAssetScriptVersion)?;
981    encode_record(
982        ASSET_LEAF_SCRIPT_VERSION,
983        &script_version.to_be_bytes(),
984        &mut out,
985    );
986    if asset.script_key.len() != 33 {
987        return Err(Error::InvalidAssetScriptKeyLength {
988            expected: 33,
989            actual: asset.script_key.len(),
990        });
991    }
992    encode_record(ASSET_LEAF_SCRIPT_KEY, &asset.script_key, &mut out);
993    if let Some(group) = asset.asset_group.as_ref() {
994        let key_bytes = &group.raw_group_key;
995        if key_bytes.len() != 33 {
996            return Err(Error::InvalidGroupKeyLength {
997                expected: 33,
998                actual: key_bytes.len(),
999            });
1000        }
1001        encode_record(ASSET_LEAF_GROUP_KEY, key_bytes, &mut out);
1002    }
1003
1004    Ok(out)
1005}
1006
1007/// Encodes a genesis record into TLV bytes.
1008fn encode_genesis_info(genesis: &GenesisInfo) -> Result<Vec<u8>, Error> {
1009    let mut out = Vec::new();
1010    encode_outpoint(&genesis.genesis_point, &mut out);
1011    encode_inline_var_bytes(genesis.name.as_bytes(), &mut out);
1012    out.extend_from_slice(&genesis.meta_hash.to_byte_array());
1013    out.extend_from_slice(&genesis.output_index.to_be_bytes());
1014    out.push(asset_type_byte(genesis.asset_type));
1015    Ok(out)
1016}
1017
1018/// Encodes a Bitcoin outpoint into bytes.
1019fn encode_outpoint(out_point: &OutPoint, out: &mut Vec<u8>) {
1020    out.extend_from_slice(&out_point.txid.to_byte_array());
1021    out.extend_from_slice(&out_point.vout.to_be_bytes());
1022}
1023
1024/// Encodes an asset type to a protocol byte.
1025fn asset_type_byte(asset_type: AssetType) -> u8 {
1026    match asset_type {
1027        AssetType::Normal => 0,
1028        AssetType::Collectible => 1,
1029    }
1030}
1031
1032/// Encodes a list of prev witnesses into TLV bytes.
1033fn encode_prev_witnesses(
1034    witnesses: &[PrevWitness],
1035    include_tx_witness: bool,
1036) -> Result<Vec<u8>, Error> {
1037    let mut out = Vec::new();
1038    encode_bigsize(witnesses.len() as u64, &mut out);
1039    for witness in witnesses {
1040        let bytes = encode_prev_witness(witness, include_tx_witness)?;
1041        encode_inline_var_bytes(&bytes, &mut out);
1042    }
1043    Ok(out)
1044}
1045
1046/// Encodes a prev witness into TLV bytes.
1047fn encode_prev_witness(witness: &PrevWitness, include_tx_witness: bool) -> Result<Vec<u8>, Error> {
1048    let mut out = Vec::new();
1049
1050    if let Some(prev_id) = witness.prev_id.as_ref() {
1051        let bytes = encode_prev_id(prev_id);
1052        encode_record(WITNESS_PREV_ID, &bytes, &mut out);
1053    }
1054    if include_tx_witness && !witness.tx_witness.is_empty() {
1055        let bytes = encode_tx_witness(&witness.tx_witness);
1056        encode_record(WITNESS_TX_WITNESS, &bytes, &mut out);
1057    }
1058    if let Some(split_commitment) = witness.split_commitment.as_ref() {
1059        let bytes = encode_split_commitment(split_commitment)?;
1060        encode_record(WITNESS_SPLIT_COMMITMENT, &bytes, &mut out);
1061    }
1062
1063    Ok(out)
1064}
1065
1066/// Encodes a prev ID into bytes.
1067fn encode_prev_id(prev_id: &PrevId) -> Vec<u8> {
1068    let mut out = Vec::new();
1069    encode_outpoint(&prev_id.out_point, &mut out);
1070    out.extend_from_slice(&prev_id.asset_id.to_byte_array());
1071    out.extend_from_slice(&prev_id.script_key.bytes);
1072    out
1073}
1074
1075/// Encodes a transaction witness into bytes.
1076fn encode_tx_witness(witness: &Witness) -> Vec<u8> {
1077    let mut out = Vec::new();
1078    encode_bigsize(witness.len() as u64, &mut out);
1079    for item in witness.iter() {
1080        encode_inline_var_bytes(item, &mut out);
1081    }
1082    out
1083}
1084
1085/// Encodes a split commitment into bytes.
1086fn encode_split_commitment(commitment: &SplitCommitment) -> Result<Vec<u8>, Error> {
1087    let proof_bytes = encode_compressed_proof(&commitment.proof)?;
1088    let asset_bytes = encode_asset(&commitment.root_asset, true)?;
1089    let mut out = Vec::new();
1090    encode_inline_var_bytes(&proof_bytes, &mut out);
1091    encode_inline_var_bytes(&asset_bytes, &mut out);
1092    Ok(out)
1093}
1094
1095/// Encodes a compressed MS-SMT proof into bytes.
1096fn encode_compressed_proof(proof: &MssmtProof) -> Result<Vec<u8>, Error> {
1097    let nodes = normalize_mssmt_nodes(&proof.nodes)?;
1098    let empty_nodes = mssmt_empty_nodes();
1099    let mut bits = Vec::with_capacity(MSSMT_TREE_LEVELS);
1100    let mut explicit_nodes = Vec::new();
1101    for (idx, node) in nodes.iter().enumerate() {
1102        let height = MSSMT_TREE_LEVELS - idx;
1103        let empty = &empty_nodes[height];
1104        let is_empty = node.hash == empty.hash && node.sum == empty.sum;
1105        bits.push(is_empty);
1106        if !is_empty {
1107            explicit_nodes.push(node);
1108        }
1109    }
1110
1111    let mut out = Vec::new();
1112    let node_count = u16::try_from(explicit_nodes.len()).unwrap_or(u16::MAX);
1113    out.extend_from_slice(&node_count.to_be_bytes());
1114    for node in explicit_nodes {
1115        out.extend_from_slice(&node.hash.to_byte_array());
1116        out.extend_from_slice(&node.sum.to_be_bytes());
1117    }
1118    let packed = pack_bits(&bits);
1119    out.extend_from_slice(&packed);
1120    Ok(out)
1121}
1122
1123/// Encodes a split commitment root node into bytes.
1124fn encode_split_commitment_root(root: &MssmtNode) -> Vec<u8> {
1125    let mut out = Vec::with_capacity(32 + 8);
1126    out.extend_from_slice(&root.hash.to_byte_array());
1127    out.extend_from_slice(&root.sum.to_be_bytes());
1128    out
1129}
1130
1131/// Packs a bit slice into bytes using little-endian bit ordering.
1132fn pack_bits(bits: &[bool]) -> Vec<u8> {
1133    let mut bytes = Vec::with_capacity((bits.len() + 7) / 8);
1134    bytes.resize((bits.len() + 7) / 8, 0);
1135    for (idx, bit) in bits.iter().enumerate() {
1136        if *bit {
1137            let byte_idx = idx / 8;
1138            let bit_idx = idx % 8;
1139            bytes[byte_idx] |= 1 << bit_idx;
1140        }
1141    }
1142    bytes
1143}
1144
1145/// Encodes a TLV record into the provided buffer.
1146fn encode_record(tlv_type: u64, value: &[u8], out: &mut Vec<u8>) {
1147    encode_bigsize(tlv_type, out);
1148    encode_bigsize(value.len() as u64, out);
1149    out.extend_from_slice(value);
1150}
1151
1152/// Encodes a BigSize varint into the provided buffer.
1153fn encode_bigsize(value: u64, out: &mut Vec<u8>) {
1154    match value {
1155        0..=0xFC => out.push(value as u8),
1156        0xFD..=0xFFFF => {
1157            out.push(0xFD);
1158            out.extend_from_slice(&(value as u16).to_be_bytes());
1159        }
1160        0x1_0000..=0xFFFF_FFFF => {
1161            out.push(0xFE);
1162            out.extend_from_slice(&(value as u32).to_be_bytes());
1163        }
1164        _ => {
1165            out.push(0xFF);
1166            out.extend_from_slice(&value.to_be_bytes());
1167        }
1168    }
1169}
1170
1171/// Encodes a BigSize varint into a new byte vector.
1172fn encode_bigsize_to_vec(value: u64) -> Vec<u8> {
1173    let mut out = Vec::new();
1174    encode_bigsize(value, &mut out);
1175    out
1176}
1177
1178/// Encodes inline var bytes into the provided buffer.
1179fn encode_inline_var_bytes(bytes: &[u8], out: &mut Vec<u8>) {
1180    encode_bigsize(bytes.len() as u64, out);
1181    out.extend_from_slice(bytes);
1182}
1183
1184/// Converts a full public key into a serialized key wrapper.
1185fn serialized_key_from_pubkey(pubkey: &bitcoin::PublicKey) -> SerializedKey {
1186    SerializedKey {
1187        bytes: pubkey.inner.serialize(),
1188    }
1189}
1190
1191/// Extracts an x-only key from a serialized compressed public key.
1192fn xonly_from_serialized_key(key: &SerializedKey) -> Result<[u8; 32], Error> {
1193    let pubkey =
1194        SecpPublicKey::from_slice(&key.bytes).map_err(|_| Error::InvalidTaprootOutputKey)?;
1195    let (xonly, _) = pubkey.x_only_public_key();
1196    Ok(xonly.serialize())
1197}
1198
1199/// TLV type for the asset version field.
1200const ASSET_LEAF_VERSION: u64 = 0;
1201/// TLV type for the asset genesis field.
1202const ASSET_LEAF_GENESIS: u64 = 2;
1203/// TLV type for the asset type field.
1204const ASSET_LEAF_TYPE: u64 = 4;
1205/// TLV type for the asset amount field.
1206const ASSET_LEAF_AMOUNT: u64 = 6;
1207/// TLV type for the asset lock time field.
1208const ASSET_LEAF_LOCK_TIME: u64 = 7;
1209/// TLV type for the asset relative lock time field.
1210const ASSET_LEAF_RELATIVE_LOCK_TIME: u64 = 9;
1211/// TLV type for the asset prev witness field.
1212const ASSET_LEAF_PREV_WITNESS: u64 = 11;
1213/// TLV type for the asset split commitment root field.
1214const ASSET_LEAF_SPLIT_COMMITMENT_ROOT: u64 = 13;
1215/// TLV type for the asset script version field.
1216const ASSET_LEAF_SCRIPT_VERSION: u64 = 14;
1217/// TLV type for the asset script key field.
1218const ASSET_LEAF_SCRIPT_KEY: u64 = 16;
1219/// TLV type for the asset group key field.
1220const ASSET_LEAF_GROUP_KEY: u64 = 17;
1221
1222/// TLV type for the witness prev ID field.
1223const WITNESS_PREV_ID: u64 = 1;
1224/// TLV type for the witness tx witness field.
1225const WITNESS_TX_WITNESS: u64 = 3;
1226/// TLV type for the witness split commitment field.
1227const WITNESS_SPLIT_COMMITMENT: u64 = 5;