Skip to content
This repository was archived by the owner on Jul 5, 2024. It is now read-only.

Commit 50c5194

Browse files
authored
Implement ADDMOD (#564)
Implement `ADDMOD`
1 parent 6501f69 commit 50c5194

File tree

4 files changed

+390
-0
lines changed

4 files changed

+390
-0
lines changed

zkevm-circuits/src/evm_circuit/execution.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use std::{collections::HashMap, convert::TryInto, iter};
2323
use strum::IntoEnumIterator;
2424

2525
mod add_sub;
26+
mod addmod;
2627
mod begin_tx;
2728
mod bitwise;
2829
mod block_ctx;
@@ -71,6 +72,7 @@ mod stop;
7172
mod swap;
7273

7374
use add_sub::AddSubGadget;
75+
use addmod::AddModGadget;
7476
use begin_tx::BeginTxGadget;
7577
use bitwise::BitwiseGadget;
7678
use block_ctx::{BlockCtxU160Gadget, BlockCtxU256Gadget, BlockCtxU64Gadget};
@@ -155,6 +157,7 @@ pub(crate) struct ExecutionConfig<F> {
155157
end_tx_gadget: EndTxGadget<F>,
156158
// opcode gadgets
157159
add_sub_gadget: AddSubGadget<F>,
160+
addmod_gadget: AddModGadget<F>,
158161
bitwise_gadget: BitwiseGadget<F>,
159162
byte_gadget: ByteGadget<F>,
160163
call_gadget: CallGadget<F>,
@@ -350,6 +353,7 @@ impl<F: Field> ExecutionConfig<F> {
350353
end_tx_gadget: configure_gadget!(),
351354
// opcode gadgets
352355
add_sub_gadget: configure_gadget!(),
356+
addmod_gadget: configure_gadget!(),
353357
bitwise_gadget: configure_gadget!(),
354358
byte_gadget: configure_gadget!(),
355359
call_gadget: configure_gadget!(),
@@ -788,6 +792,7 @@ impl<F: Field> ExecutionConfig<F> {
788792
ExecutionState::EndBlock => assign_exec_step!(self.end_block_gadget),
789793
// opcode
790794
ExecutionState::ADD_SUB => assign_exec_step!(self.add_sub_gadget),
795+
ExecutionState::ADDMOD => assign_exec_step!(self.addmod_gadget),
791796
ExecutionState::BITWISE => assign_exec_step!(self.bitwise_gadget),
792797
ExecutionState::BYTE => assign_exec_step!(self.byte_gadget),
793798
ExecutionState::CALL => assign_exec_step!(self.call_gadget),
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
use crate::{
2+
evm_circuit::{
3+
execution::ExecutionGadget,
4+
step::ExecutionState,
5+
util::{
6+
common_gadget::SameContextGadget,
7+
constraint_builder::{ConstraintBuilder, StepStateTransition, Transition::Delta},
8+
math_gadget::{
9+
AddWordsGadget, CmpWordsGadget, IsZeroGadget, MulAddWords512Gadget,
10+
MulAddWordsGadget,
11+
},
12+
not, CachedRegion, Word,
13+
},
14+
witness::{Block, Call, ExecStep, Transaction},
15+
},
16+
util::Expr,
17+
};
18+
19+
use bus_mapping::evm::OpcodeId;
20+
use eth_types::{Field, ToLittleEndian, U256, U512};
21+
use halo2_proofs::plonk::Error;
22+
23+
#[derive(Clone, Debug)]
24+
pub(crate) struct AddModGadget<F> {
25+
same_context: SameContextGadget<F>,
26+
27+
a: Word<F>,
28+
b: Word<F>,
29+
r: Word<F>,
30+
n: Word<F>,
31+
32+
k: Word<F>,
33+
d: Word<F>,
34+
a_reduced: Word<F>,
35+
36+
muladd_k_n_areduced: MulAddWordsGadget<F>,
37+
38+
sum_areduced_b: AddWordsGadget<F, 2, false>,
39+
sum_areduced_b_overflow: Word<F>,
40+
muladd_d_n_r: MulAddWords512Gadget<F>,
41+
42+
n_is_zero: IsZeroGadget<F>,
43+
cmp_r_n: CmpWordsGadget<F>,
44+
cmp_areduced_n: CmpWordsGadget<F>,
45+
}
46+
47+
impl<F: Field> ExecutionGadget<F> for AddModGadget<F> {
48+
const NAME: &'static str = "ADDMOD";
49+
50+
const EXECUTION_STATE: ExecutionState = ExecutionState::ADDMOD;
51+
52+
fn configure(cb: &mut ConstraintBuilder<F>) -> Self {
53+
let opcode = cb.query_cell();
54+
55+
// values got from stack (original r is modified if n==0)
56+
let a = cb.query_word();
57+
let b = cb.query_word();
58+
let n = cb.query_word();
59+
let r = cb.query_word();
60+
61+
// auxiliar witness
62+
let k = cb.query_word();
63+
let a_reduced = cb.query_word();
64+
let d = cb.query_word();
65+
66+
let n_is_zero = IsZeroGadget::construct(cb, n.clone().expr());
67+
68+
// 1. check k * N + a_reduced == a without overflow
69+
let muladd_k_n_areduced = MulAddWordsGadget::construct(cb, [&k, &n, &a_reduced, &a]);
70+
cb.require_zero(
71+
"k * N + a_reduced does not overflow",
72+
muladd_k_n_areduced.overflow(),
73+
);
74+
75+
// 2. check d * N + r == a_reduced + b, only checking carry if n != 0
76+
let sum_areduced_b = {
77+
let sum = cb.query_word();
78+
AddWordsGadget::construct(cb, [a_reduced.clone(), b.clone()], sum)
79+
};
80+
let sum_areduced_b_overflow = cb.query_word();
81+
let muladd_d_n_r = MulAddWords512Gadget::construct(
82+
cb,
83+
[&d, &n, &sum_areduced_b_overflow, sum_areduced_b.sum()],
84+
Some(&r),
85+
);
86+
87+
cb.require_equal(
88+
"check a_reduced + b 512 bit carry if n != 0",
89+
sum_areduced_b_overflow.expr(),
90+
sum_areduced_b.carry().clone().unwrap().expr() * not::expr(n_is_zero.expr()),
91+
);
92+
93+
let cmp_r_n = CmpWordsGadget::construct(cb, &r, &n);
94+
let cmp_areduced_n = CmpWordsGadget::construct(cb, &a_reduced, &n);
95+
96+
// 3. r < n and a_reduced < n if n > 0
97+
cb.require_zero(
98+
"{r<n, a_reduced<n} if n > 0",
99+
2.expr() - (cmp_r_n.lt.expr() + cmp_areduced_n.lt.expr() + 2.expr() * n_is_zero.expr()),
100+
);
101+
102+
// pop/push values
103+
// take care that if n==0 pushed value for r should be zero also
104+
cb.stack_pop(a.expr());
105+
cb.stack_pop(b.expr());
106+
cb.stack_pop(n.expr());
107+
cb.stack_push(r.expr() * not::expr(n_is_zero.expr()));
108+
109+
// State transition
110+
let step_state_transition = StepStateTransition {
111+
rw_counter: Delta(4.expr()),
112+
program_counter: Delta(1.expr()),
113+
stack_pointer: Delta(2.expr()),
114+
gas_left: Delta(-OpcodeId::ADDMOD.constant_gas_cost().expr()),
115+
..StepStateTransition::default()
116+
};
117+
let same_context = SameContextGadget::construct(cb, opcode, step_state_transition);
118+
119+
Self {
120+
a,
121+
b,
122+
r,
123+
n,
124+
k,
125+
d,
126+
a_reduced,
127+
muladd_d_n_r,
128+
muladd_k_n_areduced,
129+
same_context,
130+
cmp_r_n,
131+
cmp_areduced_n,
132+
n_is_zero,
133+
sum_areduced_b,
134+
sum_areduced_b_overflow,
135+
}
136+
}
137+
138+
fn assign_exec_step(
139+
&self,
140+
region: &mut CachedRegion<'_, '_, F>,
141+
offset: usize,
142+
block: &Block<F>,
143+
_: &Transaction,
144+
_: &Call,
145+
step: &ExecStep,
146+
) -> Result<(), Error> {
147+
self.same_context.assign_exec_step(region, offset, step)?;
148+
149+
// get stack values
150+
let [mut r, n, b, a] = [3, 2, 1, 0]
151+
.map(|idx| step.rw_indices[idx])
152+
.map(|idx| block.rws[idx].stack_value());
153+
154+
// assing a,b & n stack values
155+
self.a.assign(region, offset, Some(a.to_le_bytes()))?;
156+
self.b.assign(region, offset, Some(b.to_le_bytes()))?;
157+
self.n.assign(region, offset, Some(n.to_le_bytes()))?;
158+
159+
// compute a_reduced,k,d,a_reduced_plus_b,a_reduced_plus_b_overflow,r values
160+
let a_reduced;
161+
let k;
162+
let d;
163+
let a_reduced_plus_b;
164+
let a_reduced_plus_b_overflow;
165+
166+
if n.is_zero() {
167+
k = U256::zero();
168+
a_reduced = a;
169+
d = U256::zero();
170+
r = a.overflowing_add(b).0;
171+
172+
a_reduced_plus_b = a_reduced.overflowing_add(b).0;
173+
a_reduced_plus_b_overflow = U256::zero();
174+
} else {
175+
let (a_div_n, a_mod_n) = a.div_mod(n);
176+
k = a_div_n;
177+
a_reduced = a_mod_n;
178+
d = ((U512::from(a_reduced) + U512::from(b)) / U512::from(n))
179+
.try_into()
180+
.unwrap();
181+
182+
let (sum, overflow) = a_reduced.overflowing_add(b);
183+
a_reduced_plus_b = sum;
184+
a_reduced_plus_b_overflow = if overflow { U256::one() } else { U256::zero() };
185+
};
186+
187+
// rest of values and gadgets
188+
189+
self.r.assign(region, offset, Some(r.to_le_bytes()))?;
190+
self.k.assign(region, offset, Some(k.to_le_bytes()))?;
191+
self.d.assign(region, offset, Some(d.to_le_bytes()))?;
192+
self.a_reduced
193+
.assign(region, offset, Some(a_reduced.to_le_bytes()))?;
194+
195+
self.muladd_k_n_areduced
196+
.assign(region, offset, [k, n, a_reduced, a])?;
197+
198+
self.sum_areduced_b
199+
.assign(region, offset, [a_reduced, b], a_reduced_plus_b)?;
200+
201+
self.sum_areduced_b_overflow.assign(
202+
region,
203+
offset,
204+
Some(a_reduced_plus_b_overflow.to_le_bytes()),
205+
)?;
206+
self.muladd_d_n_r.assign(
207+
region,
208+
offset,
209+
[d, n, a_reduced_plus_b_overflow, a_reduced_plus_b],
210+
Some(r),
211+
)?;
212+
213+
self.cmp_r_n.assign(region, offset, r, n)?;
214+
self.cmp_areduced_n.assign(region, offset, a_reduced, n)?;
215+
216+
self.n_is_zero.assign(
217+
region,
218+
offset,
219+
Word::random_linear_combine(n.to_le_bytes(), block.randomness),
220+
)?;
221+
222+
Ok(())
223+
}
224+
}
225+
226+
#[cfg(test)]
227+
mod test {
228+
use crate::test_util::run_test_circuits;
229+
use eth_types::evm_types::Stack;
230+
use eth_types::{bytecode, Word};
231+
use mock::TestContext;
232+
233+
fn test(
234+
a: Word,
235+
b: Word,
236+
n: Word,
237+
r: Option<Word>,
238+
) -> Result<(), Vec<halo2_proofs::dev::VerifyFailure>> {
239+
let bytecode = bytecode! {
240+
PUSH32(n)
241+
PUSH32(b)
242+
PUSH32(a)
243+
ADDMOD
244+
STOP
245+
};
246+
247+
let mut ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap();
248+
if let Some(r) = r {
249+
let mut last = ctx
250+
.geth_traces
251+
.first_mut()
252+
.unwrap()
253+
.struct_logs
254+
.last_mut()
255+
.unwrap();
256+
last.stack = Stack::from_vec(vec![r]);
257+
}
258+
run_test_circuits(ctx, None)
259+
}
260+
fn test_u32(
261+
a: u32,
262+
b: u32,
263+
c: u32,
264+
r: Option<u32>,
265+
) -> Result<(), Vec<halo2_proofs::dev::VerifyFailure>> {
266+
test(a.into(), b.into(), c.into(), r.map(Word::from))
267+
}
268+
269+
#[test]
270+
fn addmod_simple() {
271+
assert_eq!(test_u32(1, 1, 10, None), Ok(()));
272+
assert_eq!(test_u32(1, 1, 11, None), Ok(()));
273+
}
274+
275+
#[test]
276+
fn addmod_limits() {
277+
assert_eq!(test(Word::MAX, Word::MAX, 0.into(), None), Ok(()));
278+
assert_eq!(test(Word::MAX, Word::MAX, 1.into(), None), Ok(()));
279+
assert_eq!(test(Word::MAX - 1, Word::MAX, Word::MAX, None), Ok(()));
280+
assert_eq!(test(Word::MAX, Word::MAX, Word::MAX, None), Ok(()));
281+
assert_eq!(test(Word::MAX, 1.into(), 0.into(), None), Ok(()));
282+
assert_eq!(test(Word::MAX, 1.into(), 1.into(), None), Ok(()));
283+
assert_eq!(test(Word::MAX, 1.into(), Word::MAX, None), Ok(()));
284+
assert_eq!(test(Word::MAX, 0.into(), 0.into(), None), Ok(()));
285+
assert_eq!(test(Word::MAX, 0.into(), 1.into(), None), Ok(()));
286+
assert_eq!(test(Word::MAX, 0.into(), Word::MAX, None), Ok(()));
287+
assert_eq!(test(0.into(), 0.into(), 0.into(), None), Ok(()));
288+
assert_eq!(test(0.into(), 0.into(), 1.into(), None), Ok(()));
289+
assert_eq!(test(0.into(), 0.into(), Word::MAX, None), Ok(()));
290+
}
291+
292+
#[test]
293+
fn addmod_bad_r_on_nonzero_n() {
294+
assert_eq!(test_u32(7, 18, 10, Some(5)), Ok(()));
295+
assert_ne!(test_u32(7, 18, 10, Some(6)), Ok(()));
296+
}
297+
298+
#[test]
299+
fn addmod_bad_r_on_zero_n() {
300+
assert_eq!(test_u32(2, 3, 0, Some(0)), Ok(()));
301+
assert_ne!(test_u32(2, 3, 0, Some(1)), Ok(()));
302+
}
303+
304+
#[test]
305+
fn addmod_bad_r_bigger_n() {
306+
assert_eq!(test_u32(2, 3, 4, Some(1)), Ok(()));
307+
assert_ne!(test_u32(2, 3, 4, Some(5)), Ok(()));
308+
}
309+
}

0 commit comments

Comments
 (0)