diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index b927b4d817..dc168048a2 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -58,6 +58,7 @@ mod memory_copy; mod msize; mod mul_div_mod; mod mulmod; +mod not; mod origin; mod pc; mod pop; @@ -107,6 +108,7 @@ use memory_copy::CopyToMemoryGadget; use msize::MsizeGadget; use mul_div_mod::MulDivModGadget; use mulmod::MulModGadget; +use not::NotGadget; use origin::OriginGadget; use pc::PcGadget; use pop::PopGadget; @@ -185,6 +187,7 @@ pub(crate) struct ExecutionConfig { msize_gadget: MsizeGadget, mul_div_mod_gadget: MulDivModGadget, mulmod_gadget: MulModGadget, + not_gadget: NotGadget, origin_gadget: OriginGadget, pc_gadget: PcGadget, pop_gadget: PopGadget, @@ -379,6 +382,7 @@ impl ExecutionConfig { msize_gadget: configure_gadget!(), mul_div_mod_gadget: configure_gadget!(), mulmod_gadget: configure_gadget!(), + not_gadget: configure_gadget!(), origin_gadget: configure_gadget!(), pc_gadget: configure_gadget!(), pop_gadget: configure_gadget!(), @@ -818,6 +822,7 @@ impl ExecutionConfig { ExecutionState::MSIZE => assign_exec_step!(self.msize_gadget), ExecutionState::MUL_DIV_MOD => assign_exec_step!(self.mul_div_mod_gadget), ExecutionState::MULMOD => assign_exec_step!(self.mulmod_gadget), + ExecutionState::NOT => assign_exec_step!(self.not_gadget), ExecutionState::ORIGIN => assign_exec_step!(self.origin_gadget), ExecutionState::PC => assign_exec_step!(self.pc_gadget), ExecutionState::POP => assign_exec_step!(self.pop_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/not.rs b/zkevm-circuits/src/evm_circuit/execution/not.rs new file mode 100644 index 0000000000..b0b245ab2e --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/not.rs @@ -0,0 +1,135 @@ +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + table::{FixedTableTag, Lookup}, + util::{ + common_gadget::SameContextGadget, + constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta}, + CachedRegion, Word, + }, + witness::{Block, Call, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::evm_types::OpcodeId; +use eth_types::{Field, ToLittleEndian}; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct NotGadget { + same_context: SameContextGadget, + input: Word, + output: Word, +} + +impl ExecutionGadget for NotGadget { + const NAME: &'static str = "NOT"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::NOT; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let opcode = cb.query_cell(); + + let input = cb.query_word(); + let output = cb.query_word(); + + cb.stack_pop(input.expr()); + cb.stack_push(output.expr()); + + for (i, o) in input.cells.iter().zip(output.cells.iter()) { + cb.add_lookup( + "input XOR output is all 1's", + Lookup::Fixed { + tag: FixedTableTag::BitwiseXor.expr(), + values: [i.expr(), o.expr(), 255.expr()], + }, + ); + } + + // State transition + let step_state_transition = StepStateTransition { + rw_counter: Delta(2.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(0.expr()), + gas_left: Delta(-OpcodeId::NOT.constant_gas_cost().expr()), + ..Default::default() + }; + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + input, + output, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + let [input, output] = + [step.rw_indices[0], step.rw_indices[1]].map(|idx| block.rws[idx].stack_value()); + self.input + .assign(region, offset, Some(input.to_le_bytes()))?; + self.output + .assign(region, offset, Some(output.to_le_bytes()))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::{ + evm_circuit::test::rand_word, + test_util::{get_fixed_table, run_test_circuits, BytecodeTestConfig, FixedTableConfig}, + }; + use eth_types::{bytecode, Word}; + use mock::TestContext; + + fn test_ok(a: Word) { + let bytecode = bytecode! { + PUSH32(a) + NOT + STOP + }; + let test_config = BytecodeTestConfig { + evm_circuit_lookup_tags: get_fixed_table(FixedTableConfig::Complete), + ..Default::default() + }; + + assert_eq!( + run_test_circuits( + TestContext::<1, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + Some(test_config) + ), + Ok(()) + ); + } + + #[test] + fn not_gadget_simple() { + test_ok(0.into()); + test_ok(1.into()); + test_ok(255.into()); + test_ok(256.into()); + test_ok(Word::MAX); + } + + #[test] + fn not_gadget_rand() { + let a = rand_word(); + // the debug statement is useful for random tests so in case it fails, the + // failing example shows up in the logs. + dbg!(a); + test_ok(a); + } +} diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 083a68fe05..3896340559 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -1207,6 +1207,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::AND => ExecutionState::BITWISE, OpcodeId::XOR => ExecutionState::BITWISE, OpcodeId::OR => ExecutionState::BITWISE, + OpcodeId::NOT => ExecutionState::NOT, OpcodeId::POP => ExecutionState::POP, OpcodeId::PUSH32 => ExecutionState::PUSH, OpcodeId::BYTE => ExecutionState::BYTE,