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 theBlockHeaderBlockMerkleCommitment: the root ofBlockMerkleTreethat accumulates all block commitments up to the currentBlockHeight, enabling efficient historical block header lookup viaBlockMerkleTreeProofPayloadCommitmentis a part ofBlockHeaderNsTabledescribed below is a part ofBlockHeader
- each rollup occupies a namespace (distinguished by a unique namespace ID) in a block
NsTableis the compact encoding of a namespace table mapping namespace id to their range inBlockPayloadNsProofis a namespace proof, attesting that some subset of bytes is the complete range of data designated to a particular namespace in aBlockPayloadidentified by itsPayloadCommitmentgiven 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 ofVidPublicParamfor the VID scheme
Private Inputs
rollup_txs: Vec<u8>: the byte representation of all transactions specific to rollup withns_idfiltered 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, whereproofproves thatrollup_txs[range]is the complete subset of namespace-specific transactions filtered from the Espresso block. EachBlockDerivationProofcontains the following:block_header: BlockHeader: block header of the original Espresso block containing the block height, the namespace tablens_table, and a commitmentpayload_commitmentto 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_commitmentvid_common: VidCommon: auxiliary information for the namespace proofns_proofverification during which its consistency againstpayload_commitmentis checkedns_proof: NsProof: a namespace proof that proves some subslice of bytes (i.e.rollup_txs[range]) is the complete subset for the namespacens_idfrom 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_txshere and in the generation of the "vm proof"
- Correct derivations for the namespace/rollup from committed Espresso blocks
- First the ranges in
block_derivation_proofsshould 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_headeris in the block Merkle tree, by checking the proofbmt_proofagainst the block Merkle tree commitmentbmt_commitment - Namespace ID
ns_idof 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_txsmatches 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. ↩