Skip to content

Commit

Permalink
Gas Cost Checks for Precompile Calls (scroll-tech#696)
Browse files Browse the repository at this point in the history
* feat: constrain step gas cost

* identity

* constraining gas cost in precompile gadgets

* `ModExp` gas costs (scroll-tech#705)

* wip

* more wip

* exp bit length ok?

* update byte size gadget for neatly code

* witness assignment for modexp gas gadget

---------

Co-authored-by: Ho Vei <[email protected]>

* fixed tables for division by 3 and 8

* fix: byte/bit size and endianness fix for modexp

* test: test modexp with big value for exponent (for gas cost calc)

* fix: ignore gas costs check for unimplemented precompiles

---------

Co-authored-by: Ho Vei <[email protected]>
  • Loading branch information
roynalnaruto and noel2004 authored Aug 8, 2023
1 parent fb86b74 commit faeced2
Show file tree
Hide file tree
Showing 18 changed files with 615 additions and 138 deletions.
11 changes: 5 additions & 6 deletions bus-mapping/src/evm/opcodes/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
let precompile_call: PrecompileCalls = code_address.0[19].into();

// get the result of the precompile call.
let (result, contract_gas_cost) = execute_precompiled(
let (result, precompile_call_gas_cost) = execute_precompiled(
&code_address,
if args_length != 0 {
let caller_memory = &state.caller_ctx()?.memory;
Expand Down Expand Up @@ -323,7 +323,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
),
(
CallContextField::GasLeft,
(geth_steps[0].gas.0 - gas_cost - contract_gas_cost).into(),
(geth_steps[0].gas.0 - gas_cost - precompile_call_gas_cost).into(),
),
(CallContextField::MemorySize, next_memory_word_size.into()),
(
Expand Down Expand Up @@ -468,9 +468,9 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {
} else {
0
},
gas_cost + contract_gas_cost
gas_cost + precompile_call_gas_cost
);
exec_step.gas_cost = GasCost(gas_cost + contract_gas_cost);
exec_step.gas_cost = GasCost(gas_cost + precompile_call_gas_cost);
if real_cost != exec_step.gas_cost.0 {
log::warn!(
"precompile gas fixed from {} to {}, step {:?}",
Expand All @@ -482,7 +482,7 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {

// Set gas left and gas cost for precompile step.
precompile_step.gas_left = Gas(callee_gas_left);
precompile_step.gas_cost = GasCost(contract_gas_cost);
precompile_step.gas_cost = GasCost(precompile_call_gas_cost);

Ok(vec![exec_step, precompile_step])
}
Expand Down Expand Up @@ -583,7 +583,6 @@ impl<const N_ARGS: usize> Opcode for CallOpcode<N_ARGS> {

Ok(vec![exec_step])
}

// 4. insufficient balance or error depth cases.
(true, _, _) => {
for (field, value) in [
Expand Down
34 changes: 19 additions & 15 deletions eth-types/src/evm_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,29 +177,33 @@ impl GasCost {
/// Times ceil exponent byte size for the EXP instruction, EIP-158 changed
/// it from 10 to 50.
pub const EXP_BYTE_TIMES: Self = Self(50);
/// Base gas price for precompile call: Elliptic curve recover
pub const PRECOMPILE_ECRECOVER_BASE: Self = Self(3000);
/// Base gas price for precompile call: SHA256
/// Base gas cost for precompile call: Elliptic curve recover
pub const PRECOMPILE_ECRECOVER_BASE: Self = Self(3_000);
/// Base gas cost for precompile call: SHA256
pub const PRECOMPILE_SHA256_BASE: Self = Self(60);
/// Per-word gas price for SHA256
/// Per-word gas cost for SHA256
pub const PRECOMPILE_SHA256_PER_WORD: Self = Self(12);
/// Base gas price for precompile call: RIPEMD160
/// Base gas cost for precompile call: RIPEMD160
pub const PRECOMPILE_RIPEMD160_BASE: Self = Self(600);
/// Per-word gas price for RIPEMD160
/// Per-word gas cost for RIPEMD160
pub const PRECOMPILE_RIPEMD160_PER_WORD: Self = Self(120);
/// Base gas price for precompile call: Identity
/// Base gas cost for precompile call: Identity
pub const PRECOMPILE_IDENTITY_BASE: Self = Self(15);
/// Per-word gas price for Identity
/// Per-word gas cost for Identity
pub const PRECOMPILE_IDENTITY_PER_WORD: Self = Self(3);
/// Base gas price for precompile call: BN256 point addition
/// Base gas cost for precompile call: BN256 point addition
pub const PRECOMPILE_BN256ADD: Self = Self(150);
/// Base gas price for precompile call: BN256 scalar multiplication
pub const PRECOMPILE_BN256MUL: Self = Self(6000);
/// Base gas price for precompile call: BN256 pairing per point
pub const PRECOMPILE_BN256PAIRING: Self = Self(45000);
/// Base gas price for precompile call: MODEXP
/// Base gas cost for precompile call: BN256 scalar multiplication
pub const PRECOMPILE_BN256MUL: Self = Self(6_000);
/// Base gas cost for precompile call: BN256 pairing op base cost
pub const PRECOMPILE_BN256PAIRING: Self = Self(45_000);
/// Per-pair gas cost for BN256 pairing
pub const PRECOMPILE_BN256PAIRING_PER_PAIR: Self = Self(34_000);
/// Base gas cost for precompile call: MODEXP
pub const PRECOMPILE_MODEXP: Self = Self(0);
/// Base gas price for precompile call: BLAKE2F
/// Minimum gas cost for precompile calls: MODEXP
pub const PRECOMPILE_MODEXP_MIN: Self = Self(200);
/// Base gas cost for precompile call: BLAKE2F
pub const PRECOMPILE_BLAKE2F: Self = Self(0);
}

Expand Down
50 changes: 36 additions & 14 deletions zkevm-circuits/src/evm_circuit/execution/callop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
no_callee_code.expr(),
true.expr(),
);
cb.require_true(
"Precompile addresses are always warm",
and::expr([is_warm.expr(), is_warm_prev.expr()]),
);

// Write to callee's context.
for (field_tag, value) in [
Expand Down Expand Up @@ -476,21 +480,39 @@ impl<F: Field> ExecutionGadget<F> for CallOpGadget<F> {
..StepStateTransition::default()
});

let precompile_gadget = PrecompileGadget::construct(
cb,
call_gadget.is_success.expr(),
call_gadget.callee_address_expr(),
cb.curr.state.call_id.expr(),
call_gadget.cd_address.offset(),
call_gadget.cd_address.length(),
call_gadget.rd_address.offset(),
call_gadget.rd_address.length(),
precompile_return_length.expr(),
precompile_input_bytes_rlc.expr(),
precompile_output_bytes_rlc.expr(),
precompile_return_bytes_rlc.expr(),
);
cb.condition(
// FIXME: skipping the gas cost checks for SHA2-256, RIPEMD-160 and BLAKE2F
// until they are implemented in zkevm-circuits.
not::expr(cb.next.execution_state_selector([
ExecutionState::PrecompileSha256,
ExecutionState::PrecompileRipemd160,
ExecutionState::PrecompileBlake2f,
])),
|cb| {
cb.require_equal(
"precompile call: step gas cost",
step_gas_cost.expr(),
gas_cost.expr() + precompile_gadget.precompile_call_gas_cost.expr(),
);
},
);

(
PrecompileGadget::construct(
cb,
call_gadget.is_success.expr(),
call_gadget.callee_address_expr(),
cb.curr.state.call_id.expr(),
call_gadget.cd_address.offset(),
call_gadget.cd_address.length(),
call_gadget.rd_address.offset(),
call_gadget.rd_address.length(),
precompile_return_length.expr(),
precompile_input_bytes_rlc.expr(),
precompile_output_bytes_rlc.expr(),
precompile_return_bytes_rlc.expr(),
),
precompile_gadget,
precompile_input_bytes_rlc,
precompile_output_bytes_rlc,
precompile_return_bytes_rlc,
Expand Down
7 changes: 4 additions & 3 deletions zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
util::{
common_gadget::CommonErrorGadget,
constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder},
math_gadget::{ByteSizeGadget, LtGadget},
math_gadget::{ByteOrWord, ByteSizeGadget, LtGadget},
CachedRegion, Cell, Word,
},
witness::{Block, Call, ExecStep, Transaction},
Expand Down Expand Up @@ -67,7 +67,7 @@ impl<F: Field> ExecutionGadget<F> for ErrorOOGExpGadget<F> {
// static_gas = 10
// dynamic_gas = exponent_byte_size * 50
// gas_cost = dynamic_gas + static_gas
exponent_byte_size.byte_size() * GasCost::EXP_BYTE_TIMES.0.expr()
exponent_byte_size.size() * GasCost::EXP_BYTE_TIMES.0.expr()
+ OpcodeId::EXP.constant_gas_cost().expr(),
);

Expand Down Expand Up @@ -111,7 +111,8 @@ impl<F: Field> ExecutionGadget<F> for ErrorOOGExpGadget<F> {
self.base.assign(region, offset, Some(base.to_le_bytes()))?;
self.exponent
.assign(region, offset, Some(exponent.to_le_bytes()))?;
self.exponent_byte_size.assign(region, offset, exponent)?;
self.exponent_byte_size
.assign(region, offset, ByteOrWord::Word(exponent))?;
self.insufficient_gas_cost.assign_value(
region,
offset,
Expand Down
7 changes: 4 additions & 3 deletions zkevm-circuits/src/evm_circuit/execution/exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::evm_circuit::{
ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition, Transition,
},
from_bytes,
math_gadget::{ByteSizeGadget, IsEqualGadget, IsZeroGadget},
math_gadget::{ByteOrWord, ByteSizeGadget, IsEqualGadget, IsZeroGadget},
CachedRegion, Cell, Word,
},
witness::{Block, Call, ExecStep, Transaction},
Expand Down Expand Up @@ -187,7 +187,7 @@ impl<F: Field> ExecutionGadget<F> for ExponentiationGadget<F> {

// Finally we build an expression for the dynamic gas cost as:
// dynamic_gas = 50 * exponent_byte_size
let dynamic_gas_cost = GasCost::EXP_BYTE_TIMES.0.expr() * exponent_byte_size.byte_size();
let dynamic_gas_cost = GasCost::EXP_BYTE_TIMES.0.expr() * exponent_byte_size.size();
let step_state_transition = StepStateTransition {
rw_counter: Transition::Delta(3.expr()), // 2 stack pops, 1 stack push
program_counter: Transition::Delta(1.expr()),
Expand Down Expand Up @@ -259,7 +259,8 @@ impl<F: Field> ExecutionGadget<F> for ExponentiationGadget<F> {
self.single_step
.assign(region, offset, Value::known(F::from(single_step as u64)))?;

self.exponent_byte_size.assign(region, offset, exponent)?;
self.exponent_byte_size
.assign(region, offset, ByteOrWord::Word(exponent))?;

Ok(())
}
Expand Down
26 changes: 23 additions & 3 deletions zkevm-circuits/src/evm_circuit/execution/precompiles/ec_add.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls};
use eth_types::{Field, ToLittleEndian, ToScalar};
use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar};
use gadgets::util::Expr;
use halo2_proofs::{circuit::Value, plonk::Error};

Expand All @@ -8,8 +8,9 @@ use crate::{
execution::ExecutionGadget,
step::ExecutionState,
util::{
common_gadget::RestoreContextGadget, constraint_builder::EVMConstraintBuilder, rlc,
CachedRegion, Cell,
common_gadget::RestoreContextGadget,
constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder},
rlc, CachedRegion, Cell,
},
},
table::CallContextFieldTag,
Expand All @@ -25,6 +26,7 @@ pub struct EcAddGadget<F> {
point_q_y_rlc: Cell<F>,
point_r_x_rlc: Cell<F>,
point_r_y_rlc: Cell<F>,
gas_cost: Cell<F>,

is_success: Cell<F>,
callee_address: Cell<F>,
Expand Down Expand Up @@ -57,6 +59,12 @@ impl<F: Field> ExecutionGadget<F> for EcAddGadget<F> {
cb.query_cell_phase2(),
cb.query_cell_phase2(),
);
let gas_cost = cb.query_cell();
cb.require_equal(
"ecAdd: gas cost",
gas_cost.expr(),
GasCost::PRECOMPILE_BN256ADD.expr(),
);

let [is_success, callee_address, caller_id, call_data_offset, call_data_length, return_data_offset, return_data_length] =
[
Expand Down Expand Up @@ -106,6 +114,7 @@ impl<F: Field> ExecutionGadget<F> for EcAddGadget<F> {
point_q_y_rlc,
point_r_x_rlc,
point_r_y_rlc,
gas_cost,

is_success,
callee_address,
Expand Down Expand Up @@ -143,6 +152,17 @@ impl<F: Field> ExecutionGadget<F> for EcAddGadget<F> {
keccak_rand.map(|r| rlc::value(&word_value.to_le_bytes(), r)),
)?;
}
// FIXME: when we handle invalid inputs (and hence failures in the precompile calls),
// this will be assigned either fixed gas cost (in case of success) or the
// entire gas passed to the precompile call (in case of failure).
self.gas_cost.assign(
region,
offset,
Value::known(F::from(GasCost::PRECOMPILE_BN256ADD.0)),
)?;
} else {
log::error!("unexpected aux_data {:?} for ecAdd", step.aux_data);
return Err(Error::Synthesis);
}

self.is_success.assign(
Expand Down
18 changes: 17 additions & 1 deletion zkevm-circuits/src/evm_circuit/execution/precompiles/ec_mul.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bus_mapping::precompile::{PrecompileAuxData, PrecompileCalls};
use eth_types::{Field, ToLittleEndian, ToScalar, U256};
use eth_types::{evm_types::GasCost, Field, ToLittleEndian, ToScalar, U256};
use gadgets::util::{and, not, or, Expr};
use halo2_proofs::{
circuit::Value,
Expand Down Expand Up @@ -34,6 +34,7 @@ pub struct EcMulGadget<F> {
scalar_s_raw_rlc: Cell<F>,
point_r_x_rlc: Cell<F>,
point_r_y_rlc: Cell<F>,
gas_cost: Cell<F>,

p_x_is_zero: IsZeroGadget<F>,
p_y_is_zero: IsZeroGadget<F>,
Expand Down Expand Up @@ -69,6 +70,12 @@ impl<F: Field> ExecutionGadget<F> for EcMulGadget<F> {
cb.query_cell_phase2(),
cb.query_cell_phase2(),
);
let gas_cost = cb.query_cell();
cb.require_equal(
"ecMul: gas cost",
gas_cost.expr(),
GasCost::PRECOMPILE_BN256MUL.expr(),
);

let (scalar_s_raw, scalar_s, n) = (
cb.query_keccak_rlc(),
Expand Down Expand Up @@ -163,6 +170,7 @@ impl<F: Field> ExecutionGadget<F> for EcMulGadget<F> {
scalar_s_raw_rlc,
point_r_x_rlc,
point_r_y_rlc,
gas_cost,

p_x_is_zero,
p_y_is_zero,
Expand Down Expand Up @@ -226,6 +234,14 @@ impl<F: Field> ExecutionGadget<F> for EcMulGadget<F> {
let (k, _) = aux_data.s_raw.div_mod(n);
self.modword
.assign(region, offset, aux_data.s_raw, n, aux_data.s, k)?;
self.gas_cost.assign(
region,
offset,
Value::known(F::from(GasCost::PRECOMPILE_BN256MUL.0)),
)?;
} else {
log::error!("unexpected aux_data {:?} for ecMul", step.aux_data);
return Err(Error::Synthesis);
}

self.is_success.assign(
Expand Down
Loading

0 comments on commit faeced2

Please sign in to comment.