1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Error {
26 InvalidTaprootOutputIndex {
28 output_index: u32,
30 output_count: usize,
32 },
33 InvalidTaprootWitnessProgram,
35 InvalidTaprootOutputKey,
37 MissingTaprootProofMethod,
39 InvalidCommitmentProof,
41 MissingCommitmentProof,
43 MissingAssetProof,
45 InvalidTapscriptProof,
47 InvalidTaprootProof,
49 EmptyTapscriptPreimage,
51 InvalidTapscriptPreimageLength {
53 expected: usize,
55 actual: usize,
57 },
58 InvalidTapLeafScriptVersion,
60 InvalidTapLeafScriptLength,
62 TapscriptPreimageIsTapCommitment,
64 MissingAssetGenesis,
66 InvalidAssetScriptKeyLength {
68 expected: usize,
70 actual: usize,
72 },
73 InvalidAssetScriptKey,
75 InvalidAssetGroupKey,
77 InvalidAssetScriptVersion,
79 InvalidGroupKeyLength {
81 expected: usize,
83 actual: usize,
85 },
86 InvalidMssmtProofLength {
88 expected: usize,
90 actual: usize,
92 },
93 MssmtSumOverflow,
95 Ops(OpsError),
97}
98
99impl From<OpsError> for Error {
100 fn from(err: OpsError) -> Self {
102 Self::Ops(err)
103 }
104}
105
106impl core::fmt::Display for Error {
107 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
168const MSSMT_TREE_LEVELS: usize = 256;
170const TAPROOT_ASSET_COMMITMENT_SCRIPT_SIZE: usize = 1 + 32 + 32 + 8;
172const TAPROOT_ASSETS_MARKER_TAG: &str = "taproot-assets";
174const TAPROOT_ASSETS_V2_TAG: &str = "taproot-assets:194243";
176const TAP_BRANCH_PREIMAGE_LEN: usize = 64;
178const MAX_TAPLEAF_SCRIPT_SIZE: usize = 4_000_000;
180
181type ProofCommitmentKeys = BTreeMap<SerializedKey, TapCommitment>;
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186pub struct TapCommitment {
187 pub version: TapCommitmentVersion,
189 pub root_hash: [u8; 32],
191 pub root_sum: u64,
193}
194
195impl TapCommitment {
196 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq)]
249struct MssmtRoot {
250 root_hash: [u8; 32],
252 root_sum: u64,
254 left_hash: [u8; 32],
256 right_hash: [u8; 32],
258}
259
260pub 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
278pub 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
291pub 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
302pub 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
314pub 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
346fn 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
362fn 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
392fn 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
424fn 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
441fn 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
462fn 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
476fn 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
528fn 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
565fn 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
594fn 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
625fn 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
639fn taproot_assets_marker() -> [u8; 32] {
641 Sha256Hash::hash(TAPROOT_ASSETS_MARKER_TAG.as_bytes()).to_byte_array()
642}
643
644fn taproot_assets_v2_tag() -> [u8; 32] {
646 Sha256Hash::hash(TAPROOT_ASSETS_V2_TAG.as_bytes()).to_byte_array()
647}
648
649fn 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
684fn 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
717fn 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
736fn 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
746fn 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
755fn 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
784fn 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
811fn 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
821fn 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
830fn 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
839fn 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
854fn 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 (¤t, sibling)
866 } else {
867 (sibling, ¤t)
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
886fn 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
892fn 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
915fn is_zero_mssmt_node(node: &MssmtNode) -> bool {
917 node.hash == Sha256Hash::all_zeros() && node.sum == 0
918}
919
920fn 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
938fn 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
945fn 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
1007fn 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
1018fn 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
1024fn asset_type_byte(asset_type: AssetType) -> u8 {
1026 match asset_type {
1027 AssetType::Normal => 0,
1028 AssetType::Collectible => 1,
1029 }
1030}
1031
1032fn 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
1046fn 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
1066fn 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
1075fn 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
1085fn 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
1095fn 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
1123fn 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
1131fn 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
1145fn 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
1152fn 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
1171fn encode_bigsize_to_vec(value: u64) -> Vec<u8> {
1173 let mut out = Vec::new();
1174 encode_bigsize(value, &mut out);
1175 out
1176}
1177
1178fn 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
1184fn serialized_key_from_pubkey(pubkey: &bitcoin::PublicKey) -> SerializedKey {
1186 SerializedKey {
1187 bytes: pubkey.inner.serialize(),
1188 }
1189}
1190
1191fn 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
1199const ASSET_LEAF_VERSION: u64 = 0;
1201const ASSET_LEAF_GENESIS: u64 = 2;
1203const ASSET_LEAF_TYPE: u64 = 4;
1205const ASSET_LEAF_AMOUNT: u64 = 6;
1207const ASSET_LEAF_LOCK_TIME: u64 = 7;
1209const ASSET_LEAF_RELATIVE_LOCK_TIME: u64 = 9;
1211const ASSET_LEAF_PREV_WITNESS: u64 = 11;
1213const ASSET_LEAF_SPLIT_COMMITMENT_ROOT: u64 = 13;
1215const ASSET_LEAF_SCRIPT_VERSION: u64 = 14;
1217const ASSET_LEAF_SCRIPT_KEY: u64 = 16;
1219const ASSET_LEAF_GROUP_KEY: u64 = 17;
1221
1222const WITNESS_PREV_ID: u64 = 1;
1224const WITNESS_TX_WITNESS: u64 = 3;
1226const WITNESS_SPLIT_COMMITMENT: u64 = 5;