Skip to main content

taproot_assets_types/
commitment.rs

1use crate::asset::AssetVersion;
2use crate::error::Error;
3use crate::mssmt::MssmtProof;
4use crate::tlv::{Stream, Type};
5use alloc::collections::BTreeMap;
6use bitcoin::io::Read;
7
8use crate::alloc::string::ToString;
9use alloc::vec::Vec;
10
11use serde::{Deserialize, Serialize};
12
13/// Denotes the structure of the Taproot Asset commitment MS-SMT and the procedure
14/// for building a TapLeaf from a Taproot Asset commitment.
15/// This corresponds to `commitment.TapCommitmentVersion` in Go.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[repr(u8)]
18pub enum TapCommitmentVersion {
19    /// Initial Taproot Asset Commitment version. Uses legacy TapLeaf format, ONLY commits to V0 assets.
20    V0 = 0,
21    /// Used by Taproot Asset Commitments that commit to V0 or V1 assets. Uses legacy TapLeaf format.
22    V1 = 1,
23    /// Used by Taproot Asset Commitments that commit to V0 or V1 assets. Uses V1 TapLeaf format.
24    V2 = 2,
25}
26
27impl TapCommitmentVersion {
28    pub(crate) fn from_u8(val: u8) -> Result<Self, Error> {
29        match val {
30            0 => Ok(TapCommitmentVersion::V0),
31            1 => Ok(TapCommitmentVersion::V1),
32            2 => Ok(TapCommitmentVersion::V2),
33            _ => Err(Error::InvalidTlvValue(
34                0,
35                alloc::format!("Unknown TapCommitmentVersion: {}", val),
36            )),
37        }
38    }
39}
40
41/// Type of tapscript sibling preimage.
42/// This corresponds to `commitment.TapscriptPreimageType` in Go.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[repr(u8)]
45pub enum TapscriptPreimageType {
46    /// Pre-image that's a leaf script.
47    LeafPreimage = 0,
48    /// Pre-image that's a branch (64-bytes of two child pre-images).
49    BranchPreimage = 1,
50}
51
52impl TapscriptPreimageType {
53    pub(crate) fn from_u8(val: u8) -> Result<Self, Error> {
54        match val {
55            0 => Ok(TapscriptPreimageType::LeafPreimage),
56            1 => Ok(TapscriptPreimageType::BranchPreimage),
57            _ => Err(Error::InvalidTlvValue(
58                0,
59                alloc::format!("Unknown TapscriptPreimageType: {}", val),
60            )),
61        }
62    }
63}
64
65/// Wraps a pre-image byte slice with a type byte that self identifies what type of pre-image it is.
66/// This corresponds to `commitment.TapscriptPreimage` in Go.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct TapscriptPreimage {
69    /// The pre-image itself. This will be 64 bytes if representing a TapBranch,
70    /// or any size under 4 MBytes if representing a TapLeaf.
71    pub sibling_preimage: Vec<u8>,
72    /// The type of the pre-image.
73    pub sibling_type: TapscriptPreimageType,
74}
75
76impl TapscriptPreimage {
77    /// Decodes a TapscriptPreimage directly from a reader.
78    /// The format is: 1-byte type, then variable-length preimage bytes.
79    pub fn decode_tlv<R: Read>(mut r: R) -> Result<Self, Error> {
80        let mut type_buf = [0u8; 1];
81        r.read_exact(&mut type_buf).map_err(Error::Io)?;
82        let sibling_type_byte = type_buf[0];
83        let sibling_type = TapscriptPreimageType::from_u8(sibling_type_byte)?;
84
85        let mut sibling_preimage = Vec::new();
86        // Manual read_to_end for no_std compatibility
87        let mut chunk = [0u8; 512]; // Read in 512-byte chunks
88        loop {
89            match r.read(&mut chunk) {
90                Ok(0) => break, // EOF
91                Ok(n) => sibling_preimage.extend_from_slice(&chunk[..n]),
92                Err(e) => return Err(Error::Io(e)), // Propagate actual IO errors
93            }
94        }
95
96        // Basic validation based on type, more can be added if needed.
97        match sibling_type {
98            TapscriptPreimageType::BranchPreimage if sibling_preimage.len() != 64 => {
99                return Err(Error::InvalidTlvValue(
100                    sibling_type_byte as u64,
101                    "BranchPreimage must be 64 bytes".to_string(),
102                ));
103            }
104            _ => {}
105        }
106
107        Ok(TapscriptPreimage {
108            sibling_preimage,
109            sibling_type,
110        })
111    }
112}
113
114/// Proof used along with an asset leaf to arrive at the root of the AssetCommitment MS-SMT.
115/// This corresponds to `commitment.AssetProof` in Go.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub struct AssetProof {
118    /// The underlying MS-SMT proof.
119    pub proof: MssmtProof,
120    /// Max version of the assets committed.
121    pub version: AssetVersion,
122    /// Common identifier for all assets found within the AssetCommitment.
123    /// Can be an asset.ID or an asset.GroupKey hash.
124    pub tap_key: [u8; 32],
125    /// Map of unknown odd types encountered during decoding.
126    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
127}
128
129impl AssetProof {
130    pub fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
131        let mut stream = Stream::new(r);
132        let mut mssmt_proof: Option<MssmtProof> = None;
133        let mut version: Option<AssetVersion> = None;
134        let mut tap_key: Option<[u8; 32]> = None;
135        let mut unknown_odd_types = BTreeMap::new();
136
137        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
138            match record.tlv_type() {
139                ASSET_PROOF_MSSMT_PROOF_TYPE => {
140                    mssmt_proof = Some(MssmtProof::decode_tlv(record.value_reader())?);
141                }
142                ASSET_PROOF_VERSION_TYPE => {
143                    if record.value().len() != 1 {
144                        return Err(Error::InvalidTlvValue(
145                            ASSET_PROOF_VERSION_TYPE.0,
146                            "Length must be 1 for AssetVersion".to_string(),
147                        ));
148                    }
149                    version = Some(AssetVersion::from_u8(record.value()[0])?);
150                }
151                ASSET_PROOF_TAP_KEY_TYPE => {
152                    if record.value().len() != 32 {
153                        return Err(Error::InvalidTlvValue(
154                            ASSET_PROOF_TAP_KEY_TYPE.0,
155                            "Length must be 32 for TapKey".to_string(),
156                        ));
157                    }
158                    let mut key_bytes = [0u8; 32];
159                    key_bytes.copy_from_slice(record.value());
160                    tap_key = Some(key_bytes);
161                }
162                type_val => {
163                    if type_val.is_odd() {
164                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
165                    } else {
166                        return Err(Error::UnknownTlvType(type_val.0));
167                    }
168                }
169            }
170        }
171        Ok(AssetProof {
172            proof: mssmt_proof.ok_or(Error::MissingTlvField("AssetProof.proof".to_string()))?,
173            version: version.ok_or(Error::MissingTlvField("AssetProof.version".to_string()))?,
174            tap_key: tap_key.ok_or(Error::MissingTlvField("AssetProof.tap_key".to_string()))?,
175            unknown_odd_types,
176        })
177    }
178}
179
180/// Proof used along with an asset commitment leaf to arrive at the root of the TapCommitment MS-SMT.
181/// This corresponds to `commitment.TaprootAssetProof` in Go.
182#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183pub struct TaprootAssetProof {
184    /// The underlying MS-SMT proof.
185    pub proof: MssmtProof,
186    /// Version of the TapCommitment used to create the proof.
187    pub version: TapCommitmentVersion,
188    /// Map of unknown odd types encountered during decoding.
189    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
190}
191
192impl TaprootAssetProof {
193    pub fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
194        let mut stream = Stream::new(r);
195        let mut mssmt_proof: Option<MssmtProof> = None;
196        let mut version: Option<TapCommitmentVersion> = None;
197        let mut unknown_odd_types = BTreeMap::new();
198
199        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
200            match record.tlv_type() {
201                TAPROOT_ASSET_PROOF_MSSMT_PROOF_TYPE => {
202                    mssmt_proof = Some(MssmtProof::decode_tlv(record.value_reader())?);
203                }
204                TAPROOT_ASSET_PROOF_VERSION_TYPE => {
205                    if record.value().len() != 1 {
206                        return Err(Error::InvalidTlvValue(
207                            TAPROOT_ASSET_PROOF_VERSION_TYPE.0,
208                            "Length must be 1 for TapCommitmentVersion".to_string(),
209                        ));
210                    }
211                    version = Some(TapCommitmentVersion::from_u8(record.value()[0])?);
212                }
213                type_val => {
214                    if type_val.is_odd() {
215                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
216                    } else {
217                        return Err(Error::UnknownTlvType(type_val.0));
218                    }
219                }
220            }
221        }
222        Ok(TaprootAssetProof {
223            proof: mssmt_proof.ok_or(Error::MissingTlvField(
224                "TaprootAssetProof.proof".to_string(),
225            ))?,
226            version: version.ok_or(Error::MissingTlvField(
227                "TaprootAssetProof.version".to_string(),
228            ))?,
229            unknown_odd_types,
230        })
231    }
232}
233
234/// Represents a full commitment proof for a particular `Asset`. It proves
235/// that an asset does or does not exist within a Taproot Asset commitment.
236/// This corresponds to `commitment.Proof` in Go.
237#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
238pub struct Proof {
239    /// Proof used along with the asset to arrive at the root of the AssetCommitment MS-SMT.
240    /// NOTE: This proof must be None if the asset commitment for this
241    /// particular asset is not found within the Taproot Asset commitment.
242    pub asset_proof: Option<AssetProof>,
243    /// Proof used along with the asset commitment to arrive at the root of the TapCommitment
244    /// MS-SMT.
245    pub taproot_asset_proof: TaprootAssetProof,
246    /// Map of unknown odd types encountered during decoding.
247    pub unknown_odd_types: BTreeMap<u64, Vec<u8>>,
248}
249
250impl Proof {
251    pub fn decode_tlv<R: Read>(r: R) -> Result<Self, Error> {
252        let mut stream = Stream::new(r);
253        let mut asset_proof: Option<AssetProof> = None;
254        let mut taproot_asset_proof: Option<TaprootAssetProof> = None;
255        let mut unknown_odd_types = BTreeMap::new();
256
257        while let Some(record) = stream.next_record().map_err(Error::TlvStream)? {
258            match record.tlv_type() {
259                PROOF_ASSET_PROOF_TYPE => {
260                    asset_proof = Some(AssetProof::decode_tlv(record.value_reader())?);
261                }
262                PROOF_TAPROOT_ASSET_PROOF_TYPE => {
263                    taproot_asset_proof =
264                        Some(TaprootAssetProof::decode_tlv(record.value_reader())?);
265                }
266                type_val => {
267                    if type_val.is_odd() {
268                        unknown_odd_types.insert(type_val.0, record.value().to_vec());
269                    } else {
270                        return Err(Error::UnknownTlvType(type_val.0));
271                    }
272                }
273            }
274        }
275        Ok(Proof {
276            asset_proof,
277            taproot_asset_proof: taproot_asset_proof.ok_or(Error::MissingTlvField(
278                "Proof.taproot_asset_proof".to_string(),
279            ))?,
280            unknown_odd_types,
281        })
282    }
283}
284
285// --- TLV Type Constants for commitment structures ---
286
287// For commitment::Proof
288const PROOF_ASSET_PROOF_TYPE: Type = Type(0);
289const PROOF_TAPROOT_ASSET_PROOF_TYPE: Type = Type(2);
290
291// For commitment::AssetProof
292const ASSET_PROOF_VERSION_TYPE: Type = Type(0);
293const ASSET_PROOF_TAP_KEY_TYPE: Type = Type(2); // Renamed from AssetID in Go for clarity
294const ASSET_PROOF_MSSMT_PROOF_TYPE: Type = Type(4); // "AssetProofRecord" in Go
295
296// For commitment::TaprootAssetProof
297const TAPROOT_ASSET_PROOF_VERSION_TYPE: Type = Type(0);
298const TAPROOT_ASSET_PROOF_MSSMT_PROOF_TYPE: Type = Type(2); // "TaprootAssetProofRecord" in Go