taproot-assets
taproot-assets copied to clipboard
[bug]: Malformed STXO proof generated on asset spend
@Liongrass reported this bug whilst testing v0.7.0-rc1:
- Attempted to send a grouped asset to a V2 group key tap address.
- Asset had been minted a little while back.
- Transfer failed on the sending node; receiving node did not receive anything.
- Logs from attempt:
2025-09-17 19:54:44.784 [INF] FRTR: Received to send request to 1 addrs: [2000000:TapAddr{specifier=AssetSpecifier(id=0000000000000000000000000000000000000000000000000000000000000000, group_pub_key=02875ce409b587a6656357639d099ad9eb08396d0dfea8930a45e742c81d6fc782), amount=2000000, script_key=034b6011daff352ec127a8734793efcff6bc50da7e68150a411f478a4019080f08}]
2025-09-17 19:54:44.785 [INF] FRTR: ChainPorter executing state: SendStateStartHandleAddrParcel
2025-09-17 19:54:44.825 [INF] FRTR: ChainPorter executing state: SendStateVirtualCommitmentSelect
2025-09-17 19:54:44.878 [INF] FRTR: Identified 4 eligible asset inputs for send of 2000000 to group_key=02875ce409b587a6656357639d099ad9eb08396d0dfea8930a45e742c81d6fc782, asset_id=, min_amt=2000000: [a13327d4b73be2057fae7cd999b7599a8d418df439b9a44f3adeac67b72ac307:0 a13327d4b73be2057fae7cd999b7599a8d418df439b9a44f3adeac67b72ac307:0 a13327d4b73be2057fae7cd999b7599a8d418df439b9a44f3adeac67b72ac307:0 a13327d4b73be2057fae7cd999b7599a8d418df439b9a44f3adeac67b72ac307:0]
2025-09-17 19:54:44.880 [INF] FRTR: Selected 1 asset inputs for send of 2000000 to AssetSpecifier(id=, group_pub_key=02875ce409b587a6656357639d099ad9eb08396d0dfea8930a45e742c81d6fc782)
2025-09-17 19:54:44.922 [INF] FRTR: ChainPorter executing state: SendStateVirtualSign
2025-09-17 19:54:44.922 [INF] FRTR: Generating Taproot Asset witnesses for send to: 024eb56b120f6d6b9fd50af497311589107dd96e0066230f77f427805c249a9334
2025-09-17 19:54:44.933 [ERR] FRTR: Error evaluating state (SendStateVirtualSign): unable to sign and commit virtual packet: unable to verify inclusion proof: error verifying proofs: invalid inclusion proof: error verifying v1 inclusion proof: error verifying STXO proof: missing asset proof
2025-09-17 19:54:44.933 [ERR] TRPC: [/taprpc.TaprootAssets/SendAsset]: unable to sign and commit virtual packet: unable to verify inclusion proof: error verifying proofs: invalid inclusion proof: error verifying v1 inclusion proof: error verifying STXO proof: missing asset proof
- Logs indicate that
AssetProoffield is not populated on STXO proof. - Execution likely took the following code path:
https://github.com/lightninglabs/taproot-assets/blob/b21d5d2ee9d4b3ae4e5e2cb5a9a9157560bac476/proof/append.go#L209-L229
- And then return here from the
params.TaprootAssetRoot.Proofcall: https://github.com/lightninglabs/taproot-assets/blob/b21d5d2ee9d4b3ae4e5e2cb5a9a9157560bac476/commitment/tap.go#L491-L496
While digging into this bug, I noticed that the following erroneous change seems quite fundamental, yet surprisingly doesn't cause any tests to fail: https://github.com/lightninglabs/taproot-assets/pull/1810
See also:
2025-09-18 21:12:19.471 [WRN] UNIV: encountered an error whilst syncing with server=(universe.ServerAddr) {
ID: (int64) 2,
addrStr: (string) (len=31) "universe.signet.laisee.org:8443",
addr: (net.Addr) <nil>
}
: failed to execute sync: unable to register proofs: unable to register proofs: unable to batch verify issuance proofs: unable to verify proof (transfer-10907cc4e4525c565c5605c41031c09968e46b9e9f4eb0cf25b24d2d7a94e130 (asset_id=0000000000000000000000000000000000000000000000000000000000000000, group_key=875ce409b587a6656357639d099ad9eb08396d0dfea8930a45e742c81d6fc782, proof_type=transfer), outpoint=4cc78d1220c5547cf8321120471e3c5022a9d06c0df1bcfa1559a9a8e023d79b:0, scriptKey=02a207ba37828ba9f9aaacddcd2da3cdf30c1d6e2c2c74cdc620f4198088691334): error verifying proofs: invalid inclusion proof: error verifying v1 inclusion proof: error verifying STXO proof: missing asset proof
~~Looking at that last log message, it looks like the minting transaction itself is what is missing the STXO proof.~~
~~We write the final proofs in storeMintingProof but at that point, all the non-chain information has already been committed.~~
EDIT: I thought it was an issuance proof rejected, but it's actually a transfer proof.
So perhaps somewhere along the way, minting proofs are missing an STXO commitment? That doesn't explain why we're only seeing this issue in an isolated instance though.
~~Thinking about it more: IIUC, the send is failing as it declare that the input proof is invalid. Above we see where the proof was rejected as it was missing STXO information. These appear to be connected, in that if the issuance proof is invalid, then when we go to spend the input, we'd reject it as the proof itself is invalid.~~
~~However, we don't add STXO proofs for a newly minted asset. Right now we also don't piggy back normal transfers on minting transactions either.~~
EDIT: It's actually a transfer proof being rejected, not an issuance proof.
Are we able to extract the raw proof that's being rejected in the comment above?
Another hunch: were script keys re-used across the inputs created for that transaction?
If so, then we may be missing/omitting a proof, see this area: https://github.com/lightninglabs/taproot-assets/blob/21f385d965852a8c447a883fc3fa6d3e046ca329/proof/append.go#L221-L234
EDIT: No that's fine, we generate unique script keys via DeriveBurnKey.
~Looking at that last log message, it looks like the minting transaction itself is what is missing the STXO proof.~
~We write the final proofs in
storeMintingProofbut at that point, all the non-chain information has already been committed.~EDIT: I thought it was an issuance proof rejected, but it's actually a transfer proof.
So perhaps somewhere along the way, minting proofs are missing an STXO commitment? That doesn't explain why we're only seeing this issue in an isolated instance though.
Yes, the bug is encountered when validating an STXO inclusion proof that is generated as part of an asset transfer. The bug stops the asset from being sent at all. The tx is never broadcasted and no proof transfers occur. Bug happens in chain porter state SendStateVirtualSign.
Update:
- I can reproduce the issue locally on every run.
- A spend input (vpacket) STXO proof is incomplete:
AssetProof == nil. - Because this is a spend input, the proof isn’t generated during the spend process but fetched from the database, where it’s already stored in an incomplete state.
- Inclusion proof verification fails in the
SendStateVirtualSignchain porter state.
I haven’t been able to reproduce how the broken STXO proof was originally created, but based on the code I can see how it could have happened:
Looking at params.TaprootAssetRoot.Proof https://github.com/lightninglabs/taproot-assets/blob/614acdcb1c51b85dc926d44e21139d4f7d176adb/commitment/tap.go#L491-L496 it’s possible to construct an STXO proof with the AssetProof field unset, which matches the bug we’ve observed. This is likely the cause.
At the inclusion proof call site where params.TaprootAssetRoot.Proof is invoked https://github.com/lightninglabs/taproot-assets/blob/614acdcb1c51b85dc926d44e21139d4f7d176adb/proof/append.go#L221-L233 the logic should be more defensive and return an error immediately instead of deferring failure to a later verification step. Here's the PR for that change: https://github.com/lightninglabs/taproot-assets/pull/1836
Here's the input signet proof that fails to verify: issue1809-verify-fail-input-proof.zip
We can probably remove the v0.7 milestone from this one, given #1836 has been merged.
Here's the input signet proof that fails to verify: issue1809-verify-fail-input-proof.zip
I've reproduced the validation error with the proof binary and tapd v0.7.0-rc1.
From stepping through the validation logic:
- The
InclusionProofinsideproof.Proofis invalid. Focus on that. - The proof is V0. Since it is not V1 it does not need STXO proofs, but they are present so they are validated.
- The V0 inclusion commitment validates correctly. Execution proceeds to STXO validation.
- One STXO alt-leaf asset is detected, which is expected. Execution continues into
verifySTXOProofSet. - A combined proof is built from the alt-leaf asset and
Proof.InclusionProof.CommitmentProof.STXOProofs. - The alt-leaf entry exists in the
STXOProofsmap, but itscommitment.Proof.AssetProoffield isnil. The validator expects this to be non-nil, so validation fails at this point.
The next step IMO is to inspect this section of code:
https://github.com/lightninglabs/taproot-assets/blob/632294a2b9c7e4834ac701ed57b2732b673e7328/commitment/tap.go#L491-L496
Specifically, check whether ok evaluates to false at that point.