mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-10 01:33:07 +00:00
* Add crate-level documentation * Revert change * Skip * Typo * Apply comments * Rephrase paragraph * Apply comments
215 lines
9.0 KiB
Rust
215 lines
9.0 KiB
Rust
//! 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))]
|
|
#![allow(clippy::needless_range_loop)]
|
|
#![allow(clippy::too_many_arguments)]
|
|
#![allow(clippy::field_reassign_with_default)]
|
|
#![allow(unused)]
|
|
#![feature(let_chains)]
|
|
|
|
pub mod all_stark;
|
|
pub mod arithmetic;
|
|
pub mod byte_packing;
|
|
pub mod config;
|
|
pub mod constraint_consumer;
|
|
pub mod cpu;
|
|
pub mod cross_table_lookup;
|
|
pub mod curve_pairings;
|
|
pub mod evaluation_frame;
|
|
pub mod extension_tower;
|
|
pub mod fixed_recursive_verifier;
|
|
pub mod generation;
|
|
mod get_challenges;
|
|
pub mod keccak;
|
|
pub mod keccak_sponge;
|
|
pub mod logic;
|
|
pub mod lookup;
|
|
pub mod memory;
|
|
pub mod proof;
|
|
pub mod prover;
|
|
pub mod recursive_verifier;
|
|
pub mod stark;
|
|
pub mod util;
|
|
pub mod vanishing_poly;
|
|
pub mod verifier;
|
|
pub mod witness;
|
|
|
|
#[cfg(test)]
|
|
mod stark_testing;
|
|
|
|
use eth_trie_utils::partial_trie::HashedPartialTrie;
|
|
// Set up Jemalloc
|
|
#[cfg(not(target_env = "msvc"))]
|
|
use jemallocator::Jemalloc;
|
|
|
|
#[cfg(not(target_env = "msvc"))]
|
|
#[global_allocator]
|
|
static GLOBAL: Jemalloc = Jemalloc;
|
|
|
|
// Public definitions and re-exports
|
|
|
|
pub type Node = eth_trie_utils::partial_trie::Node<HashedPartialTrie>;
|
|
|
|
pub use all_stark::AllStark;
|
|
pub use config::StarkConfig;
|
|
pub use fixed_recursive_verifier::AllRecursiveCircuits;
|
|
pub use generation::GenerationInputs;
|