Skip to content

Commit

Permalink
Implement circuit for CALLER and CALLVALUE opcode (privacy-scaling-ex…
Browse files Browse the repository at this point in the history
  • Loading branch information
ed255 authored Feb 18, 2022
1 parent 0acc51b commit 6aa6705
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 6 deletions.
17 changes: 11 additions & 6 deletions bus-mapping/src/evm/opcodes.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
//! Definition of each opcode of the EVM.
use crate::circuit_input_builder::CircuitInputStateRef;
use crate::Error;
use core::fmt::Debug;
use eth_types::GethExecStep;

mod caller;
mod callvalue;
mod coinbase;
mod dup;
mod gas;
Expand All @@ -16,14 +23,12 @@ mod stackonlyop;
mod stop;
mod swap;
mod timestamp;
use crate::circuit_input_builder::CircuitInputStateRef;
use crate::evm::OpcodeId;
use crate::Error;
use core::fmt::Debug;
use eth_types::GethExecStep;
use log::warn;

use self::push::Push;
use caller::Caller;
use callvalue::Callvalue;
use dup::Dup;
use gas::Gas;
use jump::Jump;
Expand Down Expand Up @@ -96,8 +101,8 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps {
// OpcodeId::ADDRESS => {},
// OpcodeId::BALANCE => {},
// OpcodeId::ORIGIN => {},
// OpcodeId::CALLER => {},
// OpcodeId::CALLVALUE => {},
OpcodeId::CALLER => Caller::gen_associated_ops,
OpcodeId::CALLVALUE => Callvalue::gen_associated_ops,
// OpcodeId::CALLDATALOAD => {},
// OpcodeId::CALLDATASIZE => {},
// OpcodeId::CALLDATACOPY => {},
Expand Down
102 changes: 102 additions & 0 deletions bus-mapping/src/evm/opcodes/caller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use super::Opcode;
use crate::circuit_input_builder::CircuitInputStateRef;
use crate::operation::{CallContextField, CallContextOp, RW};
use crate::Error;
use eth_types::GethExecStep;

/// Placeholder structure used to implement [`Opcode`] trait over it
/// corresponding to the [`OpcodeId::PC`](crate::evm::OpcodeId::PC) `OpcodeId`.
#[derive(Debug, Copy, Clone)]
pub(crate) struct Caller;

impl Opcode for Caller {
fn gen_associated_ops(
state: &mut CircuitInputStateRef,
steps: &[GethExecStep],
) -> Result<(), Error> {
let step = &steps[0];
// Get caller_address result from next step
let value = steps[1].stack.last()?;
// CallContext read of the caller_address
state.push_op(
RW::READ,
CallContextOp {
call_id: state.call().call_id,
field: CallContextField::CallerAddress,
value,
},
);
// Stack write of the caller_address
state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value);

Ok(())
}
}

#[cfg(test)]
mod caller_tests {
use super::*;
use crate::circuit_input_builder::{ExecStep, TransactionContext};
use eth_types::{bytecode, evm_types::StackAddress, ToWord};
use pretty_assertions::assert_eq;

#[test]
fn caller_opcode_impl() -> Result<(), Error> {
let code = bytecode! {
#[start]
CALLER
STOP
};

// Get the execution steps from the external tracer
let block = crate::mock::BlockData::new_from_geth_data(
mock::new_single_tx_trace_code_at_start(&code).unwrap(),
);

let mut builder = block.new_circuit_input_builder();
builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap();

let mut test_builder = block.new_circuit_input_builder();
let mut tx = test_builder
.new_tx(&block.eth_tx, !block.geth_trace.failed)
.unwrap();
let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap();

// Generate step corresponding to CALLER
let mut step = ExecStep::new(
&block.geth_trace.struct_logs[0],
0,
test_builder.block_ctx.rwc,
0,
);
let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step);

let caller_address = block.eth_tx.from.to_word();

// Add the CallContext read
state_ref.push_op(
RW::READ,
CallContextOp {
call_id: state_ref.call().call_id,
field: CallContextField::CallerAddress,
value: caller_address,
},
);
// Add the Stack write
state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 1), caller_address);

tx.steps_mut().push(step);
test_builder.block.txs_mut().push(tx);

