Skip to main content

taproot_assets_types/
proof.rs

1// --- Proof structures (corresponding to Go's `proof` package) ---
2
3use alloc::collections::{BTreeMap, BTreeSet};
4use bitcoin::PublicKey;
5pub use bitcoin::TxMerkleNode;
6use bitcoin::consensus::Decodable;
7use bitcoin::hashes::Hash;
8use bitcoin::io::Read;
9use serde::{Deserialize, Serialize};
10
11use crate::alloc::string::ToString;
12use alloc::{format, vec::Vec};
13
14use crate::asset::SerializedKey;
15use crate::commitment::TapscriptPreimage;
16use crate::error::Error;
17use crate::tlv::{Stream, Type};
18
19/// Represents a raw proof file, typically a byte vector.
20/// This was at the top of the original proof.rs file.
21pub type RawProofFile = Vec<u8>;
22
23/// Represents a full commitment proof for an asset. It can either prove inclusion or exclusion of
24/// an asset within a Taproot Asset commitment.
25/// This corresponds to `proof.CommitmentProof` in Go.
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct CommitmentProof {
28    /// The underlying Merkle proof structure from the commitment module.
29    /// This was `commitment.Proof` in Go.
30    pub proof: crate::commitment::Proof,
31
32    /// TapSiblingPreimage is an optional preimage of a tap node used to
33    /// hash together with the Taproot Asset commitment leaf node to arrive
34    /// at the tapscript root of the expected output.
35    pub tap_sibling_preimage: Option<TapscriptPreimage>,
36
37    /// STXOProofs are proofs keyed by serialized script key for v1 assets.
38    pub stxo_proofs: BTreeMap<SerializedKey, crate::commitment::Proof>,
39
40    /// UnknownOddTypes is a map of unknown odd types that were encountered
41    /// during decoding. This map is used to preserve unknown types that we
42    /// don't know of yet, so we can still encode them back when serializing.
43    /// This enables forward compatibility with future versions of the
44    /// protocol as it allows new odd (optional) types to be added without
45    /// breaking old clients that don't yet fully understand them.
46    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
47}
48
49// TLV Types for CommitmentProof (based on Go's proof/records.go)
50// No explicit type for the proof.Proof itself, assuming it's the primary content or handled differently.
51const COMMITMENT_PROOF_TAP_SIBLING_PREIMAGE_TYPE: Type = Type(5);
52/// TLV type for the STXO proof map within a commitment proof.
53const COMMITMENT_PROOF_STXO_PROOFS_TYPE: Type = Type(7);
54// Type for the Merkle Proof itself is not directly specified here for CommitmentProof's TLV stream.
55// It's often the core data. Let's assume it's decoded from the main stream if no specific type.
56// For now, we will assume CommitmentMerkleProof::decode_tlv handles its own format from a reader.
57
58impl CommitmentProof {
59    fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
60        let mut stream = Stream::new(r);
61        let mut asset_proof: Option<crate::commitment::AssetProof> = None;
62        let mut taproot_asset_proof: Option<crate::commitment::TaprootAssetProof> = None;
63        let mut tap_sibling_preimage: Option<TapscriptPreimage> = None;
64        let mut stxo_proofs: Option<BTreeMap<SerializedKey, crate::commitment::Proof>> = None;
65        let mut unknown_odd_types = BTreeMap::new();
66
67        // These type constants correspond to the underlying commitment.Proof fields
68        const PROOF_ASSET_PROOF_TYPE: Type = Type(0);
69        const PROOF_TAPROOT_ASSET_PROOF_TYPE: Type = Type(2);
70
71        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
72            match record.tlv_type() {
73                // Handle the fields from the underlying commitment.Proof
74                PROOF_ASSET_PROOF_TYPE => {
75                    asset_proof = Some(crate::commitment::AssetProof::decode_tlv(
76                        record.value_reader(),
77                    )?);
78                }
79                PROOF_TAPROOT_ASSET_PROOF_TYPE => {
80                    taproot_asset_proof = Some(crate::commitment::TaprootAssetProof::decode_tlv(
81                        record.value_reader(),
82                    )?);
83                }
84                // Handle the CommitmentProof-specific field
85                COMMITMENT_PROOF_TAP_SIBLING_PREIMAGE_TYPE => {
86                    tap_sibling_preimage =
87                        Some(TapscriptPreimage::decode_tlv(record.value_reader())?);
88                }
89                COMMITMENT_PROOF_STXO_PROOFS_TYPE => {
90                    stxo_proofs = Some(decode_commitment_proofs(record.value_reader())?);
91                }
92                type_val => {
93                    if type_val.is_odd() {
94                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
95                    } else {
96                        // As per BOLT #1: even, unknown types are an error.
97                        return Err(Error::UnknownTlvType(type_val.0));
98                    }
99                }
100            }
101        }
102
103        // Build the underlying commitment.Proof
104        let commitment_proof = crate::commitment::Proof {
105            asset_proof,
106            taproot_asset_proof: taproot_asset_proof.ok_or(Error::MissingTlvField(
107                "CommitmentProof.proof.taproot_asset_proof".to_string(),
108            ))?,
109            unknown_odd_types: BTreeMap::new(), // Handle these separately for CommitmentProof
110        };
111
112        Ok(CommitmentProof {
113            proof: commitment_proof,
114            tap_sibling_preimage,
115            stxo_proofs: stxo_proofs.unwrap_or_default(),
116            unknown_odd_types,
117        })
118    }
119}
120
121/// TapscriptProof represents a proof of a Taproot output not including a
122/// Taproot Asset commitment.
123/// This corresponds to `proof.TapscriptProof` in Go.
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct TapscriptProof {
126    /// TapPreimage1 is the preimage for a TapNode at depth 0 or 1.
127    pub tap_preimage1: Option<TapscriptPreimage>,
128
129    /// TapPreimage2, if specified, is the pair preimage for TapPreimage1 at
130    /// depth 1.
131    pub tap_preimage2: Option<TapscriptPreimage>,
132
133    /// Bip86 indicates this is a normal BIP-0086 wallet output.
134    pub bip86: bool,
135
136    /// UnknownOddTypes is a map of unknown odd types encountered during decoding.
137    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
138}
139
140// TLV Types for TapscriptProof (based on Go's proof/records.go)
141const TAPSCRIPT_PROOF_TAP_PREIMAGE1_TYPE: Type = Type(1);
142const TAPSCRIPT_PROOF_TAP_PREIMAGE2_TYPE: Type = Type(3);
143const TAPSCRIPT_PROOF_BIP86_TYPE: Type = Type(4);
144
145impl TapscriptProof {
146    fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
147        let mut stream = Stream::new(r);
148        let mut tap_preimage1: Option<TapscriptPreimage> = None;
149        let mut tap_preimage2: Option<TapscriptPreimage> = None;
150        let mut bip86: Option<bool> = None;
151        let mut unknown_odd_types = BTreeMap::new();
152
153        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
154            match record.tlv_type() {
155                TAPSCRIPT_PROOF_TAP_PREIMAGE1_TYPE => {
156                    tap_preimage1 = Some(TapscriptPreimage::decode_tlv(record.value_reader())?);
157                }
158                TAPSCRIPT_PROOF_TAP_PREIMAGE2_TYPE => {
159                    tap_preimage2 = Some(TapscriptPreimage::decode_tlv(record.value_reader())?);
160                }
161                TAPSCRIPT_PROOF_BIP86_TYPE => {
162                    if record.value().len() != 1 {
163                        return Err(Error::InvalidTlvValue(
164                            TAPSCRIPT_PROOF_BIP86_TYPE.0,
165                            "Length must be 1 for bool".to_string(),
166                        ));
167                    }
168                    bip86 = Some(record.value()[0] != 0);
169                }
170                type_val => {
171                    if type_val.is_odd() {
172                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
173                    } else {
174                        return Err(Error::UnknownTlvType(type_val.0));
175                    }
176                }
177            }
178        }
179
180        Ok(TapscriptProof {
181            tap_preimage1,
182            tap_preimage2,
183            bip86: bip86.ok_or(Error::MissingTlvField("TapscriptProof.bip86".to_string()))?,
184            unknown_odd_types,
185        })
186    }
187}
188
189/// TaprootProof represents a proof that reveals the partial contents to a
190/// tapscript tree within a taproot output.
191/// This corresponds to `proof.TaprootProof` in Go.
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
193pub struct TaprootProof {
194    /// OutputIndex is the index of the output for which the proof applies.
195    pub output_index: u32,
196
197    /// InternalKey is the internal key of the taproot output at OutputIndex.
198    pub internal_key: PublicKey,
199
200    /// CommitmentProof represents a commitment proof for an asset, proving
201    /// inclusion or exclusion of an asset within a Taproot Asset commitment.
202    pub commitment_proof: Option<CommitmentProof>,
203
204    /// TapscriptProof represents a taproot control block to prove that a
205    /// taproot output is not committing to a Taproot Asset commitment.
206    ///
207    /// NOTE: This field will be set only if the output does NOT contain a
208    /// valid Taproot Asset commitment.
209    pub tapscript_proof: Option<TapscriptProof>,
210
211    /// UnknownOddTypes is a map of unknown odd types that were encountered
212    /// during decoding. This map is used to preserve unknown types that we
213    /// don't know of yet, so we can still encode them back when serializing.
214    /// This enables forward compatibility with future versions of the
215    /// protocol as it allows new odd (optional) types to be added without
216    /// breaking old clients that don't yet fully understand them.
217    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
218}
219
220// TLV types for TaprootProof fields (from external/taproot-assets-upstream/proof/records.go)
221const TAPROOT_PROOF_OUTPUT_INDEX_TYPE: Type = Type(0);
222const TAPROOT_PROOF_INTERNAL_KEY_TYPE: Type = Type(2);
223const TAPROOT_PROOF_COMMITMENT_PROOF_TYPE: Type = Type(3);
224const TAPROOT_PROOF_TAPSCRIPT_PROOF_TYPE: Type = Type(5);
225
226/// Maximum number of taproot proofs allowed in a single record.
227const MAX_NUM_TAPROOT_PROOFS: u64 = 1_000_000 / 43;
228/// Maximum size in bytes for a single taproot proof blob.
229const MAX_TAPROOT_PROOF_SIZE_BYTES: u64 = 65_535;
230
231impl TaprootProof {
232    /// Decodes a TaprootProof from a TLV byte slice.
233    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
234        Self::decode_tlv(bytes)
235    }
236
237    fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
238        let mut stream = Stream::new(r);
239
240        let mut output_index: Option<u32> = None;
241        let mut internal_key: Option<PublicKey> = None;
242        let mut commitment_proof: Option<CommitmentProof> = None;
243        let mut tapscript_proof: Option<TapscriptProof> = None;
244        let mut unknown_odd_types = BTreeMap::new();
245
246        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
247            match record.tlv_type() {
248                TAPROOT_PROOF_OUTPUT_INDEX_TYPE => {
249                    let mut u32_bytes = [0u8; 4];
250                    record
251                        .value_reader()
252                        .read_exact(&mut u32_bytes)
253                        .map_err(Error::Io)?;
254                    output_index = Some(u32::from_be_bytes(u32_bytes));
255                }
256                TAPROOT_PROOF_INTERNAL_KEY_TYPE => {
257                    let pubkey_bytes: Vec<u8> = record.value().to_vec();
258                    // Attempt to parse as compressed (33 bytes) or uncompressed (65 bytes)
259                    internal_key = Some(PublicKey::from_slice(&pubkey_bytes).map_err(|e| {
260                        Error::BitcoinSerialization(format!("Invalid internal key: {}", e))
261                    })?);
262                }
263                TAPROOT_PROOF_COMMITMENT_PROOF_TYPE => {
264                    commitment_proof = Some(
265                        CommitmentProof::decode_tlv(record.value_reader()).map_err(|e| {
266                            Error::TlvStream(format!("CommitmentProof decode failed: {}", e))
267                        })?,
268                    );
269                }
270                TAPROOT_PROOF_TAPSCRIPT_PROOF_TYPE => {
271                    tapscript_proof = Some(TapscriptProof::decode_tlv(record.value_reader())?);
272                }
273                type_val => {
274                    if type_val.is_odd() {
275                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
276                    } else {
277                        // As per BOLT #1: even, unknown types are an error.
278                        return Err(Error::UnknownTlvType(type_val.0));
279                    }
280                }
281            }
282        }
283
284        Ok(TaprootProof {
285            output_index: output_index.ok_or(Error::MissingTlvField(
286                "TaprootProof.output_index".to_string(),
287            ))?,
288            internal_key: internal_key.ok_or(Error::MissingTlvField(
289                "TaprootProof.internal_key".to_string(),
290            ))?,
291            commitment_proof,
292            tapscript_proof,
293            unknown_odd_types,
294        })
295    }
296}
297
298/// Reads a Bitcoin-style compact-size integer from the reader.
299fn read_varint<R: Read>(r: &mut R) -> Result<u64, Error> {
300    let mut first_byte = [0u8; 1];
301    r.read_exact(&mut first_byte).map_err(Error::Io)?;
302
303    match first_byte[0] {
304        253 => {
305            let mut u16_bytes = [0u8; 2];
306            r.read_exact(&mut u16_bytes).map_err(Error::Io)?;
307            Ok(u16::from_be_bytes(u16_bytes) as u64)
308        }
309        254 => {
310            let mut u32_bytes = [0u8; 4];
311            r.read_exact(&mut u32_bytes).map_err(Error::Io)?;
312            Ok(u32::from_be_bytes(u32_bytes) as u64)
313        }
314        255 => {
315            let mut u64_bytes = [0u8; 8];
316            r.read_exact(&mut u64_bytes).map_err(Error::Io)?;
317            Ok(u64::from_be_bytes(u64_bytes))
318        }
319        _ => Ok(first_byte[0] as u64),
320    }
321}
322
323/// Reads a length-prefixed byte vector capped by `max_len`.
324fn read_inline_var_bytes<R: Read>(r: &mut R, max_len: u64) -> Result<Vec<u8>, Error> {
325    let len = read_varint(r)?;
326    if len > max_len {
327        return Err(Error::BitcoinSerialization(format!(
328            "inline var bytes too large: {} (max: {})",
329            len, max_len
330        )));
331    }
332
333    let mut bytes = alloc::vec![0u8; len as usize];
334    r.read_exact(&mut bytes).map_err(Error::Io)?;
335    Ok(bytes)
336}
337
338/// Decodes a list of taproot proofs using the inline-var-bytes format.
339fn decode_taproot_proofs<R: Read>(mut r: R) -> Result<Vec<TaprootProof>, Error> {
340    let num_proofs = read_varint(&mut r)?;
341    if num_proofs > MAX_NUM_TAPROOT_PROOFS {
342        return Err(Error::BitcoinSerialization(
343            "too many taproot proofs".to_string(),
344        ));
345    }
346
347    let mut proofs = Vec::with_capacity(num_proofs as usize);
348    for _ in 0..num_proofs {
349        let proof_bytes = read_inline_var_bytes(&mut r, MAX_TAPROOT_PROOF_SIZE_BYTES)?;
350        let proof = TaprootProof::decode_tlv(bitcoin::io::Cursor::new(&proof_bytes))?;
351        proofs.push(proof);
352    }
353
354    Ok(proofs)
355}
356
357/// Decodes additional input proof files in the same format as Go's proof file list.
358fn decode_additional_inputs<R: Read>(mut r: R) -> Result<Vec<File>, Error> {
359    let num_inputs = read_varint(&mut r)?;
360    if num_inputs > u16::MAX as u64 {
361        return Err(Error::BitcoinSerialization(
362            "too many additional inputs".to_string(),
363        ));
364    }
365
366    let mut inputs = Vec::with_capacity(num_inputs as usize);
367    for _ in 0..num_inputs {
368        let input_bytes = read_inline_var_bytes(&mut r, FILE_MAX_SIZE_BYTES)?;
369        let input_file = File::from_bytes(&input_bytes)?;
370        inputs.push(input_file);
371    }
372
373    Ok(inputs)
374}
375
376/// Decodes alt leaves encoded using the inline-var-bytes list format.
377fn decode_alt_leaves(bytes: &[u8]) -> Result<Vec<crate::asset::Asset>, Error> {
378    if bytes.len() as u64 > ALT_LEAVES_MAX_SIZE_BYTES {
379        return Err(Error::InvalidTlvValue(
380            PROOF_ALT_LEAVES_TYPE.0,
381            format!("alt leaves payload too large: {} bytes", bytes.len()),
382        ));
383    }
384
385    let mut cursor = bitcoin::io::Cursor::new(bytes);
386    let num_leaves = read_varint(&mut cursor)?;
387    let mut leaves = Vec::with_capacity(num_leaves as usize);
388    let mut leaf_keys = BTreeSet::new();
389
390    for _ in 0..num_leaves {
391        let leaf_bytes = read_inline_var_bytes(&mut cursor, ALT_LEAVES_MAX_SIZE_BYTES)?;
392        let leaf = crate::asset::decode_alt_leaf(&leaf_bytes)?;
393        if leaf.script_key.len() != 33 {
394            return Err(Error::InvalidTlvValue(
395                PROOF_ALT_LEAVES_TYPE.0,
396                format!(
397                    "alt leaf script key length must be 33, got {}",
398                    leaf.script_key.len()
399                ),
400            ));
401        }
402
403        let mut key_bytes = [0u8; 33];
404        key_bytes.copy_from_slice(&leaf.script_key);
405        let key = SerializedKey { bytes: key_bytes };
406        if !leaf_keys.insert(key) {
407            return Err(Error::InvalidTlvValue(
408                PROOF_ALT_LEAVES_TYPE.0,
409                "duplicate alt leaf script key".to_string(),
410            ));
411        }
412
413        leaves.push(leaf);
414    }
415
416    Ok(leaves)
417}
418
419/// Decodes the STXO commitment proof map keyed by serialized script keys.
420fn decode_commitment_proofs<R: Read>(
421    mut r: R,
422) -> Result<BTreeMap<SerializedKey, crate::commitment::Proof>, Error> {
423    let num_proofs = read_varint(&mut r)?;
424    if num_proofs > MAX_NUM_TAPROOT_PROOFS {
425        return Err(Error::BitcoinSerialization(
426            "too many commitment proofs".to_string(),
427        ));
428    }
429
430    let mut proofs = BTreeMap::new();
431    for _ in 0..num_proofs {
432        let mut key_bytes = [0u8; 33];
433        r.read_exact(&mut key_bytes).map_err(Error::Io)?;
434
435        let proof_bytes = read_inline_var_bytes(&mut r, MAX_TAPROOT_PROOF_SIZE_BYTES)?;
436        let proof = crate::commitment::Proof::decode_tlv(bitcoin::io::Cursor::new(&proof_bytes))?;
437        proofs.insert(SerializedKey { bytes: key_bytes }, proof);
438    }
439
440    Ok(proofs)
441}
442
443/// Decodes a meta reveal TLV stream.
444fn decode_meta_reveal<R: Read>(r: R) -> Result<MetaReveal, Error> {
445    let mut stream = Stream::new(r);
446    let mut meta_type: Option<MetaType> = None;
447    let mut data: Option<Vec<u8>> = None;
448    let mut unknown_odd_types = BTreeMap::new();
449
450    while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
451        match record.tlv_type() {
452            META_REVEAL_ENCODING_TYPE => {
453                if record.value().len() != 1 {
454                    return Err(Error::InvalidTlvValue(
455                        META_REVEAL_ENCODING_TYPE.0,
456                        "Length must be 1 for meta type".to_string(),
457                    ));
458                }
459                meta_type = Some(match record.value()[0] {
460                    0 => MetaType::Opaque,
461                    1 => MetaType::Json,
462                    other => {
463                        return Err(Error::InvalidTlvValue(
464                            META_REVEAL_ENCODING_TYPE.0,
465                            format!("Unknown meta type: {}", other),
466                        ));
467                    }
468                });
469            }
470            META_REVEAL_DATA_TYPE => {
471                data = Some(record.value().to_vec());
472            }
473            type_val => {
474                if type_val.is_odd() {
475                    unknown_odd_types.insert(type_val.0, record.value().to_vec());
476                } else {
477                    return Err(Error::UnknownTlvType(type_val.0));
478                }
479            }
480        }
481    }
482
483    Ok(MetaReveal {
484        meta_type: meta_type.ok_or(Error::MissingTlvField("MetaReveal.meta_type".to_string()))?,
485        data: data.ok_or(Error::MissingTlvField("MetaReveal.data".to_string()))?,
486        unknown_odd_types,
487    })
488}
489
490/// A Merkle proof that a transaction is included in a block.
491/// This corresponds to `proof.TxMerkleProof` in Go.
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
493pub struct TxMerkleProof {
494    /// The list of sibling hashes along the Merkle path from the transaction
495    /// up to the root.
496    pub nodes: Vec<TxMerkleNode>,
497
498    /// Direction bits: `false` means the node is on the left, `true` means on the right.
499    /// The bits correspond to entries in `nodes`.
500    pub bits: Vec<bool>,
501}
502
503/// Meta data type for genesis reveals.
504#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
505pub enum MetaType {
506    /// Opaque metadata bytes.
507    Opaque = 0,
508    /// JSON metadata bytes.
509    Json = 1,
510}
511
512/// Meta reveal data included in proof files.
513#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
514pub struct MetaReveal {
515    /// The meta data type.
516    pub meta_type: MetaType,
517    /// The raw meta data bytes.
518    pub data: Vec<u8>,
519    /// Unknown odd types for forward compatibility.
520    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
521}
522
523impl TxMerkleProof {
524    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
525        Self::decode_tlv(bytes)
526    }
527
528    /// Reads a variable-length integer from a reader.
529    fn read_varint<R: Read>(r: &mut R) -> Result<u64, Error> {
530        let mut first_byte = [0u8; 1];
531        r.read_exact(&mut first_byte).map_err(Error::Io)?;
532
533        match first_byte[0] {
534            253 => {
535                let mut u16_bytes = [0u8; 2];
536                r.read_exact(&mut u16_bytes).map_err(Error::Io)?;
537                Ok(u16::from_be_bytes(u16_bytes) as u64)
538            }
539            254 => {
540                let mut u32_bytes = [0u8; 4];
541                r.read_exact(&mut u32_bytes).map_err(Error::Io)?;
542                Ok(u32::from_be_bytes(u32_bytes) as u64)
543            }
544            255 => {
545                let mut u64_bytes = [0u8; 8];
546                r.read_exact(&mut u64_bytes).map_err(Error::Io)?;
547                Ok(u64::from_be_bytes(u64_bytes))
548            }
549            _ => Ok(first_byte[0] as u64),
550        }
551    }
552
553    /// Unpacks a bit-packed byte slice into a boolean vector.
554    fn unpack_bits_from_slice(packed_bytes: &[u8]) -> Vec<bool> {
555        let mut bits = Vec::with_capacity(packed_bytes.len() * 8);
556        for i in 0..(packed_bytes.len() * 8) {
557            let byte_index = i / 8;
558            let bit_index = i % 8; // Use little-endian bit ordering like Go
559            let bit = (packed_bytes[byte_index] >> bit_index) & 1;
560            bits.push(bit == 1);
561        }
562        bits
563    }
564
565    /// Decodes a `TxMerkleProof` from a TLV-like format.
566    ///
567    /// The format consists of:
568    /// 1. A VarInt for the number of nodes.
569    /// 2. The nodes themselves (32 bytes each).
570    /// 3. A VarInt for the length of the packed bits byte slice.
571    /// 4. The packed bits byte slice.
572    fn decode_tlv<R: Read>(mut r: R) -> Result<Self, Error> {
573        const MERKLE_PROOF_MAX_NODES: u64 = 512;
574
575        let num_nodes = Self::read_varint(&mut r)?;
576
577        if num_nodes > MERKLE_PROOF_MAX_NODES {
578            return Err(Error::TlvStream(format!(
579                "Merkle proof has too many nodes: {}",
580                num_nodes
581            )));
582        }
583
584        let mut nodes = Vec::with_capacity(num_nodes as usize);
585        for _ in 0..num_nodes {
586            let mut hash_bytes = [0u8; 32];
587            r.read_exact(&mut hash_bytes).map_err(Error::Io)?;
588            nodes.push(TxMerkleNode::from_byte_array(hash_bytes));
589        }
590
591        let mut packed_bits = Vec::<u8>::new();
592        r.read_to_limit(&mut packed_bits, num_nodes)
593            .map_err(Error::Io)?;
594
595        // let packed_bits_len = Self::read_varint(&mut r)?;
596
597        // // Calculate maximum packed bits length using same logic as Go's packedBitsLen:
598        // // (bits + 8 - 1) / 8 to round up to nearest byte
599        // let max_packed_bits_len = (num_nodes + 8 - 1) / 8;
600        // if packed_bits_len > max_packed_bits_len {
601        //     return Err(Error::TlvStream(format!(
602        //         "Packed bits length too large: maximum {}, got {}",
603        //         max_packed_bits_len, packed_bits_len
604        //     )));
605        // }
606
607        // let mut packed_bits = alloc::vec![0u8; packed_bits_len as usize];
608        // r.read_exact(&mut packed_bits).map_err(Error::Io)?;
609
610        let all_bits = Self::unpack_bits_from_slice(&packed_bits);
611
612        // Take only the first num_nodes bits, exactly like Go does with bits[:len(p.Nodes)]
613        let bits = all_bits.into_iter().take(num_nodes as usize).collect();
614
615        Ok(TxMerkleProof { nodes, bits })
616    }
617}
618
619#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
620pub struct Proof {
621    /// Version is the version of the state transition proof.
622    pub version: u32,
623
624    /// PrevOut is the previous on-chain outpoint of the asset. This outpoint
625    /// is that of the first on-chain input. Outpoints which correspond to
626    /// the other inputs can be found in AdditionalInputs.
627    pub prev_out: bitcoin::OutPoint,
628
629    /// BlockHeader is the current block header committing to the on-chain
630    /// transaction attempting an asset state transition.
631    pub block_header: bitcoin::block::Header,
632
633    /// BlockHeight is the height of the current block committing to the
634    /// on-chain transaction attempting an asset state transition.
635    pub block_height: u32,
636
637    /// AnchorTx is the on-chain transaction attempting the asset state
638    /// transition.
639    pub anchor_tx: bitcoin::Transaction,
640
641    /// TxMerkleProof is the merkle proof for AnchorTx used to prove its
642    /// inclusion within BlockHeader.
643    pub tx_merkle_proof: TxMerkleProof,
644
645    /// Asset is the resulting asset after its state transition.
646    pub asset: crate::asset::Asset,
647
648    /// InclusionProof is the TaprootProof proving the new inclusion of the
649    /// resulting asset within AnchorTx.
650    pub inclusion_proof: TaprootProof,
651
652    /// ExclusionProofs is the set of TaprootProofs proving the exclusion of
653    /// the resulting asset from all other Taproot outputs within AnchorTx.
654    pub exclusion_proofs: Vec<TaprootProof>,
655
656    /// SplitRootProof is an optional TaprootProof needed if this asset is
657    /// the result of a split. SplitRootProof proves inclusion of the root
658    /// asset of the split.
659    pub split_root_proof: Option<TaprootProof>,
660
661    /// MetaReveal is the data that was revealed to prove the derivation of the
662    /// meta data hash contained in the genesis asset.
663    ///
664    /// NOTE: This field is optional, and can only be specified if the asset
665    /// above is a genesis asset. If specified, then verifiers _should_ also
666    /// verify the hashes match up.
667    pub meta_reveal: Option<MetaReveal>,
668
669    /// AdditionalInputs is a nested full proof for any additional inputs
670    /// found within the resulting asset.
671    pub additional_inputs: Vec<File>,
672
673    /// ChallengeWitness is an optional virtual transaction witness that
674    /// serves as an ownership proof for the asset. If this is non-nil, then
675    /// it is a valid transfer witness for a 1-input, 1-output virtual
676    /// transaction that spends the asset in this proof and sends it to the
677    /// NUMS key, to prove that the creator of the proof is able to produce
678    /// a valid signature to spend the asset.
679    pub challenge_witness: Option<bitcoin::Witness>,
680
681    /// GenesisReveal is the Genesis information for an asset, that must be
682    /// provided for minting proofs, and must be empty for non-minting
683    /// proofs. This allows for derivation of the asset ID. If the asset is
684    /// part of an asset group, the Genesis information is also used for
685    /// re-derivation of the asset group key.
686    pub genesis_reveal: Option<crate::asset::GenesisReveal>,
687
688    /// GroupKeyReveal contains the data required to derive the final tweaked
689    /// group key for an asset group.
690    ///
691    /// NOTE: This field is mandatory for the group anchor (i.e., the initial
692    /// minting tranche of an asset group). Subsequent minting tranches
693    /// require only a valid signature for the previously revealed group key.
694    pub group_key_reveal: Option<crate::asset::GroupKeyReveal>,
695
696    /// AltLeaves represent data used to construct an Asset commitment, that
697    /// was inserted in the output anchor Tap commitment. These data-carrying
698    /// leaves are used for a purpose distinct from representing individual
699    /// Taproot Assets.
700    pub alt_leaves: Vec<crate::asset::Asset>,
701
702    /// UnknownOddTypes is a map of unknown odd types that were encountered
703    /// during decoding. This map is used to preserve unknown types that we
704    /// don't know of yet, so we can still encode them back when serializing.
705    /// This enables forward compatibility with future versions of the
706    /// protocol as it allows new odd (optional) types to be added without
707    /// breaking old clients that don't yet fully understand them.
708    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
709}
710
711// TLV Types for Proof (based on Go's proof/records.go)
712const PROOF_VERSION_TYPE: Type = Type(0);
713const PROOF_PREV_OUT_TYPE: Type = Type(2);
714const PROOF_BLOCK_HEADER_TYPE: Type = Type(4);
715const PROOF_ANCHOR_TX_TYPE: Type = Type(6);
716const PROOF_TX_MERKLE_PROOF_TYPE: Type = Type(8);
717const PROOF_ASSET_LEAF_TYPE: Type = Type(10);
718const PROOF_INCLUSION_PROOF_TYPE: Type = Type(12);
719const PROOF_EXCLUSION_PROOFS_TYPE: Type = Type(13);
720const PROOF_SPLIT_ROOT_PROOF_TYPE: Type = Type(15);
721const PROOF_META_REVEAL_TYPE: Type = Type(17);
722const PROOF_ADDITIONAL_INPUTS_TYPE: Type = Type(19);
723const PROOF_CHALLENGE_WITNESS_TYPE: Type = Type(21);
724const PROOF_BLOCK_HEIGHT_TYPE: Type = Type(22);
725const PROOF_GENESIS_REVEAL_TYPE: Type = Type(23);
726const PROOF_GROUP_KEY_REVEAL_TYPE: Type = Type(25);
727const PROOF_ALT_LEAVES_TYPE: Type = Type(27);
728
729/// TLV type for the meta reveal encoding field.
730const META_REVEAL_ENCODING_TYPE: Type = Type(0);
731/// TLV type for the meta reveal data field.
732const META_REVEAL_DATA_TYPE: Type = Type(2);
733
734/// Maximum total size of the alt leaves payload.
735const ALT_LEAVES_MAX_SIZE_BYTES: u64 = u16::MAX as u64;
736
737// Magic bytes for individual proofs (not proof files)
738const PROOF_PREFIX_MAGIC_BYTES: [u8; 4] = [0x54, 0x41, 0x50, 0x50]; // "TAPP"
739
740impl Proof {
741    /// Decodes a Proof from a TLV byte slice.
742    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
743        Self::decode_tlv(bytes)
744    }
745
746    fn decode_tlv<R: Read>(mut r: R) -> Result<Self, Error> {
747        // Read and verify magic bytes for individual proofs
748        let mut magic_bytes = [0u8; 4];
749        r.read_exact(&mut magic_bytes).map_err(Error::Io)?;
750
751        if magic_bytes != PROOF_PREFIX_MAGIC_BYTES {
752            return Err(Error::BitcoinSerialization(format!(
753                "Invalid proof magic bytes, expected {:?}, got {:?}",
754                PROOF_PREFIX_MAGIC_BYTES, magic_bytes
755            )));
756        }
757
758        let mut stream = Stream::new(r);
759
760        let mut version: Option<u32> = None;
761        let mut prev_out: Option<bitcoin::OutPoint> = None;
762        let mut block_header: Option<bitcoin::block::Header> = None;
763        let mut block_height: Option<u32> = None;
764        let mut anchor_tx: Option<bitcoin::Transaction> = None;
765        let mut tx_merkle_proof: Option<TxMerkleProof> = None;
766        let mut asset: Option<crate::asset::Asset> = None;
767        let mut inclusion_proof: Option<TaprootProof> = None;
768        let mut exclusion_proofs: Option<Vec<TaprootProof>> = None;
769        let mut split_root_proof: Option<TaprootProof> = None;
770        let mut meta_reveal: Option<MetaReveal> = None;
771        let mut additional_inputs: Option<Vec<File>> = None;
772        let mut challenge_witness: Option<bitcoin::Witness> = None;
773        let mut genesis_reveal: Option<crate::asset::GenesisReveal> = None;
774        let mut group_key_reveal: Option<crate::asset::GroupKeyReveal> = None;
775        let mut alt_leaves: Option<Vec<crate::asset::Asset>> = None;
776        let mut unknown_odd_types = BTreeMap::new();
777
778        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
779            match record.tlv_type() {
780                PROOF_VERSION_TYPE => {
781                    let mut u32_bytes = [0u8; 4];
782                    record
783                        .value_reader()
784                        .read_exact(&mut u32_bytes)
785                        .map_err(Error::Io)?;
786                    version = Some(u32::from_be_bytes(u32_bytes));
787                }
788                PROOF_PREV_OUT_TYPE => {
789                    // Decode OutPoint (32 bytes hash + 4 bytes index)
790                    let mut hash_bytes = [0u8; 32];
791                    let mut reader = record.value_reader();
792                    reader.read_exact(&mut hash_bytes).map_err(Error::Io)?;
793                    let mut index_bytes = [0u8; 4];
794                    reader.read_exact(&mut index_bytes).map_err(Error::Io)?;
795                    let index = u32::from_be_bytes(index_bytes);
796                    prev_out = Some(bitcoin::OutPoint {
797                        txid: bitcoin::Txid::from_byte_array(hash_bytes),
798                        vout: index,
799                    });
800                }
801                PROOF_BLOCK_HEADER_TYPE => {
802                    let header_bytes = record.value();
803                    block_header = Some(
804                        bitcoin::block::Header::consensus_decode(&mut bitcoin::io::Cursor::new(
805                            header_bytes,
806                        ))
807                        .map_err(|e| {
808                            Error::BitcoinSerialization(format!("Invalid block header: {}", e))
809                        })?,
810                    );
811                }
812                PROOF_BLOCK_HEIGHT_TYPE => {
813                    let mut u32_bytes = [0u8; 4];
814                    record
815                        .value_reader()
816                        .read_exact(&mut u32_bytes)
817                        .map_err(Error::Io)?;
818                    block_height = Some(u32::from_be_bytes(u32_bytes));
819                }
820                PROOF_ANCHOR_TX_TYPE => {
821                    // Decode bitcoin::Transaction using consensus_decode
822                    let tx_bytes = record.value();
823                    anchor_tx = Some(
824                        bitcoin::Transaction::consensus_decode(&mut bitcoin::io::Cursor::new(
825                            tx_bytes,
826                        ))
827                        .map_err(|e| {
828                            Error::BitcoinSerialization(format!(
829                                "Invalid anchor transaction: {}",
830                                e
831                            ))
832                        })?,
833                    );
834                }
835                PROOF_TX_MERKLE_PROOF_TYPE => {
836                    tx_merkle_proof = Some(TxMerkleProof::decode_tlv(record.value_reader())?);
837                }
838                PROOF_ASSET_LEAF_TYPE => {
839                    // Decode asset.Asset using proper TLV decoding
840                    asset = Some(crate::asset::Asset::decode_tlv(record.value_reader())?);
841                }
842                PROOF_INCLUSION_PROOF_TYPE => {
843                    inclusion_proof = Some(TaprootProof::decode_tlv(record.value_reader())?);
844                }
845                PROOF_EXCLUSION_PROOFS_TYPE => {
846                    exclusion_proofs = Some(decode_taproot_proofs(record.value_reader())?);
847                }
848                PROOF_SPLIT_ROOT_PROOF_TYPE => {
849                    split_root_proof = Some(TaprootProof::decode_tlv(record.value_reader())?);
850                }
851                PROOF_META_REVEAL_TYPE => {
852                    meta_reveal = Some(decode_meta_reveal(record.value_reader())?);
853                }
854                PROOF_ADDITIONAL_INPUTS_TYPE => {
855                    additional_inputs = Some(decode_additional_inputs(record.value_reader())?);
856                }
857                PROOF_CHALLENGE_WITNESS_TYPE => {
858                    // Decode bitcoin::Witness using consensus_decode
859                    let witness_bytes = record.value();
860                    challenge_witness = Some(
861                        bitcoin::Witness::consensus_decode(&mut bitcoin::io::Cursor::new(
862                            witness_bytes,
863                        ))
864                        .map_err(|e| {
865                            Error::BitcoinSerialization(format!("Invalid challenge witness: {}", e))
866                        })?,
867                    );
868                }
869                PROOF_GENESIS_REVEAL_TYPE => {
870                    let genesis_info = crate::asset::decode_genesis_info(record.value_reader())
871                        .map_err(|e| {
872                            Error::TlvStream(format!("GenesisReveal decode failed: {}", e))
873                        })?;
874                    genesis_reveal = Some(crate::asset::GenesisReveal {
875                        genesis_base: Some(genesis_info),
876                        asset_type: crate::asset::AssetType::Normal,
877                        amount: 0,
878                        meta_reveal: None,
879                    });
880                }
881                PROOF_GROUP_KEY_REVEAL_TYPE => {
882                    group_key_reveal = Some(crate::asset::decode_group_key_reveal(record.value())?);
883                }
884                PROOF_ALT_LEAVES_TYPE => {
885                    alt_leaves = Some(decode_alt_leaves(record.value())?);
886                }
887                type_val => {
888                    if type_val.is_odd() {
889                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
890                    } else {
891                        return Err(Error::UnknownTlvType(type_val.0));
892                    }
893                }
894            }
895        }
896
897        Ok(Proof {
898            version: version.ok_or(Error::MissingTlvField("Proof.version".to_string()))?,
899            prev_out: prev_out.ok_or(Error::MissingTlvField("Proof.prev_out".to_string()))?,
900            block_header: block_header
901                .ok_or(Error::MissingTlvField("Proof.block_header".to_string()))?,
902            block_height: block_height
903                .ok_or(Error::MissingTlvField("Proof.block_height".to_string()))?,
904            anchor_tx: anchor_tx.ok_or(Error::MissingTlvField("Proof.anchor_tx".to_string()))?,
905            tx_merkle_proof: tx_merkle_proof
906                .ok_or(Error::MissingTlvField("Proof.tx_merkle_proof".to_string()))?,
907            asset: asset.ok_or(Error::MissingTlvField("Proof.asset".to_string()))?,
908            inclusion_proof: inclusion_proof
909                .ok_or(Error::MissingTlvField("Proof.inclusion_proof".to_string()))?,
910            exclusion_proofs: exclusion_proofs.unwrap_or_default(),
911            split_root_proof,
912            meta_reveal,
913            additional_inputs: additional_inputs.unwrap_or_default(),
914            challenge_witness,
915            genesis_reveal,
916            group_key_reveal,
917            alt_leaves: alt_leaves.unwrap_or_default(),
918            unknown_odd_types,
919        })
920    }
921}
922
923#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
924pub struct File {
925    /// Version is the version of the proof file.
926    pub version: u32,
927
928    /// Proofs are the proofs contained within the proof file starting from
929    /// the genesis proof. Each proof includes its chained hash.
930    pub proofs: Vec<HashedProof>,
931}
932
933/// HashedProof is a struct that contains an encoded proof and its chained
934/// checksum.
935#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
936pub struct HashedProof {
937    /// ProofBytes is the encoded proof that is hashed.
938    pub proof_bytes: Vec<u8>,
939
940    /// Hash is the SHA256 sum of (prev_hash || proof).
941    pub hash: [u8; 32],
942}
943
944// Constants from Go implementation
945const FILE_MAX_NUM_PROOFS: u64 = 420000;
946const FILE_MAX_PROOF_SIZE_BYTES: u64 = 128 * 1024 * 1024; // 128 MiB
947/// Maximum size of a proof file in bytes.
948const FILE_MAX_SIZE_BYTES: u64 = 500 * 1024 * 1024;
949
950// Magic bytes for proof files
951const FILE_PREFIX_MAGIC_BYTES: [u8; 4] = [0x54, 0x41, 0x50, 0x46]; // "TAPF"
952
953impl File {
954    /// Decodes a File from a byte slice.
955    pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
956        Self::decode(bytes)
957    }
958
959    /// Decodes a proof file from a byte slice.
960    fn decode(bytes: &[u8]) -> Result<Self, Error> {
961        let mut cursor = bitcoin::io::Cursor::new(bytes);
962        Self::decode_from_reader(&mut cursor)
963    }
964
965    /// Decodes a proof file from a reader.
966    fn decode_from_reader<R: Read>(r: &mut R) -> Result<Self, Error> {
967        // Read and verify magic bytes
968        let mut magic_bytes = [0u8; 4];
969        r.read_exact(&mut magic_bytes).map_err(Error::Io)?;
970
971        if magic_bytes != FILE_PREFIX_MAGIC_BYTES {
972            return Err(Error::BitcoinSerialization(format!(
973                "Invalid file magic bytes, expected {:?}, got {:?}",
974                FILE_PREFIX_MAGIC_BYTES, magic_bytes
975            )));
976        }
977
978        // Read version (4 bytes, big endian)
979        let mut version_bytes = [0u8; 4];
980        r.read_exact(&mut version_bytes).map_err(Error::Io)?;
981        let version = u32::from_be_bytes(version_bytes);
982
983        // Read number of proofs (varint)
984        let num_proofs = Self::read_varint(r)?;
985
986        // Cap the number of proofs to avoid OOM attacks
987        if num_proofs > FILE_MAX_NUM_PROOFS {
988            return Err(Error::BitcoinSerialization(format!(
989                "Too many proofs in file: {} (max: {})",
990                num_proofs, FILE_MAX_NUM_PROOFS
991            )));
992        }
993
994        let mut proofs = Vec::with_capacity(num_proofs as usize);
995        let mut prev_hash = [0u8; 32]; // Start with zero hash
996
997        for _ in 0..num_proofs {
998            // Read proof size (varint)
999            let proof_size = Self::read_varint(r)?;
1000
1001            // Cap the size of an individual proof
1002            if proof_size > FILE_MAX_PROOF_SIZE_BYTES {
1003                return Err(Error::BitcoinSerialization(format!(
1004                    "Proof in file too large: {} bytes (max: {})",
1005                    proof_size, FILE_MAX_PROOF_SIZE_BYTES
1006                )));
1007            }
1008
1009            // Read proof bytes
1010            let mut proof_bytes = Vec::with_capacity(proof_size as usize);
1011            proof_bytes.resize(proof_size as usize, 0u8);
1012            r.read_exact(&mut proof_bytes).map_err(Error::Io)?;
1013
1014            // Read proof hash (32 bytes)
1015            let mut proof_hash = [0u8; 32];
1016            r.read_exact(&mut proof_hash).map_err(Error::Io)?;
1017
1018            // Calculate expected hash: SHA256(prev_hash || proof_bytes)
1019            let expected_hash = Self::hash_proof(&proof_bytes, &prev_hash);
1020
1021            // Verify hash matches
1022            if proof_hash != expected_hash {
1023                return Err(Error::BitcoinSerialization(
1024                    "Invalid proof file checksum".to_string(),
1025                ));
1026            }
1027
1028            proofs.push(HashedProof {
1029                proof_bytes,
1030                hash: proof_hash,
1031            });
1032
1033            // Update prev_hash for next iteration
1034            prev_hash = proof_hash;
1035        }
1036
1037        Ok(File { version, proofs })
1038    }
1039
1040    /// Reads a variable-length integer from a reader.
1041    fn read_varint<R: Read>(r: &mut R) -> Result<u64, Error> {
1042        let mut first_byte = [0u8; 1];
1043        r.read_exact(&mut first_byte).map_err(Error::Io)?;
1044
1045        match first_byte[0] {
1046            253 => {
1047                let mut u16_bytes = [0u8; 2];
1048                r.read_exact(&mut u16_bytes).map_err(Error::Io)?;
1049                Ok(u16::from_be_bytes(u16_bytes) as u64)
1050            }
1051            254 => {
1052                let mut u32_bytes = [0u8; 4];
1053                r.read_exact(&mut u32_bytes).map_err(Error::Io)?;
1054                Ok(u32::from_be_bytes(u32_bytes) as u64)
1055            }
1056            255 => {
1057                let mut u64_bytes = [0u8; 8];
1058                r.read_exact(&mut u64_bytes).map_err(Error::Io)?;
1059                Ok(u64::from_be_bytes(u64_bytes))
1060            }
1061            _ => Ok(first_byte[0] as u64),
1062        }
1063    }
1064
1065    /// Hashes a proof's content together with the previous hash:
1066    /// SHA256(prev_hash || proof_bytes)
1067    fn hash_proof(proof_bytes: &[u8], prev_hash: &[u8; 32]) -> [u8; 32] {
1068        use bitcoin::hashes::{Hash, sha256::Hash as Sha256Hash};
1069
1070        // Create a combined buffer: prev_hash || proof_bytes
1071        let mut combined = Vec::with_capacity(32 + proof_bytes.len());
1072        combined.extend_from_slice(prev_hash);
1073        combined.extend_from_slice(proof_bytes);
1074
1075        // Hash the combined buffer
1076        Sha256Hash::hash(&combined).to_byte_array()
1077    }
1078
1079    /// Returns true if the file does not contain any proofs.
1080    pub fn is_empty(&self) -> bool {
1081        self.proofs.is_empty()
1082    }
1083
1084    /// Returns the number of proofs contained in this file.
1085    pub fn num_proofs(&self) -> usize {
1086        self.proofs.len()
1087    }
1088
1089    /// Returns the proof at the given index.
1090    pub fn proof_at(&self, index: usize) -> Result<Proof, Error> {
1091        if index >= self.proofs.len() {
1092            return Err(Error::BitcoinSerialization(format!(
1093                "Invalid index {}",
1094                index
1095            )));
1096        }
1097
1098        Proof::from_bytes(&self.proofs[index].proof_bytes)
1099    }
1100
1101    /// Returns the last proof in the chain of proofs.
1102    pub fn last_proof(&self) -> Result<Proof, Error> {
1103        if self.is_empty() {
1104            return Err(Error::BitcoinSerialization(
1105                "No proof available".to_string(),
1106            ));
1107        }
1108
1109        self.proof_at(self.proofs.len() - 1)
1110    }
1111
1112    /// Returns the raw proof at the given index as a byte slice.
1113    pub fn raw_proof_at(&self, index: usize) -> Result<Vec<u8>, Error> {
1114        if index >= self.proofs.len() {
1115            return Err(Error::BitcoinSerialization(format!(
1116                "Invalid index {}",
1117                index
1118            )));
1119        }
1120
1121        Ok(self.proofs[index].proof_bytes.clone())
1122    }
1123
1124    /// Returns the raw last proof in the chain of proofs as a byte slice.
1125    pub fn raw_last_proof(&self) -> Result<Vec<u8>, Error> {
1126        if self.is_empty() {
1127            return Err(Error::BitcoinSerialization(
1128                "No proof available".to_string(),
1129            ));
1130        }
1131
1132        self.raw_proof_at(self.proofs.len() - 1)
1133    }
1134}