Problem
VerifyProof returns a DecodedProof whose asset.prev_witnesses (and therefore the predecessor PrevInputAsset entries — anchor_point, asset_id, script_key) are always empty, whereas DecodeProof can populate them via with_prev_witnesses. For a lightweight/custom-anchor integration that verifies a proof file it received (e.g. SwapDK receiving assets over a custom anchor tx), VerifyProof is the natural single call: it both validates the proof chain and hands back a decoded summary. But that summary cannot tell the caller which asset(s) were spent to produce the proven asset — the predecessor inventory (input anchor outpoints + asset IDs + script keys). To obtain that, a caller must make a second, redundant DecodeProof call with with_prev_witnesses=true, re-decoding and re-parsing the same proof file. The asymmetry is undocumented, so integrations can reasonably assume VerifyProof's DecodedProof is complete and silently get zero predecessors.
Note this is distinct from num_additional_inputs, which VerifyProof does populate (it counts nested full input proofs) — that field gives a count but never the identifying PrevInputAsset fields needed to reconstruct the input set.
Current behaviour
VerifyProof decodes the file, verifies it, takes the last proof, and marshals it with prev witnesses (and meta reveal) hard-disabled:
rpcserver/rpcserver.go:2184 — decodedProof, err := r.marshalProof(ctx, p, false, false) (the two falses are withPrevWitnesses and withMetaReveal).
- The response is assembled at
rpcserver/rpcserver.go:2192; there is no field on the request (taprpc.ProofFile, taprpc/taprootassets.proto:1439) to opt into witnesses.
DecodeProof, by contrast, threads the caller's flag through:
rpcserver/rpcserver.go:2213 and rpcserver/rpcserver.go:2248 — r.marshalProof(ctx, p, req.WithPrevWitnesses, req.WithMetaReveal).
- Request flag defined at
taprpc/taprootassets.proto:1534 (bool with_prev_witnesses = 3).
- The predecessor IDs live behind the
withWitness gate in the shared asset marshaler:
rpcutils/marshal.go:741-775 — only when withWitness is true does it iterate a.PrevWitnesses, build a taprpc.PrevInputAsset (AnchorPoint, AssetId, ScriptKey at rpcutils/marshal.go:746-750), and append to rpcAsset.PrevWitnesses. With withWitness=false, PrevWitnesses is left nil.
marshalProof itself always sets num_additional_inputs regardless of the witness flag:
rpcserver/rpcserver.go:2437 — NumAdditionalInputs: uint32(len(p.AdditionalInputs)).
- Proto shapes for reference:
DecodedProof (taprpc/taprootassets.proto:1449), VerifyProofResponse (taprpc/taprootassets.proto:1513), PrevWitness (taprpc/taprootassets.proto:661), PrevInputAsset (taprpc/taprootassets.proto:1692).
Net effect: VerifyProofResponse.decoded_proof.asset.prev_witnesses is always empty; the only way to see predecessor IDs is a separate DecodeProof with with_prev_witnesses=true.
Proposed change
Pick one (in rough order of preference):
- Add a
with_prev_witnesses (and optionally with_meta_reveal) field to the VerifyProof request and thread it into the marshalProof call at rpcserver/rpcserver.go:2184, matching DecodeProof. This lets a verify-and-inspect integration get predecessor IDs in one round trip. Because VerifyProof's request is taprpc.ProofFile (shared with other call sites via taprpc/taprootassets.proto:1439), a cleaner approach is to introduce a dedicated VerifyProofRequest message carrying raw_proof_file, genesis_point, and the new option(s), leaving ProofFile untouched.
- If the omission is intentional (e.g. to keep
VerifyProof cheap and avoid extra key-ring lookups), document it explicitly: add a comment on VerifyProofResponse.decoded_proof / the prev_witnesses field noting that VerifyProof never populates prev witnesses and that callers needing predecessor IDs must use DecodeProof with with_prev_witnesses=true. Update the marshalProof call site at rpcserver/rpcserver.go:2184 with a one-line rationale.
Either way, the asymmetry between the two handlers should stop being silent.
Context
Surfaced while building the custom-anchor transaction builder in lightninglabs/tap-sdk#158: the SDK verifies inbound proof files and wants the predecessor inventory (input anchor outpoints + asset IDs) from the same call. Because VerifyProof's DecodedProof omits prev_witnesses, the SDK cannot infer predecessors from a verify summary and fails closed — it does not fabricate predecessor data from a VerifyProof result, and instead must fall back to a separate DecodeProof (with_prev_witnesses=true) call to recover them. Exposing the option on VerifyProof (or documenting the omission) removes the redundant round trip and the ambiguity.
Problem
VerifyProofreturns aDecodedProofwhoseasset.prev_witnesses(and therefore the predecessorPrevInputAssetentries —anchor_point,asset_id,script_key) are always empty, whereasDecodeProofcan populate them viawith_prev_witnesses. For a lightweight/custom-anchor integration that verifies a proof file it received (e.g. SwapDK receiving assets over a custom anchor tx),VerifyProofis the natural single call: it both validates the proof chain and hands back a decoded summary. But that summary cannot tell the caller which asset(s) were spent to produce the proven asset — the predecessor inventory (input anchor outpoints + asset IDs + script keys). To obtain that, a caller must make a second, redundantDecodeProofcall withwith_prev_witnesses=true, re-decoding and re-parsing the same proof file. The asymmetry is undocumented, so integrations can reasonably assumeVerifyProof'sDecodedProofis complete and silently get zero predecessors.Note this is distinct from
num_additional_inputs, whichVerifyProofdoes populate (it counts nested full input proofs) — that field gives a count but never the identifyingPrevInputAssetfields needed to reconstruct the input set.Current behaviour
VerifyProofdecodes the file, verifies it, takes the last proof, and marshals it with prev witnesses (and meta reveal) hard-disabled:rpcserver/rpcserver.go:2184—decodedProof, err := r.marshalProof(ctx, p, false, false)(the twofalses arewithPrevWitnessesandwithMetaReveal).rpcserver/rpcserver.go:2192; there is no field on the request (taprpc.ProofFile,taprpc/taprootassets.proto:1439) to opt into witnesses.DecodeProof, by contrast, threads the caller's flag through:rpcserver/rpcserver.go:2213andrpcserver/rpcserver.go:2248—r.marshalProof(ctx, p, req.WithPrevWitnesses, req.WithMetaReveal).taprpc/taprootassets.proto:1534(bool with_prev_witnesses = 3).withWitnessgate in the shared asset marshaler:rpcutils/marshal.go:741-775— only whenwithWitnessis true does it iteratea.PrevWitnesses, build ataprpc.PrevInputAsset(AnchorPoint,AssetId,ScriptKeyatrpcutils/marshal.go:746-750), and append torpcAsset.PrevWitnesses. WithwithWitness=false,PrevWitnessesis left nil.marshalProofitself always setsnum_additional_inputsregardless of the witness flag:rpcserver/rpcserver.go:2437—NumAdditionalInputs: uint32(len(p.AdditionalInputs)).DecodedProof(taprpc/taprootassets.proto:1449),VerifyProofResponse(taprpc/taprootassets.proto:1513),PrevWitness(taprpc/taprootassets.proto:661),PrevInputAsset(taprpc/taprootassets.proto:1692).Net effect:
VerifyProofResponse.decoded_proof.asset.prev_witnessesis always empty; the only way to see predecessor IDs is a separateDecodeProofwithwith_prev_witnesses=true.Proposed change
Pick one (in rough order of preference):
with_prev_witnesses(and optionallywith_meta_reveal) field to theVerifyProofrequest and thread it into themarshalProofcall atrpcserver/rpcserver.go:2184, matchingDecodeProof. This lets a verify-and-inspect integration get predecessor IDs in one round trip. BecauseVerifyProof's request istaprpc.ProofFile(shared with other call sites viataprpc/taprootassets.proto:1439), a cleaner approach is to introduce a dedicatedVerifyProofRequestmessage carryingraw_proof_file,genesis_point, and the new option(s), leavingProofFileuntouched.VerifyProofcheap and avoid extra key-ring lookups), document it explicitly: add a comment onVerifyProofResponse.decoded_proof/ theprev_witnessesfield noting thatVerifyProofnever populates prev witnesses and that callers needing predecessor IDs must useDecodeProofwithwith_prev_witnesses=true. Update themarshalProofcall site atrpcserver/rpcserver.go:2184with a one-line rationale.Either way, the asymmetry between the two handlers should stop being silent.
Context
Surfaced while building the custom-anchor transaction builder in lightninglabs/tap-sdk#158: the SDK verifies inbound proof files and wants the predecessor inventory (input anchor outpoints + asset IDs) from the same call. Because
VerifyProof'sDecodedProofomitsprev_witnesses, the SDK cannot infer predecessors from a verify summary and fails closed — it does not fabricate predecessor data from aVerifyProofresult, and instead must fall back to a separateDecodeProof(with_prev_witnesses=true) call to recover them. Exposing the option onVerifyProof(or documenting the omission) removes the redundant round trip and the ambiguity.