From 474eac167b30b503e78a16797ac2410bf4f4f9cc Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 30 Sep 2025 17:33:46 -0700 Subject: [PATCH] docs: add docs re current stxo design --- docs/stxo_design.md | 908 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 908 insertions(+) create mode 100644 docs/stxo_design.md diff --git a/docs/stxo_design.md b/docs/stxo_design.md new file mode 100644 index 000000000..c355f373e --- /dev/null +++ b/docs/stxo_design.md @@ -0,0 +1,908 @@ +# STXO Proof Design + +## Table of Contents + +1. [Introduction](#introduction) +2. [The STXO Proof Concept](#the-stxo-proof-concept) +3. [Data Structures and Representation](#data-structures-and-representation) + - [The Proof Structure](#the-proof-structure) + - [STXO Asset Representation](#stxo-asset-representation) + - [The PrevID Structure](#the-previd-structure) +4. [Commitment Tree Integration](#commitment-tree-integration) + - [Two-Level Tree Architecture](#two-level-tree-architecture) + - [The EmptyGenesisID Namespace](#the-emptygenesisid-namespace) + - [Insertion via MergeAltLeaves](#insertion-via-mergealtleaves) + - [Retrieval and Proof Generation](#retrieval-and-proof-generation) +5. [Lifecycle and Creation](#lifecycle-and-creation) + - [Transfer Initiation](#transfer-initiation) + - [The IsTransferRoot Decision](#the-istransferroot-decision) + - [Burn Key Derivation](#burn-key-derivation) + - [Commitment Construction Timing](#commitment-construction-timing) +6. [Proof Generation and Verification](#proof-generation-and-verification) + - [Inclusion Proof Generation](#inclusion-proof-generation) + - [Exclusion Proof Generation](#exclusion-proof-generation) + - [Verification Process](#verification-process) + - [Completeness Checks](#completeness-checks) +7. [Security Properties and Attack Prevention](#security-properties-and-attack-prevention) + - [Double-Spend Prevention](#double-spend-prevention) + - [Replay Attack Protection](#replay-attack-protection) + - [Burn Key Unspendability](#burn-key-unspendability) + - [Collision Resistance](#collision-resistance) +8. [Versioning and Backward Compatibility](#versioning-and-backward-compatibility) + - [TransitionV0 and TransitionV1](#transitionv0-and-transitionv1) + - [Asset Channel Opt-Out](#asset-channel-opt-out) + - [Downgrade Mechanism](#downgrade-mechanism) +9. [Special Cases and Edge Conditions](#special-cases-and-edge-conditions) + - [Genesis Asset Minting](#genesis-asset-minting) + - [Split Transactions](#split-transactions) + - [Consolidation Transfers](#consolidation-transfers) + - [Burn Transactions](#burn-transactions) +10. [Conclusion](#conclusion) + +## Introduction + +In the Taproot Assets protocol, transfers must provide cryptographic proof that +input assets are validly consumed and not double-spent. The Spent Transaction +Output (STXO) proof system addresses this requirement by integrating with the +protocol's existing commitment tree structure. Unlike traditional blockchains +that track spent outputs in a global UTXO set, Taproot Assets anchors multiple +asset transfers to single Bitcoin transactions, requiring a sophisticated +approach to proving asset consumption. + +STXO proofs demonstrate two critical properties. First, inclusion: assets +consumed as inputs are properly recorded in the sender's output commitment. +Second, exclusion: these spent assets do not appear in other outputs of the same +transaction, preventing double-spends. This dual-proof mechanism enables +receivers to verify the complete lineage of transferred assets without trusted +third parties or comprehensive chain state. + +The implementation leverages the protocol's Merkle-Sum Sparse Merkle Tree +(MS-SMT) commitment structure, introducing STXO assets as "alternative leaves" +stored at a designated tree location. These STXO assets contain only provably +unspendable "burn keys" derived deterministically from spent asset metadata. +This ensures STXO proofs integrate naturally with existing proof verification +infrastructure while maintaining separation from regular asset commitments. + +STXO proofs were introduced in `TransitionV1` and are required for all transfer +root assets using this version. The system maintains backward compatibility with +`TransitionV0` proofs while establishing a path toward universal enforcement in +future versions. + +## The STXO Proof Concept + +An STXO proof is a pair of cryptographic proofs demonstrating the state of spent +assets within a transfer. When Alice transfers an asset to Bob, she must prove +Bob receives a valid asset and that she properly consumed her inputs without +spending them to multiple parties. + +The key insight is that spent assets can be represented as entries in the same +MS-SMT structure used for active assets, but stored at a special reserved +location. Rather than designing a separate proof system, the implementation +creates minimal `Asset` structures containing only a script key and version, +with all other fields set to zero. This script key is a provably unspendable +public key—a burn key—computed deterministically from the spent asset's unique +identifier. + +Each asset input is identified by its `PrevID`, which combines the Bitcoin UTXO +outpoint where it was anchored, the asset's ID, and its script key. This +three-part identifier ensures global uniqueness: no two assets can have +identical `PrevID`s because each Bitcoin UTXO can only be spent once, and within +that UTXO, each asset instance has a unique script key. The STXO proof system +derives a burn key through a taproot-style tweak of a Nothing-Up-My-Sleeve +(NUMS) point using the `PrevID` as tweak data. + +The burn key derivation follows: `burnKey = NUMSKey + H(NUMSKey || OutPoint || +AssetID || ScriptKey) × G`, where H is the tagged hash function used in taproot +key tweaking and G is the secp256k1 generator point. This ensures the key is +provably unspendable, deterministically derived from the `PrevID`, and unique +per spent asset, preventing collisions. + +These minimal STXO assets are inserted into a dedicated `AssetCommitment` at +`EmptyGenesisID`, the hash of a zero-valued `Genesis` structure. This location +serves as a namespace for all STXO assets, separate from commitments keyed by +actual genesis IDs. Within this commitment, each STXO asset is keyed by the hash +of its burn key, enabling inclusion or exclusion proofs for specific spent +assets. + +The proof mechanism operates at two levels. For the output receiving the +transfer, inclusion proofs demonstrate that STXO assets for each input exist at +`EmptyGenesisID`. For other outputs, exclusion proofs demonstrate either that +the `EmptyGenesisID` commitment does not exist or that specific STXO assets are +not present. These leverage the sparse merkle tree property allowing +non-membership proofs by showing paths leading to empty nodes. + +## Data Structures and Representation + +The STXO proof system integrates with existing `Proof` and `TaprootProof` +structures in `github.com/lightninglabs/taproot-assets/proof/proof.go`, using +minimal asset representations stored as alternative leaves. + +### The Proof Structure + +The `Proof` structure contains a `Version` field that determines whether STXO +proofs are required. For `TransitionV0`, they are optional; for `TransitionV1`, +they are mandatory for transfer root assets. The `AltLeaves` field holds STXO +assets when included. + +```mermaid +classDiagram + class Proof { + +TransitionVersion Version + +Asset Asset + +TaprootProof InclusionProof + +[]TaprootProof ExclusionProofs + +[]AltLeaf_Asset_ AltLeaves + } + + class TaprootProof { + +uint32 OutputIndex + +btcec.PublicKey InternalKey + +*CommitmentProof CommitmentProof + } + + class CommitmentProof { + +commitment.Proof Proof + +*TapscriptPreimage TapSiblingPreimage + +map[SerializedKey]Proof STXOProofs + } + + class Asset_STXO { + +ScriptKey ScriptKey + +ScriptVersion Version + +Genesis EmptyGenesis + +uint64 Amount 0 + +uint64 LockTime 0 + +uint64 RelativeLockTime 0 + } + + class PrevID { + +wire.OutPoint OutPoint + +ID AssetID + +SerializedKey ScriptKey + } + + class BurnKey { + +btcec.PublicKey Key + +string Derivation "NUMS + tapTweak(prevID)" + } + + Proof --> TaprootProof : has + TaprootProof --> CommitmentProof : contains + CommitmentProof --> Asset_STXO : STXOProofs prove + Asset_STXO --> BurnKey : uses as ScriptKey + BurnKey --> PrevID : derived from +``` + +`TaprootProof` represents a proof that an asset is committed to a specific +taproot output. It contains an `OutputIndex`, an `InternalKey`, and a +`CommitmentProof` providing the path through the commitment tree. The +`CommitmentProof`, defined in +`github.com/lightninglabs/taproot-assets/proof/taproot.go`, critically contains +a `STXOProofs` map from `asset.SerializedKey` to `commitment.Proof`. + +This map holds actual STXO inclusion or exclusion proofs. Keys are 33-byte +serialized burn keys; values are MS-SMT proofs demonstrating presence or absence +of the corresponding STXO asset. Verifiers extract `PrevWitnesses`, derive +expected burn keys, and look up proofs in this map for efficient verification. + +### STXO Asset Representation + +STXO assets, while technically `Asset` structures from +`github.com/lightninglabs/taproot-assets/asset/asset.go`, contain minimal data. +The `NewAltLeaf` function constructs these representations: + +```go +// NewAltLeaf instantiates a new valid AltLeaf. +func NewAltLeaf(key ScriptKey, keyVersion ScriptVersion) (*Asset, error) { + if key.PubKey == nil { + return nil, fmt.Errorf("script key must be non-nil") + } + + return &Asset{ + Version: V0, + Genesis: EmptyGenesis, + Amount: 0, + LockTime: 0, + RelativeLockTime: 0, + PrevWitnesses: nil, + SplitCommitmentRoot: nil, + GroupKey: nil, + ScriptKey: key, + ScriptVersion: keyVersion, + }, nil +} +``` + +This creates an asset with all fields zero or empty except `ScriptKey` (holding +the burn key) and `ScriptVersion`: + +```mermaid +graph TB + subgraph "Regular Asset" + RA[Asset] + RA --> RG[Genesis Info] + RA --> RAM[Amount] + RA --> RAI[Asset ID] + RA --> RSK[Script Key] + RA --> RW[Witnesses] + RA --> RGL[Group Key] + end + + subgraph "STXO Asset AltLeaf" + SA[Asset AltLeaf] + SA --> SG[Genesis = EMPTY] + SA --> SAM[Amount = 0] + SA --> SAI[Asset ID = N/A] + SA --> SSK[Script Key = BURN KEY] + SA --> SW[Witnesses = NONE] + SA --> SV[Script Version = V0] + end + + style SA fill:#ff9999 + style RA fill:#99ff99 + style SG fill:#ffcccc + style SAM fill:#ffcccc + style SAI fill:#ffcccc + style SW fill:#ffcccc +``` + +`MakeSpentAsset` creates STXO assets from witness data: + +```go +// MakeSpentAsset creates an Alt Leaf with the minimal asset based on the +// PrevID of the witness. +func MakeSpentAsset(witness Witness) (*Asset, error) { + if witness.PrevID == nil { + return nil, fmt.Errorf("witness has no prevID") + } + + prevIdKey := DeriveBurnKey(*witness.PrevID) + scriptKey := NewScriptKey(prevIdKey) + + spentAsset, err := NewAltLeaf(scriptKey, ScriptV0) + if err != nil { + return nil, fmt.Errorf("error creating altLeaf: %w", err) + } + + return spentAsset, nil +} +``` + +This extracts the `PrevID` from the witness, derives a burn key, wraps it in a +`ScriptKey`, and constructs the STXO asset via `NewAltLeaf`. + +### The PrevID Structure + +`PrevID` is the foundation for burn key derivation. It contains an `OutPoint` +identifying the Bitcoin transaction output where the asset was anchored, an `ID` +representing the asset's unique identifier, and a `ScriptKey` field with the +33-byte serialized public key. Together, these provide a globally unique +identifier for any asset at any point in its history. + +## Commitment Tree Integration + +The Taproot Assets protocol uses a two-level Merkle-Sum Sparse Merkle Tree to +commit assets within a taproot output, providing efficient proofs and namespace +separation. + +### Two-Level Tree Architecture + +The outer `TapCommitment` (in +`github.com/lightninglabs/taproot-assets/commitment/tap.go`) is an MS-SMT keyed +by asset genesis IDs. Each value is the root of an inner `AssetCommitment`, +itself an MS-SMT keyed by individual asset identifiers. This enables efficient +proofs while maintaining separate namespaces for different asset types. + +```mermaid +graph TD + subgraph "Taproot Output" + TO[Taproot Output Key] + end + + TO --> TL[TapLeaf Hash] + + subgraph "Level 1: TapCommitment Outer MS-SMT" + TL --> TC[TapCommitment Root] + TC --> AC1[AssetCommitment
Genesis ID 1] + TC --> AC2[AssetCommitment
Genesis ID 2] + TC --> ACSTXO[AssetCommitment
EmptyGenesisID] + TC --> ACN[AssetCommitment
Genesis ID N] + end + + subgraph "Level 2: AssetCommitments Inner MS-SMTs" + AC1 --> A1[Asset 1.1] + AC1 --> A2[Asset 1.2] + + AC2 --> A3[Asset 2.1] + + ACSTXO --> STXO1[STXO Asset 1] + ACSTXO --> STXO2[STXO Asset 2] + ACSTXO --> STXO3[STXO Asset N] + + ACN --> AN[Asset N.1] + end + + style ACSTXO fill:#ff9800 + style STXO1 fill:#ffccbc + style STXO2 fill:#ffccbc + style STXO3 fill:#ffccbc +``` + +### The EmptyGenesisID Namespace + +STXO assets occupy a dedicated entry at `EmptyGenesisID`, the `ID` of a +zero-valued `Genesis` structure. This reserved namespace never collides with +actual asset genesis IDs, since valid assets always have non-empty genesis +information. + +Within the `AssetCommitment` at `EmptyGenesisID`, individual STXO assets are +keyed by the SHA256 hash of their burn key's compressed serialization. The +`AssetCommitmentKey` method returns the asset ID for genesis assets, and the +hash of the script key for non-genesis assets. Since STXO assets use unique burn +keys as script keys, each STXO asset has a unique location. + +### Insertion via MergeAltLeaves + +STXO assets are inserted via `MergeAltLeaves` on `TapCommitment`. This function +validates that all provided STXO assets have unique `AssetCommitmentKey`s, looks +up or creates the `AssetCommitment` at `EmptyGenesisID`, inserts each STXO asset +via `Upsert` into the inner MS-SMT, and updates the outer MS-SMT with the new +root. + +Collision detection is critical for security. The function compares new STXO +asset keys against existing ones, returning `ErrDuplicateAltLeafKey` if any key +appears in both sets. This prevents inserting the same STXO proof multiple +times. + +### Retrieval and Proof Generation + +`FetchAltLeaves` retrieves STXO assets by looking up the `AssetCommitment` at +`EmptyGenesisID` and returning all contained assets. + +The `Proof` generation function creates both inclusion and exclusion proofs for +STXO assets. For STXO proofs, the `tapCommitmentKey` is always `EmptyGenesisID`, +and the `assetCommitmentKey` is the hash of the specific burn key. It generates +an MS-SMT proof from the outer tree to the `AssetCommitment`, then from the +inner tree to the specific asset (inclusion) or empty leaf (exclusion). + +Separating STXO assets at `EmptyGenesisID` allows efficient enumeration via +`FetchAltLeaves`, prevents interference with regular asset lookups, and enables +bulk operations like `Downgrade`, which removes the entire STXO commitment. + +## Lifecycle and Creation + +STXO proof creation begins with transfer initiation and proceeds through +commitment construction, proof generation, and verification. + +### Transfer Initiation + +STXO proofs are created during output commitment construction. When a sender +initiates a transfer, the system constructs virtual packets (vPackets) +representing asset movements. For each output, `CreateOutputCommitments` in +`github.com/lightninglabs/taproot-assets/tapsend/send.go` builds the commitments +to be anchored to the Bitcoin transaction. + +```mermaid +sequenceDiagram + participant Sender as Send/Transfer Flow + participant CO as CreateOutputCommitments + participant CS as CollectSTXO + participant MS as MakeSpentAsset + participant MA as MergeAltLeaves + participant PG as Proof Generation + + Sender->>CO: Create commitments for outputs + CO->>CS: CollectSTXO(outputAsset) + CS->>CS: Check if IsTransferRoot() + alt Is Transfer Root + CS->>MS: MakeSpentAsset(witness) for each PrevWitness + MS->>MS: DeriveBurnKey(prevID) + MS->>MS: Create AltLeaf with burn key + MS-->>CS: Return STXO asset + CS-->>CO: Return []STXO assets + CO->>MA: MergeAltLeaves(stxoAssets) + MA->>MA: Insert at EmptyGenesisID + MA-->>CO: Commitment updated + else Not Transfer Root + CS-->>CO: Return nil (no STXOs) + end + CO-->>Sender: Commitments ready + Sender->>PG: Generate proofs + PG->>PG: Create STXO inclusion proofs + PG-->>Sender: Proofs with STXOProofs +``` + +### The IsTransferRoot Decision + +The critical decision is the `IsTransferRoot` check. An asset is a transfer root +if it has at least one `PrevWitness` with a non-nil `PrevID`. This distinguishes +transfer roots from genesis assets (no previous witnesses) and split leaves +(created from a split commitment, do not reference inputs directly). Only +transfer root assets generate STXO proofs. + +For each transfer root asset, `CollectSTXO` in +`github.com/lightninglabs/taproot-assets/asset/asset.go` is called: + +```go +// CollectSTXO returns the assets spent by the given output asset in the form +// of minimal assets that can be used to create an STXO commitment. +func CollectSTXO(outAsset *Asset) ([]AltLeaf[Asset], error) { + // Genesis assets have no input asset, so they should have an empty + // STXO tree. Split leaves will also have a zero PrevID; we will use + // an empty STXO tree for them as well. + if !outAsset.IsTransferRoot() { + return nil, nil + } + + // At this point, the asset must have at least one witness. + if len(outAsset.PrevWitnesses) == 0 { + return nil, fmt.Errorf("asset has no witnesses") + } + + // We'll convert the PrevID of each witness into a minimal Asset, where + // the PrevID is the tweak for an un-spendable script key. + altLeaves := make([]*Asset, len(outAsset.PrevWitnesses)) + for idx, wit := range outAsset.PrevWitnesses { + altLeaf, err := MakeSpentAsset(wit) + if err != nil { + return nil, fmt.Errorf("error collecting stxo for "+ + "witness %d: %w", idx, err) + } + + altLeaves[idx] = altLeaf + } + + return ToAltLeaves(altLeaves), nil +} +``` + +This iterates over `PrevWitnesses`, calling `MakeSpentAsset` for each to create +a minimal STXO asset. `MakeSpentAsset` extracts the `PrevID`, calls +`DeriveBurnKey` to compute the burn key, wraps it in a `ScriptKey`, and calls +`NewAltLeaf` to create the STXO asset containing only the burn key and script +version. + +### Burn Key Derivation + +`DeriveBurnKey` in `github.com/lightninglabs/taproot-assets/asset/burn.go` +implements the cryptographic derivation: + +```go +// DeriveBurnKey derives a provably un-spendable but unique key by tweaking +// the public NUMS key with a tap tweak: +// +// burnTweak = h_tapTweak(NUMSKey || outPoint || assetID || scriptKey) +// burnKey = NUMSKey + burnTweak*G +// +// The prevID must be the PrevID from an input that is being spent +// by the virtual transaction that contains the burn. +func DeriveBurnKey(prevID PrevID) *btcec.PublicKey { + var b bytes.Buffer + + // The data we use in the tap tweak of the NUMS point is the serialized + // PrevID, which consists of an outpoint, the asset ID and the script + // key. Because these three values combined are larger than 32 bytes, we + // explicitly make the script spend path invalid because a merkle root + // hash would be exactly 32 bytes. + // + // NOTE: All errors here are ignored, since they can only be returned + // from a Write() call, which on the bytes.Buffer will _never_ fail. + _ = wire.WriteOutPoint(&b, 0, 0, &prevID.OutPoint) + _, _ = b.Write(prevID.ID[:]) + _, _ = b.Write(prevID.ScriptKey.SchnorrSerialized()) + + // Since we'll never query lnd for a burn key, it doesn't matter if we + // lose the parity information here. And this will only ever be + // serialized on chain in a 32-bit representation as well, as this is + // always a script key. + burnKey := txscript.ComputeTaprootOutputKey(NUMSPubKey, b.Bytes()) + burnKey, _ = schnorr.ParsePubKey(schnorr.SerializePubKey(burnKey)) + return burnKey +} +``` + +This creates a provably unspendable but deterministic public key. The formula +ensures the key is provably unspendable, deterministically derived from the +`PrevID`, and unique per spent asset, preventing collisions. + +```mermaid +graph TD + A[Witness.PrevID] --> B[Extract Components] + + B --> C[OutPoint
36 bytes] + B --> D[AssetID
32 bytes] + B --> E[ScriptKey
32 bytes] + + C --> F[Concatenate
100 bytes total] + D --> F + E --> F + + F --> G[ComputeTaprootOutputKey
NUMSKey, Data] + + G --> H["TapTweak = TaggedHash(TapTweak tag, NUMSKey || Data)"] + + H --> I[BurnKey = NUMSKey + TapTweak * G] + + I --> J[Schnorr Serialize
32 bytes] + + J --> K[SHA256 Hash] + + K --> L[AssetCommitmentKey
32 bytes] + + L --> M[Insert in Inner MS-SMT
at this key] + + style I fill:#ff9800 + style L fill:#4caf50 + style M fill:#2196f3 +``` + +### Commitment Construction Timing + +After `CollectSTXO` returns STXO assets, they are appended to `vOut.AltLeaves`. +Later, `MergeAltLeaves` inserts them into the `TapCommitment` at +`EmptyGenesisID` before the final commitment root is computed, binding the STXO +proofs to the anchor transaction. + +STXO assets are created during commitment construction, not proof generation. +This ensures STXO commitments are included in the taproot output anchored to the +blockchain. The MS-SMT proof paths are generated later during +`CreateProofSuffix`, proving statements about on-chain commitments. The +blockchain serves as the source of truth, and proofs demonstrate that truth to +receivers. + +STXO creation can be disabled via `WithNoSTXOProofs`, primarily for asset +channels where both parties maintain full state. This option also supports +backward compatibility. + +In split transactions, only the split root generates STXO proofs. Split leaves +are validated through the split commitment in the root, avoiding redundant +proofs. + +When consolidating multiple inputs, the output asset has multiple `PrevWitness` +entries. `CollectSTXO` produces multiple STXO assets, all inserted at +`EmptyGenesisID` with unique keys determined by their burn keys. The resulting +proof contains multiple `STXOProofs` map entries, one per input. + +## Proof Generation and Verification + +STXO proof generation occurs in `CreateProofSuffix` in +`github.com/lightninglabs/taproot-assets/proof/append.go`, divided into +inclusion proofs, exclusion proofs, and verification. + +### Inclusion Proof Generation + +After constructing base `TaprootProof` structures, the function checks if the +asset is a transfer root and STXO proofs are enabled. If so, it generates STXO +inclusion proofs. + +```mermaid +flowchart TD + Start[Start: Generate STXO Inclusion Proofs] --> A{Is Transfer Root?} + + A -->|No| B[Skip STXO Proofs] + A -->|Yes| C[For each PrevWitness] + + C --> D[Create STXO Asset via MakeSpentAsset] + D --> E[Derive Burn Key from PrevID] + E --> F[Compute AssetCommitmentKey = SHA256 burnKey] + + F --> G[TaprootAssetRoot.Proof
tapKey: EmptyGenesisID
assetKey: AssetCommitmentKey] + + G --> H[Lookup in Outer MS-SMT
at EmptyGenesisID] + + H --> I{Found AssetCommitment?} + + I -->|No| J[ERROR: Missing STXO AssetCommitment] + I -->|Yes| K[Lookup in Inner MS-SMT
at AssetCommitmentKey] + + K --> L{Found STXO Asset?} + + L -->|No| M[ERROR: Missing STXO Asset] + L -->|Yes| N[Generate MS-SMT Path] + + N --> O[Outer Path: Root to EmptyGenesisID] + N --> P[Inner Path: AssetCommitment Root to STXO Asset] + + O --> Q[Combine Paths into Proof] + P --> Q + + Q --> R[Store in stxoInclusionProofs map
Key: SerializedKey burnKey] + + R --> S{More PrevWitnesses?} + S -->|Yes| C + S -->|No| T[Set proof.InclusionProof.CommitmentProof.STXOProofs] + + T --> End[End: STXO Inclusion Proofs Complete] + + B --> End + J --> Error[End: Error] + M --> Error + + style N fill:#4caf50 + style T fill:#2196f3 + style Error fill:#f44336 +``` + +The function creates a map for STXO proofs and iterates over each witness in +`PrevWitnesses`. For each, it calls `MakeSpentAsset` to reconstruct the STXO +asset, then calls `Proof` on the `TaprootAssetRoot` commitment with +`EmptyGenesisID` as the `tapCommitmentKey` and the STXO asset's +`AssetCommitmentKey` as the `assetCommitmentKey`. This returns the MS-SMT proof +path from the STXO leaf to the commitment root. + +The returned `commitment.Proof` contains the merkle path with sibling hashes at +each tree level. The function serializes the burn key, stores the proof in +`stxoInclusionProofs`, and assigns this map to +`InclusionProof.CommitmentProof.STXOProofs`, ensuring all inputs are proven +spent. + +### Exclusion Proof Generation + +Exclusion proof generation follows the same pattern for outputs not receiving +the asset. Each exclusion `TaprootProof` includes STXO exclusion proofs +demonstrating spent assets are not present in other outputs. The `Proof` +function returns paths leading to empty nodes rather than inclusion proofs. + +### Verification Process + +Verification begins in `Verify` in +`github.com/lightninglabs/taproot-assets/proof/verifier.go`. The verifier +determines if STXO proofs are required (proof version V1+ and asset is transfer +root). If required but missing, verification fails with `ErrProofInvalid`. + +If present, the verifier calls `CollectSTXO` to determine expected STXO assets +based on `PrevWitnesses`, builds a map keyed by serialized burn keys, and passes +it to `verifySTXOProofSet`. + +`verifySTXOProofSet` iterates over each `STXOProofs` entry, looks up the +corresponding expected STXO asset, and calls `Verify` on the MS-SMT proof with +the expected asset and commitment root. This verifies the merkle path is valid. + +### Completeness Checks + +For inclusion proofs, `Verify` ensures the leaf node contains the expected STXO +asset by recomputing the merkle path and confirming it matches the committed +root. For exclusion proofs, it ensures the leaf is empty or nonexistent. + +Verification checks completeness: all expected STXO assets must have +corresponding proofs. Missing proofs cause failure, preventing selective +omission of STXO proofs. All inputs must be proven spent. + +Exclusion proofs verify that either the `EmptyGenesisID` `AssetCommitment` does +not exist or specific STXO assets are not present, preventing double-spend +attacks. + +## Security Properties and Attack Prevention + +The STXO proof system provides cryptographic protection through inclusion and +exclusion proofs, burn key cryptography, and blockchain anchoring. + +### Double-Spend Prevention + +Double-spend prevention is achieved through exclusion proofs. When a sender +attempts to spend the same input to two outputs, exclusion proofs fail. The +first output may have valid inclusion proofs, but the second must have exclusion +proofs. If STXO assets are present in the second output's commitment, +verification detects this and rejects the proof, providing protection equivalent +to Bitcoin's UTXO model. + +```mermaid +graph LR + subgraph "Attack Scenario" + A[Attacker Controls
Asset X] --> B[Transaction with
2 Outputs] + B --> O1[Output 1: Asset X] + B --> O2[Output 2: Asset X DUPLICATE] + end + + subgraph "STXO Protection" + O1 --> P1[Proof: STXO of X
included] + O2 --> P2[Proof: STXO of X
MUST BE EXCLUDED] + P2 --> V[Verifier] + V --> R[REJECT:
STXO found in Output 2] + end + + style O2 fill:#f44336 + style R fill:#f44336 + style P1 fill:#4caf50 +``` + +### Replay Attack Protection + +STXO proofs are bound to specific on-chain transactions. Each burn key is +derived from a `PrevID` including the `OutPoint` of the previous anchor +transaction. This `OutPoint` uniquely identifies a Bitcoin UTXO. Once spent +on-chain, it cannot be spent again. The STXO proof is cryptographically bound to +this UTXO. Reusing an STXO proof from a previous transaction causes verification +to fail because the burn key doesn't match the expected value. + +### Burn Key Unspendability + +Burn keys are provably unspendable, ensuring STXO assets cannot be confused with +real assets. The burn key is derived by tweaking a NUMS point with over 32 bytes +of data. The taproot key derivation interprets this as a script path, but +because it exceeds 32 bytes, it cannot represent a valid merkle root, making the +key unspendable via the script path. The NUMS point has no known private key, +making it unspendable via the key path. Any attempt to spend an STXO asset +on-chain fails because the necessary private key does not exist. + +### Collision Resistance + +Burn key uniqueness prevents collision attacks. The `PrevID` provides global +uniqueness through three components: `OutPoint` (unique Bitcoin UTXO), `AssetID` +(unique asset type), and `ScriptKey` (unique asset instance). No two legitimate +assets have the same `PrevID`. Deterministic derivation preserves this +uniqueness. Collision would require finding two `PrevID`s that hash to the same +burn key, requiring breaking SHA256. + +MS-SMT proofs provide non-repudiation. Once an STXO proof is generated and the +anchor transaction is confirmed, the sender cannot deny the transfer. The +blockchain serves as an immutable ledger of commitments. + +Input verification protects receivers from invalid transfers. Without STXO +proofs, a malicious sender could claim to transfer an asset without controlling +valid inputs. With STXO inclusion proofs, the receiver verifies each claimed +input exists as an STXO in the sender's commitment, enabling trustless +verification of asset lineage. + +## Versioning and Backward Compatibility + +STXO proofs were introduced in `TransitionV1`, establishing an evolution path +while maintaining backward compatibility. + +### TransitionV0 and TransitionV1 + +`TransitionVersion` in `github.com/lightninglabs/taproot-assets/proof/proof.go` +is a uint32 enumeration. `TransitionV0` is the original format without STXO +proofs. `TransitionV1` made STXO proofs mandatory for transfer root assets. The +`Version` field determines which validation rules apply. + +```mermaid +timeline + title STXO Proof Version History + + TransitionV0 : No STXO Proofs + : Assets can transfer without spend proofs + : Backward compatible + + TransitionV1 : STXO Proofs Added + : Required for transfer root assets + : Inclusion proofs in InclusionProof + : Exclusion proofs in ExclusionProofs + : Enhanced security +``` + +For `TransitionV0`, STXO proofs are optional and typically absent. The verifier +checks the version using `IsVersionV1` and skips STXO verification if V0. This +maintains backward compatibility, allowing gradual ecosystem migration. + +For `TransitionV1`, STXO proofs are required for transfer root assets. The +verification logic computes `needStxoProofs` as `IsVersionV1 && IsTransferRoot`. +If true, the verifier expects a non-empty +`InclusionProof.CommitmentProof.STXOProofs` map. Missing proofs result in +`ErrProofInvalid`. + +The `IsTransferRoot` check exempts certain asset types from STXO requirements in +V1. Genesis assets have no input assets to prove spent. Split leaves are +validated through the split commitment in the split root, not STXO proofs. This +avoids redundant proofs while maintaining security. + +### Downgrade Mechanism + +The `Downgrade` function on `TapCommitment` provides a migration path from V1 to +V0. It creates a copy with the `EmptyGenesisID` `AssetCommitment` removed, +enabling interoperation with legacy systems at the cost of reduced security. + +The default configuration in `DefaultGenConfig` sets `TransitionVersion` to +`TransitionV1` and `NoSTXOProofs` to false. All new transfers generate V1 proofs +with STXO proofs enabled unless explicitly configured otherwise, reflecting the +protocol's security priorities. + +## Special Cases and Edge Conditions + +The STXO proof system handles special transaction types through exemptions and +alternative verification paths. + +### Genesis Asset Minting + +Genesis assets have no STXO proofs. When initially created, there are no +previous assets to prove spent. The `PrevWitnesses` slice is empty. +`IsTransferRoot` returns false, causing `CollectSTXO` to return nil. No STXO +assets or proofs are created. Security for genesis assets relies on verifying +the issuer's signature and genesis parameter uniqueness. + +### Split Transactions + +Split transactions exhibit nuanced STXO behavior. One input asset is divided +into multiple outputs. The split root contains a `SplitCommitmentRoot`, a Merkle +tree proving the existence and amounts of all split leaves. The split root has a +`PrevWitness` referencing the input asset, so `IsTransferRoot` returns true and +STXO proofs are generated. Split leaves do not have `PrevWitnesses` and are +created from the split commitment. For split leaves, `IsTransferRoot` returns +false and no STXO proofs are generated. Verification relies on validating the +split commitment proof in the root. + +```mermaid +graph TD + subgraph "Split Transaction" + Input[Input Asset
Amount: 100] --> Split[Split] + + Split --> Root[Split Root
Amount: 60
HAS PrevWitnesses] + Split --> Leaf1[Split Leaf 1
Amount: 30
NO PrevWitnesses] + Split --> Leaf2[Split Leaf 2
Amount: 10
NO PrevWitnesses] + end + + Root --> STXO[Generate STXO Proofs] + Leaf1 --> NoSTXO1[No STXO Proofs] + Leaf2 --> NoSTXO2[No STXO Proofs] + + style Root fill:#4caf50 + style Leaf1 fill:#ffeb3b + style Leaf2 fill:#ffeb3b +``` + +### Consolidation Transfers + +Consolidation transfers combining multiple input assets produce multiple STXO +proofs. If three asset UTXOs are consumed to create one output, the output asset +has three `PrevWitness` entries. `CollectSTXO` iterates over all three, +producing three STXO assets with different burn keys from different `PrevID`s. +All three are inserted at `EmptyGenesisID` with unique keys. The resulting proof +contains three `STXOProofs` map entries, and verification confirms all three +inputs were properly committed as spent. + +### Burn Transactions + +Burn transactions sending assets to provably unspendable keys still generate +STXO proofs. The burn mechanism sets the output asset's script key to a burn key +derived from the asset's metadata, making the output unspendable. The STXO burn +key is derived from the input's `PrevID`, a separate computation from the output +burn key. Input STXO proofs demonstrate the sender consumed specific inputs. The +output burn key demonstrates the resulting asset cannot be spent. These are +orthogonal mechanisms. + +Multi-output transfers require STXO exclusion proofs for all outputs except the +one receiving the assets. Bob's proof includes STXO inclusion proofs showing +Alice's inputs were spent and STXO exclusion proofs for other outputs, +preventing double-spends. + +Change outputs in splits follow the split leaf pattern. When Alice sends 60 +units to Bob but her input contains 100 units, Bob receives a split root with 60 +units and STXO proofs. Alice receives a change output with 40 units as a split +leaf without STXO proofs, validated through the split commitment in Bob's +output. + +Asset group reissuance creates new assets within an existing group. The new +assets are genesis assets for their lineages and do not have `PrevWitnesses` or +generate STXO proofs. Security comes from verifying the group key signature +proving the issuer authorized creation. + +## Conclusion + +The STXO proof system integrates cryptographic proof techniques with the Taproot +Assets commitment structure. By leveraging provably unspendable burn keys +derived deterministically from spent asset identifiers, the system creates +minimal asset representations that fit naturally into the existing MS-SMT +framework. The dual-level proof mechanism—inclusion proofs demonstrating inputs +were spent, exclusion proofs demonstrating inputs were not double-spent—provides +security guarantees equivalent to Bitcoin's UTXO model. + +Placement of STXO assets at `EmptyGenesisID` ensures clean separation from +regular assets while enabling efficient proof generation and verification. The +lifecycle from creation during commitment construction through proof generation +and verification establishes a clear chain of cryptographic evidence linking +inputs to outputs. Integration with `TransitionV1` provides a migration path +from legacy proofs while establishing STXO proofs as the standard. + +Security properties emerge from careful cryptographic construction. Burn key +derivation from NUMS points with over 32 bytes of tweak data ensures provable +unspendability. Uniqueness guarantees from the `PrevID` structure prevent +collisions. MS-SMT proof verification provides cryptographic binding to on-chain +commitments. The result enables trustless verification of asset lineage without +requiring global state or trusted third parties. + +Special cases are handled through the `IsTransferRoot` check and explicit +opt-out mechanisms. Genesis assets, split leaves, and asset channels have valid +reasons for not including STXO proofs, and the verification logic accommodates +these cases without compromising security for standard transfers. The versioning +mechanism provides backward compatibility while establishing a path toward +universal STXO enforcement.