diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index 5112abccd7..938ba66cd7 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -996,13 +996,60 @@ pub struct EcMulOp { impl Default for EcMulOp { fn default() -> Self { let p = G1Affine::generator(); - let s = Fr::from_raw([2, 0, 0, 0]); + let s = Fr::one(); let r = p.mul(s).into(); Self { p, s, r } } } impl EcMulOp { + /// Creates a new EcMul op given the inputs and output. + pub fn new(p: G1Affine, s: Fr, r: G1Affine) -> Self { + assert_eq!(p.mul(s), r.into()); + Self { p, s, r } + } + + /// Creates a new EcMul op given input and output bytes from a precompile call. + pub fn new_from_bytes(input: &[u8], output: &[u8]) -> Self { + let copy_bytes = |buf: &mut [u8; 32], bytes: &[u8]| { + buf.copy_from_slice(bytes); + buf.reverse(); + }; + + assert_eq!(input.len(), 96); + assert_eq!(output.len(), 64); + + let mut buf = [0u8; 32]; + + let p: G1Affine = { + copy_bytes(&mut buf, &input[0x00..0x20]); + Fq::from_bytes(&buf).and_then(|x| { + copy_bytes(&mut buf, &input[0x20..0x40]); + Fq::from_bytes(&buf).and_then(|y| G1Affine::from_xy(x, y)) + }) + } + .unwrap(); + + let s = Fr::from_raw(Word::from_big_endian(&input[0x40..0x60]).0); + + let r_specified: G1Affine = { + copy_bytes(&mut buf, &output[0x00..0x20]); + Fq::from_bytes(&buf).and_then(|x| { + copy_bytes(&mut buf, &output[0x20..0x40]); + Fq::from_bytes(&buf).and_then(|y| G1Affine::from_xy(x, y)) + }) + } + .unwrap(); + + assert_eq!(G1Affine::from(p.mul(s)), r_specified); + + Self { + p, + s, + r: r_specified, + } + } + /// A check on the op to tell the ECC Circuit whether or not to skip the op. pub fn skip_by_ecc_circuit(&self) -> bool { self.p.is_identity().into() || self.s.is_zero().into() diff --git a/bus-mapping/src/evm/opcodes/precompiles/ec_mul.rs b/bus-mapping/src/evm/opcodes/precompiles/ec_mul.rs new file mode 100644 index 0000000000..10aec2e97a --- /dev/null +++ b/bus-mapping/src/evm/opcodes/precompiles/ec_mul.rs @@ -0,0 +1,26 @@ +use crate::{ + circuit_input_builder::{CircuitInputStateRef, EcMulOp, ExecStep, PrecompileEvent}, + precompile::{EcMulAuxData, PrecompileAuxData}, +}; + +pub(crate) fn handle( + input_bytes: Option>, + output_bytes: Option>, + state: &mut CircuitInputStateRef, + exec_step: &mut ExecStep, +) { + let input_bytes = input_bytes.map_or(vec![0u8; 96], |mut bytes| { + bytes.resize(96, 0u8); + bytes + }); + let output_bytes = output_bytes.map_or(vec![0u8; 64], |mut bytes| { + bytes.resize(64, 0u8); + bytes + }); + + let aux_data = EcMulAuxData::new(&input_bytes, &output_bytes); + exec_step.aux_data = Some(PrecompileAuxData::EcMul(aux_data)); + + let ec_mul_op = EcMulOp::new_from_bytes(&input_bytes, &output_bytes); + state.push_precompile_event(PrecompileEvent::EcMul(ec_mul_op)); +} diff --git a/bus-mapping/src/evm/opcodes/precompiles/mod.rs b/bus-mapping/src/evm/opcodes/precompiles/mod.rs index 6744195dba..8b8b2dd52d 100644 --- a/bus-mapping/src/evm/opcodes/precompiles/mod.rs +++ b/bus-mapping/src/evm/opcodes/precompiles/mod.rs @@ -8,9 +8,11 @@ use crate::{ }; mod ec_add; +mod ec_mul; mod ecrecover; use ec_add::handle as handle_ec_add; +use ec_mul::handle as handle_ec_mul; use ecrecover::handle as handle_ecrecover; type InOutRetData = (Option>, Option>, Option>); @@ -35,6 +37,9 @@ pub fn gen_associated_ops( PrecompileCalls::Bn128Add => { handle_ec_add(input_bytes, output_bytes, state, &mut exec_step) } + PrecompileCalls::Bn128Mul => { + handle_ec_mul(input_bytes, output_bytes, state, &mut exec_step) + } _ => {} } diff --git a/bus-mapping/src/precompile.rs b/bus-mapping/src/precompile.rs index 91de8dc7d6..9ea0f4498a 100644 --- a/bus-mapping/src/precompile.rs +++ b/bus-mapping/src/precompile.rs @@ -4,6 +4,8 @@ use eth_types::{evm_types::GasCost, Address, ToBigEndian, Word}; use revm_precompile::{Precompile, Precompiles}; use strum::EnumIter; +use crate::circuit_input_builder::EcMulOp; + /// Check if address is a precompiled or not. pub fn is_precompiled(address: &Address) -> bool { Precompiles::berlin() @@ -199,6 +201,41 @@ impl EcAddAuxData { } } +/// Auxiliary data for EcMul, i.e. s * P = R +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct EcMulAuxData { + /// x co-ordinate of the point. + pub p_x: Word, + /// y co-ordinate of the point. + pub p_y: Word, + /// scalar. + pub s: Word, + /// unmodulated scalar + pub s_raw: Word, + /// x co-ordinate of the result point. + pub r_x: Word, + /// y co-ordinate of the result point. + pub r_y: Word, +} + +impl EcMulAuxData { + /// Create a new instance of EcMul auxiliary data. + pub fn new(input: &[u8], output: &[u8]) -> Self { + assert_eq!(input.len(), 96); + assert_eq!(output.len(), 64); + let ec_mul_op = EcMulOp::new_from_bytes(input, output); + + Self { + p_x: Word::from_big_endian(&input[0x00..0x20]), + p_y: Word::from_big_endian(&input[0x20..0x40]), + s: Word::from_little_endian(&ec_mul_op.s.to_bytes()), + s_raw: Word::from_big_endian(&input[0x40..0x60]), + r_x: Word::from_big_endian(&output[0x00..0x20]), + r_y: Word::from_big_endian(&output[0x20..0x40]), + } + } +} + /// Auxiliary data attached to an internal state for precompile verification. #[derive(Clone, Debug, PartialEq, Eq)] pub enum PrecompileAuxData { @@ -206,6 +243,8 @@ pub enum PrecompileAuxData { Ecrecover(EcrecoverAuxData), /// EcAdd. EcAdd(EcAddAuxData), + /// EcMul. + EcMul(EcMulAuxData), } impl Default for PrecompileAuxData { diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index f6a3d8bc9f..926f8de26b 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -203,7 +203,7 @@ use opcode_not::NotGadget; use origin::OriginGadget; use pc::PcGadget; use pop::PopGadget; -use precompiles::{EcAddGadget, EcrecoverGadget, IdentityGadget}; +use precompiles::{EcAddGadget, EcMulGadget, EcrecoverGadget, IdentityGadget}; use push::PushGadget; use return_revert::ReturnRevertGadget; use returndatacopy::ReturnDataCopyGadget; @@ -354,8 +354,7 @@ pub(crate) struct ExecutionConfig { precompile_identity_gadget: Box>, precompile_modexp_gadget: Box>, precompile_bn128add_gadget: Box>, - precompile_bn128mul_gadget: - Box>, + precompile_bn128mul_gadget: Box>, precompile_bn128pairing_gadget: Box>, precompile_blake2f_gadget: Box>, diff --git a/zkevm-circuits/src/evm_circuit/execution/mulmod.rs b/zkevm-circuits/src/evm_circuit/execution/mulmod.rs index 15a25d22e6..0420e1311a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/mulmod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/mulmod.rs @@ -32,7 +32,7 @@ pub(crate) struct MulModGadget { a_reduced: util::Word, d: util::Word, e: util::Word, - modword: ModGadget, + modword: ModGadget, mul512_left: MulAddWords512Gadget, mul512_right: MulAddWords512Gadget, n_is_zero: IsZeroGadget, diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/ec_mul.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/ec_mul.rs new file mode 100644 index 0000000000..73549ff2ea --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/ec_mul.rs @@ -0,0 +1,525 @@ +use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls}; +use eth_types::{Field, ToLittleEndian, ToScalar, U256}; +use gadgets::util::{and, not, or, Expr}; +use halo2_proofs::{ + circuit::Value, + plonk::{Error, Expression}, +}; + +use crate::{ + evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{ + common_gadget::RestoreContextGadget, + constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder}, + math_gadget::{IsZeroGadget, ModGadget}, + rlc, CachedRegion, Cell, Word, + }, + }, + table::CallContextFieldTag, + witness::{Block, Call, ExecStep, Transaction}, +}; + +// Modulus for scalar field Fr +const FR_N: [u8; 32] = [ + 0x01, 0x00, 0x00, 0xF0, 0x93, 0xF5, 0xE1, 0x43, 0x91, 0x70, 0xB9, 0x79, 0x48, 0xE8, 0x33, 0x28, + 0x5D, 0x58, 0x81, 0x81, 0xB6, 0x45, 0x50, 0xB8, 0x29, 0xA0, 0x31, 0xE1, 0x72, 0x4E, 0x64, 0x30, +]; + +#[derive(Clone, Debug)] +pub struct EcMulGadget { + point_p_x_rlc: Cell, + point_p_y_rlc: Cell, + scalar_s_raw_rlc: Cell, + point_r_x_rlc: Cell, + point_r_y_rlc: Cell, + + p_x_is_zero: IsZeroGadget, + p_y_is_zero: IsZeroGadget, + s_is_zero: IsZeroGadget, + + // Two Words (s_raw, scalar_s) that satisfies + // k * FR_N + scalar_s = s_raw + // Used for proving correct modulo by Fr + scalar_s_raw: Word, // raw + scalar_s: Word, // mod by FR_N + n: Word, // modulus + modword: ModGadget, + + is_success: Cell, + callee_address: Cell, + caller_id: Cell, + call_data_offset: Cell, + call_data_length: Cell, + return_data_offset: Cell, + return_data_length: Cell, + restore_context: RestoreContextGadget, +} + +impl ExecutionGadget for EcMulGadget { + const NAME: &'static str = "EC_MUL"; + const EXECUTION_STATE: ExecutionState = ExecutionState::PrecompileBn256ScalarMul; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + let (point_p_x_rlc, point_p_y_rlc, scalar_s_raw_rlc, point_r_x_rlc, point_r_y_rlc) = ( + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + ); + + let (scalar_s_raw, scalar_s, n) = ( + cb.query_keccak_rlc(), + cb.query_keccak_rlc(), + cb.query_keccak_rlc(), + ); + // k * n + scalar_s = s_raw + let modword = ModGadget::construct(cb, [&scalar_s_raw, &n, &scalar_s]); + + // Conditions for dealing with infinity/empty points and zero/empty scalar + let p_x_is_zero = IsZeroGadget::construct(cb, "ecMul(P_x)", point_p_x_rlc.expr()); + let p_y_is_zero = IsZeroGadget::construct(cb, "ecMul(P_y)", point_p_y_rlc.expr()); + let p_is_zero = and::expr([p_x_is_zero.expr(), p_y_is_zero.expr()]); + let s_is_zero = IsZeroGadget::construct(cb, "ecMul(s)", scalar_s.expr()); + + cb.condition(or::expr([p_is_zero.expr(), s_is_zero.expr()]), |cb| { + cb.require_equal( + "if P == 0 || s == 0, then R_x == 0", + point_r_x_rlc.expr(), + 0.expr(), + ); + cb.require_equal( + "if P == 0 || s == 0, then R_y == 0", + point_r_y_rlc.expr(), + 0.expr(), + ); + }); + + // Make sure the correct modulo test is done on actual lookup inputs + cb.require_equal( + "Scalar s (raw 32-bytes) equality", + scalar_s_raw_rlc.expr(), + scalar_s_raw.expr(), + ); + + cb.condition( + not::expr(or::expr([p_is_zero.expr(), s_is_zero.expr()])), + |cb| { + cb.ecc_table_lookup( + u64::from(PrecompileCalls::Bn128Mul).expr(), + point_p_x_rlc.expr(), + point_p_y_rlc.expr(), + // we know that `scalar_s` fits in the scalar field. So we don't compute an RLC + // of that value. Instead we use the native value. + rlc::expr( + &scalar_s + .cells + .iter() + .map(Expr::expr) + .collect::>>(), + 256.expr(), + ), + 0.expr(), + 0.expr(), + point_r_x_rlc.expr(), + point_r_y_rlc.expr(), + ); + }, + ); + + let [is_success, callee_address, caller_id, call_data_offset, call_data_length, return_data_offset, return_data_length] = + [ + CallContextFieldTag::IsSuccess, + CallContextFieldTag::CalleeAddress, + CallContextFieldTag::CallerId, + CallContextFieldTag::CallDataOffset, + CallContextFieldTag::CallDataLength, + CallContextFieldTag::ReturnDataOffset, + CallContextFieldTag::ReturnDataLength, + ] + .map(|tag| cb.call_context(None, tag)); + + cb.precompile_info_lookup( + cb.execution_state().as_u64().expr(), + callee_address.expr(), + cb.execution_state().precompile_base_gas_cost().expr(), + ); + + let restore_context = RestoreContextGadget::construct( + cb, + is_success.expr(), + 0.expr(), + 0.expr(), + 0.expr(), + 0.expr(), + 0.expr(), + ); + + Self { + point_p_x_rlc, + point_p_y_rlc, + scalar_s_raw_rlc, + point_r_x_rlc, + point_r_y_rlc, + + p_x_is_zero, + p_y_is_zero, + s_is_zero, + + scalar_s_raw, + scalar_s, + n, + modword, + + is_success, + callee_address, + caller_id, + call_data_offset, + call_data_length, + return_data_offset, + return_data_length, + restore_context, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + _tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + if let Some(PrecompileAuxData::EcMul(aux_data)) = &step.aux_data { + for (col, is_zero_gadget, word_value) in [ + (&self.point_p_x_rlc, &self.p_x_is_zero, aux_data.p_x), + (&self.point_p_y_rlc, &self.p_y_is_zero, aux_data.p_y), + ] { + let rlc_val = region.keccak_rlc(&word_value.to_le_bytes()); + col.assign(region, offset, rlc_val)?; + is_zero_gadget.assign_value(region, offset, rlc_val)?; + } + + for (col, word_value) in [ + (&self.scalar_s_raw_rlc, aux_data.s_raw), + (&self.point_r_x_rlc, aux_data.r_x), + (&self.point_r_y_rlc, aux_data.r_y), + ] { + col.assign(region, offset, region.keccak_rlc(&word_value.to_le_bytes()))?; + } + + let n = U256::from_little_endian(&FR_N); + for (col, word_value) in [(&self.scalar_s_raw, aux_data.s_raw), (&self.n, n)] { + col.assign(region, offset, Some(word_value.to_le_bytes()))?; + } + self.scalar_s + .assign(region, offset, Some(aux_data.s.to_le_bytes()))?; + self.s_is_zero.assign_value( + region, + offset, + region.keccak_rlc(&aux_data.s.to_le_bytes()), + )?; + + let (k, _) = aux_data.s_raw.div_mod(n); + self.modword + .assign(region, offset, aux_data.s_raw, n, aux_data.s, k)?; + } + + self.is_success.assign( + region, + offset, + Value::known(F::from(u64::from(call.is_success))), + )?; + self.callee_address.assign( + region, + offset, + Value::known(call.code_address.unwrap().to_scalar().unwrap()), + )?; + self.caller_id + .assign(region, offset, Value::known(F::from(call.caller_id as u64)))?; + self.call_data_offset.assign( + region, + offset, + Value::known(F::from(call.call_data_offset)), + )?; + self.call_data_length.assign( + region, + offset, + Value::known(F::from(call.call_data_length)), + )?; + self.return_data_offset.assign( + region, + offset, + Value::known(F::from(call.return_data_offset)), + )?; + self.return_data_length.assign( + region, + offset, + Value::known(F::from(call.return_data_length)), + )?; + + self.restore_context + .assign(region, offset, block, call, step, 7) + } +} + +#[cfg(test)] +mod test { + use bus_mapping::{ + evm::{OpcodeId, PrecompileCallArgs}, + precompile::PrecompileCalls, + }; + use eth_types::{bytecode, word, ToWord}; + use itertools::Itertools; + use mock::TestContext; + use rayon::iter::{ParallelBridge, ParallelIterator}; + + use crate::test_util::CircuitTestBuilder; + + lazy_static::lazy_static! { + static ref TEST_VECTOR: Vec = { + vec![ + PrecompileCallArgs { + name: "ecMul (valid input)", + // P = (2, 16059845205665218889595687631975406613746683471807856151558479858750240882195) + // s = 7 + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x23818CDE28CF4EA953FE59B1C377FAFD461039C17251FF4377313DA64AD07E13")) + PUSH1(0x20) + MSTORE + + // s + PUSH1(0x07) + PUSH1(0x40) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x60.into(), + ret_offset: 0x60.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (invalid input: point not on curve)", + // P = (2, 3) + // s = 7 + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH1(0x03) + PUSH1(0x20) + MSTORE + + // s + PUSH1(0x07) + PUSH1(0x40) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x60.into(), + ret_offset: 0x60.into(), + ret_size: 0x00.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (valid input < 96 bytes)", + // P = (2, 16059845205665218889595687631975406613746683471807856151558479858750240882195) + // s = blank + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x23818CDE28CF4EA953FE59B1C377FAFD461039C17251FF4377313DA64AD07E13")) + PUSH1(0x20) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x40.into(), + ret_offset: 0x40.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (should succeed on empty inputs)", + setup_code: bytecode! {}, + call_data_offset: 0x00.into(), + call_data_length: 0x00.into(), + ret_offset: 0x00.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (valid inputs > 96 bytes)", + // P = (2, 16059845205665218889595687631975406613746683471807856151558479858750240882195) + // s = 7 + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x23818CDE28CF4EA953FE59B1C377FAFD461039C17251FF4377313DA64AD07E13")) + PUSH1(0x20) + MSTORE + + // s + PUSH1(0x07) + PUSH1(0x40) + MSTORE + + // junk bytes, will be truncated + PUSH32(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFu128) + PUSH1(0x80) + SHL + PUSH32(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFu128) + ADD + PUSH1(0x60) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x80.into(), + ret_offset: 0x80.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (invalid input: must mod p to be valid)", + // P = (p + 1, p + 2) + // s = 7 + setup_code: bytecode! { + // p_x + PUSH32(word!("0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD48")) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD49")) + PUSH1(0x20) + MSTORE + + // s = 7 + PUSH1(0x07) + PUSH1(0x40) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x60.into(), + ret_offset: 0x60.into(), + ret_size: 0x00.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (valid: scalar larger than scalar field order n but less than base field p)", + // P = (2, 16059845205665218889595687631975406613746683471807856151558479858750240882195) + + // For bn256 (alt_bn128) scalar field: + // n = 21888242871839275222246405745257275088548364400416034343698204186575808495617 + + // Choose scalar s such that n < s < p + // s = 21888242871839275222246405745257275088548364400416034343698204186575808500000 + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x23818CDE28CF4EA953FE59B1C377FAFD461039C17251FF4377313DA64AD07E13")) + PUSH1(0x20) + MSTORE + // s + PUSH32(word!("0x30644E72E131A029B85045B68181585D2833E84879B9709143E1F593F0001120")) + PUSH1(0x40) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x60.into(), + ret_offset: 0x60.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + }, + + PrecompileCallArgs { + name: "ecMul (valid: scalar larger than base field order)", + // P = (2, 16059845205665218889595687631975406613746683471807856151558479858750240882195) + // s = 2^256 - 1 + setup_code: bytecode! { + // p_x + PUSH1(0x02) + PUSH1(0x00) + MSTORE + + // p_y + PUSH32(word!("0x23818CDE28CF4EA953FE59B1C377FAFD461039C17251FF4377313DA64AD07E13")) + PUSH1(0x20) + MSTORE + + // s + PUSH32(word!("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")) + PUSH1(0x40) + MSTORE + }, + call_data_offset: 0x00.into(), + call_data_length: 0x60.into(), + ret_offset: 0x60.into(), + ret_size: 0x40.into(), + address: PrecompileCalls::Bn128Mul.address().to_word(), + ..Default::default() + } + ] + }; + } + + #[test] + fn precompile_ec_mul_test() { + let call_kinds = vec![ + OpcodeId::CALL, + OpcodeId::STATICCALL, + OpcodeId::DELEGATECALL, + OpcodeId::CALLCODE, + ]; + + TEST_VECTOR + .iter() + .cartesian_product(&call_kinds) + .par_bridge() + .for_each(|(test_vector, &call_kind)| { + let bytecode = test_vector.with_call_op(call_kind); + + CircuitTestBuilder::new_from_test_ctx( + TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(), + ) + .run(); + }) + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs index 5909350a14..41b55fa183 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/mod.rs @@ -21,6 +21,9 @@ pub use ec_add::EcAddGadget; mod ecrecover; pub use ecrecover::EcrecoverGadget; +mod ec_mul; +pub use ec_mul::EcMulGadget; + mod identity; pub use identity::IdentityGadget; diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/modulo.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/modulo.rs index 67675bc53c..9dba5146ef 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/modulo.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/modulo.rs @@ -20,7 +20,7 @@ use halo2_proofs::plonk::Error; /// case of n=0. Unlike the usual k * n + r = a, which forces r = a when n=0, /// this equation assures that r { +pub(crate) struct ModGadget { k: util::Word, a_or_zero: util::Word, mul_add_words: MulAddWordsGadget, @@ -28,11 +28,19 @@ pub(crate) struct ModGadget { a_or_is_zero: IsZeroGadget, lt: LtWordGadget, } -impl ModGadget { +impl ModGadget { pub(crate) fn construct(cb: &mut EVMConstraintBuilder, words: [&util::Word; 3]) -> Self { let (a, n, r) = (words[0], words[1], words[2]); - let k = cb.query_word_rlc(); - let a_or_zero = cb.query_word_rlc(); + let k = if IS_EVM { + cb.query_word_rlc() + } else { + cb.query_keccak_rlc() + }; + let a_or_zero = if IS_EVM { + cb.query_word_rlc() + } else { + cb.query_keccak_rlc() + }; let n_is_zero = IsZeroGadget::construct(cb, "", sum::expr(&n.cells)); let a_or_is_zero = IsZeroGadget::construct(cb, "", sum::expr(&a_or_zero.cells)); let mul_add_words = MulAddWordsGadget::construct(cb, [&k, n, r, &a_or_zero]); @@ -99,7 +107,7 @@ mod tests { #[derive(Clone)] /// ModGadgetTestContainer: require(a % n == r) struct ModGadgetTestContainer { - mod_gadget: ModGadget, + mod_gadget: ModGadget, a: util::Word, n: util::Word, r: util::Word, @@ -110,7 +118,7 @@ mod tests { let a = cb.query_word_rlc(); let n = cb.query_word_rlc(); let r = cb.query_word_rlc(); - let mod_gadget = ModGadget::::construct(cb, [&a, &n, &r]); + let mod_gadget = ModGadget::construct(cb, [&a, &n, &r]); ModGadgetTestContainer { mod_gadget, a, diff --git a/zkevm-circuits/src/evm_circuit/util/precompile_gadget.rs b/zkevm-circuits/src/evm_circuit/util/precompile_gadget.rs index 0b865d3306..eb792a6dc0 100644 --- a/zkevm-circuits/src/evm_circuit/util/precompile_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/precompile_gadget.rs @@ -186,7 +186,35 @@ impl PrecompileGadget { r_x_rlc.expr() * r_pow_32 + r_y_rlc.expr(), ); }), - Box::new(|_cb| { /* Bn128Mul */ }), + Box::new(|cb| { + let (p_x_rlc, p_y_rlc, scalar_s_raw_rlc, r_x_rlc, r_y_rlc) = ( + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + cb.query_cell_phase2(), + ); + let (r_pow_32, r_pow_64) = { + let challenges = cb.challenges().keccak_powers_of_randomness::<16>(); + let r_pow_16 = challenges[15].clone(); + let r_pow_32 = r_pow_16.square(); + let r_pow_64 = r_pow_32.expr().square(); + (r_pow_32, r_pow_64) + }; + cb.require_equal( + "input bytes (RLC) = [ p_x | p_y | s ]", + padding_gadget.padded_rlc(), + (p_x_rlc.expr() * r_pow_64) + + (p_y_rlc.expr() * r_pow_32.expr()) + + scalar_s_raw_rlc.expr(), + ); + // RLC of output bytes always equals RLC of result elliptic curve point R. + cb.require_equal( + "output bytes (RLC) = [ r_x | r_y ]", + output_bytes_rlc.expr(), + r_x_rlc.expr() * r_pow_32 + r_y_rlc.expr(), + ); + }), Box::new(|_cb| { /* Bn128Pairing */ }), Box::new(|_cb| { /* Blake2F */ }), ]; diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 3fec110e17..1ffda15018 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -2479,6 +2479,7 @@ impl EccTable { Value::known(F::one()), fq_to_value(mul_op.p.x, keccak_rand), fq_to_value(mul_op.p.y, keccak_rand), + // no need to RLC the scalar s, since it will fit within the scalar field. Value::known(mul_op.s.into()), Value::known(F::zero()), Value::known(F::zero()),