diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs index 355d4a3736..83ceb117d5 100644 --- a/integration-tests/tests/circuits.rs +++ b/integration-tests/tests/circuits.rs @@ -113,7 +113,6 @@ async fn test_tx_circuit_block(block_num: u64) { pub async fn test_bytecode_circuit_block(block_num: u64) { const DEGREE: u32 = 16; - let randomness = Fr::from(123456); log::info!("test bytecode circuit, block number: {}", block_num); let cli = get_client(); @@ -121,7 +120,7 @@ pub async fn test_bytecode_circuit_block(block_num: u64) { let (builder, _) = cli.gen_inputs(block_num).await.unwrap(); let bytecodes: Vec<Vec<u8>> = builder.code_db.0.values().cloned().collect(); - test_bytecode_circuit(DEGREE, bytecodes, randomness); + test_bytecode_circuit::<Fr>(DEGREE, bytecodes); } pub async fn test_copy_circuit_block(block_num: u64) { diff --git a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs index d651e8a110..5c9dd2c158 100644 --- a/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs +++ b/zkevm-circuits/src/bytecode_circuit/bytecode_unroller.rs @@ -3,7 +3,7 @@ use crate::{ and, constraint_builder::BaseConstraintBuilder, not, or, select, RandomLinearCombination, }, table::{BytecodeFieldTag, BytecodeTable, DynamicTableColumns, KeccakTable}, - util::Expr, + util::{Challenges, Expr}, }; use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian, Word}; @@ -20,7 +20,7 @@ use super::param::PUSH_TABLE_WIDTH; /// Public data for the bytecode #[derive(Clone, Debug, PartialEq)] pub(crate) struct BytecodeRow<F: Field> { - code_hash: F, + code_hash: Word, tag: F, index: F, is_code: F, @@ -37,7 +37,6 @@ pub struct UnrolledBytecode<F: Field> { #[derive(Clone, Debug)] /// Bytecode circuit configuration pub struct Config<F> { - randomness: Expression<F>, minimum_rows: usize, q_enable: Column<Fixed>, q_first: Column<Fixed>, @@ -60,9 +59,9 @@ pub struct Config<F> { impl<F: Field> Config<F> { pub(crate) fn configure( meta: &mut ConstraintSystem<F>, - randomness: Expression<F>, bytecode_table: BytecodeTable, keccak_table: KeccakTable, + challenges: Challenges<Expression<F>>, ) -> Self { let q_enable = meta.fixed_column(); let q_first = meta.fixed_column(); @@ -157,9 +156,9 @@ impl<F: Field> Config<F> { ), ); cb.require_equal( - "hash_input_rlc := hash_input_rlc_prev * randomness + byte", + "hash_input_rlc := hash_input_rlc_prev * challenges.keccak_input + byte", meta.query_advice(hash_input_rlc, Rotation::cur()), - meta.query_advice(hash_input_rlc, Rotation::prev()) * randomness.clone() + meta.query_advice(hash_input_rlc, Rotation::prev()) * challenges.keccak_input() + meta.query_advice(value, Rotation::cur()), ); cb.require_equal( @@ -362,7 +361,6 @@ impl<F: Field> Config<F> { }); Config { - randomness, minimum_rows: meta.minimum_rows(), q_enable, q_first, @@ -388,9 +386,9 @@ impl<F: Field> Config<F> { layouter: &mut impl Layouter<F>, size: usize, witness: &[UnrolledBytecode<F>], - randomness: F, + challenges: &Challenges<Value<F>>, ) -> Result<(), Error> { - self.assign_internal(layouter, size, witness, randomness, true) + self.assign_internal(layouter, size, witness, challenges, true) } pub(crate) fn assign_internal( @@ -398,7 +396,7 @@ impl<F: Field> Config<F> { layouter: &mut impl Layouter<F>, size: usize, witness: &[UnrolledBytecode<F>], - randomness: F, + challenges: &Challenges<Value<F>>, fail_fast: bool, ) -> Result<(), Error> { let push_rindex_is_zero_chip = IsZeroChip::construct(self.push_rindex_is_zero.clone()); @@ -416,7 +414,7 @@ impl<F: Field> Config<F> { // Run over all the bytes let mut push_rindex = 0; let mut byte_push_size = 0; - let mut hash_input_rlc = F::zero(); + let mut hash_input_rlc = challenges.keccak_input().map(|_| F::zero()); let code_length = F::from(bytecode.bytes.len() as u64); for (idx, row) in bytecode.rows.iter().enumerate() { if fail_fast && offset > last_row_offset { @@ -428,6 +426,13 @@ impl<F: Field> Config<F> { return Err(Error::Synthesis); } + let code_hash = challenges.evm_word().map(|challenge| { + RandomLinearCombination::<F, 32>::random_linear_combine( + row.code_hash.to_le_bytes(), + challenge, + ) + }); + // Track which byte is an opcode and which is push // data let is_code = push_rindex == 0; @@ -438,7 +443,11 @@ impl<F: Field> Config<F> { } else { push_rindex - 1 }; - hash_input_rlc = hash_input_rlc * randomness + row.value; + hash_input_rlc.as_mut().zip(challenges.keccak_input()).map( + |(hash_input_rlc, challenge)| { + *hash_input_rlc = *hash_input_rlc * challenge + row.value + }, + ); } // Set the data for this row @@ -450,7 +459,7 @@ impl<F: Field> Config<F> { offset, true, offset == last_row_offset, - row.code_hash, + code_hash, row.tag, row.index, row.is_code, @@ -478,13 +487,13 @@ impl<F: Field> Config<F> { idx, idx < last_row_offset, idx == last_row_offset, - F::zero(), + Value::known(F::zero()), F::from(BytecodeFieldTag::Padding as u64), F::zero(), F::one(), F::zero(), 0, - F::zero(), + Value::known(F::zero()), F::zero(), F::zero(), true, @@ -507,13 +516,13 @@ impl<F: Field> Config<F> { offset: usize, enable: bool, last: bool, - code_hash: F, + code_hash: Value<F>, tag: F, index: F, is_code: F, value: F, push_rindex: u64, - hash_input_rlc: F, + hash_input_rlc: Value<F>, code_length: F, byte_push_size: F, is_final: bool, @@ -542,14 +551,12 @@ impl<F: Field> Config<F> { } // Advices - for (name, column, value) in &[ - ("code_hash", self.bytecode_table.code_hash, code_hash), + for (name, column, value) in [ ("tag", self.bytecode_table.tag, tag), ("index", self.bytecode_table.index, index), ("is_code", self.bytecode_table.is_code, is_code), ("value", self.bytecode_table.value, value), ("push_rindex", self.push_rindex, F::from(push_rindex)), - ("hash_input_rlc", self.hash_input_rlc, hash_input_rlc), ("code_length", self.code_length, code_length), ("byte_push_size", self.byte_push_size, byte_push_size), ("is_final", self.is_final, F::from(is_final as u64)), @@ -557,9 +564,20 @@ impl<F: Field> Config<F> { ] { region.assign_advice( || format!("assign {} {}", name, offset), - *column, + column, + offset, + || Value::known(value), + )?; + } + for (name, column, value) in [ + ("code_hash", self.bytecode_table.code_hash, code_hash), + ("hash_input_rlc", self.hash_input_rlc, hash_input_rlc), + ] { + region.assign_advice( + || format!("assign {} {}", name, offset), + column, offset, - || Value::known(*value), + || value, )?; } @@ -604,8 +622,8 @@ impl<F: Field> Config<F> { } /// Get unrolled bytecode from raw bytes -pub fn unroll<F: Field>(bytes: Vec<u8>, randomness: F) -> UnrolledBytecode<F> { - let code_hash = keccak(&bytes[..], randomness); +pub fn unroll<F: Field>(bytes: Vec<u8>) -> UnrolledBytecode<F> { + let code_hash = keccak(&bytes[..]); let mut rows = vec![BytecodeRow::<F> { code_hash, tag: F::from(BytecodeFieldTag::Length as u64), @@ -647,13 +665,10 @@ fn get_push_size(byte: u8) -> u64 { } } -fn keccak<F: Field>(msg: &[u8], randomness: F) -> F { +fn keccak(msg: &[u8]) -> Word { let mut keccak = Keccak::default(); keccak.update(msg); - RandomLinearCombination::<F, 32>::random_linear_combine( - Word::from_big_endian(keccak.digest().as_slice()).to_le_bytes(), - randomness, - ) + Word::from_big_endian(keccak.digest().as_slice()) } fn into_words(message: &[u8]) -> Vec<u64> { @@ -684,7 +699,6 @@ mod tests { #[test] fn bytecode_unrolling() { let k = 10; - let randomness = get_randomness(); let mut rows = vec![]; let mut bytecode = Bytecode::default(); // First add all non-push bytes, which should all be seen as code @@ -692,7 +706,7 @@ mod tests { if !is_push(byte) { bytecode.write(byte, true); rows.push(BytecodeRow { - code_hash: Fr::zero(), + code_hash: Word::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(true as u64), @@ -705,7 +719,7 @@ mod tests { let data_byte = OpcodeId::PUSH32.as_u8(); bytecode.push(n, Word::from_little_endian(&vec![data_byte; n][..])); rows.push(BytecodeRow { - code_hash: Fr::zero(), + code_hash: Word::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(true as u64), @@ -713,7 +727,7 @@ mod tests { }); for _ in 0..n { rows.push(BytecodeRow { - code_hash: Fr::zero(), + code_hash: Word::zero(), tag: Fr::from(BytecodeFieldTag::Byte as u64), index: Fr::from(rows.len() as u64), is_code: Fr::from(false as u64), @@ -722,7 +736,7 @@ mod tests { } } // Set the code_hash of the complete bytecode in the rows - let code_hash = keccak(&bytecode.to_vec()[..], randomness); + let code_hash = keccak(&bytecode.to_vec()[..]); for row in rows.iter_mut() { row.code_hash = code_hash; } @@ -737,7 +751,7 @@ mod tests { }, ); // Unroll the bytecode - let unrolled = unroll(bytecode.to_vec(), randomness); + let unrolled = unroll(bytecode.to_vec()); // Check if the bytecode was unrolled correctly assert_eq!( UnrolledBytecode { @@ -747,83 +761,54 @@ mod tests { unrolled, ); // Verify the unrolling in the circuit - test_bytecode_circuit_unrolled(k, vec![unrolled], randomness, true); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unrolled], true); } /// Tests a fully empty circuit #[test] fn bytecode_empty() { let k = 9; - let randomness: Fr = get_randomness(); - test_bytecode_circuit_unrolled(k, vec![unroll(vec![], randomness)], randomness, true); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unroll(vec![])], true); } #[test] fn bytecode_simple() { let k = 9; - let randomness: Fr = get_randomness(); - let bytecodes = vec![ - unroll(vec![7u8], randomness), - unroll(vec![6u8], randomness), - unroll(vec![5u8], randomness), - ]; - test_bytecode_circuit_unrolled(k, bytecodes, randomness, true); + let bytecodes = vec![unroll(vec![7u8]), unroll(vec![6u8]), unroll(vec![5u8])]; + test_bytecode_circuit_unrolled::<Fr>(k, bytecodes, true); } /// Tests a fully full circuit #[test] fn bytecode_full() { let k = 9; - let randomness: Fr = get_randomness(); - test_bytecode_circuit_unrolled( - k, - vec![unroll(vec![7u8; 2usize.pow(k) - 7], randomness)], - randomness, - true, - ); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unroll(vec![7u8; 2usize.pow(k) - 7])], true); } /// Tests a circuit with incomplete bytecode #[test] fn bytecode_incomplete() { let k = 9; - let randomness: Fr = get_randomness(); - test_bytecode_circuit_unrolled( - k, - vec![unroll(vec![7u8; 2usize.pow(k) + 1], randomness)], - randomness, - false, - ); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unroll(vec![7u8; 2usize.pow(k) + 1])], false); } /// Tests multiple bytecodes in a single circuit #[test] fn bytecode_push() { let k = 9; - let randomness: Fr = get_randomness(); - test_bytecode_circuit_unrolled( + test_bytecode_circuit_unrolled::<Fr>( k, vec![ - unroll(vec![], randomness), - unroll(vec![OpcodeId::PUSH32.as_u8()], randomness), - unroll( - vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()], - randomness, - ), - unroll( - vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()], - randomness, - ), - unroll( - vec![ - OpcodeId::ADD.as_u8(), - OpcodeId::PUSH32.as_u8(), - OpcodeId::ADD.as_u8(), - ], - randomness, - ), + unroll(vec![]), + unroll(vec![OpcodeId::PUSH32.as_u8()]), + unroll(vec![OpcodeId::PUSH32.as_u8(), OpcodeId::ADD.as_u8()]), + unroll(vec![OpcodeId::ADD.as_u8(), OpcodeId::PUSH32.as_u8()]), + unroll(vec![ + OpcodeId::ADD.as_u8(), + OpcodeId::PUSH32.as_u8(), + OpcodeId::ADD.as_u8(), + ]), ], - randomness, true, ); } @@ -832,29 +817,28 @@ mod tests { #[test] fn bytecode_invalid_hash_data() { let k = 9; - let randomness = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, randomness); - test_bytecode_circuit_unrolled(k, vec![unrolled.clone()], randomness, true); + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unrolled.clone()], true); // Change the code_hash on the first position { let mut invalid = unrolled.clone(); - invalid.rows[0].code_hash += Fr::from(1u64); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + invalid.rows[0].code_hash += Word::one(); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Change the code_hash on another position { let mut invalid = unrolled.clone(); - invalid.rows[4].code_hash += Fr::from(1u64); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + invalid.rows[4].code_hash += Word::one(); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Change all the hashes so it doesn't match the keccak lookup code_hash { let mut invalid = unrolled; for row in invalid.rows.iter_mut() { - row.code_hash = Fr::one(); + row.code_hash = Word::one(); } - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } } @@ -863,23 +847,22 @@ mod tests { #[ignore] fn bytecode_invalid_index() { let k = 9; - let randomness: Fr = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, randomness); - test_bytecode_circuit_unrolled(k, vec![unrolled.clone()], randomness, true); + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unrolled.clone()], true); // Start the index at 1 { let mut invalid = unrolled.clone(); for row in invalid.rows.iter_mut() { row.index += Fr::one(); } - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Don't increment an index once { let mut invalid = unrolled; invalid.rows.last_mut().unwrap().index -= Fr::one(); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } } @@ -887,27 +870,26 @@ mod tests { #[test] fn bytecode_invalid_byte_data() { let k = 9; - let randomness = get_randomness(); let bytecode = vec![8u8, 2, 3, 8, 9, 7, 128]; - let unrolled = unroll(bytecode, randomness); - test_bytecode_circuit_unrolled(k, vec![unrolled.clone()], randomness, true); + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unrolled.clone()], true); // Change the first byte { let mut invalid = unrolled.clone(); invalid.rows[1].value = Fr::from(9u64); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Change a byte on another position { let mut invalid = unrolled.clone(); invalid.rows[5].value = Fr::from(6u64); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Set a byte value out of range { let mut invalid = unrolled; invalid.rows[3].value = Fr::from(256u64); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } } @@ -915,7 +897,6 @@ mod tests { #[test] fn bytecode_invalid_is_code() { let k = 9; - let randomness = get_randomness(); let bytecode = vec![ OpcodeId::ADD.as_u8(), OpcodeId::PUSH1.as_u8(), @@ -925,25 +906,25 @@ mod tests { OpcodeId::ADD.as_u8(), OpcodeId::PUSH6.as_u8(), ]; - let unrolled = unroll(bytecode, randomness); - test_bytecode_circuit_unrolled(k, vec![unrolled.clone()], randomness, true); + let unrolled = unroll(bytecode); + test_bytecode_circuit_unrolled::<Fr>(k, vec![unrolled.clone()], true); // Mark the 3rd byte as code (is push data from the first PUSH1) { let mut invalid = unrolled.clone(); invalid.rows[3].is_code = Fr::one(); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Mark the 4rd byte as data (is code) { let mut invalid = unrolled.clone(); invalid.rows[4].is_code = Fr::zero(); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } // Mark the 7th byte as code (is data for the PUSH7) { let mut invalid = unrolled; invalid.rows[7].is_code = Fr::one(); - test_bytecode_circuit_unrolled(k, vec![invalid], randomness, false); + test_bytecode_circuit_unrolled::<Fr>(k, vec![invalid], false); } } } diff --git a/zkevm-circuits/src/bytecode_circuit/dev.rs b/zkevm-circuits/src/bytecode_circuit/dev.rs index 59805dbffa..2608b1e4fa 100644 --- a/zkevm-circuits/src/bytecode_circuit/dev.rs +++ b/zkevm-circuits/src/bytecode_circuit/dev.rs @@ -1,27 +1,21 @@ use super::bytecode_unroller::{unroll, Config, UnrolledBytecode}; use crate::table::{BytecodeTable, KeccakTable}; -use crate::util::power_of_randomness_from_instance; +use crate::util::Challenges; use eth_types::Field; use halo2_proofs::{ circuit::Layouter, plonk::{ConstraintSystem, Error}, }; use halo2_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit}; -use std::vec; #[derive(Default)] pub(crate) struct BytecodeCircuitTester<F: Field> { bytecodes: Vec<UnrolledBytecode<F>>, size: usize, - randomness: F, -} - -fn get_randomness<F: Field>() -> F { - F::from(123456) } impl<F: Field> Circuit<F> for BytecodeCircuitTester<F> { - type Config = Config<F>; + type Config = (Config<F>, Challenges); type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -30,29 +24,35 @@ impl<F: Field> Circuit<F> for BytecodeCircuitTester<F> { fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config { let bytecode_table = BytecodeTable::construct(meta); - - let randomness = power_of_randomness_from_instance::<_, 1>(meta); let keccak_table = KeccakTable::construct(meta); + let challenges = Challenges::construct(meta); - Config::configure(meta, randomness[0].clone(), bytecode_table, keccak_table) + let config = { + let challenges = challenges.exprs(meta); + Config::configure(meta, bytecode_table, keccak_table, challenges) + }; + + (config, challenges) } fn synthesize( &self, - config: Self::Config, + (config, challenges): Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), Error> { + let challenges = challenges.values(&mut layouter); + config.load(&mut layouter)?; config.keccak_table.dev_load( &mut layouter, self.bytecodes.iter().map(|b| &b.bytes), - self.randomness, + &challenges, )?; config.assign_internal( &mut layouter, self.size, &self.bytecodes, - self.randomness, + &challenges, false, )?; Ok(()) @@ -61,30 +61,19 @@ impl<F: Field> Circuit<F> for BytecodeCircuitTester<F> { impl<F: Field> BytecodeCircuitTester<F> { /// Verify that the selected bytecode fulfills the circuit - pub fn verify_raw(k: u32, bytecodes: Vec<Vec<u8>>, randomness: F) { - let unrolled: Vec<_> = bytecodes - .iter() - .map(|b| unroll(b.clone(), randomness)) - .collect(); - Self::verify(k, unrolled, randomness, true); + pub fn verify_raw(k: u32, bytecodes: Vec<Vec<u8>>) { + let unrolled: Vec<_> = bytecodes.iter().map(|b| unroll(b.clone())).collect(); + Self::verify(k, unrolled, true); } - pub(crate) fn verify( - k: u32, - bytecodes: Vec<UnrolledBytecode<F>>, - randomness: F, - success: bool, - ) { + pub(crate) fn verify(k: u32, bytecodes: Vec<UnrolledBytecode<F>>, success: bool) { let circuit = BytecodeCircuitTester::<F> { bytecodes, size: 2usize.pow(k), - randomness, }; - let num_rows = 1 << k; const NUM_BLINDING_ROWS: usize = 7 - 1; - let instance = vec![vec![randomness; num_rows - NUM_BLINDING_ROWS]]; - let prover = MockProver::<F>::run(k, &circuit, instance).unwrap(); + let prover = MockProver::<F>::run(k, &circuit, Vec::new()).unwrap(); let result = prover.verify(); if let Err(failures) = &result { for failure in failures.iter() { @@ -96,31 +85,24 @@ impl<F: Field> BytecodeCircuitTester<F> { } /// Test bytecode circuit with raw bytecode -pub fn test_bytecode_circuit<F: Field>(k: u32, bytecodes: Vec<Vec<u8>>, randomness: F) { - let unrolled: Vec<_> = bytecodes - .iter() - .map(|b| unroll(b.clone(), randomness)) - .collect(); - test_bytecode_circuit_unrolled(k, unrolled, randomness, true); +pub fn test_bytecode_circuit<F: Field>(k: u32, bytecodes: Vec<Vec<u8>>) { + let unrolled: Vec<_> = bytecodes.iter().map(|b| unroll::<F>(b.clone())).collect(); + test_bytecode_circuit_unrolled(k, unrolled, true); } /// Test bytecode circuit with unrolled bytecode pub fn test_bytecode_circuit_unrolled<F: Field>( k: u32, bytecodes: Vec<UnrolledBytecode<F>>, - randomness: F, success: bool, ) { let circuit = BytecodeCircuitTester::<F> { bytecodes, size: 2usize.pow(k), - randomness, }; - let num_rows = 1 << k; const NUM_BLINDING_ROWS: usize = 7 - 1; - let instance = vec![vec![randomness; num_rows - NUM_BLINDING_ROWS]]; - let prover = MockProver::<F>::run(k, &circuit, instance).unwrap(); + let prover = MockProver::<F>::run(k, &circuit, Vec::new()).unwrap(); let result = prover.verify(); if let Err(failures) = &result { for failure in failures.iter() { diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index c29612e115..c5139c55c4 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -741,7 +741,7 @@ pub mod dev { use crate::{ evm_circuit::witness::Block, table::{BytecodeTable, RwTable, TxTable}, - util::power_of_randomness_from_instance, + util::{power_of_randomness_from_instance, Challenges}, }; #[derive(Clone)] @@ -810,6 +810,8 @@ pub mod dev { config: Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), halo2_proofs::plonk::Error> { + let challenges = Challenges::mock(Value::known(self.randomness)); + config .tx_table .load(&mut layouter, &self.block.txs, self.randomness)?; @@ -822,7 +824,7 @@ pub mod dev { config.bytecode_table.load( &mut layouter, self.block.bytecodes.values(), - self.randomness, + &challenges, )?; config .copy_circuit diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index a03c169d04..6198838ffc 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -154,12 +154,12 @@ pub mod test { use crate::{ evm_circuit::{table::FixedTableTag, witness::Block, EvmCircuit}, table::{BlockTable, BytecodeTable, CopyTable, KeccakTable, RwTable, TxTable}, - util::power_of_randomness_from_instance, + util::{power_of_randomness_from_instance, Challenges}, }; use bus_mapping::evm::OpcodeId; use eth_types::{Field, Word}; use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, dev::{MockProver, VerifyFailure}, plonk::{Circuit, ConstraintSystem, Error}, }; @@ -285,6 +285,8 @@ pub mod test { config: Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), Error> { + let challenges = Challenges::mock(Value::known(self.block.randomness)); + config .evm_circuit .load_fixed_table(&mut layouter, self.fixed_table_tags.clone())?; @@ -302,7 +304,7 @@ pub mod test { config.bytecode_table.load( &mut layouter, self.block.bytecodes.values(), - self.block.randomness, + &challenges, )?; config .block_table @@ -311,11 +313,9 @@ pub mod test { .copy_table .load(&mut layouter, &self.block, self.block.randomness)?; - config.keccak_table.dev_load( - &mut layouter, - &self.block.sha3_inputs, - self.block.randomness, - )?; + config + .keccak_table + .dev_load(&mut layouter, &self.block.sha3_inputs, &challenges)?; config .evm_circuit diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index ccc9c74a09..1e3d8d371f 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -11,7 +11,7 @@ use crate::{ witness::{Block, Call, ExecStep, Transaction}, }, table::LookupTable, - util::Expr, + util::{query_expression, Expr}, }; use eth_types::Field; use halo2_proofs::{ @@ -559,11 +559,8 @@ impl<F: Field> ExecutionConfig<F> { let gadget = G::configure(&mut cb); // Enforce the step height for this opcode - let mut num_rows_until_next_step_next = 0.expr(); - meta.create_gate("query num rows", |meta| { - num_rows_until_next_step_next = - meta.query_advice(num_rows_until_next_step, Rotation::next()); - vec![0.expr()] + let num_rows_until_next_step_next = query_expression(meta, |meta| { + meta.query_advice(num_rows_until_next_step, Rotation::next()) }); cb.require_equal( "num_rows_until_next_step_next := height - 1", diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index e047182690..af93434667 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -3,7 +3,7 @@ use crate::{ param::{LOOKUP_CONFIG, N_BYTES_MEMORY_ADDRESS}, table::Table, }, - util::Expr, + util::{query_expression, Expr}, }; use eth_types::U256; use halo2_proofs::{ @@ -218,7 +218,7 @@ impl<F: FieldExt> CellManager<F> { let width = advices.len(); let mut cells = Vec::with_capacity(height * width); let mut columns = Vec::with_capacity(width); - meta.create_gate("Query rows for step", |meta| { + query_expression(meta, |meta| { for c in 0..width { for r in 0..height { cells.push(Cell::new(meta, advices[c], height_offset + r)); @@ -230,7 +230,6 @@ impl<F: FieldExt> CellManager<F> { expr: cells[c * height].expr(), }); } - vec![0.expr()] }); // Mark columns used for lookups diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 8b1137a6cf..d75ea352f3 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -57,11 +57,11 @@ use crate::bytecode_circuit::bytecode_unroller::{ use crate::evm_circuit::{table::FixedTableTag, EvmCircuit}; use crate::table::{BlockTable, BytecodeTable, CopyTable, MptTable, RwTable, TxTable}; -use crate::util::power_of_randomness_from_instance; +use crate::util::{power_of_randomness_from_instance, Challenges}; use crate::witness::Block; use eth_types::Field; use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{Circuit, ConstraintSystem, Error}, }; @@ -197,9 +197,9 @@ impl<F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize> Circuit<F> ), bytecode_circuit: BytecodeConfig::configure( meta, - power_of_randomness[0].clone(), bytecode_table, keccak_table, + Challenges::mock(power_of_randomness[0].clone()), ), keccak_circuit, } @@ -210,6 +210,8 @@ impl<F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize> Circuit<F> config: Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), Error> { + let challenges = Challenges::mock(Value::known(self.block.randomness)); + // --- EVM Circuit --- config .evm_circuit @@ -244,14 +246,14 @@ impl<F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize> Circuit<F> .block .bytecodes .iter() - .map(|(_, b)| unroll(b.bytes.clone(), self.block.randomness)) + .map(|(_, b)| unroll(b.bytes.clone())) .collect(); config.bytecode_circuit.load(&mut layouter)?; config.bytecode_circuit.assign( &mut layouter, self.bytecode_size, &bytecodes, - self.block.randomness, + &challenges, )?; // --- Keccak Table --- config.keccak_circuit.load(&mut layouter)?; diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 1aab0ef0b8..1bc5ac1747 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -3,6 +3,7 @@ use crate::copy_circuit::number_or_hash_to_field; use crate::evm_circuit::util::{rlc, RandomLinearCombination}; use crate::impl_expr; +use crate::util::Challenges; use crate::witness::{ Block, BlockContext, Bytecode, MptUpdateRow, MptUpdates, Rw, RwMap, RwRow, Transaction, }; @@ -17,6 +18,7 @@ use halo2_proofs::{ use halo2_proofs::{circuit::Layouter, plonk::*, poly::Rotation}; use itertools::Itertools; use keccak256::plain::Keccak; +use std::array; use strum_macros::{EnumCount, EnumIter}; /// Trait used for dynamic tables. Used to get an automatic implementation of @@ -539,12 +541,14 @@ pub struct BytecodeTable { impl BytecodeTable { /// Construct a new BytecodeTable pub fn construct<F: Field>(meta: &mut ConstraintSystem<F>) -> Self { + let [tag, index, is_code, value] = array::from_fn(|_| meta.advice_column()); + let code_hash = meta.advice_column_in(SecondPhase); Self { - code_hash: meta.advice_column(), - tag: meta.advice_column(), - index: meta.advice_column(), - is_code: meta.advice_column(), - value: meta.advice_column(), + code_hash, + tag, + index, + is_code, + value, } } @@ -554,7 +558,7 @@ impl BytecodeTable { &self, layouter: &mut impl Layouter<F>, bytecodes: impl IntoIterator<Item = &'a Bytecode> + Clone, - randomness: F, + challenges: &Challenges<Value<F>>, ) -> Result<(), Error> { layouter.assign_region( || "bytecode table", @@ -572,13 +576,13 @@ impl BytecodeTable { let bytecode_table_columns = self.columns(); for bytecode in bytecodes.clone() { - for row in bytecode.table_assignments(randomness) { + for row in bytecode.table_assignments(challenges) { for (column, value) in bytecode_table_columns.iter().zip_eq(row) { region.assign_advice( || format!("bytecode table row {}", offset), *column, offset, - || Value::known(value), + || value, )?; } offset += 1; @@ -711,25 +715,37 @@ impl KeccakTable { pub fn construct<F: Field>(meta: &mut ConstraintSystem<F>) -> Self { Self { is_enabled: meta.advice_column(), - input_rlc: meta.advice_column(), + input_rlc: meta.advice_column_in(SecondPhase), input_len: meta.advice_column(), - output_rlc: meta.advice_column(), + output_rlc: meta.advice_column_in(SecondPhase), } } /// Generate the keccak table assignments from a byte array input. - pub fn assignments<F: Field>(input: &[u8], randomness: F) -> Vec<[F; 4]> { - let input_rlc: F = rlc::value(input.iter().rev(), randomness); + pub fn assignments<F: Field>( + input: &[u8], + challenges: &Challenges<Value<F>>, + ) -> Vec<[Value<F>; 4]> { + let input_rlc = challenges + .keccak_input() + .map(|challenge| rlc::value(input.iter().rev(), challenge)); let input_len = F::from(input.len() as u64); let mut keccak = Keccak::default(); keccak.update(input); let output = keccak.digest(); - let output_rlc = RandomLinearCombination::<F, 32>::random_linear_combine( - Word::from_big_endian(output.as_slice()).to_le_bytes(), - randomness, - ); + let output_rlc = challenges.evm_word().map(|challenge| { + RandomLinearCombination::<F, 32>::random_linear_combine( + Word::from_big_endian(output.as_slice()).to_le_bytes(), + challenge, + ) + }); - vec![[F::one(), input_rlc, input_len, output_rlc]] + vec![[ + Value::known(F::one()), + input_rlc, + Value::known(input_len), + output_rlc, + ]] } /// Assign a table row for keccak table @@ -756,7 +772,7 @@ impl KeccakTable { &self, layouter: &mut impl Layouter<F>, inputs: impl IntoIterator<Item = &'a Vec<u8>> + Clone, - randomness: F, + challenges: &Challenges<Value<F>>, ) -> Result<(), Error> { layouter.assign_region( || "keccak table", @@ -774,14 +790,14 @@ impl KeccakTable { let keccak_table_columns = self.columns(); for input in inputs.clone() { - for row in Self::assignments(input, randomness) { + for row in Self::assignments(input, challenges) { // let mut column_index = 0; for (column, value) in keccak_table_columns.iter().zip_eq(row) { region.assign_advice( || format!("keccak table row {}", offset), *column, offset, - || Value::known(value), + || value, )?; } offset += 1; diff --git a/zkevm-circuits/src/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index 8d5f94bd06..10df199dc2 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -7,7 +7,9 @@ pub mod sign_verify; use crate::table::{KeccakTable, TxFieldTag, TxTable}; -use crate::util::{power_of_randomness_from_instance, random_linear_combine_word as rlc}; +use crate::util::{ + power_of_randomness_from_instance, random_linear_combine_word as rlc, Challenges, +}; use bus_mapping::circuit_input_builder::keccak_inputs_tx_circuit; use eth_types::{ sign_types::SignData, @@ -307,6 +309,8 @@ impl<F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize> Circuit<F> config: Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), Error> { + let challenges = Challenges::mock(Value::known(self.randomness)); + config.sign_verify.load_range(&mut layouter)?; self.assign(&config, &mut layouter)?; config.keccak_table.dev_load( @@ -315,7 +319,7 @@ impl<F: Field, const MAX_TXS: usize, const MAX_CALLDATA: usize> Circuit<F> error!("keccak_inputs_tx_circuit error: {:?}", e); Error::Synthesis })?, - self.randomness, + &challenges, ) } } diff --git a/zkevm-circuits/src/tx_circuit/sign_verify.rs b/zkevm-circuits/src/tx_circuit/sign_verify.rs index 6d9bd15bd2..ccf56e7c6a 100644 --- a/zkevm-circuits/src/tx_circuit/sign_verify.rs +++ b/zkevm-circuits/src/tx_circuit/sign_verify.rs @@ -631,7 +631,7 @@ fn pub_key_hash_to_address<F: Field>(pk_hash: &[u8]) -> F { #[cfg(test)] mod sign_verify_tests { use super::*; - use crate::util::power_of_randomness_from_instance; + use crate::util::{power_of_randomness_from_instance, Challenges}; use bus_mapping::circuit_input_builder::keccak_inputs_sign_verify; use eth_types::sign_types::sign; use halo2_proofs::arithmetic::Field as HaloField; @@ -688,6 +688,8 @@ mod sign_verify_tests { config: Self::Config, mut layouter: impl Layouter<F>, ) -> Result<(), Error> { + let challenges = Challenges::mock(Value::known(self.randomness)); + self.sign_verify.assign( &config.sign_verify, &mut layouter, @@ -697,7 +699,7 @@ mod sign_verify_tests { config.sign_verify.keccak_table.dev_load( &mut layouter, &keccak_inputs_sign_verify(&self.signatures), - self.randomness, + &challenges, )?; config.sign_verify.load_range(&mut layouter)?; Ok(()) diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index 5a650be787..3416b15191 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -1,20 +1,33 @@ //! Common utility traits and functions. -use eth_types::Field; use halo2_proofs::{ - plonk::{ConstraintSystem, Expression}, + arithmetic::FieldExt, + circuit::{Layouter, Value}, + plonk::{Challenge, ConstraintSystem, Expression, FirstPhase, VirtualCells}, poly::Rotation, }; pub use gadgets::util::Expr; -pub(crate) fn random_linear_combine_word<F: Field>(bytes: [u8; 32], randomness: F) -> F { +pub(crate) fn query_expression<F: FieldExt, T>( + meta: &mut ConstraintSystem<F>, + mut f: impl FnMut(&mut VirtualCells<F>) -> T, +) -> T { + let mut expr = None; + meta.create_gate("Query expression", |meta| { + expr = Some(f(meta)); + Some(0.expr()) + }); + expr.unwrap() +} + +pub(crate) fn random_linear_combine_word<F: FieldExt>(bytes: [u8; 32], randomness: F) -> F { crate::evm_circuit::util::Word::random_linear_combine(bytes, randomness) } /// Query N instances at current rotation and return their expressions. This /// function is used to get the power of randomness (passed as /// instances) in our tests. -pub fn power_of_randomness_from_instance<F: Field, const N: usize>( +pub fn power_of_randomness_from_instance<F: FieldExt, const N: usize>( meta: &mut ConstraintSystem<F>, ) -> [Expression<F>; N] { // This gate is used just to get the array of expressions from the power of @@ -23,14 +36,62 @@ pub fn power_of_randomness_from_instance<F: Field, const N: usize>( // expression everywhere. The gate itself doesn't add any constraints. let columns = [(); N].map(|_| meta.instance_column()); - let mut power_of_randomness = None; + query_expression(meta, |meta| { + columns.map(|column| meta.query_instance(column, Rotation::cur())) + }) +} + +/// All challenges used in `SuperCircuit`. +#[derive(Clone, Copy, Debug)] +pub struct Challenges<T = Challenge> { + evm_word: T, + keccak_input: T, +} - meta.create_gate("power of randomness from instance", |meta| { - power_of_randomness = - Some(columns.map(|column| meta.query_instance(column, Rotation::cur()))); +impl Challenges { + /// Construct `Challenges` by allocating challenges in specific phases. + pub fn construct<F: FieldExt>(meta: &mut ConstraintSystem<F>) -> Self { + Self { + evm_word: meta.challenge_usable_after(FirstPhase), + keccak_input: meta.challenge_usable_after(FirstPhase), + } + } - [0.expr()] - }); + /// Returns `Expression` of challenges from `ConstraintSystem`. + pub fn exprs<F: FieldExt>(&self, meta: &mut ConstraintSystem<F>) -> Challenges<Expression<F>> { + let [evm_word, keccak_input] = query_expression(meta, |meta| { + [self.evm_word, self.keccak_input].map(|challenge| meta.query_challenge(challenge)) + }); + Challenges { + evm_word, + keccak_input, + } + } + + /// Returns `Value` of challenges from `Layouter`. + pub fn values<F: FieldExt>(&self, layouter: &mut impl Layouter<F>) -> Challenges<Value<F>> { + Challenges { + evm_word: layouter.get_challenge(self.evm_word), + keccak_input: layouter.get_challenge(self.keccak_input), + } + } +} + +impl<T: Clone> Challenges<T> { + /// Returns challenge of `evm_word`. + pub fn evm_word(&self) -> T { + self.evm_word.clone() + } + + /// Returns challenge of `keccak_input`. + pub fn keccak_input(&self) -> T { + self.keccak_input.clone() + } - power_of_randomness.unwrap() + pub(crate) fn mock(challenge: T) -> Self { + Self { + evm_word: challenge.clone(), + keccak_input: challenge, + } + } } diff --git a/zkevm-circuits/src/witness/bytecode.rs b/zkevm-circuits/src/witness/bytecode.rs index 1c700d2e44..1acb6261ac 100644 --- a/zkevm-circuits/src/witness/bytecode.rs +++ b/zkevm-circuits/src/witness/bytecode.rs @@ -1,8 +1,11 @@ use bus_mapping::evm::OpcodeId; use eth_types::{Field, ToLittleEndian, Word}; +use halo2_proofs::circuit::Value; use sha3::{Digest, Keccak256}; -use crate::{evm_circuit::util::RandomLinearCombination, table::BytecodeFieldTag}; +use crate::{ + evm_circuit::util::RandomLinearCombination, table::BytecodeFieldTag, util::Challenges, +}; /// Bytecode #[derive(Clone, Debug)] @@ -21,18 +24,22 @@ impl Bytecode { } /// Assignments for bytecode table - pub fn table_assignments<F: Field>(&self, randomness: F) -> Vec<[F; 5]> { + pub fn table_assignments<F: Field>( + &self, + challenges: &Challenges<Value<F>>, + ) -> Vec<[Value<F>; 5]> { let n = 1 + self.bytes.len(); let mut rows = Vec::with_capacity(n); - let hash = - RandomLinearCombination::random_linear_combine(self.hash.to_le_bytes(), randomness); + let hash = challenges.evm_word().map(|challenge| { + RandomLinearCombination::random_linear_combine(self.hash.to_le_bytes(), challenge) + }); rows.push([ hash, - F::from(BytecodeFieldTag::Length as u64), - F::zero(), - F::zero(), - F::from(self.bytes.len() as u64), + Value::known(F::from(BytecodeFieldTag::Length as u64)), + Value::known(F::zero()), + Value::known(F::zero()), + Value::known(F::from(self.bytes.len() as u64)), ]); let mut push_data_left = 0; @@ -46,10 +53,10 @@ impl Bytecode { } rows.push([ hash, - F::from(BytecodeFieldTag::Byte as u64), - F::from(idx as u64), - F::from(is_code as u64), - F::from(*byte as u64), + Value::known(F::from(BytecodeFieldTag::Byte as u64)), + Value::known(F::from(idx as u64)), + Value::known(F::from(is_code as u64)), + Value::known(F::from(*byte as u64)), ]) } rows