diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 348aa2c1fe..450627be75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,8 +134,16 @@ jobs: with: command: test args: --verbose --release --features scroll --all --exclude integration-tests --exclude circuit-benchmarks serial_ -- --ignored - - + - name: Run parallel assignment tests(bytecode) + uses: actions-rs/cargo@v1 + with: + command: test + args: --release --package zkevm-circuits --lib bytecode_circuit::test --features scroll,parallel_syn -- --nocapture + - name: Run parallel assignment tests(state) + uses: actions-rs/cargo@v1 + with: + command: test + args: --release --package zkevm-circuits --lib state_circuit::test --features scroll,parallel_syn -- --nocapture build: needs: [skip_check] if: | diff --git a/zkevm-circuits/Cargo.toml b/zkevm-circuits/Cargo.toml index 7efe5d2bfe..49548a2e96 100644 --- a/zkevm-circuits/Cargo.toml +++ b/zkevm-circuits/Cargo.toml @@ -84,5 +84,6 @@ warn-unimplemented = ["eth-types/warn-unimplemented"] onephase = [] # debug only zktrie = [] poseidon-codehash = [] +parallel_syn = ["halo2_proofs/parallel_syn"] -debug-annotations = [] \ No newline at end of file +debug-annotations = [] diff --git a/zkevm-circuits/src/bytecode_circuit/circuit.rs b/zkevm-circuits/src/bytecode_circuit/circuit.rs index e26c60d64c..5ee2b504b4 100644 --- a/zkevm-circuits/src/bytecode_circuit/circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit/circuit.rs @@ -545,6 +545,7 @@ impl BytecodeCircuitConfig { &push_data_left_is_zero_chip, &index_length_diff_is_zero_chip, empty_hash, + 0, &mut offset, last_row_offset, fail_fast, @@ -631,6 +632,7 @@ impl BytecodeCircuitConfig { push_data_left_is_zero_chip: &IsZeroChip, index_length_diff_is_zero_chip: &IsZeroChip, empty_hash: Value, + offset_begin: usize, offset: &mut usize, last_row_offset: usize, fail_fast: bool, @@ -653,10 +655,13 @@ impl BytecodeCircuitConfig { }); for (idx, row) in bytecode.rows.iter().enumerate() { - if fail_fast && *offset > last_row_offset { + // for serial assignment api assign_region(), offset always begins with 0 + // `offset` == global_offset + let global_offset = *offset + offset_begin; + if fail_fast && global_offset > last_row_offset { log::error!( - "Bytecode Circuit: offset={} > last_row_offset={}", - offset, + "Bytecode Circuit: global_offset={} > last_row_offset={}", + global_offset, last_row_offset ); return Err(Error::Synthesis); @@ -682,14 +687,14 @@ impl BytecodeCircuitConfig { } // Set the data for this row - if *offset < last_row_offset { + if global_offset < last_row_offset { self.set_row( region, push_data_left_is_zero_chip, index_length_diff_is_zero_chip, *offset, true, - *offset == last_row_offset, + global_offset == last_row_offset, code_hash, row.tag, row.index, @@ -703,8 +708,8 @@ impl BytecodeCircuitConfig { /* trace!( "bytecode.set_row({}): last:{} h:{:?} t:{:?} i:{:?} c:{:?} v:{:?} pdl:{} rlc:{:?} l:{:?} pds:{:?}", - offset, - offset == last_row_offset, + global_offset, + global_offset == last_row_offset, code_hash, row.tag.get_lower_32(), row.index.get_lower_32(), @@ -720,14 +725,22 @@ impl BytecodeCircuitConfig { *offset += 1; push_data_left = next_push_data_left } - if *offset == last_row_offset { + + let global_offset = *offset + offset_begin; + if global_offset == last_row_offset { + // for serial assignment api assign_region(), offset always begins with 0 + // `offset` == global_offset, + // so set `offset` here is equivalent to set `last_row_offset` + // if assign_bytecode() is called by assign_regions() + // the rows are divided into multiple regions, + // so the maximum value is the accumulated offset self.set_padding_row( region, push_data_left_is_zero_chip, index_length_diff_is_zero_chip, empty_hash, *offset, - last_row_offset, + *offset, )?; } } diff --git a/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs index b24518c310..f271122e9b 100644 --- a/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs +++ b/zkevm-circuits/src/bytecode_circuit/circuit/to_poseidon_hash.rs @@ -24,6 +24,8 @@ use super::{ BytecodeCircuitConfig, BytecodeCircuitConfigArgs, }; +use itertools::Itertools; + #[derive(Clone, Debug)] /// Bytecode circuit (for hash block) configuration /// basically the BytecodeCircuit include two parts: @@ -290,7 +292,6 @@ impl ToHashBlockCircuitConfig, inp_i: usize| { debug_assert_eq!(PoseidonTable::INPUT_WIDTH, 2); @@ -417,6 +418,30 @@ impl ToHashBlockCircuitConfig>, fail_fast: bool, ) -> Result<(), Error> { + #[cfg(feature = "parallel_syn")] + { + // if feature "parallel_syn" is enabled, + // `parallel` assignment is turned on by default + // we can turn it off by set the environment variable + // `CIRCUIT_ASSIGNMENT_TYPE=serial` + let assignment_type = std::env::var("CIRCUIT_ASSIGNMENT_TYPE") + .ok() + .unwrap_or_default(); + let is_parallel_assignment = match assignment_type.as_str() { + "serial" => false, + "parallel" => true, + &_ => true, + }; + log::debug!("CIRCUIT_ASSIGNMENT_TYPE: {}", assignment_type); + log::debug!("is_parallel_assignment: {}", is_parallel_assignment); + + if is_parallel_assignment { + return self.assign_internal_parallel( + layouter, size, witness, overwrite, challenges, fail_fast, + ); + } + } + let base_conf = &self.base_conf; let push_data_left_is_zero_chip = IsZeroChip::construct(base_conf.push_data_left_is_zero.clone()); @@ -436,9 +461,23 @@ impl ToHashBlockCircuitConfig ToHashBlockCircuitConfig ToHashBlockCircuitConfig, + size: usize, + witness: &[UnrolledBytecode], + overwrite: &UnrolledBytecode, + challenges: &Challenges>, + fail_fast: bool, + ) -> Result<(), Error> { + let base_conf = &self.base_conf; + let push_data_left_is_zero_chip = + IsZeroChip::construct(base_conf.push_data_left_is_zero.clone()); + let index_length_diff_is_zero_chip = + IsZeroChip::construct(base_conf.index_length_diff_is_zero.clone()); + + // Subtract the unusable rows from the size + assert!(size > base_conf.minimum_rows); + let last_row_offset = size - base_conf.minimum_rows + 1; + let empty_hash = Value::known(POSEIDON_CODE_HASH_ZERO.to_word().to_scalar().unwrap()); + + let mut is_first_time_vec = vec![true; witness.len()]; + + let offsets_of_each_witness = witness + .iter() + .map(|bytecode| bytecode.rows.len()) + .collect::>(); + + //start from 0 + let offsets_prefix_sum = [0] + .iter() + .chain(offsets_of_each_witness.iter()) + .scan(0, |sum, &x| { + *sum += x; + Some(*sum) + }) + .collect::>(); + + layouter.assign_regions( + || "assign bytecode with poseidon hash extension(part1: regions)", + witness + .iter() + .zip(is_first_time_vec.iter_mut()) + .enumerate() + .map(|(idx, (bytecode, is_first_time))| { + let push_data_left_is_zero_chip = push_data_left_is_zero_chip.clone(); + let index_length_diff_is_zero_chip = index_length_diff_is_zero_chip.clone(); + let offsets_prefix_sum = offsets_prefix_sum.clone(); + move |region: Region<'_, F>| { + let mut offset = 0; + let mut region = region; + + if *is_first_time { + *is_first_time = false; + let mut dummy_offset = bytecode.rows.len() - 1; + // last witness + if idx == witness.len() - 1 { + dummy_offset = last_row_offset - offsets_prefix_sum[idx]; + } + base_conf.set_padding_row( + &mut region, + &push_data_left_is_zero_chip, + &index_length_diff_is_zero_chip, + empty_hash, + dummy_offset, + dummy_offset, + )?; + return Ok(()); + } + + base_conf.assign_bytecode( + &mut region, + bytecode, + challenges, + &push_data_left_is_zero_chip, + &index_length_diff_is_zero_chip, + empty_hash, + offsets_prefix_sum[idx], + &mut offset, + last_row_offset, + fail_fast, + )?; + + let last_offset = last_row_offset - offsets_prefix_sum[idx]; + // last witness + if idx == witness.len() - 1 { + // Padding + for idx in offset..=last_offset { + base_conf.set_padding_row( + &mut region, + &push_data_left_is_zero_chip, + &index_length_diff_is_zero_chip, + empty_hash, + idx, + last_offset, + )?; + self.set_header_row(&mut region, 0, idx)?; + } + + base_conf.assign_overwrite(&mut region, overwrite, challenges)?; + } + + Ok(()) + } + }) + .collect_vec(), + )?; + + layouter.assign_region( + || "assign bytecode with poseidon hash extension(part2)", + |mut region| { + let mut row_input = F::zero(); + for (witness_idx, bytecode) in witness.iter().enumerate() { + for (idx, row) in bytecode.rows.iter().enumerate() { + // if the base_conf's assignment not fail fast, + // we also avoid the failure of "NotEnoughRowsAvailable" + // in prover creation (so bytecode_incomplete test could pass) + let offset = offsets_prefix_sum[witness_idx] + idx; + if offset <= last_row_offset { + row_input = self.assign_extended_row( + &mut region, + offset, + row, + row_input, + bytecode.bytes.len(), + )?; + } + } + } + Ok(()) + }, + ) + } + /// Assign a header row (at padding or start line of each bytecodes) fn set_header_row( &self, diff --git a/zkevm-circuits/src/bytecode_circuit/test.rs b/zkevm-circuits/src/bytecode_circuit/test.rs index 07f252a232..6043ae7e44 100644 --- a/zkevm-circuits/src/bytecode_circuit/test.rs +++ b/zkevm-circuits/src/bytecode_circuit/test.rs @@ -1,14 +1,33 @@ #![allow(unused_imports)] use crate::{ - bytecode_circuit::{bytecode_unroller::*, circuit::BytecodeCircuit}, + bytecode_circuit::{bytecode_unroller::*, circuit::BytecodeCircuit, TestBytecodeCircuit}, table::BytecodeFieldTag, util::{is_push_with_data, keccak, unusable_rows, Challenges, SubCircuit}, }; use bus_mapping::{evm::OpcodeId, state_db::CodeDB}; use eth_types::{Bytecode, Field, ToWord, Word}; -use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use log::error; +use halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}, + poly::{ + commitment::ParamsProver, + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG, ParamsVerifierKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, + }, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, +}; +use rand::SeedableRng; +use rand_xorshift::XorShiftRng; +use std::env::var; + #[test] fn bytecode_circuit_unusable_rows() { assert_eq!( @@ -45,7 +64,6 @@ pub fn test_bytecode_circuit_unrolled( success: bool, ) { let circuit = BytecodeCircuit::::new(bytecodes, 2usize.pow(k)); - let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); let result = prover.verify_par(); if let Err(failures) = &result { @@ -326,3 +344,132 @@ fn bytecode_soundness_bug_1() { let prover = MockProver::::run(k, &circuit, Vec::new()).unwrap(); prover.assert_satisfied_par(); } + +/// fill bytecodes_num * bytecode_len bytes to the witness table +fn fillup_codebytes( + bytecodes_num: usize, + bytecode_len: usize, +) -> Vec> { + fn valid_or(base: OpcodeId, or: OpcodeId) -> OpcodeId { + match base { + OpcodeId::INVALID(_) => or, + _ => base, + } + } + + let mut codebytes = vec![]; + (0..bytecodes_num).for_each(|_| { + let bytecodes = (0..bytecode_len) + .map(|v| valid_or(OpcodeId::from(v as u8), OpcodeId::STOP).as_u8()) + .collect::>(); + let unrolled_bytes = unroll::(bytecodes); + codebytes.push(unrolled_bytes); + }); + codebytes +} + +fn set_assignment_env_var(value: &str) { + std::env::set_var("CIRCUIT_ASSIGNMENT_TYPE", value); + let assign_var = std::env::var("CIRCUIT_ASSIGNMENT_TYPE") + .ok() + .unwrap_or_default(); + log::info!("CIRCUIT_ASSIGNMENT_TYPE: {}", assign_var); +} + +/// Bytecode circuit parallel assignment test +/// modiefied from `fn bench_bytecode_circuit_prover()` +/// in: circuit-benchmarks/src/bytecode_circuit.rs +/// +/// Run with: +/// `cargo test --package zkevm-circuits --lib +/// bytecode_circuit::test::bytecode_circuit_parallel_assignment --features scroll,parallel_syn -- +/// --nocapture` +#[test] +#[cfg(feature = "scroll")] +#[cfg(feature = "parallel_syn")] +fn bytecode_circuit_parallel_assignment() { + // Contract code size exceeds 24576 bytes may not be deployable on Mainnet. + const MAX_BYTECODE_LEN: usize = 4096; + + let degree: u32 = 15; + let num_rows = 1 << degree; + const NUM_BLINDING_ROWS: usize = 7 - 1; + let max_bytecode_row_num = num_rows - NUM_BLINDING_ROWS; + let bytecode_len = std::cmp::min(MAX_BYTECODE_LEN, max_bytecode_row_num); + let bytecodes_num: usize = max_bytecode_row_num / bytecode_len; + log::info!( + "Bytecode length: {}, Bytecodes number: {}", + bytecode_len, + bytecodes_num + ); + + // Create the circuit + let bytecode_circuit = TestBytecodeCircuit::::new( + fillup_codebytes(bytecodes_num, bytecode_len), + 2usize.pow(degree), + ); + + // Initialize the polynomial commitment parameters + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, + 0xe5, + ]); + + // Setup generation + log::info!( + "test bytecode circuit parallel assignment with degree = {}", + degree + ); + let general_params = ParamsKZG::::setup(degree, &mut rng); + let verifier_params: ParamsVerifierKZG = general_params.verifier_params().clone(); + + // Initialize the proving key + // The serial assignment is used to generate pk and vk, + // which are then used to verify the proof generated by the parallel assignment + set_assignment_env_var("serial"); + let vk = keygen_vk(&general_params, &bytecode_circuit).expect("keygen_vk should not fail"); + let pk = keygen_pk(&general_params, vk, &bytecode_circuit).expect("keygen_pk should not fail"); + + // Create a proof + let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); + + // Proof generation + // Set parallel assignment env var + set_assignment_env_var("parallel"); + create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255, + XorShiftRng, + Blake2bWrite, G1Affine, Challenge255>, + TestBytecodeCircuit, + >( + &general_params, + &pk, + &[bytecode_circuit], + &[&[]], + rng, + &mut transcript, + ) + .expect("proof generation should not fail"); + let proof = transcript.finalize(); + + let mut verifier_transcript = Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&proof[..]); + let strategy = SingleStrategy::new(&general_params); + verify_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >( + &verifier_params, + pk.get_vk(), + strategy, + &[&[]], + &mut verifier_transcript, + ) + .expect("failed to verify bench circuit"); + + set_assignment_env_var(""); +}