multi: Taproot Assets Ark integration (PoC) - #1062
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1aa2174bbb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // The composed output must at least be a taproot output; its key is | ||
| // re-derived and proven at spend time. | ||
| if !txscript.IsPayToTaproot(vtxoOutput.PkScript) { | ||
| return nil, fmt.Errorf("asset VTXO output is not P2TR") |
There was a problem hiding this comment.
Bind the asset leaf script before signing the round
When an operator returns an asset-aware round, this validation accepts any P2TR output and compares the requested asset identity and amount only with the operator-supplied AssetContext. The client can then sign the tree and release its boarding input signature; the exact composed-script check in buildClientVTXOs happens only after BoardingConfirmed, when the commitment is already on chain. A compromised or misconfigured operator could therefore place the purported asset leaf under a key the client cannot spend and irreversibly consume the boarding input. Validate the sealed leaf package/commitment root and require the resulting composed script to equal the leaf output before signing.
Useful? React with 👍 / 👎.
| @@ -47,6 +47,10 @@ const ( | |||
| // lnd instance. | |||
| DefaultLndHost = "localhost:10009" | |||
|
|
|||
| // DefaultTapdHost is the default address of the local tapd instance | |||
| // used by the optional Taproot Asset OOR PoC. | |||
| DefaultTapdHost = "localhost:10029" | |||
There was a problem hiding this comment.
Choose a tapd default distinct from waved's RPC port
With taprootassets.enabled=true and no host override, the tap-sdk client is configured for localhost:10029, which is also DefaultRPCHost for waved in this file. Consequently the adapter connects back to waved's own gRPC server rather than tapd, and every tapd RPC fails as an unimplemented service. Use a non-conflicting tapd default or require an explicit host when enabling the integration.
Useful? React with 👍 / 👎.
1aa2174 to
0dfe1af
Compare
Add a versioned sealed-package container and bind optional Taproot Asset roots to OOR input and recipient policies. Persist input and start-message roots so durable retries can reconstruct the same composed scripts.
Extend submit and offline-receive messages with optional asset roots and the versioned sealed-package container. Keep Bitcoin-only payloads backward compatible and validate asset bytes at the typed request boundary.
Accept externally committed OOR graphs, validate them before signing, and retain sealed Taproot Asset packages through durable actor messages, FSM states, snapshots, and byte-identical submit retries.
Store the optional Taproot Asset commitment root with each VTXO and derive composed policy scripts and spend control blocks for OOR, forfeit, and unilateral exit paths. Exclude asset-bearing rows from generic Bitcoin coin selection until rounds carry asset transitions.
Carry recipient commitment roots and sealed asset transition packages through offline receive, durable actor snapshots, VTXO materialization, and OOR artifact persistence. Preserve Bitcoin-only package behavior while requiring asset-aware stores for asset sessions.
Add a proof-selected public intent and a restart-safe preparer seam so Wavelength can commit an asset graph before its durable OOR actor signs it. Keep Bitcoin-only sends unchanged and reject unsupported change, missing adapters, and tampered results before actor submission.
Adapt the merged tap-sdk custom-anchor boundary to the Wavelength OOR graph. Verify the selected tapd inventory, commit both transitions, and persist outcome-aware replay state before the actor can sign.
Keep tapd credentials in the client daemon and install the adapter only after explicit configuration. Preserve embedded config isolation and document the PoC recovery and custody boundaries.
Document why ordinary round boarding cannot create the first asset-bearing VTXO and record the fully rebased client validation results.
tapd chooses which of a multi-source transition's inputs the proof continues, so assuming the first source left the confirmed base and the recorded step describing a different input than the one tapd named.
A round checkpoints each boarding intent against the wallet's own records, and the wallet never derived this composed address, so the checkpoint failed on a foreign key the moment the client had signed.
It existed only for the operator-derived asset tree fallback, which is gone: every asset leaf belongs to the client that requested it and is cosigned through the round's own ceremony.
Direct registration predates asset rounds: it admitted a confirmed tapd anchor straight into the operator's VTXO set because there was no tree to board it into. There is one now, so onboarding exists only to create a composed boarding output for a round to consume. That removes the RegisterTaprootAssetVTXO RPC and its whole client side — the registrar, the operator-admission state machine, and the pending/ready reporting the CLI polled on. The asset output is now always OP_TRUE-keyed, since a round's operator is what spends it.
The poll interval, the onboard-call alias, an outpoint helper, and a stub error field all existed only for the deleted admission flow.
Coin selection skips a VTXO only when it carries an asset commitment root. A round asset leaf had none — the client validated its own request against the tree and then dropped the ref and amount — so the leaf was selectable as ordinary sats and spending its carrier destroyed the asset it held. The operator's own row was asset-null too, so nothing stopped it on either side. The operator now discloses each leaf's commitment root alongside the per-node material the tree already carried, and the owner rebuilds the composed script from it and requires the leaf output to match. That fixes a second bug in passing: the stored pkScript was the plain policy script, never the composed one the leaf actually pays to.
Spending an asset leaf out of round needs the compact proof path and the OP_TRUE witness behind it, and the owner has no source for either: the operator holds every tree node's sealed package. It now ships each client its own leaves' packages with the round's batch info — scoped to that client, so no other node's package leaves the operator — and the owner persists it beside the leaf. The existing proof-less OOR path resolves a spend from exactly such a package, so this is the material it was missing rather than a new mechanism.
A node merging several boarded asset inputs failed roughly half its commits because the SDK's commit verifier folded alternate-leaf order into a byte comparison. Repin once tap-sdk #175 merges.
A refresh round carries no asset transition, so forfeiting an asset leaf into one destroys the asset commitment while preserving only its carrier sats. The expiry ladder now leaves the coin live; the critical-expiry escalation still broadcasts its tree.
The sweep spends the target to a plain wallet output, which would destroy the asset commitment. The unroll still broadcasts the tree, which is the exit itself: the composed output lands on chain under the owner's exit leaf. Claiming the assets takes an asset-aware spend.
A composed boarding output embeds an asset commitment branch, so the plain timeout sweep would destroy the boarded assets. Detect it by recompiling the plain policy script from the intent's own keys and comparing with the stored address.
Refreshing an asset-bearing VTXO forfeits the leaf and asks the same units back as a fixed-amount asset VTXO request, so the round reissues them through its asset transition. The wallet descriptor and the RPC preview learn the asset identity; the blanket refresh rejection is lifted for explicitly named targets while the catch-all selection still skips them.
The proof-of-funds input for a forfeited VTXO derived the default timeout path, whose witness program is the bare policy output. An asset leaf pays to the policy branched with its asset root, so the proof needs the composed control block, the same way boarding disclosures already build theirs.
The round store's ClientVTXO loader dropped the Taproot Asset columns, so a forfeited asset leaf looked like plain sats to the join-auth builder and its proof spent the wrong witness program.
The unroll puts the composed leaf on chain; once its CSV delay matures, the claim completes the leaf's sealed lineage into a confirmed proof file, spends the anchor through the exit leaf, and commits one transition into a fresh tapd-owned anchor. Script and internal keys both come from tapd, so the units become ordinary, spendable wallet balance when the claim confirms.
An asset leaf's worth is the assets riding on it, so the feasibility verdict priced in carrier sats refused every asset exit as uneconomical.
The wallet script plan derives a fresh asset script key at commit time, so a preview's commitment root never matches the commit and the anchor output script is only knowable afterwards. Ship a placeholder template and adopt the committed transaction's script instead.
Publishing verifies the transition against tapd's own proof archive, so the leaf's confirmed lineage is imported first. The OP_TRUE script key is declared from its own witness material: script and control block carry the internal key and tapscript root. Declaring at claim time is correct, since the anchor is on chain under the owner's exclusive control and the claim spends it in the same flow.
The asset metadata checks pushed the incoming handler's Receive past the function-length limit. The script derivation and descriptor assembly move into a helper; behavior is unchanged.
0dfe1af to
66b879c
Compare
The asset integration tests stop skipping when no tapd override is set, so the default image becomes what CI actually runs. v0.8.1 is the latest release; the tests need the CommitVirtualPsbts transition-proof v1 support and stay red until a release carries it. That red is deliberate: a silent skip hides lost coverage, a failing job names it.
MuSig2CreateSession sorts the signer slice it is handed in place, so concurrent sessions over nodes sharing one backing array race on the sort. The aggregation paths already copy through sortableCopy; the session path now does the same.
Client half of the Taproot Assets Ark integration, consolidated and rebased onto main. Operator counterpart: lumos#731; tracking epic: lumos#772; design: https://taproot-assets-ark.lightning.wiki/.
This branch now carries the whole client side of the proof of concept: the previous stack (#1084, #1091, #1107, #1108, #1128, #1129) was merged into it commit by commit and the result rebased onto current main. The commits stay atomic; the branch will be split into focused, reviewable PRs before anything merges. Treat this PR as the integration point for testing, not as a review unit.
What the branch contains, in stack order:
Rebase notes, for whoever reviews the eventual split: the OOR durable-message TLV types moved to 8 and 10 (main took 7 for the admission deadline), the asset migrations renumbered to 000017 through 000020 behind main's 000016, the
taproot_assetproto field moved to 7 behind main's admission fields, and required-input selection now coexists with main's exact-outpoint selection in the VTXO manager. The test harness now defaults tapd to the released taproot-assets v0.8.1 image so the lumos asset integration tests run unconditionally; they stay red until a tapd release carries the transition-proof v1 support.Verified after the rebase: full unit suite, plus the four asset integration tests (
TestRoundAssetBoarding,TestRoundAssetBatchOutput,TestRoundAssetRefresh,TestRoundAssetExit) and the core Bitcoin boarding and unroll itests against the rebased operator branch.