2024-01-08 14:08:53 +01:00
|
|
|
//! An implementation of a Type 1 zk-EVM by Polygon Zero.
|
|
|
|
|
//!
|
|
|
|
|
//! Following the [zk-EVM classification of V. Buterin](https://vitalik.eth.limo/general/2022/08/04/zkevm.html),
|
|
|
|
|
//! the plonky2_evm crate aims at providing an efficient solution for the problem of generating cryptographic
|
|
|
|
|
//! proofs of Ethereum-like transactions with *full Ethereum capability*.
|
|
|
|
|
//!
|
|
|
|
|
//! To this end, the plonky2 zk-EVM is tailored for an AIR-based STARK system satisfying degree 3 constraints,
|
|
|
|
|
//! with support for recursive aggregation leveraging plonky2 circuits with FRI-based plonkish arithmetization.
|
|
|
|
|
//! These circuits require a one-time, offline preprocessing phase.
|
|
|
|
|
//! See the [`fixed_recursive_verifier`] module for more details on how this works.
|
|
|
|
|
//! These preprocessed circuits are gathered within the [`AllRecursiveCircuits`] prover state,
|
|
|
|
|
//! and can be generated as such:
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! // Specify the base field to use.
|
|
|
|
|
//! type F = GoldilocksField;
|
|
|
|
|
//! // Specify the extension degree to use.
|
|
|
|
|
//! const D: usize = 2;
|
|
|
|
|
//! // Specify the recursive configuration to use, here leveraging Poseidon hash
|
|
|
|
|
//! // over the Goldilocks field both natively and in-circuit.
|
|
|
|
|
//! type C = PoseidonGoldilocksConfig;
|
|
|
|
|
//!
|
|
|
|
|
//! let all_stark = AllStark::<F, D>::default();
|
|
|
|
|
//! let config = StarkConfig::standard_fast_config();
|
|
|
|
|
//!
|
|
|
|
|
//! // Generate all the recursive circuits needed to generate succinct proofs for blocks.
|
|
|
|
|
//! // The ranges correspond to the supported table sizes for each individual STARK component.
|
|
|
|
|
//! let prover_state = AllRecursiveCircuits::<F, C, D>::new(
|
|
|
|
|
//! &all_stark,
|
|
|
|
|
//! &[16..25, 10..20, 12..25, 14..25, 9..20, 12..20, 17..30],
|
|
|
|
|
//! &config,
|
|
|
|
|
//! );
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! # Inputs type
|
|
|
|
|
//!
|
|
|
|
|
//! Transactions need to be processed into an Intermediary Representation (IR) format for the prover
|
|
|
|
|
//! to be able to generate proofs of valid state transition. This involves passing the encoded transaction,
|
|
|
|
|
//! the header of the block in which it was included, some information on the state prior execution
|
|
|
|
|
//! of this transaction, etc.
|
|
|
|
|
//! This intermediary representation is called [`GenerationInputs`].
|
|
|
|
|
//!
|
|
|
|
|
//!
|
|
|
|
|
//! # Generating succinct proofs
|
|
|
|
|
//!
|
|
|
|
|
//! ## Transaction proofs
|
|
|
|
|
//!
|
|
|
|
|
//! To generate a proof for a transaction, given its [`GenerationInputs`] and an [`AllRecursiveCircuits`]
|
|
|
|
|
//! prover state, one can simply call the [prove_root](AllRecursiveCircuits::prove_root) method.
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let mut timing = TimingTree::new("prove", log::Level::Debug);
|
|
|
|
|
//! let kill_signal = None; // Useful only with distributed proving to kill hanging jobs.
|
|
|
|
|
//! let (proof, public_values) =
|
|
|
|
|
//! prover_state.prove_root(all_stark, config, inputs, &mut timing, kill_signal);
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! This outputs a transaction proof and its associated public values. These are necessary during the
|
|
|
|
|
//! aggregation levels (see below). If one were to miss the public values, they are also retrievable directly
|
|
|
|
|
//! from the proof's encoded public inputs, as such:
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let public_values = PublicValues::from_public_inputs(&proof.public_inputs);
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! ## Aggregation proofs
|
|
|
|
|
//!
|
|
|
|
|
//! Because the plonky2 zkEVM generates proofs on a transaction basis, we then need to aggregate them for succinct
|
|
|
|
|
//! verification. This is done in a binary tree fashion, where each inner node proof verifies two children proofs,
|
|
|
|
|
//! through the [prove_aggregation](AllRecursiveCircuits::prove_aggregation) method.
|
|
|
|
|
//! Note that the tree does *not* need to be complete, as this aggregation process can take as inputs both regular
|
|
|
|
|
//! transaction proofs and aggregation proofs. We only need to specify for each child if it is an aggregation proof
|
|
|
|
|
//! or a regular one.
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let (proof_1, pv_1) =
|
|
|
|
|
//! prover_state.prove_root(all_stark, config, inputs_1, &mut timing, None);
|
|
|
|
|
//! let (proof_2, pv_2) =
|
|
|
|
|
//! prover_state.prove_root(all_stark, config, inputs_2, &mut timing, None);
|
|
|
|
|
//! let (proof_3, pv_3) =
|
|
|
|
|
//! prover_state.prove_root(all_stark, config, inputs_3, &mut timing, None);
|
|
|
|
|
//!
|
|
|
|
|
//! // Now aggregate proofs for txn 1 and 2.
|
|
|
|
|
//! let (agg_proof_1_2, pv_1_2) =
|
|
|
|
|
//! prover_state.prove_aggregation(false, proof_1, pv_1, false, proof_2, pv_2);
|
|
|
|
|
//!
|
|
|
|
|
//! // Now aggregate the newly generated aggregation proof with the last regular txn proof.
|
|
|
|
|
//! let (agg_proof_1_3, pv_1_3) =
|
|
|
|
|
//! prover_state.prove_aggregation(true, agg_proof_1_2, pv_1_2, false, proof_3, pv_3);
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! **Note**: The proofs provided to the [prove_aggregation](AllRecursiveCircuits::prove_aggregation) method *MUST* have contiguous states.
|
|
|
|
|
//! Trying to combine `proof_1` and `proof_3` from the example above would fail.
|
|
|
|
|
//!
|
|
|
|
|
//! ## Block proofs
|
|
|
|
|
//!
|
|
|
|
|
//! Once all transactions of a block have been proven and we are left with a single aggregation proof and its public values,
|
|
|
|
|
//! we can then wrap it into a final block proof, attesting validity of the entire block.
|
|
|
|
|
//! This [prove_block](AllRecursiveCircuits::prove_block) method accepts an optional previous block proof as argument,
|
|
|
|
|
//! which will then try combining the previously proven block with the current one, generating a validity proof for both.
|
|
|
|
|
//! Applying this process from genesis would yield a single proof attesting correctness of the entire chain.
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let previous_block_proof = { ... };
|
|
|
|
|
//! let (block_proof, block_public_values) =
|
|
|
|
|
//! prover_state.prove_block(Some(&previous_block_proof), &agg_proof, agg_pv)?;
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! ### Checkpoint heights
|
|
|
|
|
//!
|
|
|
|
|
//! The process of always providing a previous block proof when generating a proof for the current block may yield some
|
|
|
|
|
//! undesirable issues. For this reason, the plonky2 zk-EVM supports checkpoint heights. At given block heights,
|
|
|
|
|
//! the prover does not have to pass a previous block proof. This would in practice correspond to block heights at which
|
|
|
|
|
//! a proof has been generated and sent to L1 for settlement.
|
|
|
|
|
//!
|
|
|
|
|
//! The only requirement when generating a block proof without passing a previous one as argument is to have the
|
|
|
|
|
//! `checkpoint_state_trie_root` metadata in the `PublicValues` of the final aggregation proof be matching the state
|
|
|
|
|
//! trie before applying all the included transactions. If this condition is not met, the prover will fail to generate
|
|
|
|
|
//! a valid proof.
|
|
|
|
|
//!
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let (block_proof, block_public_values) =
|
|
|
|
|
//! prover_state.prove_block(None, &agg_proof, agg_pv)?;
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! # Prover state serialization
|
|
|
|
|
//!
|
|
|
|
|
//! Because the recursive circuits only need to be generated once, they can be saved to disk once the preprocessing phase
|
|
|
|
|
//! completed successfully, and deserialized on-demand.
|
|
|
|
|
//! The plonky2 zk-EVM provides serialization methods to convert the entire prover state to a vector of bytes, and vice-versa.
|
|
|
|
|
//! This requires the use of custom serializers for gates and generators for proper recursive circuit encoding. This crate provides
|
|
|
|
|
//! default serializers supporting all custom gates and associated generators defined within the [`plonky2`] crate.
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! let prover_state = AllRecursiveCircuits::<F, C, D>::new(...);
|
|
|
|
|
//!
|
|
|
|
|
//! // Default serializers
|
|
|
|
|
//! let gate_serializer = DefaultGateSerializer;
|
|
|
|
|
//! let generator_serializer = DefaultGeneratorSerializer::<C, D> {
|
|
|
|
|
//! _phantom: PhantomData::<C>,
|
|
|
|
|
//! };
|
|
|
|
|
//!
|
|
|
|
|
//! // Serialize the prover state to a sequence of bytes
|
|
|
|
|
//! let bytes = prover_state.to_bytes(false, &gate_serializer, &generator_serializer).unwrap();
|
|
|
|
|
//!
|
|
|
|
|
//! // Deserialize the bytes into a prover state
|
|
|
|
|
//! let recovered_prover_state = AllRecursiveCircuits::<F, C, D>::from_bytes(
|
|
|
|
|
//! &all_circuits_bytes,
|
|
|
|
|
//! false,
|
|
|
|
|
//! &gate_serializer,
|
|
|
|
|
//! &generator_serializer,
|
|
|
|
|
//! ).unwrap();
|
|
|
|
|
//!
|
|
|
|
|
//! assert_eq!(prover_state, recovered_prover_state);
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! Note that an entire prover state built with wide ranges may be particularly large (up to ~25 GB), hence serialization methods,
|
|
|
|
|
//! while faster than doing another preprocessing, may take some non-negligible time.
|
|
|
|
|
|
|
|
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
2022-06-07 14:20:51 -07:00
|
|
|
#![allow(clippy::needless_range_loop)]
|
2022-05-04 20:57:07 +02:00
|
|
|
#![allow(clippy::too_many_arguments)]
|
2022-12-02 17:06:30 -08:00
|
|
|
#![allow(clippy::field_reassign_with_default)]
|
2023-11-13 09:26:56 -05:00
|
|
|
#![allow(unused)]
|
2022-09-01 09:32:18 -07:00
|
|
|
#![feature(let_chains)]
|
2022-05-04 20:57:07 +02:00
|
|
|
|
2022-05-18 09:22:58 +02:00
|
|
|
pub mod all_stark;
|
2022-06-29 11:56:48 +10:00
|
|
|
pub mod arithmetic;
|
2023-09-12 14:45:37 -04:00
|
|
|
pub mod byte_packing;
|
2022-05-04 20:57:07 +02:00
|
|
|
pub mod config;
|
|
|
|
|
pub mod constraint_consumer;
|
2022-05-18 09:22:58 +02:00
|
|
|
pub mod cpu;
|
2022-05-06 17:36:32 +02:00
|
|
|
pub mod cross_table_lookup;
|
2023-04-27 16:26:40 -07:00
|
|
|
pub mod curve_pairings;
|
2023-09-22 09:19:13 -04:00
|
|
|
pub mod evaluation_frame;
|
2023-03-20 11:55:43 -07:00
|
|
|
pub mod extension_tower;
|
Shrink STARK proofs to a constant degree
The goal here is to end up with a single "root" circuit representing any EVM proof. I.e. it must verify each STARK, but be general enough to work with any combination of STARK sizes (within some range of sizes that we chose to support). This root circuit can then be plugged into our aggregation circuit.
In particular, for each STARK, and for each initial `degree_bits` (within a range that we choose to support), this adds a "shrinking chain" of circuits. Such a chain shrinks a STARK proof from that initial `degree_bits` down to a constant, `THRESHOLD_DEGREE_BITS`.
The root circuit then combines these shrunk-to-constant proofs for each table. It's similar to `RecursiveAllProof::verify_circuit`; I adapted the code from there and I think we can remove it after. The main difference is that now instead of having one verification key per STARK, we have several possible VKs, one per initial `degree_bits`. We bake the list of possible VKs into the root circuit, and have the prover indicate the index of the VK they're actually using.
This also partially removes the default feature of CTLs. So far we've used filters instead of defaults. Until now it was easy to keep supporting defaults just in case, but here maintaining support would require some more work. E.g. we couldn't use `exp_u64` any more, since the size delta is now dynamic, it can't be hardcoded. If there are no concerns, I'll fully remove the feature after.
2022-12-27 18:15:18 -08:00
|
|
|
pub mod fixed_recursive_verifier;
|
2022-06-15 09:33:52 -07:00
|
|
|
pub mod generation;
|
2022-05-04 20:57:07 +02:00
|
|
|
mod get_challenges;
|
2022-05-18 09:22:58 +02:00
|
|
|
pub mod keccak;
|
2022-08-29 18:10:51 -07:00
|
|
|
pub mod keccak_sponge;
|
2022-06-13 10:34:33 -07:00
|
|
|
pub mod logic;
|
2022-06-21 14:35:19 -07:00
|
|
|
pub mod lookup;
|
2022-06-13 14:37:29 -07:00
|
|
|
pub mod memory;
|
2022-05-04 20:57:07 +02:00
|
|
|
pub mod proof;
|
|
|
|
|
pub mod prover;
|
|
|
|
|
pub mod recursive_verifier;
|
|
|
|
|
pub mod stark;
|
|
|
|
|
pub mod util;
|
|
|
|
|
pub mod vanishing_poly;
|
|
|
|
|
pub mod verifier;
|
2022-11-28 13:19:40 -08:00
|
|
|
pub mod witness;
|
2022-12-04 10:15:28 -08:00
|
|
|
|
2023-11-13 09:26:56 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod stark_testing;
|
|
|
|
|
|
2023-03-27 17:30:11 -06:00
|
|
|
use eth_trie_utils::partial_trie::HashedPartialTrie;
|
2022-12-04 10:15:28 -08:00
|
|
|
// Set up Jemalloc
|
|
|
|
|
#[cfg(not(target_env = "msvc"))]
|
|
|
|
|
use jemallocator::Jemalloc;
|
|
|
|
|
|
|
|
|
|
#[cfg(not(target_env = "msvc"))]
|
|
|
|
|
#[global_allocator]
|
|
|
|
|
static GLOBAL: Jemalloc = Jemalloc;
|
2023-03-27 17:30:11 -06:00
|
|
|
|
2024-01-08 14:08:53 +01:00
|
|
|
// Public definitions and re-exports
|
|
|
|
|
|
2023-03-28 14:38:58 -06:00
|
|
|
pub type Node = eth_trie_utils::partial_trie::Node<HashedPartialTrie>;
|
2024-01-08 14:08:53 +01:00
|
|
|
|
|
|
|
|
pub use all_stark::AllStark;
|
|
|
|
|
pub use config::StarkConfig;
|
|
|
|
|
pub use fixed_recursive_verifier::AllRecursiveCircuits;
|
|
|
|
|
pub use generation::GenerationInputs;
|