1use midnight_proofs::{circuit::Layouter, plonk::Error};
25use num_bigint::BigUint;
26
27use crate::{
28 instructions::{AssignmentInstructions, FieldInstructions},
29 types::{AssignedBit, InnerConstants, Instantiable},
30 CircuitField,
31};
32
33pub trait CanonicityInstructions<F, Assigned>:
35 FieldInstructions<F, Assigned> + AssignmentInstructions<F, AssignedBit<F>>
36where
37 F: CircuitField,
38 Assigned::Element: CircuitField,
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, PrimeField};
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, cost_measure_end, cost_measure_start},
142 util::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: CircuitField,
168 Assigned::Element: CircuitField,
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 &mut vec![],
190 &mut vec![],
191 &[committed_instance_column, instance_column],
192 )
193 }
194
195 fn synthesize(
196 &self,
197 config: Self::Config,
198 mut layouter: impl Layouter<F>,
199 ) -> Result<(), Error> {
200 let chip = CanonicityChip::new_from_scratch(&config);
201
202 let bits = self
203 .bits
204 .iter()
205 .map(|b| chip.assign_fixed(&mut layouter, *b))
206 .collect::<Result<Vec<_>, Error>>()?;
207 let bound = self.bound.clone();
208
209 cost_measure_start(&mut layouter);
210 let res = match self.operation {
211 Operation::Canonical => chip.is_canonical(&mut layouter, &bits),
212 Operation::Lower => chip.le_bits_lower_than(&mut layouter, &bits, bound),
213 Operation::Geq => chip.le_bits_geq_than(&mut layouter, &bits, bound),
214 }?;
215 cost_measure_end(&mut layouter);
216
217 chip.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
218
219 chip.load_from_scratch(&mut layouter)
220 }
221 }
222
223 #[allow(clippy::too_many_arguments)]
224 fn run<F, Assigned, CanonicityChip>(
225 bits: &[u8],
226 bound: Option<&BigUint>,
227 expected: bool,
228 operation: Operation,
229 must_pass: bool,
230 cost_model: bool,
231 chip_name: &str,
232 op_name: &str,
233 ) where
234 F: CircuitField + FromUniformBytes<64> + Ord,
235 Assigned::Element: CircuitField,
236 Assigned: Instantiable<F> + InnerConstants + Clone,
237 CanonicityChip: CanonicityInstructions<F, Assigned>
238 + AssertionInstructions<F, Assigned>
239 + AssertionInstructions<F, AssignedBit<F>>
240 + AssignmentInstructions<F, Assigned>
241 + FromScratch<F>,
242 {
243 let circuit = TestCircuit::<F, Assigned, CanonicityChip> {
244 bits: bits.iter().map(|b| *b != 0).collect::<Vec<_>>(),
245 bound: bound.unwrap_or(&BigUint::default()).clone(),
246 expected,
247 operation,
248 _marker: PhantomData,
249 };
250 let public_inputs = vec![vec![], vec![]];
251 match MockProver::run(&circuit, public_inputs) {
252 Ok(prover) => match prover.verify() {
253 Ok(()) => assert!(must_pass),
254 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
255 },
256 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
257 }
258
259 if cost_model {
260 circuit_to_json(chip_name, op_name, circuit);
261 }
262 }
263
264 fn decompose_biguint(n: &BigUint) -> Vec<u8> {
268 (0..(n.bits() as usize)).map(|i| if n.bit(i as u64) { 1 } else { 0 }).collect()
269 }
270
271 pub fn test_canonical<F, Assigned, CanonicityChip>(name: &str)
272 where
273 F: CircuitField + FromUniformBytes<64> + Ord,
274 Assigned::Element: CircuitField,
275 Assigned: Instantiable<F> + InnerConstants + Clone,
276 CanonicityChip: CanonicityInstructions<F, Assigned>
277 + AssertionInstructions<F, Assigned>
278 + AssertionInstructions<F, AssignedBit<F>>
279 + AssignmentInstructions<F, Assigned>
280 + FromScratch<F>,
281 {
282 let m = Assigned::Element::modulus();
283 let mut cost_model = true;
284 [
285 (vec![0], true),
286 (vec![1], true),
287 (vec![1, 0, 1], true),
288 (decompose_biguint(&m), false),
289 (decompose_biguint(&(m - BigUint::one())), true),
290 (vec![0; Assigned::Element::NUM_BITS as usize], true),
291 (vec![1; Assigned::Element::NUM_BITS as usize], false),
292 (vec![0; 1 + Assigned::Element::NUM_BITS as usize], false),
293 ]
294 .iter()
295 .for_each(|(bits, expected)| {
296 run::<F, Assigned, CanonicityChip>(
297 bits,
298 None,
299 *expected,
300 Operation::Canonical,
301 true,
302 cost_model,
303 name,
304 "canonical",
305 );
306 cost_model = false;
307 run::<F, Assigned, CanonicityChip>(
308 bits,
309 None,
310 !expected,
311 Operation::Canonical,
312 false,
313 false,
314 "",
315 "",
316 );
317 });
318 }
319
320 pub fn test_le_bits_lower_and_geq<F, Assigned, CanonChip>(name: &str)
321 where
322 F: CircuitField + FromUniformBytes<64> + Ord,
323 Assigned::Element: CircuitField,
324 Assigned: Instantiable<F> + InnerConstants + Clone,
325 CanonChip: CanonicityInstructions<F, Assigned>
326 + AssertionInstructions<F, Assigned>
327 + AssertionInstructions<F, AssignedBit<F>>
328 + AssignmentInstructions<F, Assigned>
329 + FromScratch<F>,
330 {
331 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
332 let r: BigUint = rng.next_u64().into();
333 let m = Assigned::Element::modulus();
334 let mut cost_model = true;
335 [
336 (decompose_biguint(&r), r.clone() - BigUint::one(), true),
337 (decompose_biguint(&r), r.clone(), true),
338 (decompose_biguint(&r), r + BigUint::one(), false),
339 (decompose_biguint(&m), m.clone(), true),
340 (decompose_biguint(&m), m + BigUint::one(), false),
341 (vec![0], BigUint::zero(), true),
342 (vec![1], BigUint::zero(), true),
343 (vec![1, 0, 1], BigUint::from(5u64), true),
344 (vec![1, 0, 1], BigUint::from(6u64), false),
345 (vec![1, 1, 1, 0, 0, 0], BigUint::from(7u64), true),
346 (vec![1, 1, 1, 0, 0, 0], BigUint::from(8u64), false),
347 ]
348 .iter()
349 .for_each(|(bits, bound, geq)| {
350 run::<_, _, CanonChip>(
351 bits,
352 Some(bound),
353 !geq,
354 Operation::Lower,
355 true,
356 cost_model,
357 name,
358 "lt",
359 );
360 run::<_, _, CanonChip>(
361 bits,
362 Some(bound),
363 *geq,
364 Operation::Geq,
365 true,
366 cost_model,
367 name,
368 "geq",
369 );
370 cost_model = false;
371 run::<_, _, CanonChip>(
372 bits,
373 Some(bound),
374 *geq,
375 Operation::Lower,
376 false,
377 false,
378 "",
379 "",
380 );
381 run::<_, _, CanonChip>(
382 bits,
383 Some(bound),
384 !geq,
385 Operation::Geq,
386 false,
387 false,
388 "",
389 "",
390 );
391 });
392 }
393}