Skip to content

Commit e48d06f

Browse files
nooma-42nooma
and
nooma
authored
### Description Solve privacy-scaling-explorations#1513 by adding a testutil.rs and test cases ### Issue Link privacy-scaling-explorations#1513 ### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [X] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Contents - Add a unit test to buffer reader gadget in memory gadget and test util. - The testcases cover completeness and soundness. ### Rationale I followed test util in `math_gadget` and the design of test container. I removed lookup table part in the `test_util.rs` of math_gadget, since buffer reader gadget didn't use lookup table. ### How Has This Been Tested? `cargo test --package zkevm-circuits --features test-circuits -- evm_circuit::util::memory_gadget` --------- Co-authored-by: nooma <[email protected]>
1 parent 7617394 commit e48d06f

File tree

2 files changed

+389
-0
lines changed

2 files changed

+389
-0
lines changed

zkevm-circuits/src/evm_circuit/util/memory_gadget.rs

+183
Original file line numberDiff line numberDiff line change
@@ -686,3 +686,186 @@ impl<F: Field, const MAX_BYTES: usize, const ADDR_SIZE_IN_BYTES: usize>
686686
self.selectors[idx].expr()
687687
}
688688
}
689+
690+
#[cfg(test)]
691+
mod test_util;
692+
693+
#[cfg(test)]
694+
mod test {
695+
use crate::evm_circuit::util::{constraint_builder::ConstrainBuilderCommon, Cell, U64Cell};
696+
use eth_types::Word;
697+
use halo2_proofs::{halo2curves::bn256::Fr, plonk::Error};
698+
699+
use super::{test_util::*, *};
700+
701+
#[derive(Clone)]
702+
struct BufferReaderGadgetTestContainer<
703+
F,
704+
const MAX_BYTES: usize,
705+
const ADDR_SIZE_IN_BYTES: usize,
706+
> {
707+
buffer_reader_gadget: BufferReaderGadget<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>,
708+
addr_start: U64Cell<F>,
709+
addr_end: U64Cell<F>,
710+
bytes: [Cell<F>; MAX_BYTES],
711+
}
712+
713+
impl<F: Field, const MAX_BYTES: usize, const ADDR_SIZE_IN_BYTES: usize> MemoryGadgetContainer<F>
714+
for BufferReaderGadgetTestContainer<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>
715+
{
716+
fn configure_gadget_container(cb: &mut EVMConstraintBuilder<F>) -> Self {
717+
let addr_start = cb.query_u64();
718+
let addr_end = cb.query_u64();
719+
let buffer_reader_gadget =
720+
BufferReaderGadget::<F, MAX_BYTES, ADDR_SIZE_IN_BYTES>::construct(
721+
cb,
722+
addr_start.expr(),
723+
addr_end.expr(),
724+
);
725+
let bytes = cb.query_bytes();
726+
let bytes_expr: Vec<Expression<F>> = bytes
727+
.iter()
728+
.map(|e| e.expr())
729+
.collect::<Vec<Expression<F>>>();
730+
let buffer_reader_gadget_bytes_expr = (0..MAX_BYTES)
731+
.map(|e| buffer_reader_gadget.byte(e))
732+
.collect::<Vec<Expression<F>>>();
733+
let buffer_reader_gadget_seletor = (0..MAX_BYTES)
734+
.clone()
735+
.map(|e| buffer_reader_gadget.read_flag(e))
736+
.collect::<Vec<Expression<F>>>();
737+
738+
// test byte API
739+
for (byte_expr, buffer_reader_gadget_byte_expr) in bytes_expr
740+
.iter()
741+
.zip(buffer_reader_gadget_bytes_expr.iter())
742+
{
743+
cb.require_equal(
744+
"every byte equal",
745+
byte_expr.expr(),
746+
buffer_reader_gadget_byte_expr.expr(),
747+
);
748+
}
749+
750+
// test read_flag API
751+
cb.require_equal(
752+
"selector length equal",
753+
sum::expr(buffer_reader_gadget_seletor),
754+
addr_end.expr() - addr_start.expr(),
755+
);
756+
757+
BufferReaderGadgetTestContainer {
758+
buffer_reader_gadget,
759+
addr_start,
760+
addr_end,
761+
bytes,
762+
}
763+
}
764+
765+
fn assign_gadget_container(
766+
&self,
767+
witnesses: &[Word],
768+
region: &mut CachedRegion<'_, '_, F>,
769+
) -> Result<(), Error> {
770+
let offset = 0;
771+
let addr_start =
772+
u64::from_le_bytes(witnesses[0].to_le_bytes()[..8].try_into().unwrap());
773+
let addr_end = u64::from_le_bytes(witnesses[1].to_le_bytes()[..8].try_into().unwrap());
774+
let mut input_bytes: Vec<u8> = Vec::new();
775+
input_bytes
776+
.extend_from_slice(&witnesses[2].to_le_bytes()[0..required_bytes(witnesses[2])]);
777+
self.addr_start
778+
.assign(region, offset, Some(addr_start.to_le_bytes()))?;
779+
self.addr_end
780+
.assign(region, offset, Some(addr_end.to_le_bytes()))?;
781+
self.bytes
782+
.iter()
783+
.zip(input_bytes.iter())
784+
.map(|(byte, input_byte)| {
785+
byte.assign(region, offset, Value::known(F::from(*input_byte as u64)))
786+
})
787+
.collect::<Result<Vec<_>, _>>()?;
788+
self.buffer_reader_gadget.assign(
789+
region,
790+
offset,
791+
addr_start,
792+
addr_end,
793+
&input_bytes[..],
794+
)?;
795+
796+
Ok(())
797+
}
798+
}
799+
800+
#[test]
801+
fn test_buffer_reader_gadget_completness() {
802+
// buffer len = data len
803+
try_test!(
804+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
805+
vec![
806+
Word::from(0),
807+
Word::from(2),
808+
Word::from(256),
809+
],
810+
true,
811+
);
812+
813+
// buffer len > data len
814+
try_test!(
815+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
816+
vec![
817+
Word::from(0),
818+
Word::from(2),
819+
Word::from(256),
820+
],
821+
true,
822+
);
823+
}
824+
825+
#[test]
826+
fn test_buffer_reader_gadget_soundness() {
827+
// buffer len < data len
828+
try_test!(
829+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
830+
vec![
831+
Word::from(0),
832+
Word::from(1),
833+
Word::from(256),
834+
],
835+
false,
836+
);
837+
838+
// buffer len <= 0
839+
try_test!(
840+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
841+
vec![
842+
Word::from(1),
843+
Word::from(0),
844+
Word::from(256),
845+
],
846+
false,
847+
);
848+
849+
// empty buffer
850+
try_test!(
851+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
852+
vec![
853+
Word::from(0),
854+
Word::from(0),
855+
Word::from(256),
856+
],
857+
false,
858+
);
859+
860+
// MAX_BYTES < buffer size
861+
try_test!(
862+
BufferReaderGadgetTestContainer<Fr, 2, 10>,
863+
vec![
864+
Word::from(0),
865+
Word::from(31),
866+
Word::from(256),
867+
],
868+
false,
869+
);
870+
}
871+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
use std::marker::PhantomData;
2+
3+
use crate::{
4+
evm_circuit::{
5+
param::{MAX_STEP_HEIGHT, N_PHASE2_COLUMNS, STEP_WIDTH},
6+
step::{ExecutionState, Step},
7+
util::{
8+
constraint_builder::EVMConstraintBuilder, CachedRegion, StoredExpression, LOOKUP_CONFIG,
9+
},
10+
Advice, Column,
11+
},
12+
util::Challenges,
13+
};
14+
use eth_types::{Field, Word};
15+
pub(crate) use halo2_proofs::circuit::Layouter;
16+
use halo2_proofs::{
17+
circuit::SimpleFloorPlanner,
18+
dev::MockProver,
19+
plonk::{Circuit, ConstraintSystem, Error, FirstPhase, SecondPhase, Selector, ThirdPhase},
20+
};
21+
22+
pub(crate) trait MemoryGadgetContainer<F: Field>: Clone {
23+
fn configure_gadget_container(cb: &mut EVMConstraintBuilder<F>) -> Self
24+
where
25+
Self: Sized;
26+
27+
fn assign_gadget_container(
28+
&self,
29+
witnesses: &[Word],
30+
region: &mut CachedRegion<'_, '_, F>,
31+
) -> Result<(), Error>;
32+
}
33+
34+
#[derive(Debug, Clone)]
35+
pub(crate) struct UnitTestMemoryGadgetBaseCircuitConfig<F: Field, G>
36+
where
37+
G: MemoryGadgetContainer<F>,
38+
{
39+
q_usable: Selector,
40+
advices: [Column<Advice>; STEP_WIDTH],
41+
step: Step<F>,
42+
stored_expressions: Vec<StoredExpression<F>>,
43+
memory_gadget_container: G,
44+
_marker: PhantomData<F>,
45+
}
46+
47+
pub(crate) struct UnitTestMemoryGadgetBaseCircuit<G> {
48+
witnesses: Vec<Word>,
49+
_marker: PhantomData<G>,
50+
}
51+
52+
impl<G> UnitTestMemoryGadgetBaseCircuit<G> {
53+
fn new(witnesses: Vec<Word>) -> Self {
54+
UnitTestMemoryGadgetBaseCircuit {
55+
witnesses,
56+
_marker: PhantomData,
57+
}
58+
}
59+
}
60+
61+
impl<F: Field, G: MemoryGadgetContainer<F>> Circuit<F> for UnitTestMemoryGadgetBaseCircuit<G> {
62+
type Config = (UnitTestMemoryGadgetBaseCircuitConfig<F, G>, Challenges);
63+
type FloorPlanner = SimpleFloorPlanner;
64+
type Params = ();
65+
66+
fn without_witnesses(&self) -> Self {
67+
UnitTestMemoryGadgetBaseCircuit {
68+
witnesses: vec![],
69+
_marker: PhantomData,
70+
}
71+
}
72+
73+
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
74+
let challenges = Challenges::construct(meta);
75+
let challenges_exprs = challenges.exprs(meta);
76+
77+
let q_usable = meta.selector();
78+
79+
let lookup_column_count: usize = LOOKUP_CONFIG.iter().map(|(_, count)| *count).sum();
80+
let advices = [(); STEP_WIDTH]
81+
.iter()
82+
.enumerate()
83+
.map(|(n, _)| {
84+
if n < lookup_column_count {
85+
meta.advice_column_in(ThirdPhase)
86+
} else if n < lookup_column_count + N_PHASE2_COLUMNS {
87+
meta.advice_column_in(SecondPhase)
88+
} else {
89+
meta.advice_column_in(FirstPhase)
90+
}
91+
})
92+
.collect::<Vec<_>>()
93+
.try_into()
94+
.unwrap();
95+
96+
let step_curr = Step::new(meta, advices, 0);
97+
let step_next = Step::new(meta, advices, MAX_STEP_HEIGHT);
98+
let mut cb = EVMConstraintBuilder::new(
99+
meta,
100+
step_curr.clone(),
101+
step_next,
102+
&challenges_exprs,
103+
ExecutionState::STOP,
104+
);
105+
let memory_gadget_container = G::configure_gadget_container(&mut cb);
106+
let (constraints, stored_expressions, _, _) = cb.build();
107+
108+
if !constraints.step.is_empty() {
109+
let step_constraints = constraints.step;
110+
meta.create_gate("MemoryGadgetTestContainer", |meta| {
111+
let q_usable = meta.query_selector(q_usable);
112+
step_constraints
113+
.into_iter()
114+
.map(move |(name, constraint)| (name, q_usable.clone() * constraint))
115+
});
116+
}
117+
118+
(
119+
UnitTestMemoryGadgetBaseCircuitConfig::<F, G> {
120+
q_usable,
121+
advices,
122+
step: step_curr,
123+
stored_expressions,
124+
memory_gadget_container,
125+
_marker: PhantomData,
126+
},
127+
challenges,
128+
)
129+
}
130+
131+
fn synthesize(
132+
&self,
133+
config: Self::Config,
134+
mut layouter: impl Layouter<F>,
135+
) -> Result<(), Error> {
136+
let (config, challenges) = config;
137+
let challenge_values = challenges.values(&mut layouter);
138+
layouter.assign_region(
139+
|| "assign test container",
140+
|mut region| {
141+
let offset = 0;
142+
config.q_usable.enable(&mut region, offset)?;
143+
let cached_region = &mut CachedRegion::<'_, '_, F>::new(
144+
&mut region,
145+
&challenge_values,
146+
config.advices.to_vec(),
147+
MAX_STEP_HEIGHT * 3,
148+
offset,
149+
);
150+
config.step.state.execution_state.assign(
151+
cached_region,
152+
offset,
153+
ExecutionState::STOP as usize,
154+
)?;
155+
config
156+
.memory_gadget_container
157+
.assign_gadget_container(&self.witnesses, cached_region)?;
158+
for stored_expr in &config.stored_expressions {
159+
stored_expr.assign(cached_region, offset)?;
160+
}
161+
Ok(())
162+
},
163+
)?;
164+
165+
Ok(())
166+
}
167+
}
168+
169+
/// This fn test_math_gadget_container takes math gadget container and run a
170+
/// container based circuit. All test logic should be included in the container,
171+
/// and witness words are used for both input & output data. How to deal with
172+
/// the witness words is left to each container.
173+
pub(crate) fn test_memory_gadget_container<F: Field, G: MemoryGadgetContainer<F>>(
174+
witnesses: Vec<Word>,
175+
expected_success: bool,
176+
) {
177+
const K: usize = 12;
178+
let circuit = UnitTestMemoryGadgetBaseCircuit::<G>::new(witnesses);
179+
180+
let prover = MockProver::<F>::run(K as u32, &circuit, vec![]).unwrap();
181+
if expected_success {
182+
assert_eq!(prover.verify(), Ok(()));
183+
} else {
184+
assert_ne!(prover.verify(), Ok(()));
185+
}
186+
}
187+
188+
pub(crate) fn required_bytes(value: Word) -> usize {
189+
for i in (0..32).rev() {
190+
let byte = (value >> (i * 8)) & Word::from(0xff);
191+
if byte != Word::zero() {
192+
return i + 1;
193+
}
194+
}
195+
0
196+
}
197+
198+
/// A simple macro for less code & better readability
199+
macro_rules! try_test {
200+
($base_class:ty, $witnesses:expr, $expect_success:expr $(,)?) => {{
201+
test_memory_gadget_container::<Fr, $base_class>($witnesses.to_vec(), $expect_success)
202+
}};
203+
}
204+
205+
#[cfg(test)]
206+
pub(crate) use try_test;

0 commit comments

Comments
 (0)