// Compare first step bus mapping instance
assert_eq!(
builder.block.txs()[0].steps()[0].bus_mapping_instance,
test_builder.block.txs()[0].steps()[0].bus_mapping_instance,
);

// Compare containers
assert_eq!(builder.block.container, test_builder.block.container);

Ok(())
}
}
102 changes: 102 additions & 0 deletions bus-mapping/src/evm/opcodes/callvalue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use super::Opcode;
use crate::circuit_input_builder::CircuitInputStateRef;
use crate::operation::{CallContextField, CallContextOp, RW};
use crate::Error;
use eth_types::GethExecStep;

/// Placeholder structure used to implement [`Opcode`] trait over it
/// corresponding to the [`OpcodeId::PC`](crate::evm::OpcodeId::PC) `OpcodeId`.
#[derive(Debug, Copy, Clone)]
pub(crate) struct Callvalue;

impl Opcode for Callvalue {
fn gen_associated_ops(
state: &mut CircuitInputStateRef,
steps: &[GethExecStep],
) -> Result<(), Error> {
let step = &steps[0];
// Get call_value result from next step
let value = steps[1].stack.last()?;
// CallContext read of the call_value
state.push_op(
RW::READ,
CallContextOp {
call_id: state.call().call_id,
field: CallContextField::Value,
value,
},
);
// Stack write of the call_value
state.push_stack_op(RW::WRITE, step.stack.last_filled().map(|a| a - 1), value);

Ok(())
}
}

