1use std::fmt::Debug;
24
25use midnight_proofs::{circuit::Layouter, plonk::Error};
26
27use crate::{
28 instructions::{ArithInstructions, CanonicityInstructions, ConversionInstructions},
29 types::{AssignedBit, AssignedByte, AssignedNative, InnerConstants, Instantiable},
30 CircuitField,
31};
32
33pub trait DecompositionInstructions<F, Assigned>:
35 CanonicityInstructions<F, Assigned>
36 + ArithInstructions<F, Assigned>
37 + ConversionInstructions<F, AssignedBit<F>, Assigned>
38 + ConversionInstructions<F, AssignedByte<F>, Assigned>
39where
40 F: CircuitField,
41 Assigned::Element: CircuitField,
42 Assigned: Instantiable<F> + InnerConstants + Clone,
43{
44 fn assigned_to_le_bits(
86 &self,
87 layouter: &mut impl Layouter<F>,
88 x: &Assigned,
89 nb_bits: Option<usize>,
90 enforce_canonical: bool,
91 ) -> Result<Vec<AssignedBit<F>>, Error>;
92
93 fn assigned_to_be_bits(
96 &self,
97 layouter: &mut impl Layouter<F>,
98 x: &Assigned,
99 nb_bits: Option<usize>,
100 enforce_canonical: bool,
101 ) -> Result<Vec<AssignedBit<F>>, Error> {
102 let mut bits = self.assigned_to_le_bits(layouter, x, nb_bits, enforce_canonical)?;
103 bits.reverse();
104 Ok(bits)
105 }
106
107 fn assigned_to_le_bytes(
147 &self,
148 layouter: &mut impl Layouter<F>,
149 x: &Assigned,
150 nb_bytes: Option<usize>,
151 ) -> Result<Vec<AssignedByte<F>>, Error>;
152
153 fn assigned_to_be_bytes(
156 &self,
157 layouter: &mut impl Layouter<F>,
158 x: &Assigned,
159 nb_bytes: Option<usize>,
160 ) -> Result<Vec<AssignedByte<F>>, Error> {
161 let mut bytes = self.assigned_to_le_bytes(layouter, x, nb_bytes)?;
162 bytes.reverse();
163 Ok(bytes)
164 }
165
166 fn assigned_from_le_bits(
182 &self,
183 layouter: &mut impl Layouter<F>,
184 bits: &[AssignedBit<F>],
185 ) -> Result<Assigned, Error> {
186 let mut coeff = Assigned::Element::from(1);
187 let mut terms = vec![];
188 for b in bits {
189 let b_as_element: Assigned = self.convert(layouter, b)?;
190 terms.push((coeff, b_as_element));
191 coeff = coeff + coeff; }
193 let terms = terms.iter().map(|(c, b)| (*c, b.clone())).collect::<Vec<_>>();
194 self.linear_combination(layouter, &terms, Assigned::Element::from(0))
195 }
196
197 fn assigned_from_be_bits(
200 &self,
201 layouter: &mut impl Layouter<F>,
202 bits: &[AssignedBit<F>],
203 ) -> Result<Assigned, Error> {
204 let mut bits = bits.to_vec();
205 bits.reverse();
206 self.assigned_from_le_bits(layouter, &bits)
207 }
208
209 fn assigned_from_le_bytes(
229 &self,
230 layouter: &mut impl Layouter<F>,
231 bytes: &[AssignedByte<F>],
232 ) -> Result<Assigned, Error> {
233 let mut coeff = Assigned::Element::from(1);
234 let mut terms = vec![];
235 for byte in bytes {
236 let byte_as_element: Assigned = self.convert(layouter, byte)?;
237 terms.push((coeff, byte_as_element));
238 coeff = Assigned::Element::from(256) * coeff; }
240 let terms = terms.iter().map(|(c, b)| (*c, b.clone())).collect::<Vec<_>>();
241 self.linear_combination(layouter, &terms, Assigned::Element::from(0))
242 }
243
244 fn assigned_from_be_bytes(
247 &self,
248 layouter: &mut impl Layouter<F>,
249 bytes: &[AssignedByte<F>],
250 ) -> Result<Assigned, Error> {
251 let mut bytes = bytes.to_vec();
252 bytes.reverse();
253 self.assigned_from_le_bytes(layouter, &bytes)
254 }
255
256 fn assigned_to_le_chunks(
276 &self,
277 layouter: &mut impl Layouter<F>,
278 x: &Assigned,
279 nb_bits_per_chunk: usize,
280 nb_chunks: Option<usize>,
281 ) -> Result<Vec<AssignedNative<F>>, Error>;
282
283 fn sgn0(&self, layouter: &mut impl Layouter<F>, x: &Assigned) -> Result<AssignedBit<F>, Error> {
285 let bits = self.assigned_to_le_bits(layouter, x, None, true)?;
286 Ok(bits[0].clone())
287 }
288}
289
290pub trait Pow2RangeInstructions<F: CircuitField>: Debug + Clone {
292 fn assert_values_lower_than_2_pow_n(
294 &self,
295 layouter: &mut impl Layouter<F>,
296 values: &[AssignedNative<F>],
297 n: usize,
298 ) -> Result<(), Error>;
299}
300
301#[cfg(test)]
302pub(crate) mod tests {
303 use std::marker::PhantomData;
304
305 use ff::{Field, FromUniformBytes};
306 use midnight_proofs::{
307 circuit::{Layouter, SimpleFloorPlanner},
308 dev::MockProver,
309 plonk::{Circuit, ConstraintSystem},
310 };
311 use num_bigint::BigUint;
312 use num_traits::One;
313 use rand::{RngCore, SeedableRng};
314 use rand_chacha::ChaCha8Rng;
315
316 use super::*;
317 use crate::{
318 instructions::{AssertionInstructions, AssignmentInstructions},
319 testing_utils::FromScratch,
320 types::InnerValue,
321 utils::{
322 circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
323 util::big_to_fe,
324 },
325 };
326
327 #[derive(Clone, Debug)]
328 enum Endianess {
329 LE,
330 BE,
331 }
332
333 #[derive(Clone, Debug)]
334 enum Operation {
335 ToBits,
336 ToBytes,
337 FromBits,
338 FromBytes,
339 Sgn0,
340 }
341
342 #[derive(Clone, Debug)]
343 struct TestCircuit<F, Assigned, DecompChip, AuxChip>
344 where
345 Assigned: InnerValue,
346 {
347 x: Assigned::Element,
348 decomposed: Vec<u8>,
349 nb_parts: Option<usize>,
350 endianess: Endianess,
351 operation: Operation,
352 _marker: PhantomData<(F, Assigned, DecompChip, AuxChip)>,
353 }
354
355 impl<F, Assigned, DecompChip, AuxChip> Circuit<F> for TestCircuit<F, Assigned, DecompChip, AuxChip>
356 where
357 F: CircuitField,
358 Assigned::Element: CircuitField,
359 Assigned: Instantiable<F> + InnerConstants + Clone,
360 DecompChip: DecompositionInstructions<F, Assigned> + FromScratch<F>,
361 AuxChip: AssertionInstructions<F, AssignedBit<F>>
362 + AssertionInstructions<F, AssignedByte<F>>
363 + AssignmentInstructions<F, AssignedBit<F>>
364 + AssignmentInstructions<F, AssignedByte<F>>
365 + FromScratch<F>,
366 {
367 type Config = (
368 <DecompChip as FromScratch<F>>::Config,
369 <AuxChip as FromScratch<F>>::Config,
370 );
371 type FloorPlanner = SimpleFloorPlanner;
372 type Params = ();
373
374 fn without_witnesses(&self) -> Self {
375 unreachable!()
376 }
377
378 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
379 let committed_instance_column = meta.instance_column();
380 let instance_column = meta.instance_column();
381 let instance_columns = [committed_instance_column, instance_column];
382 let mut advice_columns = vec![];
383 let mut fixed_columns = vec![];
384 (
385 DecompChip::configure_from_scratch(
386 meta,
387 &mut advice_columns,
388 &mut fixed_columns,
389 &instance_columns,
390 ),
391 AuxChip::configure_from_scratch(
392 meta,
393 &mut advice_columns,
394 &mut fixed_columns,
395 &instance_columns,
396 ),
397 )
398 }
399
400 fn synthesize(
401 &self,
402 config: Self::Config,
403 mut layouter: impl Layouter<F>,
404 ) -> Result<(), Error> {
405 let chip = DecompChip::new_from_scratch(&config.0);
406 let aux_chip = AuxChip::new_from_scratch(&config.1);
407
408 use Endianess::*;
409 cost_measure_start(&mut layouter);
410 match self.operation {
411 Operation::ToBits => {
412 let x: Assigned = chip.assign_fixed(&mut layouter, self.x)?;
413 let nb_bits = self.nb_parts;
414 let bits = match self.endianess {
415 LE => chip.assigned_to_le_bits(&mut layouter, &x, nb_bits, true),
416 BE => chip.assigned_to_be_bits(&mut layouter, &x, nb_bits, true),
417 }?;
418 assert_eq!(bits.len(), self.decomposed.len());
419 bits.iter().zip(self.decomposed.iter()).try_for_each(|(bit, expected)| {
420 aux_chip.assert_equal_to_fixed(&mut layouter, bit, *expected == 1)
421 })
422 }
423 Operation::ToBytes => {
424 let x: Assigned = chip.assign_fixed(&mut layouter, self.x)?;
425 let nb_bytes = self.nb_parts;
426 let bytes = match self.endianess {
427 LE => chip.assigned_to_le_bytes(&mut layouter, &x, nb_bytes),
428 BE => chip.assigned_to_be_bytes(&mut layouter, &x, nb_bytes),
429 }?;
430 bytes.iter().zip(self.decomposed.iter()).try_for_each(|(bit, expected)| {
431 aux_chip.assert_equal_to_fixed(&mut layouter, bit, *expected)
432 })
433 }
434 Operation::FromBits => {
435 let bits = self
436 .decomposed
437 .iter()
438 .map(|b| chip.assign_fixed(&mut layouter, *b == 1))
439 .collect::<Result<Vec<_>, Error>>()?;
440 let x = match self.endianess {
441 LE => chip.assigned_from_le_bits(&mut layouter, &bits),
442 BE => chip.assigned_from_be_bits(&mut layouter, &bits),
443 }?;
444 chip.assert_equal_to_fixed(&mut layouter, &x, self.x)
445 }
446 Operation::FromBytes => {
447 let bytes = self
448 .decomposed
449 .iter()
450 .map(|byte| aux_chip.assign_fixed(&mut layouter, *byte))
451 .collect::<Result<Vec<_>, Error>>()?;
452 let x = match self.endianess {
453 LE => chip.assigned_from_le_bytes(&mut layouter, &bytes),
454 BE => chip.assigned_from_be_bytes(&mut layouter, &bytes),
455 }?;
456 chip.assert_equal_to_fixed(&mut layouter, &x, self.x)
457 }
458 Operation::Sgn0 => {
459 let decomposed = if self.decomposed.is_empty() {
461 &vec![0u8]
462 } else {
463 &self.decomposed
464 };
465 let x: Assigned = chip.assign_fixed(&mut layouter, self.x)?;
466 let sign = chip.sgn0(&mut layouter, &x)?;
467 let lsb = match self.endianess {
468 LE => decomposed[0] & 1,
469 BE => decomposed.last().unwrap() & 1,
470 };
471 aux_chip.assert_equal_to_fixed(&mut layouter, &sign, lsb == 1u8)
472 }
473 }?;
474 cost_measure_end(&mut layouter);
475
476 chip.load_from_scratch(&mut layouter)?;
477 aux_chip.load_from_scratch(&mut layouter)
478 }
479 }
480
481 #[allow(clippy::too_many_arguments)]
482 fn run<F, Assigned, DecompChip, AuxChip>(
483 x: Assigned::Element,
484 decomposed: &[u8],
485 nb_parts: Option<usize>,
486 endianess: Endianess,
487 operation: Operation,
488 must_pass: bool,
489 cost_model: bool,
490 chip_name: &str,
491 op_name: &str,
492 ) where
493 F: CircuitField + FromUniformBytes<64> + Ord,
494 Assigned::Element: CircuitField,
495 Assigned: Instantiable<F> + InnerConstants + Clone,
496 DecompChip: DecompositionInstructions<F, Assigned> + FromScratch<F>,
497 AuxChip: AssertionInstructions<F, AssignedBit<F>>
498 + AssertionInstructions<F, AssignedByte<F>>
499 + AssignmentInstructions<F, AssignedBit<F>>
500 + AssignmentInstructions<F, AssignedByte<F>>
501 + FromScratch<F>,
502 {
503 let circuit = TestCircuit::<F, Assigned, DecompChip, AuxChip> {
504 x,
505 decomposed: decomposed.to_vec(),
506 nb_parts,
507 endianess,
508 operation,
509 _marker: PhantomData,
510 };
511 let public_inputs = vec![vec![], vec![]];
512 match MockProver::run(&circuit, public_inputs) {
513 Ok(prover) => match prover.verify() {
514 Ok(()) => assert!(must_pass),
515 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
516 },
517 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
518 }
519
520 if cost_model {
521 circuit_to_json(chip_name, op_name, circuit);
522 }
523 }
524
525 fn biguint_to_bits(n: &BigUint) -> Vec<u8> {
529 (0..(n.bits() as usize)).map(|i| if n.bit(i as u64) { 1 } else { 0 }).collect()
530 }
531
532 fn biguint_to_bytes(n: &BigUint) -> Vec<u8> {
533 biguint_to_bits(n)
534 .chunks(8)
535 .map(|chunk| chunk.iter().enumerate().map(|(i, bi)| (1 << i) * bi).sum())
536 .collect()
537 }
538
539 macro_rules! parse {
540 ($n:expr) => {
541 Assigned::Element::from($n)
542 };
543 }
544
545 pub fn test_bit_decomposition<F, Assigned, DecompChip, AuxChip>(name: &str)
546 where
547 F: CircuitField + FromUniformBytes<64> + Ord,
548 Assigned::Element: CircuitField,
549 Assigned: Instantiable<F> + InnerConstants + Clone,
550 DecompChip: DecompositionInstructions<F, Assigned> + FromScratch<F>,
551 AuxChip: AssertionInstructions<F, AssignedBit<F>>
552 + AssertionInstructions<F, AssignedByte<F>>
553 + AssignmentInstructions<F, AssignedBit<F>>
554 + AssignmentInstructions<F, AssignedByte<F>>
555 + FromScratch<F>,
556 {
557 use Endianess::*;
558 use Operation::*;
559 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
560 let r = rng.next_u64();
561 let mut bits_of_r = biguint_to_bits(&r.into());
562 bits_of_r.resize(64, 0);
563 let m = Assigned::Element::modulus();
564 let m_minus_1 = m.clone() - BigUint::one();
565 let mut cost_model = true;
566 [
567 (parse!(r), bits_of_r, Some(64), true, true),
569 (parse!(0), biguint_to_bits(&m), None, false, true),
570 (-parse!(1), biguint_to_bits(&m_minus_1), None, true, true),
571 (parse!(0), vec![], Some(0), true, true),
572 (parse!(0), vec![0], Some(1), true, true),
573 (parse!(1), vec![1], Some(1), true, true),
574 (parse!(3), vec![1, 1, 0, 0, 0], Some(5), true, true),
575 (parse!(3), vec![0, 0, 0, 1, 1], Some(5), false, false),
576 ]
577 .iter()
578 .for_each(|(x, bits, nb_bits, ok_to, ok_from)| {
579 let mut rev = bits.clone();
580 rev.reverse();
581 run::<F, Assigned, DecompChip, AuxChip>(
582 *x, bits, *nb_bits, LE, ToBits, *ok_to, cost_model, name, "to_bits",
583 );
584 run::<F, Assigned, DecompChip, AuxChip>(
585 *x,
586 bits,
587 None,
588 LE,
589 FromBits,
590 *ok_from,
591 cost_model,
592 name,
593 "from_bits",
594 );
595 cost_model = false;
596 run::<F, Assigned, DecompChip, AuxChip>(
597 *x, &rev, *nb_bits, BE, ToBits, *ok_to, false, "", "",
598 );
599 run::<F, Assigned, DecompChip, AuxChip>(
600 *x, &rev, None, BE, FromBits, *ok_from, false, "", "",
601 );
602 });
603 }
604
605 pub fn test_byte_decomposition<F, Assigned, DecompChip, AuxChip>(name: &str)
606 where
607 F: CircuitField + FromUniformBytes<64> + Ord,
608 Assigned::Element: CircuitField,
609 Assigned: Instantiable<F> + InnerConstants + Clone,
610 DecompChip: DecompositionInstructions<F, Assigned> + FromScratch<F>,
611 AuxChip: AssertionInstructions<F, AssignedBit<F>>
612 + AssertionInstructions<F, AssignedByte<F>>
613 + AssignmentInstructions<F, AssignedBit<F>>
614 + AssignmentInstructions<F, AssignedByte<F>>
615 + FromScratch<F>,
616 {
617 use Endianess::*;
618 use Operation::*;
619
620 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
621 let r = rng.next_u64();
622 let mut bytes_of_r = biguint_to_bytes(&r.into());
623 bytes_of_r.resize(8, 0);
624 let m = Assigned::Element::modulus();
625 let m_minus_1 = m.clone() - BigUint::one();
626 let mut cost_model = true;
627 [
628 (parse!(r), bytes_of_r, Some(8), true, true),
630 (parse!(0), biguint_to_bytes(&m), None, false, true),
631 (-parse!(1), biguint_to_bytes(&m_minus_1), None, true, true),
632 (parse!(0), vec![], Some(0), true, true),
633 (parse!(255), vec![255, 0], Some(2), true, true),
634 (parse!(256), vec![0, 1], Some(2), true, true),
635 (parse!(256), vec![1, 0], Some(2), false, false),
636 (
637 parse!(0x12345678),
638 vec![0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00],
639 Some(7),
640 true,
641 true,
642 ),
643 ]
644 .iter()
645 .for_each(|(x, bytes, nb_bytes, ok_to, ok_from)| {
646 let mut rev = bytes.clone();
647 rev.reverse();
648 run::<F, Assigned, DecompChip, AuxChip>(
649 *x, bytes, *nb_bytes, LE, ToBytes, *ok_to, cost_model, name, "to_bytes",
650 );
651 run::<F, Assigned, DecompChip, AuxChip>(
652 *x,
653 bytes,
654 None,
655 LE,
656 FromBytes,
657 *ok_from,
658 cost_model,
659 name,
660 "from_bytes",
661 );
662 cost_model = false;
663 run::<F, Assigned, DecompChip, AuxChip>(
664 *x, &rev, *nb_bytes, BE, ToBytes, *ok_to, false, "", "",
665 );
666 run::<F, Assigned, DecompChip, AuxChip>(
667 *x, &rev, None, BE, FromBytes, *ok_from, false, "", "",
668 );
669 });
670 }
671
672 pub fn test_sgn0<F, Assigned, DecompChip, AuxChip>(name: &str)
673 where
674 F: CircuitField + FromUniformBytes<64> + Ord,
675 Assigned::Element: CircuitField,
676 Assigned: Instantiable<F> + InnerConstants + Clone,
677 DecompChip: DecompositionInstructions<F, Assigned> + FromScratch<F>,
678 AuxChip: AssertionInstructions<F, AssignedBit<F>>
679 + AssertionInstructions<F, AssignedByte<F>>
680 + AssignmentInstructions<F, AssignedBit<F>>
681 + AssignmentInstructions<F, AssignedByte<F>>
682 + FromScratch<F>,
683 {
684 let mut rng = ChaCha8Rng::seed_from_u64(0xc0ffee);
686 let random_test_cases: Vec<_> =
687 (0..100).map(|_| Assigned::Element::random(&mut rng)).collect();
688
689 let mut p = Assigned::Element::modulus();
692 p.set_bit(F::NUM_BITS as u64 - 1, false);
693 let x = big_to_fe(p.clone());
694
695 let edge_cases = &[
696 parse!(0),
697 parse!(1),
698 x,
699 big_to_fe(Assigned::Element::modulus() - BigUint::one()),
700 ];
701
702 let test_cases = &[random_test_cases.as_slice(), edge_cases].concat();
703
704 test_cases.iter().enumerate().for_each(|(i, x)| {
705 let bytes = biguint_to_bytes(&x.to_biguint());
706 run::<F, Assigned, DecompChip, AuxChip>(
707 *x,
708 &bytes,
709 None,
710 Endianess::LE,
711 Operation::Sgn0,
712 true,
713 i == 0, name,
715 "sgn0",
716 );
717 })
718 }
719}