Transaction and receipt proofs
Three methods prove a transaction, a receipt, or all of a block’s receipts against the block hash. No SP1 proof is involved, so the response is returned immediately, on every chain.
A block’s header commits to every transaction and receipt in the block. Its
transactionsRoot and receiptsRoot are the roots of two Merkle-Patricia
tries that contain each transaction’s and each receipt’s consensus bytes, keyed
by the item’s position in the block. A proof contains the block’s header, as
RLP, and the items from which the response is computed, each with the trie
nodes on its path from the root. A proof of a whole block instead contains
every transaction and receipt, from which the verifier reconstructs both roots.
| Method | Params | Proves | Contains |
|---|---|---|---|
willow_getTransactionProof | A transaction hash, or a block and an index | The eth_getTransactionByHash response | The transaction. For an OP Stack deposit, also its receipt, which contains the deposit’s nonce. |
willow_getReceiptProof | A transaction hash, or a block and an index | The eth_getTransactionReceipt response | The receipt and its transaction, the previous receipt (for gasUsed), and, on the OP Stack chains, the block’s first transaction, its L1 info deposit (for the L1 fee fields). |
willow_getBlockReceiptsProof | A block | The eth_getBlockReceipts response, and every transaction object of the block | Every transaction and receipt of the block. |
A block is a number, a tag, a block hash, or {"blockHash": …} /
{"blockNumber": …} (EIP-1898), as for eth_getBlockReceipts. The params of
the first two methods can therefore be ["0x4560…87c"], ["0x18e8f2a", "0x0"], or
[{"blockHash": "0xdb98…53f"}, "0x0"].
These proofs are metered in units by their gzipped size, like any other response, and are not charged per block. They are available on every plan, including Free, and for every block Willow holds, including history.
Scope of these proofs
Section titled “Scope of these proofs”The verifier computes the standard response from the proven bytes and the
header, in the same way Willow computes the data it serves (see
How Willow verifies data). Each field is either
contained in those bytes or computed from them: for example, a transaction’s
hash and from, the gasPrice of a fee-market transaction, a receipt’s
gasUsed, contractAddress, and effectiveGasPrice, the blob gas fields, the
OP Stack L1 fee and deposit fields, and Nitro’s l1BlockNumber. blockHash,
blockNumber, and blockTimestamp are taken from the proven header. This
covers every field of the response except the following:
- Log indexes of a single receipt. A log’s
logIndexis its position among all the logs of the block, so it depends on every earlier receipt. A receipt proof contains only the previous receipt, so it proveslogIndexfor a block’s first two transactions and omits it from the third onward.willow_getBlockReceiptsProofproves it for every log. gasUsedForL1andtimeboostedon Arbitrum One and Robinhood Chain receipts. No block data determines these fields, so they are served as the provider reported them and are not covered by any proof.
Trust assumptions
Section titled “Trust assumptions”- One block hash. The proof establishes that the items are in the block whose header it contains. That block’s hash is confirmed with a node or light client of the chain selected by the verifying party.
- Keccak-256 and the chain’s signature scheme, as relied on by the chain
itself: the hashes bind the items to the header, and a signed transaction’s
fromis recovered from its signature. - The verifier’s code, which computes the derived fields from the proven
bytes. For Ethereum’s
blobGasPrice, this includes the verifier’s table of blob fee forks, so a verifier that predates a new blob fork reports a discrepancy in that field until it is updated.
The block and the transaction are verified against the original request (the requested hash or position), never against the server’s response, so no other dependency on Willow’s server exists.
Example
Section titled “Example”Request:
{"jsonrpc":"2.0","id":2,"method":"willow_getReceiptProof","params":["0x4560d377ceb62f875b054d83a86caabe34e7f681271f2d204ac692a472cf987c"]}Response (truncated; this transaction is the first in its block, so there is no previous receipt):
{ "id": 2, "jsonrpc": "2.0", "result": { "blockHash": "0xdb98a28f352acb72751ec8012c5a617acfb2d7469aadb9bc4690db6b6dd8d53f", "blockNumber": "0x18e8f2a", "chainId": "0x1", "header": "0xf90286a0834f5b002df089d24ecbce7da6966b10fdaf01d953aaa55a6783f07b…", "receiptProofs": [ { "index": "0x0", "proof": [ "0xf90131a07d5015253d479db7c33d3f289ff5be07268c6991e2422acb15df3a6c…", "0xf851a0890e88aba4e1656ab2e6573c7ce99aa9d48d6b77548fd01b039f7fe085…", "0xf9063d20b9063902f90635018303b309b9010000000000000000000400000000…" ], "value": "0x02f90635018303b309b901000000000000000000040000000000000000000000…" } ], "transactionIndex": "0x0", "transactionProofs": [ { "index": "0x0", "proof": [ "0xf90131a05eb1fdbdc9e0bda5e2ccd6d30af827e48965362aa3b7568b0d343e82…", "0xf851a08ade0b72943ea4d0b80dcfec145b5f991e82ef8ebffe6d6ef6c996a2bc…", "0xf9035820b9035402f90350018302260e808413f1d6838306d871940000000aa2…" ], "value": "0x02f90350018302260e808413f1d6838306d871940000000aa232009084bd71a5…" } ] }}chainId,blockNumber,blockHash: hexadecimal. The verifier recomputes the block hash fromheaderand reads the number from it, and both must match.header: the block header’s RLP encoding. Its keccak256 is the block hash.transactionIndex(transaction and receipt proofs only): the transaction’s position in the block.transactionProofs,receiptProofs(transaction and receipt proofs only): one entry per item.valueis the item’s consensus bytes as stored in the trie: a transaction’s EIP-2718 encoding, and, for a receipt, its type byte (none for a legacy receipt) followed by the RLP of its status, cumulative gas used, logs bloom, and logs (and, on the OP Stack, a deposit’s nonce and receipt version).proofis the list of trie nodes from the root down to the item, root first. Itemiis stored under the keyrlp(i).transactions,receipts(block proofs only): every transaction’s and receipt’s consensus bytes, in block order, instead of item proofs.
In testing, a transaction proof was 2 to 9 KB of JSON and a receipt proof 3 to 94 KB, since it contains the receipt’s logs. A block’s receipts proof was 650 KB to 1.1 MB on Ethereum (229 and 355 transactions), 170 to 820 KB on OP Mainnet and Base, and 5 to 12 KB on the other chains.
A request for a transaction Willow does not hold is refused with -32000, as
with the standard methods. An index beyond the block’s last transaction is
refused with -32602:
{"error":{"code":-32602,"message":"invalid params: block 26119978 has 229 transactions; there is no transaction 4096"},"id":6,"jsonrpc":"2.0"}Verifying proofs describes verification of these proofs in a browser or from the command line.