Skip to content

Commit bccb3ef

Browse files
authored
Merge pull request #1759 from lightninglabs/developer-docs
docs: add generated and reviewed developer deep-dive docs
2 parents 9d0b4d2 + 6414b90 commit bccb3ef

11 files changed

+2825
-0
lines changed

docs/developer-deep-dive.md

Lines changed: 1175 additions & 0 deletions
Large diffs are not rendered by default.

docs/developer-transaction-flow.md

Lines changed: 1053 additions & 0 deletions
Large diffs are not rendered by default.

docs/hardware-wallet-support.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# How to add Hardware Wallet support
2+
3+
With `v0.7.0`, `tapd` uses four different forms of scripts/tweaks for anchoring
4+
commitment roots into Bitcoin transactions that can be relevant for hardware
5+
signing devices:
6+
- The "Taproot Asset Root Commitment": The pseudo-leaf that's placed in the
7+
Taproot merkle tree to commit to asset mints and transfers.
8+
- Relevant when signing any asset mint or transfer transaction. The Taproot
9+
internal key used for the mint or transfer output(s) would be a key that
10+
belongs to the signing device. And the Taproot Asset Root Commitment pseudo
11+
leaf would be the single leaf in the tree (unless there are more
12+
user-defined script leaves added, as would be the case for Lightning
13+
Channel outputs)
14+
- The V0 group key scheme that tweaks the group internal key twice.
15+
- Deprecated, should not be used when targeting Hardware Wallet support. The
16+
commitment is a simple double tweak to arrive at the final ("tweaked")
17+
group key:
18+
```go
19+
// internalKey = rawKey + singleTweak * G
20+
// tweakedGroupKey = TapTweak(internalKey, tapscriptRoot)
21+
```
22+
Where `singleTweak` is the asset ID of the group anchor and the
23+
`tapscriptRoot` is either an empty byte slice or the root of a custom
24+
script tapscript tree.
25+
- The `OP_RETURN` commitment scheme for signing minting events with the group
26+
key.
27+
- Currently only relevant as an option to choose from when defining a group
28+
key V1. Relevant for signing new tranches of assets only: A single
29+
`OP_RETURN` leaf would be present in the group key's tapscript tree that
30+
commits to the group anchor's asset ID.
31+
- The Pedersen commitment scheme for signing minting events with the group key
32+
and for generating unique script keys in V2 TAP address sends.
33+
- Relevant as an option to choose from when defining a group
34+
key V1. Relevant for signing new tranches of assets: A single
35+
`<pedersen_key> OP_CHECKSIG` leaf would be present in the group key's
36+
tapscript tree that commits to the group anchor's asset ID through a
37+
Pedersen commitment key.
38+
- This is also relevant for outputs created for sends to V2 TAP addresses.
39+
The receiver script keys are constructed with a Pedersen commitment, so
40+
if the internal key of the script key is held in a signing device, then
41+
authorizing the spend of such an output would require the signing device
42+
to be able to deal with such a leaf being present.
43+
44+
## On-chain Taproot Asset Root Commitment Structure
45+
46+
The Taproot Asset commitment is what is placed in a tap leaf of a transaction
47+
output's tapscript tree. The exact structure of the leaf script depends on the
48+
commitment version.
49+
50+
### V0 and V1 Commitments
51+
52+
For `TapCommitmentV0` and `TapCommitmentV1`, the tap leaf script is constructed
53+
as follows:
54+
55+
`version (1 byte) || TaprootAssetsMarker (32 bytes) || root_hash (32 bytes) ||
56+
root_sum (8 bytes)`
57+
58+
Where:
59+
- `version`: The `TapCommitmentVersion`, which is `0` for V0 and `1` for V1.
60+
- `TaprootAssetsMarker`: A static marker to identify the leaf as a Taproot Asset
61+
commitment. It is the `sha256` hash of the string `taproot-assets`.
62+
- `root_hash`: The MS-SMT root of the `TapCommitment`, which commits to all the
63+
asset commitments within it.
64+
- `root_sum`: The sum of all asset amounts under that `TapCommitment` root.
65+
66+
### V2 Commitments
67+
68+
For `TapCommitmentV2`, the tap leaf script is constructed as follows:
69+
70+
`tag (32 bytes) || version (1 byte) || root_hash (32 bytes) ||
71+
root_sum (8 bytes)`
72+
73+
Where:
74+
- `tag`: A tagged hash to uniquely identify this as a V2+ Taproot Asset
75+
commitment. It is the `sha256` hash of the string `taproot-assets:194243`.
76+
- `version`: The `TapCommitmentVersion`, which is `2` for V2.
77+
- `root_hash`: The MS-SMT root of the `TapCommitment`, which commits to all the
78+
asset commitments within it.
79+
- `root_sum`: The sum of all asset amounts under that `TapCommitment` root.
80+
81+
## Group Key Commitment Schemes
82+
83+
To commit to a group key, two main schemes are used to create non-spendable
84+
tapscript leaves. These leaves are used to commit to the genesis asset ID within
85+
the group key's tapscript tree and therefore a signing device signing a new
86+
asset tranche needs to be able to deal with such a leaf being present in the
87+
tapscript tree.
88+
89+
### OP_RETURN Commitment
90+
91+
This scheme creates a non-spendable script by using the `OP_RETURN` opcode.
92+
The script is constructed as follows:
93+
94+
`OP_RETURN || <data>`
95+
96+
Where:
97+
- `data`: The data to be committed to, which is typically the genesis asset ID.
98+
99+
This creates a script that will terminate execution early, making it provably
100+
unspendable.
101+
102+
### Pedersen Commitment
103+
104+
This scheme uses a normal `OP_CHECKSIG` operator with a public key that cannot
105+
be signed for. This special public key is generated using a Pedersen
106+
commitment. The script is constructed as follows:
107+
108+
`<tweaked_nums_key> OP_CHECKSIG`
109+
110+
Where:
111+
- `tweaked_nums_key`: A public key derived from a Pedersen commitment. The
112+
message for the commitment is the asset ID (or 32 zero bytes if no data is
113+
provided). To achieve hardware wallet support, this key is turned into an
114+
extended key (xpub), and a child key at path `0/0` is used as the actual
115+
public key that goes into the `OP_CHECKSIG` script.
116+

