Add /tx/:txid/witness-merkle-proof (+ blockchain.transaction.get_witness_merkle)#235
Open
ordspv wants to merge 1 commit into
Open
Add /tx/:txid/witness-merkle-proof (+ blockchain.transaction.get_witness_merkle)#235ordspv wants to merge 1 commit into
ordspv wants to merge 1 commit into
Conversation
New: GET /tx/:txid/witness-merkle-proof and
blockchain.transaction.get_witness_merkle(txid, height). Both return a
merkle inclusion proof over the block's BIP-141 witness tree (leaf i =
wtxid(tx_i), leaf 0 zeroed for the coinbase) as { block_height, merkle[],
pos, witness_root }.
Why: a txid merkle proof binds only the stripped serialization. Witness
data is unbound, so protocols whose payload lives in the witness (ordinals
envelopes are the largest) cannot get SPV assurance from the existing
endpoint: a lying server can swap the witness and the txid proof still
verifies. The consensus fix (prove against the witness tree, check the
coinbase witness commitment) needs witness-tree branches, which no public
API serves today, so light clients download whole raw blocks instead. One
response of about 1 KB replaces a 1-2 MB block download.
Implementation: ChainQuery::get_block_wtxids (block txids -> lookup_txns
batch -> compute_wtxid, coinbase zeroed; same DB pattern as the raw-block
reconstruction path) and electrum_merkle::get_tx_witness_merkle_proof,
reusing the existing branch fold (now pub for the bench). Handlers mirror
their txid-proof twins. Everything is cfg(not(feature = "liquid")):
witness commitments are bitcoin-specific here. No new index rows, no
migration.
Tests: the REST integration test folds the wallet tx's wtxid and the
zeroed coinbase leaf to witness_root and verifies sha256d(root ||
reserved) against the coinbase 6a24aa21a9ed commitment, the full BIP-141
loop. The Electrum raw-socket test mirrors get_merkle semantics including
the invalid-height error. Criterion benches on mainnet block 702861
(~1000 txs): ~5.25 ms wtxid computation plus ~1.53 ms branch construction
per uncached proof.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A witness-tree twin of the existing
/tx/:txid/merkle-proof:and the Electrum-protocol equivalent
blockchain.transaction.get_witness_merkle(txid, height)mirroringblockchain.transaction.get_merkle.The proof is over the block's witness tree: leaf
iiswtxid(tx_i), except leaf 0 (coinbase), which is the zero hash per BIP-141. That tree's root is what the coinbase witness commitment (sha256d(root ‖ reserved)behind6a24aa21a9ed) commits to.Why
A txid merkle proof binds a transaction's stripped serialization to a header. It does not bind witness data: two different witnesses for the same inputs produce the same txid. Any protocol whose payload LIVES in the witness (ordinals inscription envelopes being the largest today) therefore can't get SPV-grade assurance from
/tx/:txid/merkle-proofalone: a lying server can swap the witness and the txid proof still verifies.The fix has always been available in consensus data (prove the tx against the witness tree, prove the coinbase against the txid tree, check the coinbase's witness commitment), but no public API serves witness-tree branches, so light clients today must download the entire raw block (~1–2 MB) to verify ~1 KB of content. This endpoint replaces that with one ~1 KB response. Esplora already serves every other ingredient (
merkle-proof,/block/:hash/header,/tx/:txid/hex, coinbase txid via/block/:hash/txid/0).Concretely, the consumer flow (implemented in a public verifier library that motivated this PR, github.com/ordspv/ordspv; a full L3 verification is header PoW, a txid proof of the coinbase, the witness commitment check, and this endpoint's branch):
GET /tx/:txid/witness-merkle-proof→ branch, pos, witness_rootwtxid(tx)(or the zero leaf for the coinbase) up the branch → must equalwitness_rootsha256d(witness_root ‖ coinbase.witness[0])must equal the coinbase's6a24aa21a9edcommitment; coinbase bound by an ordinary txid proof at pos 0Implementation
ChainQuery::get_block_wtxids(hash): block txids →lookup_txnsbatch → per-txcompute_wtxid(), coinbase leaf zeroed. Same DB access pattern and cost as the existingGET /block/:hash/rawreconstruction path.electrum_merkle::get_tx_witness_merkle_proofreuses the existingcreate_merkle_branch_and_rootfold (madepubfor the bench).#[cfg(not(feature = "liquid"))](witness commitments are bitcoin-specific here; elements has its own commitment structure).Tests
test_rest_tx_witness_merkle_proof(regtest, corepc-node): folds the wallet tx's wtxid through the returned branch towitness_root, folds the ZEROED coinbase leaf to the same root, and checkssha256d(root ‖ reserved) == coinbase 6a24aa21a9ed commitment. That is the full BIP-141 loop rather than shape assertions alone; plus 404 for unknown txids.test_electrum_get_witness_merkle(raw socket): result fields + invalid height → invalid-params error, mirroringget_merklebehavior.Benchmarks (criterion,
benches/benches.rs, real mainnet block 702861, ~1 000 txs)CPU cost ≈ 7 ms per uncached proof on a ~1 000-tx block (Apple M-series; DB lookups equal the existing raw-block path). Responses are immutable and CDN-cacheable; a per-block wtxid cache would amortize the first term across transactions of the same block if needed.
API.md addition (Blockstream/esplora)