#[cfg(test)]
mod callvalue_tests {
use super::*;
use crate::circuit_input_builder::{ExecStep, TransactionContext};
use eth_types::{bytecode, evm_types::StackAddress};
use pretty_assertions::assert_eq;

#[test]
fn callvalue_opcode_impl() -> Result<(), Error> {
let code = bytecode! {
#[start]
CALLVALUE
STOP
};

// Get the execution steps from the external tracer
let block = crate::mock::BlockData::new_from_geth_data(
mock::new_single_tx_trace_code_at_start(&code).unwrap(),
);

let mut builder = block.new_circuit_input_builder();
builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap();

let mut test_builder = block.new_circuit_input_builder();
let mut tx = test_builder
.new_tx(&block.eth_tx, !block.geth_trace.failed)
.unwrap();
let mut tx_ctx = TransactionContext::new(&block.eth_tx, &block.geth_trace).unwrap();

// Generate step corresponding to CALLVALUE
let mut step = ExecStep::new(
&block.geth_trace.struct_logs[0],
0,
test_builder.block_ctx.rwc,
0,
);
let mut state_ref = test_builder.state_ref(&mut tx, &mut tx_ctx, &mut step);

let call_value = block.eth_tx.value;

// Add the CallContext read
state_ref.push_op(
RW::READ,
CallContextOp {
call_id: state_ref.call().call_id,
field: CallContextField::Value,
value: call_value,
},
);
// Add the Stack write
state_ref.push_stack_op(RW::WRITE, StackAddress::from(1024 - 1), call_value);

tx.steps_mut().push(step);
test_builder.block.txs_mut().push(tx);

// Compare first step bus mapping instance
assert_eq!(
builder.block.txs()[0].steps()[0].bus_mapping_instance,
test_builder.block.txs()[0].steps()[0].bus_mapping_instance,
);

// Compare containers
assert_eq!(builder.block.container, test_builder.block.container);

Ok(())
}
}
12 changes: 12 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ mod add;
mod begin_tx;
mod bitwise;
mod byte;
mod caller;
mod callvalue;
mod coinbase;
mod comparator;
mod dup;
Expand All @@ -44,6 +46,8 @@ use add::AddGadget;
use begin_tx::BeginTxGadget;
use bitwise::BitwiseGadget;
use byte::ByteGadget;
use caller::CallerGadget;
use callvalue::CallValueGadget;
use coinbase::CoinbaseGadget;
use comparator::ComparatorGadget;
use dup::DupGadget;
Expand Down Expand Up @@ -93,6 +97,8 @@ pub(crate) struct ExecutionConfig<F> {
bitwise_gadget: BitwiseGadget<F>,
begin_tx_gadget: BeginTxGadget<F>,
byte_gadget: ByteGadget<F>,
caller_gadget: CallerGadget<F>,
call_value_gadget: CallValueGadget<F>,
comparator_gadget: ComparatorGadget<F>,
dup_gadget: DupGadget<F>,
error_oog_pure_memory_gadget: ErrorOOGPureMemoryGadget<F>,
Expand Down Expand Up @@ -221,6 +227,8 @@ impl<F: FieldExt> ExecutionConfig<F> {
bitwise_gadget: configure_gadget!(),
begin_tx_gadget: configure_gadget!(),
byte_gadget: configure_gadget!(),
caller_gadget: configure_gadget!(),
call_value_gadget: configure_gadget!(),
comparator_gadget: configure_gadget!(),
dup_gadget: configure_gadget!(),
error_oog_pure_memory_gadget: configure_gadget!(),
Expand Down Expand Up @@ -485,6 +493,10 @@ impl<F: FieldExt> ExecutionConfig<F> {
ExecutionState::PUSH => assign_exec_step!(self.push_gadget),
ExecutionState::DUP => assign_exec_step!(self.dup_gadget),
ExecutionState::SWAP => assign_exec_step!(self.swap_gadget),
ExecutionState::CALLER => assign_exec_step!(self.caller_gadget),
ExecutionState::CALLVALUE => {
assign_exec_step!(self.call_value_gadget)
}
ExecutionState::COINBASE => assign_exec_step!(self.coinbase_gadget),
ExecutionState::TIMESTAMP => {
assign_exec_step!(self.timestamp_gadget)
Expand Down
106 changes: 106 additions & 0 deletions zkevm-circuits/src/evm_circuit/execution/caller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::{
evm_circuit::{
execution::ExecutionGadget,
param::N_BYTES_ACCOUNT_ADDRESS,
step::ExecutionState,
table::CallContextFieldTag,
util::{
common_gadget::SameContextGadget,
constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta},
from_bytes, RandomLinearCombination,
},
witness::{Block, Call, ExecStep, Transaction},
},
util::Expr,
};
use eth_types::ToLittleEndian;
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
use std::convert::TryInto;

#[derive(Clone, Debug)]
pub(crate) struct CallerGadget<F> {
same_context: SameContextGadget<F>,
// Using RLC to match against rw_table->stack_op value
caller_address: RandomLinearCombination<F, 20>,
}

impl<F: FieldExt> ExecutionGadget<F> for CallerGadget<F> {
const NAME: &'static str = "CALLER";

const EXECUTION_STATE: ExecutionState = ExecutionState::CALLER;

fn configure(cb: &mut ConstraintBuilder<F>) -> Self {
let caller_address = cb.query_rlc::<N_BYTES_ACCOUNT_ADDRESS>();

// Lookup rw_table -> call_context with caller address
cb.call_context_lookup(
false.expr(),
None, // cb.curr.state.call_id
CallContextFieldTag::CallerAddress,
from_bytes::expr(&caller_address.cells),
);

// Push the value to the stack
cb.stack_push(caller_address.expr());

// State transition
let opcode = cb.query_cell();
let step_state_transition = StepStateTransition {
rw_counter: Delta(2.expr()),
program_counter: Delta(1.expr()),
stack_pointer: Delta((-1).expr()),
..Default::default()
};
let same_context = SameContextGadget::construct(cb, opcode, step_state_transition, None);

Self {
same_context,
caller_address,
}
}

fn assign_exec_step(
&self,
region: &mut Region<'_, F>,
offset: usize,
block: &Block<F>,
_: &Transaction,
_: &Call,
step: &ExecStep,
) -> Result<(), Error> {
self.same_context.assign_exec_step(region, offset, step)?;

let caller = block.rws[step.rw_indices[1]].stack_value();

self.caller_address.assign(
region,
offset,
Some(
caller.to_le_bytes()[..N_BYTES_ACCOUNT_ADDRESS]
.try_into()
.unwrap(),
),
)?;

Ok(())
}
}

#[cfg(test)]
mod test {
use crate::test_util::run_test_circuits;
use eth_types::bytecode;

fn test_ok() {
let bytecode = bytecode! {
#[start]
CALLER
STOP
};
assert_eq!(run_test_circuits(bytecode), Ok(()));
}
#[test]
fn caller_gadget_test() {
test_ok();
}
}
Loading

0 comments on commit 6aa6705

Please sign in to comment.