Skip to content

Verifying proofs

Willow proofs can be verified without any request to Willow beyond fetching the proof. Two verifiers are available: a browser page, and a command-line verifier that runs on a local machine. Both perform the same steps, which are listed below so that an independent verifier can also be written.

A log proof relies on three assumptions:

  • One block hash. It is confirmed with a node of the chain selected by the verifying party, or the browser page verifies it against Ethereum. That verification relies on Ethereum’s sync committee and, on an L2, on the chain’s fault proofs or its sequencer.
  • The soundness of SP1, the proof system with which the proofs are produced.
  • The proving programs, whose verifying keys the verifier pins.

No other dependency on Willow’s server exists: its eth_getLogs response and all data sent with the proof are verified. A transaction or receipt proof relies on one block hash, Keccak-256, the chain’s signature scheme, and the verifier’s code (see Transaction and receipt proofs).

Open api.willow.tech/verify, select Logs, the chain, and a filter, and press Verify. The page supports all seven chains. It downloads the SP1 proofs and verifies each one in the browser, verifies the range proofs and the links between parts, verifies the last block, and compares Willow’s eth_getLogs response with the proven logs.

The page verifies the block independently. On Ethereum, it follows the validators’ signatures from a checkpoint built into the page. On the other six chains, it reads the chain’s claim on Ethereum, which settles once the challenge period ends, and verifies newer blocks by the sequencer’s signature. If Willow cannot provide that data, the page queries public nodes and displays their response in gold, never as verified.

Under Advanced:

  • Willow RPC endpoint. By default it ends in public, which requires no key and verifies blocks Willow has already proven. With an API key in its place, Willow also proves new blocks on request, and those proofs are charged to the key.
  • Ethereum node, through which Ethereum is read instead of public nodes.
  • Another provider for comparison. Its response is also compared with the proven one. It must allow browser requests.
  • The program keys the page pins. Replace them only if Willow announces that a new program is in use.

willow-logs-verify performs the same steps on a local machine, on all seven chains, without relying on the page’s code. It also verifies transaction and receipt proofs. It is available on request from [email protected].

For a log proof, the verifier:

  1. Calls willow_getLogsProof with explicit block numbers.
  2. Fetches each part’s SP1 proof with willow_getProof and verifies it against the pinned program keys.
  3. Verifies that the parts cover the entire range without gaps, and that each part’s parent hash equals the previous part’s last block hash.
  4. For segment parts, determines from the filter itself which index ranges must be proven, then verifies each range proof against the segment’s committed index. The server therefore cannot determine what counts as complete. For block and leaf parts, verifies that each block’s entries hash to the proven root.
  5. Applies the filter to the proven entries. The result is the proven list of logs.
  6. Confirms the last block’s hash with a node of the chain selected by the verifying party (the browser page verifies it against Ethereum instead).
  7. Compares an eth_getLogs response with the proven logs on the six proven fields, matched by block number and log index.

In the browser. On api.willow.tech/verify, select Transaction or Block and the chain, and enter a transaction hash or a block. The page verifies the proof in the browser, verifies the block against Ethereum as described above, and compares Willow’s own responses with the proven ones, field by field. It lists the proven fields and identifies the fields the proof does not cover.

On the command line:

Terminal window
willow-logs-verify --rpc https://api.willow.tech/v1/rpc/1/{YOUR_KEY} \
--receipt 0x4560d377ceb62f875b054d83a86caabe34e7f681271f2d204ac692a472cf987c \
--compare-rpc https://your-provider.example/rpc \
--anchor-rpc https://your-node.example/rpc

--tx and --receipt take a transaction hash or <block>:<index>, and --block-receipts takes a block number, a block hash, or latest. The verifier:

  1. Calls willow_getTransactionProof, willow_getReceiptProof, or willow_getBlockReceiptsProof with the given hash or position.
  2. Verifies that the header hashes to blockHash, and that the block and transaction are those requested.
  3. Verifies every item against the header’s roots, either by walking each item proof from the root or by reconstructing both roots from a whole block’s items.
  4. Computes the standard response from the proven bytes.
  5. Confirms the block hash with the specified node (--anchor-rpc).
  6. Compares another endpoint’s response (--compare-rpc) with the proven one, field by field.

Output for the request above, compared with a public Ethereum node:

{
"block": {
"canonical": true,
"hash": "0xdb98a28f352acb72751ec8012c5a617acfb2d7469aadb9bc4690db6b6dd8d53f",
"number": "0x18e8f2a"
},
"chain": "ethereum",
"chainId": 1,
"compare": {
"eth_getTransactionReceipt": {
"differ": [],
"matched": 94,
"omitted": [],
"unproven": []
}
},
"method": "willow_getReceiptProof",
"proofBytes": 12981,
"transactionHash": "0x4560d377ceb62f875b054d83a86caabe34e7f681271f2d204ac692a472cf987c",
"transactionIndex": "0x0",
"unproven": [],
"verified": true,
"verifySeconds": 0.010043125
}
  • unproven: the fields of the response that this proof does not cover.
  • compare: matched counts the proven values that the other endpoint reports identically. differ lists every value it reports differently, and every proven field it omits. unproven lists the fields of its response that this proof does not cover, and omitted the proven fields that some nodes do not report: blockTimestamp (omitted, or 0x0 on logs from Robinhood Chain’s own RPC), and a deposit’s nonce and depositReceiptVersion on some OP Mainnet nodes.

The verifier exits with status 1 if the proof fails verification, a value differs, or the block is not on the specified node’s chain.