Tools for zkRollups to integrate with Espresso.
When a zk-rollup integrates with a shared finality gadget, it will receive streams of finalized blocks containing transactions from all participating rollups. As part of (batched) state update on L1, provers for a zk-rollup periodically submit a validity proof attesting to the correctness of a new rollup state. A rollup state corresponds to a specific finalized block. Solely proving valid state transition on the rollup VM against a list of transactions from the block (a.k.a "VM proof") is insufficient. With a decentralized finality gadget, the validity proof needs to encapsulate a proof of consensus on the new finalized block commitment (a.k.a "(consensus) light client proof"). With a shared finality gadget, the proof needs to further encapsulate correct "filtering of rollup-specific transactions" from the overall block (a.k.a. "derivation proof"). This repo provides circuits for the derivation proof and the consistency among the three (sub-)proofs.1
Note
Espresso uses a non-executing consensus, thus the consensus state doesn't embed post-execution rollup states: the consensus nodes only agree on the committed block payload (thus the total ordering among txs) and its availability. The validity of updated rollup/VM states, after executing those newly committed txs, is left to the rollup prover (aka batcher).
Terminology wise:
- a block is abstractly referring to a list of new txs/state transitions; concretely manifests as:
BlockPayload
: the actual payload that contains the raw tx data- instead of full block replication at each node, we use Verifiable Information Dispersal (VID) schemes where each replica stores a chunk.
- our concrete instantiation of VID relies on KZG polynomial commitment, thus requires structured reference string (SRS) from a trusted setup
- all replicas unequivocally refer to the payload by a
PayloadCommitment
, a cryptographic commitment to the entire payload sent to every replica alongside their designated chunk
- instead of full block replication at each node, we use Verifiable Information Dispersal (VID) schemes where each replica stores a chunk.
BlockHeader
: the summarized new consensus state and chain metadata (for consensus nodes)BlockCommitment
: a short cryptographic commitment to theBlockHeader
BlockMerkleCommitment
: the root ofBlockMerkleTree
that accumulates all block commitments up to the currentBlockHeight
, enabling efficient historical block header lookup viaBlockMerkleTreeProof
PayloadCommitment
is a part ofBlockHeader
NsTable
described below is a part ofBlockHeader
- each rollup occupies a namespace (distinguished by a unique namespace ID) in a block
NsTable
is the compact encoding of a namespace table mapping namespace id to their range inBlockPayload
NsProof
is a namespace proof, attesting that some subset of bytes is the complete range of data designated to a particular namespace in aBlockPayload
identified by itsPayloadCommitment
given aNsTable
- a light client is an agent that can verify the latest finalized consensus state without running a full node
- an off-chain light client usually receive the block header and the quorum certificate (QC)
- an on-chain light client stores a pruned
LightClientState
, a strict subset of fields inBlockHeader
, verified through light client proof (the simplest form is using the QC from consensus, but we use a more EVM and SNARK-friendly light client protocol).
Generally speaking, we are proving that a list of rollup's transactions are correctly derived from finalized Espresso blocks.
Public Inputs
rollup_txs_commit: [u8; 32]
: commitment to the transactions designated to the rollupns_id
, also one of the public inputs from the VM execution proof- the concrete commitment scheme depends on the VM prover design, we use
Sha256(rollup_txs)
in the demo
- the concrete commitment scheme depends on the VM prover design, we use
ns_id: u32
: namespace ID of this rollupbmt_commitment: BlockMerkleCommitment
: root of the newest Espresso block commitment tree, accumulated all historical Espresso block commitmentsvid_pp_hash: [u8; 32]
: Sha256 ofVidPublicParam
for the VID scheme
Private Inputs
rollup_txs: Vec<u8>
: the byte representation of all transactions specific to rollup withns_id
filtered from a batch of Espresso blocksvid_param: VidParam
: public parameter for Espresso's VID schemeblock_derivation_proofs: Vec<(Range, BlockDerivationProof)>
: a list of(range, proof)
pairs, one for each block, whereproof
proves thatrollup_txs[range]
is the complete subset of namespace-specific transactions filtered from the Espresso block. EachBlockDerivationProof
contains the following:block_header: BlockHeader
: block header of the original Espresso block containing the block height, the namespace tablens_table
, and a commitmentpayload_commitment
to the entire Espresso block payload (which contains transactions from all rollups)bmt_proof: BlockMerkleTreeProof
: a proof that the given block is in the block Merkle tree committed bybmt_commitment
vid_common: VidCommon
: auxiliary information for the namespace proofns_proof
verification during which its consistency againstpayload_commitment
is checkedns_proof: NsProof
: a namespace proof that proves some subslice of bytes (i.e.rollup_txs[range]
) is the complete subset for the namespacens_id
from the overall Espresso block payload committed inblock_header
Relations
- Recompute the payload commitment using the "VM execution prover" way:
rollup_txs_commit == Sha256(rollup_txs)
- note: by marking this as a public input, the verifier can cross-check it with the public inputs from the "vm proof", thus ensuring the same batch of transactions is used in
rollup_txs
here and in the generation of the "vm proof"
- Correct derivations for the namespace/rollup from committed Espresso blocks
- First the ranges in
block_derivation_proofs
should be non-overlapping and cover the whole payload, i.e.range[i].end == range[i+1].start && range[i].start == 0 && range[-1].end == rollup_txs.len()
. - For each
BlockDerivationProof
, we check- the
block_header
is in the block Merkle tree, by checking the proofbmt_proof
against the block Merkle tree commitmentbmt_commitment
- Namespace ID
ns_id
of this rollup is contained in the namespace tableblock_header.ns_table
, and given the specified range in the Espresso block and a namespace proofNsProof
, checks whether the slice of rollup's transactionsrollup_txs
matches the specified slice in the Espresso block payload committed byblock_header.payload_commitment
- the
- First the ranges in
Read our doc for a more detailed description; read our blog on Derivation Pipeline for rollup integration.
To enter the development shell: nix develop
To build the ELF executable for your program and generate the proof, you will have to run outside the nix dev-shell. For contract developments, you can enter nix shell to use necessary tools.
# this will first rebuild the program to elf, then generate plonky3 proof and verify it
just sp1-prove
# this will generate a proof for solidity, and creates fixture for contract verifier
just sp1-prove --evm
Footnotes
-
Circuits for VM proof are usually offered by zk-rollup project themselves (e.g. Polygon's CDK, Scroll's zkEVM); circuit (written using
jellyfish
's constraint system) for Espresso's light client proof can be found here. ↩