diff --git a/evm/src/fixed_recursive_verifier.rs b/evm/src/fixed_recursive_verifier.rs index 6a71072b..b7c824a8 100644 --- a/evm/src/fixed_recursive_verifier.rs +++ b/evm/src/fixed_recursive_verifier.rs @@ -15,7 +15,7 @@ use plonky2::iop::target::{BoolTarget, Target}; use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::{ - CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitTarget, + CircuitConfig, CircuitData, CommonCircuitData, VerifierCircuitData, VerifierCircuitTarget, }; use plonky2::plonk::config::{AlgebraicHasher, GenericConfig}; use plonky2::plonk::proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}; @@ -430,6 +430,78 @@ where } } + /// Expand the preprocessed STARK table circuits with the provided ranges. + /// + /// If a range for a given table is contained within the current one, this will be a no-op. + /// Otherwise, it will add the circuits for the missing table sizes, and regenerate the upper circuits. + pub fn expand( + &mut self, + all_stark: &AllStark, + degree_bits_ranges: &[Range; NUM_TABLES], + stark_config: &StarkConfig, + ) { + self.by_table[Table::Arithmetic as usize].expand( + Table::Arithmetic, + &all_stark.arithmetic_stark, + degree_bits_ranges[Table::Arithmetic as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::BytePacking as usize].expand( + Table::BytePacking, + &all_stark.byte_packing_stark, + degree_bits_ranges[Table::BytePacking as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::Cpu as usize].expand( + Table::Cpu, + &all_stark.cpu_stark, + degree_bits_ranges[Table::Cpu as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::Keccak as usize].expand( + Table::Keccak, + &all_stark.keccak_stark, + degree_bits_ranges[Table::Keccak as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::KeccakSponge as usize].expand( + Table::KeccakSponge, + &all_stark.keccak_sponge_stark, + degree_bits_ranges[Table::KeccakSponge as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::Logic as usize].expand( + Table::Logic, + &all_stark.logic_stark, + degree_bits_ranges[Table::Logic as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + self.by_table[Table::Memory as usize].expand( + Table::Memory, + &all_stark.memory_stark, + degree_bits_ranges[Table::Memory as usize].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + + // Regenerate the upper circuits. + self.root = Self::create_root_circuit(&self.by_table, stark_config); + self.aggregation = Self::create_aggregation_circuit(&self.root); + self.block = Self::create_block_circuit(&self.aggregation); + } + + /// Outputs the `VerifierCircuitData` needed to verify any block proof + /// generated by an honest prover. + pub fn final_verifier_data(&self) -> VerifierCircuitData { + self.block.circuit.verifier_data() + } + fn create_root_circuit( by_table: &[RecursiveCircuitsForTable; NUM_TABLES], stark_config: &StarkConfig, @@ -1201,6 +1273,32 @@ where Self { by_stark_size } } + fn expand>( + &mut self, + table: Table, + stark: &S, + degree_bits_range: Range, + all_ctls: &[CrossTableLookup], + stark_config: &StarkConfig, + ) { + let new_ranges = degree_bits_range + .filter(|degree_bits| !self.by_stark_size.contains_key(degree_bits)) + .collect_vec(); + + for degree_bits in new_ranges { + self.by_stark_size.insert( + degree_bits, + RecursiveCircuitsForTableSize::new::( + table, + stark, + degree_bits, + all_ctls, + stark_config, + ), + ); + } + } + /// For each initial `degree_bits`, get the final circuit at the end of that shrinking chain. /// Each of these final circuits should have degree `THRESHOLD_DEGREE_BITS`. fn final_circuits(&self) -> Vec<&CircuitData> { diff --git a/evm/tests/empty_txn_list.rs b/evm/tests/empty_txn_list.rs index 47d893d2..02e3c5b6 100644 --- a/evm/tests/empty_txn_list.rs +++ b/evm/tests/empty_txn_list.rs @@ -77,9 +77,12 @@ fn test_empty_txn_list() -> anyhow::Result<()> { addresses: vec![], }; - let all_circuits = AllRecursiveCircuits::::new( + // Initialize the preprocessed circuits for the zkEVM. + // The provided ranges are the minimal ones to prove an empty list, except the one of the CPU + // that is wrong for testing purposes, see below. + let mut all_circuits = AllRecursiveCircuits::::new( &all_stark, - &[16..17, 10..11, 15..16, 14..15, 9..11, 12..13, 18..19], // Minimal ranges to prove an empty list + &[16..17, 10..11, 12..13, 14..15, 9..11, 12..13, 18..19], // Minimal ranges to prove an empty list &config, ); @@ -111,6 +114,20 @@ fn test_empty_txn_list() -> anyhow::Result<()> { assert_eq!(all_circuits, all_circuits_from_bytes); } + let mut timing = TimingTree::new("prove", log::Level::Info); + // We're missing some preprocessed circuits. + assert!(all_circuits + .prove_root(&all_stark, &config, inputs.clone(), &mut timing) + .is_err()); + + // Expand the preprocessed circuits. + // We pass an empty range if we don't want to add different table sizes. + all_circuits.expand( + &all_stark, + &[0..0, 0..0, 15..16, 0..0, 0..0, 0..0, 0..0], + &StarkConfig::standard_fast_config(), + ); + let mut timing = TimingTree::new("prove", log::Level::Info); let (root_proof, public_values) = all_circuits.prove_root(&all_stark, &config, inputs, &mut timing)?; @@ -129,7 +146,11 @@ fn test_empty_txn_list() -> anyhow::Result<()> { all_circuits.verify_aggregation(&agg_proof)?; let (block_proof, _) = all_circuits.prove_block(None, &agg_proof, public_values)?; - all_circuits.verify_block(&block_proof) + all_circuits.verify_block(&block_proof)?; + + // Get the verifier associated to these preprocessed circuits, and have it verify the block_proof. + let verifier = all_circuits.final_verifier_data(); + verifier.verify(block_proof) } fn init_logger() {