diff --git a/halo2-base/src/gates/circuit/builder.rs b/halo2-base/src/gates/circuit/builder.rs index 15c92c84..4176657b 100644 --- a/halo2-base/src/gates/circuit/builder.rs +++ b/halo2-base/src/gates/circuit/builder.rs @@ -222,6 +222,7 @@ impl BaseCircuitBuilder { for lm in &mut self.lookup_manager { lm.clear(); } + self.assigned_instances.clear(); } /// Returns a mutable reference to the [Context] of a gate thread. Spawns a new thread for the given phase, if none exists. diff --git a/halo2-base/src/gates/flex_gate/threads/single_phase.rs b/halo2-base/src/gates/flex_gate/threads/single_phase.rs index dd8b30d5..fe916c59 100644 --- a/halo2-base/src/gates/flex_gate/threads/single_phase.rs +++ b/halo2-base/src/gates/flex_gate/threads/single_phase.rs @@ -265,6 +265,7 @@ pub fn assign_with_constraints( row_offset += 1; } } + println!("break points: {:?}", break_points); break_points } diff --git a/hashes/zkevm/Cargo.toml b/hashes/zkevm/Cargo.toml index 845ca67a..b0a16f8c 100644 --- a/hashes/zkevm/Cargo.toml +++ b/hashes/zkevm/Cargo.toml @@ -12,7 +12,7 @@ itertools = "0.11" lazy_static = "1.4" log = "0.4" num-bigint = { version = "0.4" } -halo2-base = { path = "../../halo2-base", default-features = false } +halo2-base = { path = "../../halo2-base", default-features = true } rayon = "1.7" sha3 = "0.10.8" pse-poseidon = { git = "https://github.com/axiom-crypto/pse-poseidon.git" } diff --git a/hashes/zkevm/src/keccak/coprocessor/circuit.rs b/hashes/zkevm/src/keccak/coprocessor/circuit/leaf.rs similarity index 68% rename from hashes/zkevm/src/keccak/coprocessor/circuit.rs rename to hashes/zkevm/src/keccak/coprocessor/circuit/leaf.rs index ea2ff58c..9d8bb47d 100644 --- a/hashes/zkevm/src/keccak/coprocessor/circuit.rs +++ b/hashes/zkevm/src/keccak/coprocessor/circuit/leaf.rs @@ -1,48 +1,58 @@ use std::cell::RefCell; -use super::{ - encode::{encode_inputs_from_keccak_fs, encode_native_input}, - param::*, -}; use crate::{ - keccak::native::{ - keccak_packed_multi::get_num_keccak_f, param::*, witness::multi_keccak, KeccakAssignedRow, - KeccakCircuitConfig, KeccakConfigParams, + keccak::{ + coprocessor::{ + encode::{ + get_words_to_witness_multipilers, num_poseidon_absorb_per_keccak_f, + num_word_per_witness, + }, + output::{dummy_circuit_output, KeccakCircuitOutput}, + param::*, + }, + native::{ + param::*, witness::multi_keccak, KeccakAssignedRow, KeccakCircuitConfig, + KeccakConfigParams, + }, }, util::eth_types::Field, }; use getset::{CopyGetters, Getters}; use halo2_base::{ gates::{ - circuit::{builder::BaseCircuitBuilder, BaseCircuitParams, BaseConfig}, + circuit::{builder::BaseCircuitBuilder, BaseCircuitParams, BaseConfig, MaybeRangeConfig}, GateInstructions, RangeInstructions, }, halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner}, plonk::{Circuit, ConstraintSystem, Error}, }, - poseidon::hasher::{spec::OptimizedPoseidonSpec, PoseidonCompactOutput, PoseidonHasher}, + poseidon::hasher::{ + spec::OptimizedPoseidonSpec, PoseidonCompactInput, PoseidonCompactOutput, PoseidonHasher, + }, + safe_types::SafeTypeChip, + virtual_region::manager::VirtualRegionManager, AssignedValue, Context, }; use itertools::Itertools; -use sha3::{Digest, Keccak256}; -/// Keccak Coprocessor Circuit +/// Keccak Coprocessor Leaf Circuit #[derive(Getters)] -pub struct KeccakCoprocessorCircuit { +pub struct KeccakCoprocessorLeafCircuit { inputs: Vec>, /// Parameters of this circuit. The same parameters always construct the same circuit. #[getset(get = "pub")] - params: KeccakCoprocessorCircuitParams, + params: KeccakCoprocessorLeafCircuitParams, + witness_gen_only: bool, base_circuit_builder: RefCell>, hasher: RefCell>, } -/// Parameters of KeccakCoprocessorCircuit. +/// Parameters of KeccakCoprocessorLeafCircuit. #[derive(Default, Clone, CopyGetters)] -pub struct KeccakCoprocessorCircuitParams { +pub struct KeccakCoprocessorLeafCircuitParams { /// This circuit has 2^k rows. #[getset(get_copy = "pub")] k: usize, @@ -64,8 +74,8 @@ pub struct KeccakCoprocessorCircuitParams { keccak_circuit_params: KeccakConfigParams, } -impl KeccakCoprocessorCircuitParams { - /// Create a new KeccakCoprocessorCircuitParams. +impl KeccakCoprocessorLeafCircuitParams { + /// Create a new KeccakCoprocessorLeafCircuitParams. pub fn new( k: usize, num_unusable_row: usize, @@ -90,18 +100,18 @@ impl KeccakCoprocessorCircuitParams { } } -/// Circuit::Config for Keccak Coprocessor Circuit. +/// Circuit::Config for Keccak Coprocessor Leaf Circuit. #[derive(Clone)] -pub struct KeccakCoprocessorConfig { +pub struct KeccakCoprocessorLeafConfig { base_circuit_params: BaseCircuitParams, base_circuit_config: BaseConfig, keccak_circuit_config: KeccakCircuitConfig, } -impl Circuit for KeccakCoprocessorCircuit { - type Config = KeccakCoprocessorConfig; +impl Circuit for KeccakCoprocessorLeafCircuit { + type Config = KeccakCoprocessorLeafConfig; type FloorPlanner = SimpleFloorPlanner; - type Params = KeccakCoprocessorCircuitParams; + type Params = KeccakCoprocessorLeafCircuitParams; fn params(&self) -> Self::Params { self.params.clone() @@ -147,27 +157,20 @@ impl Circuit for KeccakCoprocessorCircuit { }, )?; + // self.base_circuit_builder.replace(BaseCircuitBuilder::new(self.witness_gen_only)); + self.base_circuit_builder.borrow_mut().clear(); + self.base_circuit_builder.borrow_mut().set_params(config.base_circuit_params); // Base circuit witness generation. - let loaded_keccak_fs = self.load_keccak_assigned_rows(keccak_assigned_rows); - self.generate_base_circuit_phase0_witnesses(&loaded_keccak_fs); + // let loaded_keccak_fs = self.load_keccak_assigned_rows(keccak_assigned_rows); + // self.generate_base_circuit_phase0_witnesses(&loaded_keccak_fs); + self.generate_base_circuit_phase0_witnesses(&[]); self.base_circuit_builder.borrow().synthesize(config.base_circuit_config, layouter)?; Ok(()) } } -/// Witnesses to be exposed as circuit outputs. -#[derive(Clone, Copy, PartialEq, Debug)] -pub struct KeccakCircuitOutput { - /// Key for App circuits to lookup keccak hash. - pub key: E, - /// Low 128 bits of Keccak hash. - pub hash_lo: E, - /// High 128 bits of Keccak hash. - pub hash_hi: E, -} - /// Witnesses of a keccak_f which are necessary to be loaded into halo2-lib. pub(crate) struct LoadedKeccakF { // bytes_left of the first row of the first round of this keccak_f. This could be used to determine the length of the input. @@ -180,16 +183,16 @@ pub(crate) struct LoadedKeccakF { pub(crate) hash_hi: AssignedValue, } -impl KeccakCoprocessorCircuit { - /// Create a new KeccakComputationCircuit - pub fn new(inputs: Vec>, params: KeccakCoprocessorCircuitParams) -> Self { +impl KeccakCoprocessorLeafCircuit { + /// Create a new KeccakCoprocessorLeafCircuit + pub fn new(inputs: Vec>, params: KeccakCoprocessorLeafCircuitParams) -> Self { Self::new_impl(inputs, params, false) } /// Implementation of Self::new. witness_gen_only can be customized. fn new_impl( inputs: Vec>, - params: KeccakCoprocessorCircuitParams, + params: KeccakCoprocessorLeafCircuitParams, witness_gen_only: bool, ) -> Self { let mut base_circuit_builder = BaseCircuitBuilder::new(witness_gen_only) @@ -210,6 +213,7 @@ impl KeccakCoprocessorCircuit { Self { inputs, params, + witness_gen_only, base_circuit_builder: RefCell::new(base_circuit_builder), hasher: RefCell::new(poseidon_hasher), } @@ -218,12 +222,13 @@ impl KeccakCoprocessorCircuit { /// Simulate witness generation of the base circuit to determine BaseCircuitParams because the number of columns /// of the base circuit can only be known after witness generation. pub fn calculate_base_circuit_params( - params: KeccakCoprocessorCircuitParams, + params: KeccakCoprocessorLeafCircuitParams, ) -> BaseCircuitParams { // Create a simulation circuit to calculate base circuit parameters. let simulation_circuit = Self::new_impl(vec![], params.clone(), false); - let loaded_keccak_fs = simulation_circuit.mock_load_keccak_assigned_rows(); - simulation_circuit.generate_base_circuit_phase0_witnesses(&loaded_keccak_fs); + // let loaded_keccak_fs = simulation_circuit.mock_load_keccak_assigned_rows(); + // simulation_circuit.generate_base_circuit_phase0_witnesses(&loaded_keccak_fs); + simulation_circuit.generate_base_circuit_phase0_witnesses(&[]); let base_circuit_params = simulation_circuit .base_circuit_builder @@ -282,7 +287,7 @@ impl KeccakCoprocessorCircuit { /// Generate phase0 witnesses of the base circuit. fn generate_base_circuit_phase0_witnesses(&self, loaded_keccak_fs: &[LoadedKeccakF]) { - let circuit_final_outputs; + // let circuit_final_outputs; { let range_chip = self.base_circuit_builder.borrow().range_chip(); let mut base_circuit_builder_mut = self.base_circuit_builder.borrow_mut(); @@ -290,16 +295,16 @@ impl KeccakCoprocessorCircuit { let mut hasher = self.hasher.borrow_mut(); hasher.initialize_consts(ctx, range_chip.gate()); - let lookup_key_per_keccak_f = - encode_inputs_from_keccak_fs(ctx, &range_chip, &hasher, loaded_keccak_fs); - circuit_final_outputs = Self::generate_circuit_final_outputs( - ctx, - &range_chip, - &lookup_key_per_keccak_f, - loaded_keccak_fs, - ); + // let lookup_key_per_keccak_f = + // encode_inputs_from_keccak_fs(ctx, &range_chip, &hasher, loaded_keccak_fs); + // circuit_final_outputs = Self::generate_circuit_final_outputs( + // ctx, + // &range_chip, + // &lookup_key_per_keccak_f, + // loaded_keccak_fs, + // ); } - self.publish_outputs(&circuit_final_outputs); + // self.publish_outputs(&circuit_final_outputs); } /// Combine lookup keys and Keccak results to generate final outputs of the circuit. @@ -385,46 +390,85 @@ impl KeccakCoprocessorCircuit { } } -/// Return circuit outputs of the specified Keccak corprocessor circuit for a specified input. -pub fn multi_inputs_to_circuit_outputs( - inputs: &[Vec], - capacity: usize, -) -> Vec> { - assert!(u128::BITS <= F::CAPACITY); - let mut outputs = - inputs.iter().flat_map(|input| input_to_circuit_outputs::(input)).collect_vec(); - assert!(outputs.len() <= capacity); - outputs.resize(capacity, dummy_circuit_output()); - outputs -} - -/// Return corresponding circuit outputs of a native input in bytes. An logical input could produce multiple -/// outputs. The last one is the lookup key and hash of the input. Other outputs are paddings which are the lookup -/// key and hash of an empty input. -pub fn input_to_circuit_outputs(bytes: &[u8]) -> Vec> { - assert!(u128::BITS <= F::CAPACITY); - let len = bytes.len(); - let num_keccak_f = get_num_keccak_f(len); - - let mut output = Vec::with_capacity(num_keccak_f); - output.resize(num_keccak_f - 1, dummy_circuit_output()); - - let key = encode_native_input(bytes); - let hash = Keccak256::digest(bytes); - let hash_lo = F::from_u128(u128::from_be_bytes(hash[16..].try_into().unwrap())); - let hash_hi = F::from_u128(u128::from_be_bytes(hash[..16].try_into().unwrap())); - output.push(KeccakCircuitOutput { key, hash_lo, hash_hi }); +/// Encode raw inputs from Keccak circuit witnesses into lookup keys. +/// +/// Each element in the return value corrresponds to a Keccak chunk. If is_final = true, this element is the lookup key of the corresponding logical input. +fn encode_inputs_from_keccak_fs( + ctx: &mut Context, + range_chip: &impl RangeInstructions, + initialized_hasher: &PoseidonHasher, + loaded_keccak_fs: &[LoadedKeccakF], +) -> Vec> { + // Circuit parameters + let num_poseidon_absorb_per_keccak_f = num_poseidon_absorb_per_keccak_f::(); + let num_word_per_witness = num_word_per_witness::(); + let num_witness_per_keccak_f = POSEIDON_RATE * num_poseidon_absorb_per_keccak_f; + + // Constant witnesses + let rate_witness = ctx.load_constant(F::from(POSEIDON_RATE as u64)); + let one_witness = ctx.load_constant(F::ONE); + let zero_witness = ctx.load_zero(); + let multiplier_witnesses = ctx.assign_witnesses(get_words_to_witness_multipilers::()); + + let compact_input_len = loaded_keccak_fs.len() * num_poseidon_absorb_per_keccak_f; + let mut compact_inputs = Vec::with_capacity(compact_input_len); + let mut is_final_last = one_witness; + for loaded_keccak_f in loaded_keccak_fs { + // If this keccak_f is the last of a logical input. + let is_final = loaded_keccak_f.is_final; + let mut poseidon_absorb_data = Vec::with_capacity(num_witness_per_keccak_f); + + // First witness of a keccak_f: [, word_values[0], word_values[1], ...] + // is the length of the input if this is the first keccak_f of a logical input. Otherwise 0. + let mut words = Vec::with_capacity(num_word_per_witness); + let len_word = + range_chip.gate().select(ctx, loaded_keccak_f.bytes_left, zero_witness, is_final_last); + words.push(len_word); + words.extend_from_slice(&loaded_keccak_f.word_values[0..(num_word_per_witness - 1)]); + let first_witness = range_chip.gate().inner_product( + ctx, + multiplier_witnesses.clone(), + words.iter().map(|w| halo2_base::QuantumCell::Existing(*w)), + ); + poseidon_absorb_data.push(first_witness); + + // Turn every num_word_per_witness words later into a witness. + for words in &loaded_keccak_f + .word_values + .into_iter() + .skip(num_word_per_witness - 1) + .chunks(num_word_per_witness) + { + let mut words = words.collect_vec(); + words.resize(num_word_per_witness, zero_witness); + let witness = range_chip.gate().inner_product( + ctx, + multiplier_witnesses.clone(), + words.iter().map(|w| halo2_base::QuantumCell::Existing(*w)), + ); + poseidon_absorb_data.push(witness); + } + // Pad 0s to make sure poseidon_absorb_data.len() % RATE == 0. + poseidon_absorb_data.resize(num_witness_per_keccak_f, zero_witness); + for (i, poseidon_absorb) in poseidon_absorb_data.chunks(POSEIDON_RATE).enumerate() { + compact_inputs.push(PoseidonCompactInput::new( + poseidon_absorb.try_into().unwrap(), + if i + 1 == num_poseidon_absorb_per_keccak_f { + SafeTypeChip::unsafe_to_bool(is_final) + } else { + SafeTypeChip::unsafe_to_bool(zero_witness) + }, + rate_witness, + )); + } + is_final_last = is_final; + } - output -} + let compact_outputs = initialized_hasher.hash_compact_input(ctx, range_chip, &compact_inputs); -/// Return the dummy circuit output for padding. -pub fn dummy_circuit_output() -> KeccakCircuitOutput { - assert!(u128::BITS <= F::CAPACITY); - let key = encode_native_input(&[]); - // Output of Keccak256::digest is big endian. - let hash = Keccak256::digest([]); - let hash_lo = F::from_u128(u128::from_be_bytes(hash[16..].try_into().unwrap())); - let hash_hi = F::from_u128(u128::from_be_bytes(hash[..16].try_into().unwrap())); - KeccakCircuitOutput { key, hash_lo, hash_hi } + compact_outputs + .into_iter() + .skip(num_poseidon_absorb_per_keccak_f - 1) + .step_by(num_poseidon_absorb_per_keccak_f) + .collect_vec() } diff --git a/hashes/zkevm/src/keccak/coprocessor/circuit/mod.rs b/hashes/zkevm/src/keccak/coprocessor/circuit/mod.rs new file mode 100644 index 00000000..6a66fc13 --- /dev/null +++ b/hashes/zkevm/src/keccak/coprocessor/circuit/mod.rs @@ -0,0 +1,3 @@ +pub mod leaf; +#[cfg(test)] +mod tests; diff --git a/hashes/zkevm/src/keccak/coprocessor/circuit/tests/leaf.rs b/hashes/zkevm/src/keccak/coprocessor/circuit/tests/leaf.rs new file mode 100644 index 00000000..975c3c7b --- /dev/null +++ b/hashes/zkevm/src/keccak/coprocessor/circuit/tests/leaf.rs @@ -0,0 +1,131 @@ +use crate::{ + halo2_proofs::{ + dev::MockProver, + halo2curves::bn256::Fr, + halo2curves::bn256::{Bn256, 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, + }, + }, + keccak::coprocessor::{ + circuit::leaf::{KeccakCoprocessorLeafCircuit, KeccakCoprocessorLeafCircuitParams}, + output::multi_inputs_to_circuit_outputs, + }, +}; + +use halo2_base::utils::testing::{check_proof_with_instances, gen_proof_with_instances}; +use itertools::Itertools; +use rand_core::OsRng; + +#[test] +fn test_mock_leaf_circuit() { + let k: usize = 18; + let num_unusable_row: usize = 109; + let lookup_bits: usize = 4; + let capacity: usize = 10; + let publish_raw_outputs: bool = true; + + let inputs = vec![ + (0u8..200).collect::>(), + vec![], + (0u8..1).collect::>(), + (0u8..135).collect::>(), + (0u8..136).collect::>(), + (0u8..200).collect::>(), + ]; + + let params = KeccakCoprocessorLeafCircuitParams::new( + k, + num_unusable_row, + lookup_bits, + capacity, + publish_raw_outputs, + ); + let circuit = KeccakCoprocessorLeafCircuit::::new(inputs.clone(), params.clone()); + let circuit_outputs = multi_inputs_to_circuit_outputs::(&inputs, params.capacity()); + + let instances = vec![ + circuit_outputs.iter().map(|o| o.key).collect_vec(), + circuit_outputs.iter().map(|o| o.hash_lo).collect_vec(), + circuit_outputs.iter().map(|o| o.hash_hi).collect_vec(), + ]; + + let prover = MockProver::::run(k as u32, &circuit, instances).unwrap(); + prover.assert_satisfied(); +} + +#[test] +fn test_prove_leaf_circuit() { + let k: usize = 15; + let num_unusable_row: usize = 109; + let lookup_bits: usize = 4; + let capacity: usize = 1; + let publish_raw_outputs: bool = true; + + let inputs = vec![ + // (0u8..200).collect::>(), + // vec![], + (0u8..1).collect::>(), + // (0u8..135).collect::>(), + // (0u8..136).collect::>(), + // (0u8..200).collect::>(), + ]; + + let circuit_params = KeccakCoprocessorLeafCircuitParams::new( + k.clone(), + num_unusable_row, + lookup_bits, + capacity, + publish_raw_outputs, + ); + let circuit = KeccakCoprocessorLeafCircuit::::new(inputs.clone(), circuit_params.clone()); + + let _circuit_outputs = + multi_inputs_to_circuit_outputs::(&inputs, circuit_params.capacity()); + let instances: Vec> = vec![ + vec![], + vec![], + vec![], + // circuit_outputs.iter().map(|o| o.key).collect_vec(), + // circuit_outputs.iter().map(|o| o.hash_lo).collect_vec(), + // circuit_outputs.iter().map(|o| o.hash_hi).collect_vec(), + ]; + + // let prover = MockProver::::run(k as u32, &circuit, instances.clone()).unwrap(); + // prover.assert_satisfied(); + + println!("After mock prover"); + + let _ = env_logger::builder().is_test(true).try_init(); + + let params = ParamsKZG::::setup(k as u32, OsRng); + + let vk = keygen_vk(¶ms, &circuit).unwrap(); + // println!("{:?}", &circuit.base_circuit_builder().borrow().clone()); + let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); + // println!("{:?}", &circuit.base_circuit_builder().borrow().clone()); + + let circuit = KeccakCoprocessorLeafCircuit::::new(inputs.clone(), circuit_params.clone()); + let proof = gen_proof_with_instances( + ¶ms, + &pk, + circuit, + instances.iter().map(|f| f.as_slice()).collect_vec().as_slice(), + ); + check_proof_with_instances( + ¶ms, + pk.get_vk(), + &proof, + instances.iter().map(|f| f.as_slice()).collect_vec().as_slice(), + true, + ); +} diff --git a/hashes/zkevm/src/keccak/coprocessor/circuit/tests/mod.rs b/hashes/zkevm/src/keccak/coprocessor/circuit/tests/mod.rs new file mode 100644 index 00000000..4d6a7f45 --- /dev/null +++ b/hashes/zkevm/src/keccak/coprocessor/circuit/tests/mod.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +pub mod leaf; diff --git a/hashes/zkevm/src/keccak/coprocessor/encode.rs b/hashes/zkevm/src/keccak/coprocessor/encode.rs index eff9eef3..59d16ca7 100644 --- a/hashes/zkevm/src/keccak/coprocessor/encode.rs +++ b/hashes/zkevm/src/keccak/coprocessor/encode.rs @@ -1,14 +1,8 @@ -use halo2_base::{ - gates::{GateInstructions, RangeInstructions}, - poseidon::hasher::{PoseidonCompactInput, PoseidonCompactOutput, PoseidonHasher}, - safe_types::SafeTypeChip, - Context, -}; use itertools::Itertools; use crate::{keccak::native::param::*, util::eth_types::Field}; -use super::{circuit::LoadedKeccakF, param::*}; +use super::param::*; // TODO: Abstract this module into a trait for all coprocessor circuits. @@ -77,89 +71,6 @@ pub fn encode_native_input(bytes: &[u8]) -> F { // TODO: Add a function to encode a VarLenBytes into a lookup key. The function should be used by App Circuits. -/// Encode raw inputs from Keccak circuit witnesses into lookup keys. -/// -/// Each element in the return value corrresponds to a Keccak chunk. If is_final = true, this element is the lookup key of the corresponding logical input. -pub(crate) fn encode_inputs_from_keccak_fs( - ctx: &mut Context, - range_chip: &impl RangeInstructions, - initialized_hasher: &PoseidonHasher, - loaded_keccak_fs: &[LoadedKeccakF], -) -> Vec> { - // Circuit parameters - let num_poseidon_absorb_per_keccak_f = num_poseidon_absorb_per_keccak_f::(); - let num_word_per_witness = num_word_per_witness::(); - let num_witness_per_keccak_f = POSEIDON_RATE * num_poseidon_absorb_per_keccak_f; - - // Constant witnesses - let rate_witness = ctx.load_constant(F::from(POSEIDON_RATE as u64)); - let one_witness = ctx.load_constant(F::ONE); - let zero_witness = ctx.load_zero(); - let multiplier_witnesses = ctx.assign_witnesses(get_words_to_witness_multipilers::()); - - let compact_input_len = loaded_keccak_fs.len() * num_poseidon_absorb_per_keccak_f; - let mut compact_inputs = Vec::with_capacity(compact_input_len); - let mut is_final_last = one_witness; - for loaded_keccak_f in loaded_keccak_fs { - // If this keccak_f is the last of a logical input. - let is_final = loaded_keccak_f.is_final; - let mut poseidon_absorb_data = Vec::with_capacity(num_witness_per_keccak_f); - - // First witness of a keccak_f: [, word_values[0], word_values[1], ...] - // is the length of the input if this is the first keccak_f of a logical input. Otherwise 0. - let mut words = Vec::with_capacity(num_word_per_witness); - let len_word = - range_chip.gate().select(ctx, loaded_keccak_f.bytes_left, zero_witness, is_final_last); - words.push(len_word); - words.extend_from_slice(&loaded_keccak_f.word_values[0..(num_word_per_witness - 1)]); - let first_witness = range_chip.gate().inner_product( - ctx, - multiplier_witnesses.clone(), - words.iter().map(|w| halo2_base::QuantumCell::Existing(*w)), - ); - poseidon_absorb_data.push(first_witness); - - // Turn every num_word_per_witness words later into a witness. - for words in &loaded_keccak_f - .word_values - .into_iter() - .skip(num_word_per_witness - 1) - .chunks(num_word_per_witness) - { - let mut words = words.collect_vec(); - words.resize(num_word_per_witness, zero_witness); - let witness = range_chip.gate().inner_product( - ctx, - multiplier_witnesses.clone(), - words.iter().map(|w| halo2_base::QuantumCell::Existing(*w)), - ); - poseidon_absorb_data.push(witness); - } - // Pad 0s to make sure poseidon_absorb_data.len() % RATE == 0. - poseidon_absorb_data.resize(num_witness_per_keccak_f, zero_witness); - for (i, poseidon_absorb) in poseidon_absorb_data.chunks(POSEIDON_RATE).enumerate() { - compact_inputs.push(PoseidonCompactInput::new( - poseidon_absorb.try_into().unwrap(), - if i + 1 == num_poseidon_absorb_per_keccak_f { - SafeTypeChip::unsafe_to_bool(is_final) - } else { - SafeTypeChip::unsafe_to_bool(zero_witness) - }, - rate_witness, - )); - } - is_final_last = is_final; - } - - let compact_outputs = initialized_hasher.hash_compact_input(ctx, range_chip, &compact_inputs); - - compact_outputs - .into_iter() - .skip(num_poseidon_absorb_per_keccak_f - 1) - .step_by(num_poseidon_absorb_per_keccak_f) - .collect_vec() -} - /// Number of Keccak words in each encoded input for Poseidon. pub fn num_word_per_witness() -> usize { (F::CAPACITY as usize) / NUM_BITS_PER_WORD @@ -181,7 +92,7 @@ pub fn num_poseidon_absorb_per_keccak_f() -> usize { (num_witness_per_keccak_f::() - 1) / POSEIDON_RATE + 1 } -fn get_words_to_witness_multipilers() -> Vec { +pub(crate) fn get_words_to_witness_multipilers() -> Vec { let num_word_per_witness = num_word_per_witness::(); let mut multiplier_f = F::ONE; let mut multipliers = Vec::with_capacity(num_word_per_witness); diff --git a/hashes/zkevm/src/keccak/coprocessor/mod.rs b/hashes/zkevm/src/keccak/coprocessor/mod.rs index 61e2e74c..135a96b4 100644 --- a/hashes/zkevm/src/keccak/coprocessor/mod.rs +++ b/hashes/zkevm/src/keccak/coprocessor/mod.rs @@ -2,6 +2,8 @@ pub mod circuit; /// Module of encoding raw inputs to coprocessor circuit lookup keys. pub mod encode; +/// Module of Keccak coprocessor circuit output. +pub mod output; /// Module of Keccak coprocessor circuit constant parameters. pub mod param; #[cfg(test)] diff --git a/hashes/zkevm/src/keccak/coprocessor/output.rs b/hashes/zkevm/src/keccak/coprocessor/output.rs new file mode 100644 index 00000000..32949913 --- /dev/null +++ b/hashes/zkevm/src/keccak/coprocessor/output.rs @@ -0,0 +1,59 @@ +use super::encode::encode_native_input; +use crate::{keccak::native::keccak_packed_multi::get_num_keccak_f, util::eth_types::Field}; +use itertools::Itertools; +use sha3::{Digest, Keccak256}; + +/// Witnesses to be exposed as circuit outputs. +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct KeccakCircuitOutput { + /// Key for App circuits to lookup keccak hash. + pub key: E, + /// Low 128 bits of Keccak hash. + pub hash_lo: E, + /// High 128 bits of Keccak hash. + pub hash_hi: E, +} + +/// Return circuit outputs of the specified Keccak corprocessor circuit for a specified input. +pub fn multi_inputs_to_circuit_outputs( + inputs: &[Vec], + capacity: usize, +) -> Vec> { + assert!(u128::BITS <= F::CAPACITY); + let mut outputs = + inputs.iter().flat_map(|input| input_to_circuit_outputs::(input)).collect_vec(); + assert!(outputs.len() <= capacity); + outputs.resize(capacity, dummy_circuit_output()); + outputs +} + +/// Return corresponding circuit outputs of a native input in bytes. An logical input could produce multiple +/// outputs. The last one is the lookup key and hash of the input. Other outputs are paddings which are the lookup +/// key and hash of an empty input. +pub fn input_to_circuit_outputs(bytes: &[u8]) -> Vec> { + assert!(u128::BITS <= F::CAPACITY); + let len = bytes.len(); + let num_keccak_f = get_num_keccak_f(len); + + let mut output = Vec::with_capacity(num_keccak_f); + output.resize(num_keccak_f - 1, dummy_circuit_output()); + + let key = encode_native_input(bytes); + let hash = Keccak256::digest(bytes); + let hash_lo = F::from_u128(u128::from_be_bytes(hash[16..].try_into().unwrap())); + let hash_hi = F::from_u128(u128::from_be_bytes(hash[..16].try_into().unwrap())); + output.push(KeccakCircuitOutput { key, hash_lo, hash_hi }); + + output +} + +/// Return the dummy circuit output for padding. +pub fn dummy_circuit_output() -> KeccakCircuitOutput { + assert!(u128::BITS <= F::CAPACITY); + let key = encode_native_input(&[]); + // Output of Keccak256::digest is big endian. + let hash = Keccak256::digest([]); + let hash_lo = F::from_u128(u128::from_be_bytes(hash[16..].try_into().unwrap())); + let hash_hi = F::from_u128(u128::from_be_bytes(hash[..16].try_into().unwrap())); + KeccakCircuitOutput { key, hash_lo, hash_hi } +} diff --git a/hashes/zkevm/src/keccak/coprocessor/tests/mod.rs b/hashes/zkevm/src/keccak/coprocessor/tests/mod.rs index 0bfb7cc2..d47b2026 100644 --- a/hashes/zkevm/src/keccak/coprocessor/tests/mod.rs +++ b/hashes/zkevm/src/keccak/coprocessor/tests/mod.rs @@ -1,12 +1,13 @@ -use super::circuit::{ - multi_inputs_to_circuit_outputs, KeccakCoprocessorCircuit, KeccakCoprocessorCircuitParams, +use super::{ + circuit::leaf::{KeccakCoprocessorLeafCircuit, KeccakCoprocessorLeafCircuitParams}, + output::multi_inputs_to_circuit_outputs, }; use halo2_base::halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use itertools::Itertools; #[cfg(test)] -mod circuit; +mod output; #[test] fn test_mock_leaf_circuit() { @@ -25,14 +26,14 @@ fn test_mock_leaf_circuit() { (0u8..200).collect::>(), ]; - let params = KeccakCoprocessorCircuitParams::new( + let params = KeccakCoprocessorLeafCircuitParams::new( k, num_unusable_row, lookup_bits, capacity, publish_raw_outputs, ); - let circuit = KeccakCoprocessorCircuit::::new(inputs.clone(), params.clone()); + let circuit = KeccakCoprocessorLeafCircuit::::new(inputs.clone(), params.clone()); let circuit_outputs = multi_inputs_to_circuit_outputs::(&inputs, params.capacity()); let instances = vec![ diff --git a/hashes/zkevm/src/keccak/coprocessor/tests/circuit.rs b/hashes/zkevm/src/keccak/coprocessor/tests/output.rs similarity index 98% rename from hashes/zkevm/src/keccak/coprocessor/tests/circuit.rs rename to hashes/zkevm/src/keccak/coprocessor/tests/output.rs index c93b2078..c72c518c 100644 --- a/hashes/zkevm/src/keccak/coprocessor/tests/circuit.rs +++ b/hashes/zkevm/src/keccak/coprocessor/tests/output.rs @@ -1,4 +1,4 @@ -use crate::keccak::coprocessor::circuit::{ +use crate::keccak::coprocessor::output::{ dummy_circuit_output, input_to_circuit_outputs, multi_inputs_to_circuit_outputs, KeccakCircuitOutput, };