docs/taproot-asset-channels.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Taproot Asset Channels
2+
3+
This document provides a technical overview of how Taproot Asset Channels are implemented, leveraging LND's extensibility features to support asset transfers within Lightning Channels without requiring LND to have native knowledge of Taproot Assets.
4+
5+
## How LND Handles Custom Channels
6+
7+
The core principle behind Taproot Asset Channels is LND's ability to manage "custom channels." LND itself does not understand the concept of Taproot Assets. Instead, it provides a set of hooks and delegates the logic for handling custom channel types to an external process. In this case, that external process is `tapd`.
8+
9+
LND manages the BTC layer of the channel (funding, commitments, HTLCs) as it normally would. However, for channels that are designated as "custom," LND will:
10+
1. Store opaque data blobs associated with the channel, commitments, and HTLCs.
11+
2. Call out to the external process (`tapd`) at critical stages of the channel lifecycle (funding, state updates, closing, etc.) via a set of gRPC-based hooks.
12+
13+
`tapd` is responsible for managing the asset layer, providing LND with the necessary information to correctly construct and sign transactions that also commit to the state of the assets in the channel.
14+
15+
## On-Chain Footprint
16+
17+
A Taproot Asset Channel modifies the standard Lightning channel structure by adding an additional leaf to the tapscript tree of the funding output.
18+
19+
A standard Taproot channel's funding output is a 2-of-2 multisig key, represented as a Taproot output. The script path for this output might contain scripts for revocation and CSV-delayed spends.
20+
21+
For a Taproot Asset Channel, an additional leaf is added to this tree: the **Taproot Asset Commitment**.
22+
23+
![Taproot Asset Channel On-Chain Structure](taproot-asset-channels/taproot-chans-to-local.png)
24+
25+
This new leaf (`hC` in the diagram) contains a commitment to the assets held within the channel, effectively anchoring the asset state to the on-chain funding transaction.
26+
27+
## Off-Chain Data: The "Blob" Architecture
28+
29+
To manage the state of assets off-chain, `tapd` provides LND with opaque data blobs that are stored at different levels of the channel structure. LND does not interpret these blobs; it simply stores them and provides them back to `tapd` when invoking the hooks.
30+
31+
![taproot asset channel blobs](taproot-asset-channels/taproot-asset-channels.png)
32+
33+
There are three main types of blobs:
34+
35+
### 1. `OpenChannel` Blob (Channel Level)
36+
37+
This blob is associated with the channel as a whole and is created during the funding process. It contains the initial state of the assets in the channel.
38+
39+
```go
40+
// OpenChannel is a record that represents the capacity information related to
41+
// a commitment. This entails all the (asset_id, amount, proof) tuples and other
42+
// information that we may need to be able to sign the TAP portion of the
43+
// commitment transaction.
44+
type OpenChannel struct {
45+
// FundedAssets is a list of asset outputs that was committed to the
46+
// funding output of a commitment.
47+
FundedAssets tlv.RecordT[tlv.TlvType0, AssetOutputListRecord]
48+
49+
// DecimalDisplay is the asset's unit precision.
50+
DecimalDisplay tlv.RecordT[tlv.TlvType1, uint8]
51+
52+
// GroupKey is the optional group key used to fund this channel.
53+
GroupKey tlv.OptionalRecordT[tlv.TlvType2, *btcec.PublicKey]
54+
}
55+
```
56+
57+
### 2. `Commitment` Blob (Commitment Level)
58+
59+
For each commitment transaction in the channel, a `Commitment` blob is stored. This blob represents the state of the assets for that specific commitment.
60+
61+
```go
62+
// Commitment is a record that represents the current state of a commitment.
63+
// This entails all the (asset_id, amount, proof) tuples and other information
64+
// that we may need to be able to sign the TAP portion of the commitment
65+
// transaction.
66+
type Commitment struct {
67+
// LocalAssets is a list of all asset outputs that represent the current
68+
// local asset balance of the commitment.
69+
LocalAssets tlv.RecordT[tlv.TlvType0, AssetOutputListRecord]
70+
71+
// RemoteAssets is a list of all asset outputs that represents the
72+
// current remote asset balance of the commitment.
73+
RemoteAssets tlv.RecordT[tlv.TlvType1, AssetOutputListRecord]
74+
75+
// OutgoingHtlcAssets is a list of all outgoing in-flight HTLCs and the
76+
// asset balance change that they represent.
77+
OutgoingHtlcAssets tlv.RecordT[tlv.TlvType2, HtlcAssetOutput]
78+
79+
// IncomingHtlcAssets is a list of all incoming in-flight HTLCs and the
80+
// asset balance change that they represent.
81+
IncomingHtlcAssets tlv.RecordT[tlv.TlvType3, HtlcAssetOutput]
82+
83+
// AuxLeaves are the auxiliary leaves that correspond to the commitment.
84+
AuxLeaves tlv.RecordT[tlv.TlvType4, AuxLeaves]
85+
}
86+
```
87+
88+
### 3. `Htlc` Blob (HTLC Level)
89+
90+
Each HTLC within a commitment can have its own blob. For Taproot Asset Channels, this is used to carry the asset information for that specific HTLC.
91+
92+
```go
93+
// Htlc is a record that represents the capacity change related to an in-flight
94+
// HTLC. This entails all the (asset_id, amount) tuples and other information
95+
// that we may need to be able to update the TAP portion of a commitment
96+
// balance.
97+
type Htlc struct {
98+
// Amounts is a list of asset balances that are changed by the HTLC.
99+
Amounts tlv.RecordT[HtlcAmountRecordType, AssetBalanceListRecord]
100+
101+
// RfqID is the RFQ ID that corresponds to the HTLC.
102+
RfqID tlv.OptionalRecordT[HtlcRfqIDType, ID]
103+
}
104+
```
105+
106+
## LND Hooks for Custom Logic
107+
108+
`tapd` implements a series of interfaces defined by LND to inject asset-specific logic into the channel management process. These hooks are collectively managed through a set of "Auxiliary" components.
109+
110+
Here is a summary of the key hooks and their functions:
111+
112+
- **`AuxFundingController`**: Manages the asset-specific parts of the channel funding process. It handles custom messages exchanged between peers to agree on the assets being committed to the channel.
113+
- **`AuxLeafCreator`**: Responsible for creating the auxiliary tapscript leaves that commit to the asset state. When LND builds a commitment transaction, it calls this hook to get the asset commitment leaf.
114+
- **`AuxLeafSigner`**: Signs the asset-related parts of the commitment transaction. For HTLCs, this involves signing second-level transactions that are part of the HTLC scripts.
115+
- **`AuxTrafficShaper`**: Controls how payments are routed. For asset channels, it ensures that an HTLC carrying assets is only forwarded over channels that also contain assets. It also calculates the available asset bandwidth.
116+
- **`InvoiceHtlcModifier`**: Intercepts incoming HTLCs for an invoice. For asset invoices, it verifies the incoming asset amount and modifies the BTC amount of the HTLC to reflect the agreed-upon exchange rate.
117+
- **`AuxChanCloser`**: Handles the cooperative closure of a channel, ensuring that the assets are correctly distributed in the closing transaction.
118+
- **`AuxSweeper` / `AuxContractResolver`**: Manages the process of sweeping on-chain funds from a force-closed channel. This includes handling the outputs from commitment transactions and second-level HTLC transactions to recover assets.
119+
120+
Anything `lnd` needs from an aux component it will get through a call through
121+
the code hooks. Anything `tapd` needs from `lnd`, it gets normally through a
122+
gRPC call:
123+
124+
![litd hooks](./taproot-asset-channels/litd-hooks.png)
125+
126+
## Channel Funding Sequence
127+
128+
The funding of a Taproot Asset Channel involves a custom message flow between the two `tapd` instances, orchestrated by LND.
129+
130+
![Channel Funding Sequence](taproot-asset-channels/tap-channel-funding.png)
131+
132+
1. **Initiation**: The process starts with a `fundchannel` command.
133+
2. **Proof Exchange**: The initiator's `tapd` sends `SendCustomMessage` calls to its LND node, containing proofs of the assets to be funded. LND forwards these as `CustomMessage`s to the peer.
134+
3. **Funding Ack**: The responder's `tapd` verifies the proofs and sends back a funding acknowledgment, again via custom messages.
135+
4. **LND Funding Flow**: Once the asset states are agreed upon, the standard LND channel funding flow (`OpenChannel`, `AcceptChannel`, etc.) proceeds. `tapd`'s `AuxFundingController` provides LND with the necessary asset commitment information.
136+
5. **Asset Funding Created**: After the BTC funding transaction is created, the initiator's `tapd` sends a final custom message with the proofs of the assets in their final state within the channel.
137+
6. **Finalization**: The channel is finalized with `FundingCreated` and `FundingSigned` messages, and the funding transaction is broadcast.
138+
139+
## Asset Payment Flow
140+
141+
Sending an asset payment across the network also involves the LND hooks. The following diagram illustrates a multi-hop payment from Alice to Dave, through Bob and Charlie.
142+
143+
![Asset Payment Flow](taproot-asset-channels/tap-channel-payment.png)
144+
145+
Here's a step-by-step breakdown:
146+
147+
1. **Alice (Payer)**:
148+
* Alice wants to pay an invoice from Dave. The invoice is for a certain amount of "beefbuxx".
149+
* Her `tapd`'s **`TrafficShaper`** is invoked. It determines the outgoing channel, generates the HTLC blob containing the asset information (`beefbuxx: 10`), and overwrites the BTC amount of the HTLC to a minimal on-chain value (e.g., 550 sat).
150+
151+
2. **Bob (Hop)**:
152+
* Bob's LND node receives the HTLC. Since it has custom records, it invokes the hooks.
153+
* The **`HtlcInterceptor`** (a general LND concept, implemented by `tapd` for assets) is triggered.
154+
* Bob's `tapd` inspects the incoming HTLC blob. In this example, it checks an RFQ ID and amount, and then overwrites the outgoing HTLC's BTC amount to a new value (e.g., 50001 sat) for the next hop. The asset blob is forwarded.
155+
156+
3. **Charlie (Hop)**:
157+
* Charlie's LND node also invokes the **`HtlcInterceptor`**.
158+
* His `tapd` checks the incoming short channel ID (SCID) and amount, generates a new HTLC blob for the next hop, and again overwrites the BTC amount.
159+
160+
4. **Dave (Payee)**:
161+
* Dave's LND node receives the final HTLC.
162+
* The **`InvoiceHtlcModifier`** hook is called.
163+
* Dave's `tapd` inspects the HTLC blob, verifies that it contains the correct asset and amount (`beefbuxx: 10`), and then modifies the BTC amount of the HTLC to match the full value of the invoice (e.g., 50k sats). This allows the invoice to be settled in LND's accounting system.
164+
165+
This flow allows assets to be tunneled through the Lightning Network, with `tapd` instances at each hop managing the asset state transitions, while LND handles the underlying BTC-level HTLC mechanics.
254 KB
Loading
203 KB
Loading

0 commit comments

Comments
 (0)