1use ff::PrimeField;
25use midnight_proofs::{circuit::Layouter, plonk::Error};
26use num_bigint::BigUint;
27
28use crate::{
29 instructions::{AssignmentInstructions, FieldInstructions},
30 types::{AssignedBit, InnerConstants, Instantiable},
31};
32
33pub trait CanonicityInstructions<F, Assigned>:
35 FieldInstructions<F, Assigned> + AssignmentInstructions<F, AssignedBit<F>>
36where
37 F: PrimeField,
38 Assigned::Element: PrimeField,
39 Assigned: Instantiable<F> + InnerConstants + Clone,
40{
41 fn is_canonical(
66 &self,
67 layouter: &mut impl Layouter<F>,
68 bits: &[AssignedBit<F>],
69 ) -> Result<AssignedBit<F>, Error> {
70 let order = self.order();
71 if bits.len() > order.bits() as usize {
72 self.assign_fixed(layouter, false)
73 } else {
74 self.le_bits_lower_than(layouter, bits, order)
75 }
76 }
77
78 fn le_bits_lower_than(
105 &self,
106 layouter: &mut impl Layouter<F>,
107 bits: &[AssignedBit<F>],
108 bound: BigUint,
109 ) -> Result<AssignedBit<F>, Error>;
110
111 fn le_bits_geq_than(
115 &self,
116 layouter: &mut impl Layouter<F>,
117 bits: &[AssignedBit<F>],
118 bound: BigUint,
119 ) -> Result<AssignedBit<F>, Error>;
120}
121
122#[cfg(test)]
123pub(crate) mod tests {
124 use std::marker::PhantomData;
125
126 use ff::FromUniformBytes;
127 use midnight_proofs::{
128 circuit::{Layouter, SimpleFloorPlanner},
129 dev::MockProver,
130 plonk::{Circuit, ConstraintSystem},
131 };
132 use num_traits::{One, Zero};
133 use rand::{RngCore, SeedableRng};
134 use rand_chacha::ChaCha8Rng;
135
136 use super::*;
137 use crate::{
138 instructions::{AssertionInstructions, AssignmentInstructions},
139 types::InnerValue,
140 utils::{
141 circuit_modeling::circuit_to_json,
142 util::{modulus, FromScratch},
143 },
144 };
145
146 #[derive(Clone, Debug)]
147 enum Operation {
148 Canonical,
149 Lower,
150 Geq,
151 }
152
153 #[derive(Clone, Debug)]
154 struct TestCircuit<F, Assigned, CanonicityChip>
155 where
156 Assigned: InnerValue,
157 {
158 bits: Vec<bool>,
159 bound: BigUint,
160 expected: bool,
161 operation: Operation,
162 _marker: PhantomData<(F, Assigned, CanonicityChip)>,
163 }
164
165 impl<F, Assigned, CanonicityChip> Circuit<F> for TestCircuit<F, Assigned, CanonicityChip>
166 where
167 F: PrimeField,
168 Assigned::Element: PrimeField,
169 Assigned: Instantiable<F> + InnerConstants + Clone,
170 CanonicityChip: CanonicityInstructions<F, Assigned>
171 + AssertionInstructions<F, Assigned>
172 + AssertionInstructions<F, AssignedBit<F>>
173 + AssignmentInstructions<F, Assigned>
174 + FromScratch<F>,
175 {
176 type Config = <CanonicityChip as FromScratch<F>>::Config;
177 type FloorPlanner = SimpleFloorPlanner;
178 type Params = ();
179
180 fn without_witnesses(&self) -> Self {
181 unreachable!()
182 }
183
184 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
185 let committed_instance_column = meta.instance_column();
186 let instance_column = meta.instance_column();
187 CanonicityChip::configure_from_scratch(
188 meta,
189 &[committed_instance_column, instance_column],
190 )
191 }
192
193 fn synthesize(
194 &self,
195 config: Self::Config,
196 mut layouter: impl Layouter<F>,
197 ) -> Result<(), Error> {
198 let chip = CanonicityChip::new_from_scratch(&config);
199
200 let bits = self
201 .bits
202 .iter()
203 .map(|b| chip.assign_fixed(&mut layouter, *b))
204 .collect::<Result<Vec<_>, Error>>()?;
205 let bound = self.bound.clone();
206
207 let res = match self.operation {
208 Operation::Canonical => chip.is_canonical(&mut layouter, &bits),
209 Operation::Lower => chip.le_bits_lower_than(&mut layouter, &bits, bound),
210 Operation::Geq => chip.le_bits_geq_than(&mut layouter, &bits, bound),
211 }?;
212
213 chip.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
214
215 chip.load_from_scratch(&mut layouter)
216 }
217 }
218
219 #[allow(clippy::too_many_arguments)]
220 fn run<F, Assigned, CanonicityChip>(
221 bits: &[u8],
222 bound: Option<&BigUint>,
223 expected: bool,
224 operation: Operation,
225 must_pass: bool,
226 cost_model: bool,
227 chip_name: &str,
228 op_name: &str,
229 ) where
230 F: PrimeField + FromUniformBytes<64> + Ord,
231 Assigned::Element: PrimeField,
232 Assigned: Instantiable<F> + InnerConstants + Clone,
233 CanonicityChip: CanonicityInstructions<F, Assigned>
234 + AssertionInstructions<F, Assigned>
235 + AssertionInstructions<F, AssignedBit<F>>
236 + AssignmentInstructions<F, Assigned>
237 + FromScratch<F>,
238 {
239 let circuit = TestCircuit::<F, Assigned, CanonicityChip> {
240 bits: bits.iter().map(|b| *b != 0).collect::<Vec<_>>(),
241 bound: bound.unwrap_or(&BigUint::default()).clone(),
242 expected,
243 operation,
244 _marker: PhantomData,
245 };
246 let log2_nb_rows = 10;
247 let public_inputs = vec![vec![], vec![]];
248 match MockProver::run(log2_nb_rows, &circuit, public_inputs) {
249 Ok(prover) => match prover.verify() {
250 Ok(()) => assert!(must_pass),
251 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
252 },
253 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
254 }
255
256 if cost_model {
257 circuit_to_json(chip_name, op_name, circuit);
258 }
259 }
260
261 fn decompose_biguint(n: &BigUint) -> Vec<u8> {
265 (0..(n.bits() as usize)).map(|i| if n.bit(i as u64) { 1 } else { 0 }).collect()
266 }
267
268 pub fn test_canonical<F, Assigned, CanonicityChip>(name: &str)
269 where
270 F: PrimeField + FromUniformBytes<64> + Ord,
271 Assigned::Element: PrimeField,
272 Assigned: Instantiable<F> + InnerConstants + Clone,
273 CanonicityChip: CanonicityInstructions<F, Assigned>
274 + AssertionInstructions<F, Assigned>
275 + AssertionInstructions<F, AssignedBit<F>>
276 + AssignmentInstructions<F, Assigned>
277 + FromScratch<F>,
278 {
279 let m = modulus::<Assigned::Element>();
280 let mut cost_model = true;
281 [
282 (vec![0], true),
283 (vec![1], true),
284 (vec![1, 0, 1], true),
285 (decompose_biguint(&m), false),
286 (decompose_biguint(&(m - BigUint::one())), true),
287 (vec![0; Assigned::Element::NUM_BITS as usize], true),
288 (vec![1; Assigned::Element::NUM_BITS as usize], false),
289 (vec![0; 1 + Assigned::Element::NUM_BITS as usize], false),
290 ]
291 .iter()
292 .for_each(|(bits, expected)| {
293 run::<F, Assigned, CanonicityChip>(
294 bits,
295 None,
296 *expected,
297 Operation::Canonical,
298 true,
299 cost_model,
300 name,
301 "canonical",
302 );
303 cost_model = false;
304 run::<F, Assigned, CanonicityChip>(
305 bits,
306 None,
307 !expected,
308 Operation::Canonical,
309 false,
310 false,
311 "",
312 "",
313 );
314 });
315 }
316
317 pub fn test_le_bits_lower_and_geq<F, Assigned, CanonChip>(name: &str)
318 where
319 F: PrimeField + FromUniformBytes<64> + Ord,
320 Assigned::Element: PrimeField,
321 Assigned: Instantiable<F> + InnerConstants + Clone,
322 CanonChip: CanonicityInstructions<F, Assigned>
323 + AssertionInstructions<F, Assigned>
324 + AssertionInstructions<F, AssignedBit<F>>
325 + AssignmentInstructions<F, Assigned>
326 + FromScratch<F>,
327 {
328 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
329 let r: BigUint = rng.next_u64().into();
330 let m = modulus::<Assigned::Element>();
331 let mut cost_model = true;
332 [
333 (decompose_biguint(&r), r.clone() - BigUint::one(), true),
334 (decompose_biguint(&r), r.clone(), true),
335 (decompose_biguint(&r), r + BigUint::one(), false),
336 (decompose_biguint(&m), m.clone(), true),
337 (decompose_biguint(&m), m + BigUint::one(), false),
338 (vec![0], BigUint::zero(), true),
339 (vec![1], BigUint::zero(), true),
340 (vec![1, 0, 1], BigUint::from(5u64), true),
341 (vec![1, 0, 1], BigUint::from(6u64), false),
342 (vec![1, 1, 1, 0, 0, 0], BigUint::from(7u64), true),
343 (vec![1, 1, 1, 0, 0, 0], BigUint::from(8u64), false),
344 ]
345 .iter()
346 .for_each(|(bits, bound, geq)| {
347 run::<_, _, CanonChip>(
348 bits,
349 Some(bound),
350 !geq,
351 Operation::Lower,
352 true,
353 cost_model,
354 name,
355 "lt",
356 );
357 run::<_, _, CanonChip>(
358 bits,
359 Some(bound),
360 *geq,
361 Operation::Geq,
362 true,
363 cost_model,
364 name,
365 "geq",
366 );
367 cost_model = false;
368 run::<_, _, CanonChip>(
369 bits,
370 Some(bound),
371 *geq,
372 Operation::Lower,
373 false,
374 false,
375 "",
376 "",
377 );
378 run::<_, _, CanonChip>(
379 bits,
380 Some(bound),
381 !geq,
382 Operation::Geq,
383 false,
384 false,
385 "",
386 "",
387 );
388 });
389 }
390}