1use super::ipa;
135use crate::transcript::Transcript;
136use bytes::{Buf, BufMut};
137use commonware_codec::{Encode, EncodeSize, Error, Read, Write};
138use commonware_math::{
139 algebra::{powers, Additive, CryptoGroup, Field, HashToGroup, Random, Ring, Space},
140 synthetic::Synthetic,
141};
142use commonware_parallel::{Sequential, Strategy};
143use rand_core::CryptoRng;
144use std::{
145 collections::BTreeMap,
146 ops::{Index, IndexMut, Mul},
147};
148
149pub struct SparseMatrix<F> {
153 width: usize,
154 height: usize,
155 weights: BTreeMap<(usize, usize), F>,
156 zero: F,
158}
159
160impl<F> SparseMatrix<F> {
161 pub const fn width(&self) -> usize {
165 self.width
166 }
167
168 pub const fn height(&self) -> usize {
172 self.height
173 }
174
175 pub fn pad(&mut self, width: usize, height: usize) {
177 self.width = self.width.max(width);
178 self.height = self.height.max(height);
179 }
180}
181
182impl<F> IntoIterator for SparseMatrix<F> {
183 type Item = ((usize, usize), F);
184
185 type IntoIter = <BTreeMap<(usize, usize), F> as IntoIterator>::IntoIter;
186
187 fn into_iter(self) -> Self::IntoIter {
188 self.weights.into_iter()
189 }
190}
191
192impl<F: Additive> Default for SparseMatrix<F> {
193 fn default() -> Self {
194 Self {
195 width: 0,
196 height: 0,
197 weights: Default::default(),
198 zero: F::zero(),
199 }
200 }
201}
202
203impl<F: Additive> Index<(usize, usize)> for SparseMatrix<F> {
204 type Output = F;
205
206 fn index(&self, idx: (usize, usize)) -> &Self::Output {
207 self.weights.get(&idx).unwrap_or(&self.zero)
208 }
209}
210
211impl<F: Additive> IndexMut<(usize, usize)> for SparseMatrix<F> {
212 fn index_mut(&mut self, idx: (usize, usize)) -> &mut Self::Output {
213 self.height = self
214 .height
215 .max(idx.0.checked_add(1).expect("row index overflow"));
216 self.width = self
217 .width
218 .max(idx.1.checked_add(1).expect("column index overflow"));
219 self.weights.entry(idx).or_insert(F::zero())
220 }
221}
222
223impl<F: Ring> Mul<&[F]> for &SparseMatrix<F> {
224 type Output = Vec<F>;
225
226 fn mul(self, rhs: &[F]) -> Self::Output {
227 let mut out = vec![F::zero(); self.height];
228 for (&(i, j), weight) in &self.weights {
229 let Some(value) = rhs.get(j) else {
230 continue;
231 };
232 out[i] += &(weight.clone() * value);
233 }
234 out
235 }
236}
237
238impl<F: Write> Write for SparseMatrix<F> {
239 fn write(&self, buf: &mut impl BufMut) {
240 self.weights.write(buf);
241 }
242}
243
244impl<F: EncodeSize> EncodeSize for SparseMatrix<F> {
245 fn encode_size(&self) -> usize {
246 self.weights.encode_size()
247 }
248}
249
250pub struct Circuit<F> {
252 committed_vars: usize,
253 internal_vars: usize,
254 weights: SparseMatrix<F>,
255}
256
257impl<F: Write> Write for Circuit<F> {
258 fn write(&self, buf: &mut impl BufMut) {
259 self.committed_vars.write(buf);
260 self.weights.write(buf);
261 }
262}
263
264impl<F: Encode> Circuit<F> {
265 fn commit(&self, transcript: &mut Transcript) {
266 transcript.commit(self.encode());
267 }
268}
269
270impl<F: EncodeSize> EncodeSize for Circuit<F> {
271 fn encode_size(&self) -> usize {
272 self.committed_vars.encode_size() + self.weights.encode_size()
273 }
274}
275
276impl<F: Ring> Circuit<F> {
277 pub fn new(committed_vars: usize, weights: SparseMatrix<F>) -> Option<Self> {
290 let remaining_vars = weights.width.checked_sub(committed_vars.checked_add(1)?)?;
291 if remaining_vars % 3 != 0 {
292 return None;
293 }
294 let internal_vars = remaining_vars / 3;
295 Some(Self {
296 committed_vars,
297 internal_vars,
298 weights,
299 })
300 }
301
302 pub const fn internal_vars(&self) -> usize {
304 self.internal_vars
305 }
306
307 pub const fn committed_vars(&self) -> usize {
309 self.committed_vars
310 }
311
312 #[must_use]
317 pub fn is_satisfied(
318 &self,
319 committed_values: &[F],
320 left_values: &[F],
321 right_values: &[F],
322 ) -> bool {
323 if committed_values.len() != self.committed_vars
324 || left_values.len() != self.internal_vars
325 || right_values.len() != self.internal_vars
326 {
327 return false;
328 }
329 let mut output = Vec::with_capacity(1 + self.committed_vars + 3 * self.internal_vars);
330 output.push(F::one());
331 output.extend_from_slice(committed_values);
332 output.extend_from_slice(left_values);
333 output.extend_from_slice(right_values);
334 output.extend(
335 left_values
336 .iter()
337 .zip(right_values)
338 .map(|(l_i, r_i)| l_i.clone() * r_i),
339 );
340 let mut res = vec![F::zero(); self.weights.height];
341 for (&(i, j), w_ij) in &self.weights.weights {
342 res[i] += &(output[j].clone() * w_ij);
343 }
344 let zero = F::zero();
345 res.iter().all(|r_i| r_i == &zero)
346 }
347}
348
349mod zkc {
358 use crate::zk::circuit as zk;
359 use commonware_math::algebra::{Field, Random, Ring};
360 use commonware_utils::ordered::Map;
361 use rand_core::CryptoRng;
362 use std::{borrow::Cow, collections::BTreeMap};
363
364 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
371 enum Location {
372 One,
373 Witness(usize),
374 Left(usize),
375 Right(usize),
376 Output(usize),
377 Committed(usize),
378 }
379
380 #[derive(Clone)]
386 enum LinComb<F> {
387 Location(Location),
388 Constant(F),
389 General(Map<Location, F>),
390 }
391
392 impl<F: Ring> LinComb<F> {
393 fn mul(&self, other: &Self) -> Option<Self> {
398 let (constant, other) = match (self, other) {
399 (Self::Constant(c), other) => (c.clone(), other),
400 (other, Self::Constant(c)) => (c.clone(), other),
401 _ => return None,
402 };
403 let out = match other {
404 Self::Location(location) => Self::General(
405 Map::try_from([(*location, constant)])
406 .expect("single entry cannot duplicate keys"),
407 ),
408 Self::Constant(other_constant) => Self::Constant(constant * other_constant),
409 Self::General(items) => {
410 let mut items = items.clone();
411 for w in items.values_mut() {
412 *w = w.clone() * &constant;
413 }
414 Self::General(items)
415 }
416 };
417 Some(out)
418 }
419
420 fn sum(&self, other: &Self) -> Self {
424 let mut terms: Vec<(Location, F)> = self
425 .iter()
426 .chain(other.iter())
427 .map(|(w, loc)| (loc, w.into_owned()))
428 .collect();
429 terms.sort_by_key(|&(loc, _)| loc);
430 let mut merged: Vec<(Location, F)> = Vec::with_capacity(terms.len());
431 for (loc, w) in terms {
432 match merged.last_mut() {
433 Some((last, acc)) if *last == loc => *acc += &w,
434 _ => merged.push((loc, w)),
435 }
436 }
437 Self::General(Map::try_from(merged).expect("merged locations should be unique"))
439 }
440
441 fn iter(&self) -> impl Iterator<Item = (Cow<'_, F>, Location)> {
443 let (single, general) = match self {
444 Self::Location(loc) => (Some((Cow::Owned(F::one()), *loc)), None),
445 Self::Constant(w) => (Some((Cow::Borrowed(w), Location::One)), None),
446 Self::General(items) => (None, Some(items.iter_pairs())),
447 };
448 single.into_iter().chain(
449 general
450 .into_iter()
451 .flatten()
452 .map(|(loc, w)| (Cow::Borrowed(w), *loc)),
453 )
454 }
455
456 const fn witness(&self) -> Option<usize> {
459 match self {
460 Self::Location(Location::Witness(w)) => Some(*w),
461 _ => None,
462 }
463 }
464 }
465
466 pub struct ZKCConverter<F> {
472 linearize_queue: Vec<zk::CircuitIdx>,
474 linearize_cache: BTreeMap<zk::CircuitIdx, LinComb<F>>,
476 assertions: Vec<(zk::CircuitIdx, zk::CircuitIdx)>,
478 extra_assertions: Vec<(zk::CircuitIdx, Location)>,
481 witness_locations: BTreeMap<usize, Location>,
483 committed_indices: Vec<zk::CircuitIdx>,
485 committed_positions: BTreeMap<zk::CircuitIdx, usize>,
488 internal_vars: Vec<(
493 zk::CircuitIdx,
494 Option<zk::CircuitIdx>,
495 Option<zk::CircuitIdx>,
496 )>,
497 }
498
499 impl<F: Field + Random> ZKCConverter<F> {
500 pub fn new(committed_indices: Vec<zk::CircuitIdx>) -> Self {
503 let mut committed_positions = BTreeMap::new();
504 for (i, &idx) in committed_indices.iter().enumerate() {
505 committed_positions.entry(idx).or_insert(i);
509 }
510 Self {
511 linearize_queue: Vec::new(),
512 linearize_cache: BTreeMap::new(),
513 assertions: Vec::new(),
514 extra_assertions: Vec::new(),
515 witness_locations: BTreeMap::new(),
516 committed_indices,
517 committed_positions,
518 internal_vars: Default::default(),
519 }
520 }
521
522 pub fn circuit(mut self, zkc: zk::Circuit<F>) -> super::Circuit<F> {
524 self.populate(&zkc);
525 self.reckon_circuit()
526 }
527
528 pub fn circuit_and_witness(
532 mut self,
533 blinding_rng: Option<&mut impl CryptoRng>,
534 zkc: zk::ValuedCircuit<F>,
535 ) -> (super::Circuit<F>, super::Witness<F>) {
536 self.populate(&zkc.circuit);
537 let blinding = blinding_rng.map_or_else(
538 || vec![F::zero(); self.committed_indices.len()],
539 |rng| {
540 (0..self.committed_indices.len())
541 .map(|_| F::random(&mut *rng))
542 .collect::<Vec<_>>()
543 },
544 );
545 let values = self
546 .committed_indices
547 .iter()
548 .map(|&i| zkc[i].clone())
549 .collect::<Vec<_>>();
550 let mut left = Vec::with_capacity(self.internal_vars.len());
551 let mut right = Vec::with_capacity(self.internal_vars.len());
552 let mut out = Vec::with_capacity(self.internal_vars.len());
553 for &(l_i, r_i, o_i) in &self.internal_vars {
554 left.push(zkc[l_i].clone());
555 match (r_i, o_i) {
556 (None, _) => {
557 right.push(F::zero());
558 out.push(F::zero());
559 }
560 (Some(r_i), None) => {
561 right.push(zkc[r_i].clone());
562 out.push(zkc[l_i].clone() * &zkc[r_i]);
563 }
564 (Some(r_i), Some(o_i)) => {
565 right.push(zkc[r_i].clone());
566 out.push(zkc[o_i].clone());
567 }
568 }
569 }
570 let witness = super::Witness {
571 values,
572 blinding,
573 left,
574 right,
575 out,
576 };
577 (self.reckon_circuit(), witness)
578 }
579
580 fn populate(&mut self, zkc: &zk::Circuit<F>) {
582 for &(l, r) in &zkc.assertions {
583 self.assertions.push((l, r));
584 self.linearize(zkc, l);
585 self.linearize(zkc, r);
586 }
587 {
589 let mut left = true;
590
591 for loc in self.witness_locations.values_mut() {
592 let &mut Location::Witness(w) = loc else {
593 continue;
594 };
595 if left {
596 *loc = Location::Left(self.internal_vars.len());
597 self.internal_vars
598 .push((zk::CircuitIdx::Witness(w as u32), None, None));
599 left = false;
600 } else {
601 let i = self.internal_vars.len() - 1;
602 *loc = Location::Right(i);
603 self.internal_vars[i].1 = Some(zk::CircuitIdx::Witness(w as u32));
604 left = true;
605 }
606 }
607 }
608 let committed = self.committed_indices.clone();
612 for (i, c_pos) in committed.into_iter().enumerate() {
613 self.linearize(zkc, c_pos);
614 if matches!(
627 self.linearize_cache.get(&c_pos),
628 Some(LinComb::Location(Location::Committed(_)))
629 ) {
630 let k = self.internal_vars.len();
631 self.internal_vars.push((c_pos, None, None));
632 self.linearize_cache
633 .insert(c_pos, LinComb::Location(Location::Left(k)));
634 }
635 self.extra_assertions.push((c_pos, Location::Committed(i)));
636 }
637 }
638
639 fn location_to_col(&self, loc: Location) -> usize {
641 fn inner<F>(this: &ZKCConverter<F>, loc: Location, no_witness: bool) -> usize {
642 match loc {
643 Location::One => 0,
644 Location::Left(i) => 1 + this.committed_indices.len() + i,
645 Location::Right(i) => {
646 1 + this.committed_indices.len() + this.internal_vars.len() + i
647 }
648 Location::Output(i) => {
649 1 + this.committed_indices.len() + 2 * this.internal_vars.len() + i
650 }
651 Location::Committed(i) => 1 + i,
652 Location::Witness(i) => {
653 if no_witness {
654 unreachable!("unexpected witness location")
655 } else {
656 inner(this, this.witness_locations[&i], true)
657 }
658 }
659 }
660 }
661
662 inner(self, loc, false)
663 }
664
665 fn reckon_circuit(&self) -> super::Circuit<F> {
671 let mut weights = super::SparseMatrix::default();
672 for (row, (l, r)) in self
673 .assertions
674 .iter()
675 .map(|(l, r)| {
676 (
677 Cow::Borrowed(
678 self.linearize_cache
679 .get(l)
680 .expect("linearize_cache_should_be_populated"),
681 ),
682 Cow::Borrowed(
683 self.linearize_cache
684 .get(r)
685 .expect("linearize_cache should be populated"),
686 ),
687 )
688 })
689 .chain(self.extra_assertions.iter().map(|(cidx, loc)| {
690 (
691 Cow::Borrowed(
692 self.linearize_cache
693 .get(cidx)
694 .expect("linearize_cache should be populated"),
695 ),
696 Cow::Owned(LinComb::Location(*loc)),
697 )
698 }))
699 .enumerate()
700 {
701 for (w, loc) in l.iter() {
702 weights[(row, self.location_to_col(loc))] += w.as_ref();
703 }
704 for (w, loc) in r.iter() {
705 weights[(row, self.location_to_col(loc))] -= w.as_ref();
706 }
707 }
708 super::Circuit {
709 committed_vars: self.committed_indices.len(),
710 internal_vars: self.internal_vars.len(),
711 weights,
712 }
713 }
714
715 fn assign_witness_location(&mut self, witness: usize, loc: Location) -> Location {
718 *self
719 .witness_locations
720 .entry(witness)
721 .and_modify(|current_loc| {
722 if let Location::Witness(_) = *current_loc {
723 *current_loc = loc;
724 }
725 })
726 .or_insert(loc)
727 }
728
729 fn linearize(&mut self, zkc: &zk::Circuit<F>, i: zk::CircuitIdx) {
735 self.linearize_queue.clear();
736 self.linearize_queue.push(i);
737 while let Some(i) = self.linearize_queue.pop() {
738 if self.linearize_cache.contains_key(&i) {
739 continue;
740 }
741 let comb = match i {
742 zk::CircuitIdx::Constant(i) => {
743 LinComb::Constant(zkc.constants[i as usize].clone())
744 }
745 this @ zk::CircuitIdx::Witness(i) => {
746 let i_usize = i as usize;
747 let w_loc = self
748 .committed_positions
749 .get(&this)
750 .map_or(Location::Witness(i_usize), |&i| Location::Committed(i));
751 let loc = self.assign_witness_location(i_usize, w_loc);
752 LinComb::Location(loc)
753 }
754 this @ zk::CircuitIdx::Node(i) => {
755 let this_node = &zkc.nodes[i as usize];
756 let (l, r) = match *this_node {
757 zk::CircuitNode::Add(l, r) => (l, r),
758 zk::CircuitNode::Mul(l, r) => (l, r),
759 };
760 let (l_comb, r_comb) =
761 match (self.linearize_cache.get(&l), self.linearize_cache.get(&r)) {
762 (None, None) => {
763 self.linearize_queue.extend([this, r, l]);
764 continue;
765 }
766 (Some(_), None) => {
767 self.linearize_queue.extend([this, r]);
768 continue;
769 }
770 (None, Some(_)) => {
771 self.linearize_queue.extend([this, l]);
772 continue;
773 }
774 (Some(l_comb), Some(r_comb)) => (l_comb, r_comb),
775 };
776 match *this_node {
777 zk::CircuitNode::Add(_, _) => l_comb.sum(r_comb),
778 zk::CircuitNode::Mul(l, r) => {
779 if let Some(out) = l_comb.mul(r_comb) {
780 out
781 } else {
782 let i = self.internal_vars.len();
783 self.internal_vars.push((l, Some(r), Some(this)));
784 self.extra_assertions.push((l, Location::Left(i)));
785 self.extra_assertions.push((r, Location::Right(i)));
786
787 let w_l = l_comb.witness();
789 let w_r = r_comb.witness();
790 if let Some(w) = w_l {
791 self.assign_witness_location(w, Location::Left(i));
792 }
793 if let Some(w) = w_r {
794 self.assign_witness_location(w, Location::Right(i));
795 }
796 LinComb::Location(Location::Output(i))
797 }
798 }
799 }
800 }
801 };
802 self.linearize_cache.insert(i, comb);
803 }
804 }
805 }
806}
807
808pub fn zkc_to_circuit<F: Field + Random>(
820 zkc: crate::zk::circuit::Circuit<F>,
821 committed_indices: &[crate::zk::circuit::CircuitIdx],
822) -> Circuit<F> {
823 zkc::ZKCConverter::new(committed_indices.to_vec()).circuit(zkc)
824}
825
826pub fn zkc_to_circuit_and_witness<F: Field + Random>(
836 blinding_rng: Option<&mut impl CryptoRng>,
837 zkc: crate::zk::circuit::ValuedCircuit<F>,
838 committed_indices: &[crate::zk::circuit::CircuitIdx],
839) -> (Circuit<F>, Witness<F>) {
840 zkc::ZKCConverter::new(committed_indices.to_vec()).circuit_and_witness(blinding_rng, zkc)
841}
842
843#[derive(PartialEq)]
848pub struct Setup<G> {
849 ipa: ipa::Setup<G>,
850 value_generator: G,
851 blinding_generator: G,
852}
853
854impl<G> Setup<G> {
855 pub const fn new(ipa: ipa::Setup<G>, value_generator: G, blinding_generator: G) -> Self {
859 Self {
860 ipa,
861 value_generator,
862 blinding_generator,
863 }
864 }
865
866 pub const fn value_generator(&self) -> &G {
867 &self.value_generator
868 }
869
870 pub const fn blinding_generator(&self) -> &G {
871 &self.blinding_generator
872 }
873
874 pub const fn supports(&self, lg_len: u8) -> bool {
876 self.ipa.supports(lg_len)
877 }
878
879 pub fn hashed(domain_separator: &[u8], lg_len: u8, value_generator: G) -> Self
893 where
894 G: HashToGroup,
895 {
896 let n: usize = 1usize << lg_len;
897 let product_generator = G::hash_to_group(domain_separator, b"product");
898 let blinding_generator = G::hash_to_group(domain_separator, b"blinding");
899 let g_and_h = (0..n).map(|i| {
900 let i_bytes = (i as u64).to_le_bytes();
901 let mut g_msg = Vec::with_capacity(2 + i_bytes.len());
902 g_msg.extend_from_slice(b"g/");
903 g_msg.extend_from_slice(&i_bytes);
904 let mut h_msg = Vec::with_capacity(2 + i_bytes.len());
905 h_msg.extend_from_slice(b"h/");
906 h_msg.extend_from_slice(&i_bytes);
907 (
908 G::hash_to_group(domain_separator, &g_msg),
909 G::hash_to_group(domain_separator, &h_msg),
910 )
911 });
912 Self::new(
913 ipa::Setup::new(product_generator, g_and_h),
914 value_generator,
915 blinding_generator,
916 )
917 }
918
919 fn build_virtual<F: Field>(&self) -> (Setup<Synthetic<F, G>>, Vec<G>)
922 where
923 G: Clone,
924 {
925 let n = self.ipa.g().len();
926 let mut gens = Synthetic::<F, G>::generators();
927 let vg: Vec<_> = (0..n)
928 .map(|_| gens.next().expect("generators is infinite"))
929 .collect();
930 let vh: Vec<_> = (0..n)
931 .map(|_| gens.next().expect("generators is infinite"))
932 .collect();
933 let vq = gens.next().expect("generators is infinite");
934 let ipa_vs = ipa::Setup::new(vq, vg.into_iter().zip(vh));
935 let pv = gens.next().expect("generators is infinite");
936 let pb = gens.next().expect("generators is infinite");
937 let vs = Setup::new(ipa_vs, pv, pb);
938 let mut flat = Vec::with_capacity(2 * n + 3);
939 flat.extend_from_slice(self.ipa.g());
940 flat.extend_from_slice(self.ipa.h());
941 flat.push(self.ipa.product_generator().clone());
942 flat.push(self.value_generator.clone());
943 flat.push(self.blinding_generator.clone());
944 (vs, flat)
945 }
946
947 pub fn eval<F: Field>(
950 &self,
951 f: impl FnOnce(&Setup<Synthetic<F, G>>) -> Option<Synthetic<F, G>>,
952 strategy: &impl Strategy,
953 ) -> Option<G>
954 where
955 G: Space<F>,
956 {
957 let (vs, flat) = self.build_virtual::<F>();
958 f(&vs).map(|v| v.eval(&flat, strategy))
959 }
960
961 pub fn eval_check_batched<F: Field + Random, R: CryptoRng>(
987 &self,
988 rng: &mut R,
989 f: impl FnOnce(&Setup<Synthetic<F, G>>, &mut R) -> Option<Vec<Option<Synthetic<F, G>>>>,
990 strategy: &impl Strategy,
991 ) -> Option<Vec<bool>>
992 where
993 G: Space<F> + PartialEq,
994 {
995 let (vs, flat) = self.build_virtual::<F>();
996 let synths = f(&vs, &mut *rng)?;
997 let n = synths.len();
998
999 let scaled: Vec<Option<Synthetic<F, G>>> = synths
1003 .into_iter()
1004 .map(|opt| opt.map(|s| s * &F::random(&mut *rng)))
1005 .collect();
1006
1007 let active: Vec<usize> = (0..n).filter(|&i| scaled[i].is_some()).collect();
1009
1010 let check = |range: &[usize]| -> bool {
1012 let mut acc = Synthetic::<F, G>::default();
1013 for &i in range {
1014 acc += scaled[i].as_ref().expect("active indices are Some");
1015 }
1016 acc.eval(&flat, strategy) == G::zero()
1017 };
1018
1019 let mut valid = vec![false; n];
1024 let mut stack: Vec<&[usize]> = Vec::new();
1025 if !active.is_empty() {
1026 stack.push(&active);
1027 }
1028 while let Some(range) = stack.pop() {
1029 if check(range) {
1030 for &i in range {
1031 valid[i] = true;
1032 }
1033 } else if range.len() > 1 {
1034 let mid = range.len() / 2;
1035 let (left, right) = range.split_at(mid);
1036 stack.push(right);
1037 stack.push(left);
1038 }
1039 }
1040 Some(valid)
1041 }
1042}
1043
1044impl<G: Write> Write for Setup<G> {
1045 fn write(&self, buf: &mut impl BufMut) {
1046 self.ipa.write(buf);
1047 self.value_generator.write(buf);
1048 self.blinding_generator.write(buf);
1049 }
1050}
1051
1052impl<G: EncodeSize> EncodeSize for Setup<G> {
1053 fn encode_size(&self) -> usize {
1054 self.ipa.encode_size()
1055 + self.value_generator.encode_size()
1056 + self.blinding_generator.encode_size()
1057 }
1058}
1059
1060impl<G: Read> Read for Setup<G>
1061where
1062 G::Cfg: Clone,
1063{
1064 type Cfg = (usize, G::Cfg);
1065
1066 fn read_cfg(buf: &mut impl Buf, (max_len, cfg): &Self::Cfg) -> Result<Self, Error> {
1067 let ipa = ipa::Setup::read_cfg(buf, &(*max_len, cfg.clone()))?;
1068 let value_generator = G::read_cfg(buf, cfg)?;
1069 let blinding_generator = G::read_cfg(buf, cfg)?;
1070 Ok(Self::new(ipa, value_generator, blinding_generator))
1071 }
1072}
1073
1074#[allow(dead_code)]
1079pub struct Witness<F> {
1080 values: Vec<F>,
1081 blinding: Vec<F>,
1082 left: Vec<F>,
1083 right: Vec<F>,
1084 out: Vec<F>,
1085}
1086
1087impl<F> Witness<F> {
1088 pub fn new(
1094 values: Vec<F>,
1095 blinding: Vec<F>,
1096 left: Vec<F>,
1097 right: Vec<F>,
1098 out: Vec<F>,
1099 ) -> Option<Self> {
1100 if values.len() != blinding.len() {
1101 return None;
1102 }
1103 if left.len() != right.len() || right.len() != out.len() {
1104 return None;
1105 }
1106 Some(Self {
1107 values,
1108 blinding,
1109 left,
1110 right,
1111 out,
1112 })
1113 }
1114
1115 pub fn values(&self) -> &[F] {
1116 &self.values
1117 }
1118
1119 #[must_use]
1124 pub fn is_satisfied(&self, circuit: &Circuit<F>) -> bool
1125 where
1126 F: Ring,
1127 {
1128 circuit.is_satisfied(&self.values, &self.left, &self.right)
1129 }
1130
1131 pub fn claim<G: Space<F>>(&self, setup: &Setup<G>) -> Claim<G> {
1136 Claim {
1137 commitments: self
1138 .values
1139 .iter()
1140 .zip(&self.blinding)
1141 .map(|(value, blind)| {
1142 setup.value_generator.clone() * value
1143 + &(setup.blinding_generator.clone() * blind)
1144 })
1145 .collect(),
1146 }
1147 }
1148}
1149
1150pub struct Claim<G> {
1158 pub commitments: Vec<G>,
1159}
1160
1161impl<G: Write> Write for Claim<G> {
1162 fn write(&self, buf: &mut impl BufMut) {
1163 self.commitments.write(buf);
1164 }
1165}
1166
1167impl<G: EncodeSize> EncodeSize for Claim<G> {
1168 fn encode_size(&self) -> usize {
1169 self.commitments.encode_size()
1170 }
1171}
1172
1173#[allow(dead_code)]
1178#[derive(Clone)]
1179pub struct Proof<F, G> {
1180 m_big: G,
1181 o_big: G,
1182 m_big_tilde: G,
1183 t_big: [G; 5],
1184 s_tilde: F,
1185 t_x: F,
1186 t_tilde_x: F,
1187 p_big: G,
1188 ipa_proof: ipa::Proof<F, G>,
1189}
1190
1191impl<F: Write, G: Write> Write for Proof<F, G> {
1192 fn write(&self, buf: &mut impl BufMut) {
1193 self.m_big.write(buf);
1194 self.o_big.write(buf);
1195 self.m_big_tilde.write(buf);
1196 for t in &self.t_big {
1197 t.write(buf);
1198 }
1199 self.s_tilde.write(buf);
1200 self.t_x.write(buf);
1201 self.t_tilde_x.write(buf);
1202 self.p_big.write(buf);
1203 self.ipa_proof.write(buf);
1204 }
1205}
1206
1207impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
1208 fn encode_size(&self) -> usize {
1209 self.m_big.encode_size()
1210 + self.o_big.encode_size()
1211 + self.m_big_tilde.encode_size()
1212 + self.t_big.iter().map(|t| t.encode_size()).sum::<usize>()
1213 + self.s_tilde.encode_size()
1214 + self.t_x.encode_size()
1215 + self.t_tilde_x.encode_size()
1216 + self.p_big.encode_size()
1217 + self.ipa_proof.encode_size()
1218 }
1219}
1220
1221impl<F: Read, G: Read> Read for Proof<F, G>
1222where
1223 F::Cfg: Clone,
1224 G::Cfg: Clone,
1225{
1226 type Cfg = (usize, (G::Cfg, F::Cfg));
1228
1229 fn read_cfg(buf: &mut impl Buf, cfg @ (_, (g_cfg, f_cfg)): &Self::Cfg) -> Result<Self, Error> {
1230 let m_big = G::read_cfg(buf, g_cfg)?;
1231 let o_big = G::read_cfg(buf, g_cfg)?;
1232 let m_big_tilde = G::read_cfg(buf, g_cfg)?;
1233 let t_big = [
1234 G::read_cfg(buf, g_cfg)?,
1235 G::read_cfg(buf, g_cfg)?,
1236 G::read_cfg(buf, g_cfg)?,
1237 G::read_cfg(buf, g_cfg)?,
1238 G::read_cfg(buf, g_cfg)?,
1239 ];
1240 let s_tilde = F::read_cfg(buf, f_cfg)?;
1241 let t_x = F::read_cfg(buf, f_cfg)?;
1242 let t_tilde_x = F::read_cfg(buf, f_cfg)?;
1243 let p_big = G::read_cfg(buf, g_cfg)?;
1244 let ipa_proof = ipa::Proof::read_cfg(buf, cfg)?;
1245 Ok(Self {
1246 m_big,
1247 o_big,
1248 m_big_tilde,
1249 t_big,
1250 s_tilde,
1251 t_x,
1252 t_tilde_x,
1253 p_big,
1254 ipa_proof,
1255 })
1256 }
1257}
1258
1259pub fn prove<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1268 rng: &mut impl CryptoRng,
1269 transcript: &mut Transcript,
1270 setup: &Setup<G>,
1271 circuit: &Circuit<F>,
1272 claim: &Claim<G>,
1273 witness: &Witness<F>,
1274 strategy: &impl Strategy,
1275) -> Option<Proof<F, G>> {
1276 let l_tilde = (0..circuit.internal_vars)
1495 .map(|_| F::random(&mut *rng))
1496 .collect::<Vec<_>>();
1497 let r_tilde = (0..circuit.internal_vars)
1498 .map(|_| F::random(&mut *rng))
1499 .collect::<Vec<_>>();
1500 let m = F::random(&mut *rng);
1501 let o_tilde = F::random(&mut *rng);
1502 let m_tilde = F::random(&mut *rng);
1503 let g_internal = &setup.ipa.g()[..circuit.internal_vars];
1504 let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1505 let m_big = G::msm(g_internal, &witness.left, strategy)
1506 + &G::msm(h_internal, &witness.right, strategy)
1507 + &(setup.blinding_generator.clone() * &m);
1508 let o_big =
1509 G::msm(g_internal, &witness.out, strategy) + &(setup.blinding_generator.clone() * &o_tilde);
1510 let m_big_tilde = G::msm(g_internal, &l_tilde, strategy)
1511 + &G::msm(h_internal, &r_tilde, strategy)
1512 + &(setup.blinding_generator.clone() * &m_tilde);
1513 circuit.commit(transcript);
1515 transcript.commit(claim.encode());
1516 transcript.commit(m_big.encode());
1517 transcript.commit(o_big.encode());
1518 transcript.commit(m_big_tilde.encode());
1519 let padded_vars = circuit.internal_vars.next_power_of_two();
1520 let y = F::random(transcript.noise(b"y"));
1521 let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1522 let y_inv = y.inv();
1523 let y_inv_powers = powers(F::one(), &y_inv)
1524 .take(padded_vars)
1525 .collect::<Vec<_>>();
1526 let z = F::random(transcript.noise(b"z"));
1527 let z_powers = powers(z.clone(), &z)
1528 .take(circuit.weights.height())
1529 .collect::<Vec<_>>();
1530 let (kappa, theta, lambda, rho, omega) = {
1531 let mut kappa = F::zero();
1532 let mut theta = vec![F::zero(); circuit.committed_vars];
1533 let mut lambda = vec![F::zero(); circuit.internal_vars];
1534 let mut rho = vec![F::zero(); circuit.internal_vars];
1535 let mut omega = vec![F::zero(); circuit.internal_vars];
1536 let theta_start = 1;
1537 let lambda_start = theta_start + circuit.committed_vars;
1538 let rho_start = lambda_start + circuit.internal_vars;
1539 let omega_start = rho_start + circuit.internal_vars;
1540 for (&(i, j), w_ij) in &circuit.weights.weights {
1541 let w_ij = w_ij.clone();
1542 if j >= omega_start {
1543 omega[j - omega_start] += &(w_ij * &z_powers[i]);
1544 } else if j >= rho_start {
1545 rho[j - rho_start] += &(w_ij * &z_powers[i]);
1546 } else if j >= lambda_start {
1547 lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1548 } else if j >= theta_start {
1549 theta[j - theta_start] += &(w_ij * &z_powers[i]);
1550 } else {
1551 kappa += &(w_ij * &z_powers[i]);
1552 }
1553 }
1554 (kappa, theta, lambda, rho, omega)
1555 };
1556
1557 let mut omega_minus_y = omega
1559 .iter()
1560 .cloned()
1561 .zip(&y_powers)
1562 .map(|(omega_i, y_i)| omega_i - y_i)
1563 .collect::<Vec<_>>();
1564 omega_minus_y.extend(
1565 y_powers
1566 .iter()
1567 .skip(circuit.internal_vars)
1568 .cloned()
1569 .map(|y_i| -y_i),
1570 );
1571 let y_inv_rho = y_inv_powers
1572 .iter()
1573 .cloned()
1574 .zip(&rho)
1575 .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1576 .collect::<Vec<_>>();
1577 let y_inv_lambda = y_inv_powers
1578 .iter()
1579 .cloned()
1580 .zip(&lambda)
1581 .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1582 .collect::<Vec<_>>();
1583 let y_inv_omega_minus_y = y_inv_powers
1584 .iter()
1585 .cloned()
1586 .zip(&omega_minus_y)
1587 .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1588 .collect::<Vec<_>>();
1589 let y_r = y_powers
1590 .iter()
1591 .cloned()
1592 .zip(&witness.right)
1593 .map(|(y_i, r_i)| y_i * r_i)
1594 .collect::<Vec<_>>();
1595 let y_r_tilde = y_powers
1596 .iter()
1597 .cloned()
1598 .zip(&r_tilde)
1599 .map(|(y_i, r_i)| y_i * r_i)
1600 .collect::<Vec<_>>();
1601
1602 let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1603
1604 let t = {
1606 let mut t = std::array::from_fn::<_, 6, _>(|_| F::zero());
1607 for i in 0..circuit.internal_vars {
1609 t[0] += &((witness.left[i].clone() + &y_inv_rho[i]) * &omega_minus_y[i]);
1610 }
1611 t[1] = delta_y_z - &kappa - &<F as Space<F>>::msm(&theta, &witness.values, strategy);
1613 for i in 0..circuit.internal_vars {
1615 t[2] += &(l_tilde[i].clone() * &omega_minus_y[i]);
1616 t[2] += &(witness.out[i].clone() * &(y_r[i].clone() + &lambda[i]));
1617 }
1618 for i in 0..circuit.internal_vars {
1620 t[3] += &(l_tilde[i].clone() * &(y_r[i].clone() + &lambda[i]));
1621 t[3] += &((witness.left[i].clone() + &y_inv_rho[i]) * &y_r_tilde[i]);
1622 }
1623 t[4] = <F as Space<F>>::msm(&witness.out, &y_r_tilde, strategy);
1625 t[5] = <F as Space<F>>::msm(&l_tilde, &y_r_tilde, strategy);
1627 t
1628 };
1629 let t_tilde = std::array::from_fn::<_, 6, _>(|i| {
1630 if i == 1 {
1631 -<F as Space<F>>::msm(&theta, &witness.blinding, strategy)
1632 } else {
1633 F::random(&mut *rng)
1634 }
1635 });
1636 let t_big = std::array::from_fn::<_, 5, _>(|i| {
1637 let i = if i >= 1 { i + 1 } else { i };
1639 setup.value_generator.clone() * &t[i] + &(setup.blinding_generator.clone() * &t_tilde[i])
1640 });
1641
1642 let p_0 = G::msm(
1645 &setup.ipa.h()[..padded_vars],
1646 &y_inv_omega_minus_y,
1647 strategy,
1648 );
1649 let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1650 let p_1 =
1651 G::msm(g_internal, &y_inv_rho, strategy) + &G::msm(h_internal, &y_inv_lambda, strategy);
1652
1653 for t_big_i in &t_big {
1656 transcript.commit(t_big_i.encode());
1657 }
1658 let x = F::random(transcript.noise(b"x"));
1659 let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1660 let s_tilde = m * &x[0] + &(o_tilde * &x[1]) + &(m_tilde * &x[2]);
1661 let p = setup.blinding_generator.clone() * &(-s_tilde.clone())
1662 + &p_0
1663 + &((p_1 + &m_big) * &x[0])
1664 + &(o_big.clone() * &x[1])
1665 + &(m_big_tilde.clone() * &x[2]);
1666 let t_x = <F as Space<F>>::msm(&t, &x, strategy);
1667 let t_tilde_x = <F as Space<F>>::msm(&t_tilde, &x, strategy);
1668 let ipa_claim = ipa::Claim {
1669 commitment: p.clone(),
1670 product: t_x.clone(),
1671 y: y_inv,
1672 log_len: padded_vars.ilog2().try_into().ok()?,
1673 };
1674 let mut f_x = (0..circuit.internal_vars)
1675 .map(|i| {
1676 (witness.left[i].clone() + &y_inv_rho[i]) * &x[0]
1677 + &(witness.out[i].clone() * &x[1])
1678 + &(l_tilde[i].clone() * &x[2])
1679 })
1680 .collect::<Vec<_>>();
1681 f_x.resize(padded_vars, F::zero());
1682 let mut g_x = (0..circuit.internal_vars)
1683 .map(|i| {
1684 (y_r[i].clone() + &lambda[i]) * &x[0]
1685 + &omega_minus_y[i]
1686 + &(y_r_tilde[i].clone() * &x[2])
1687 })
1688 .collect::<Vec<_>>();
1689 g_x.extend_from_slice(&omega_minus_y[circuit.internal_vars..]);
1690 let witness = ipa::Witness::new(f_x.into_iter().zip(g_x))?;
1691 let ipa_proof = ipa::prove(transcript, &setup.ipa, &ipa_claim, witness, strategy)?;
1692 Some(Proof {
1693 m_big,
1694 o_big,
1695 m_big_tilde,
1696 t_big,
1697 s_tilde,
1698 t_x,
1699 t_tilde_x,
1700 p_big: p,
1701 ipa_proof,
1702 })
1703}
1704
1705pub fn verify<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1713 rng: &mut impl CryptoRng,
1714 transcript: &mut Transcript,
1715 setup: &Setup<Synthetic<F, G>>,
1716 circuit: &Circuit<F>,
1717 claim: &Claim<G>,
1718 proof: Proof<F, G>,
1719 strategy: &impl Strategy,
1720) -> Option<Synthetic<F, G>> {
1721 let Proof {
1722 m_big,
1723 o_big,
1724 m_big_tilde,
1725 t_big,
1726 s_tilde,
1727 t_x,
1728 t_tilde_x,
1729 ipa_proof,
1730 p_big: p,
1731 } = proof;
1732 if claim.commitments.len() != circuit.committed_vars {
1737 return None;
1738 }
1739 circuit.commit(transcript);
1740 transcript.commit(claim.encode());
1741 transcript.commit(m_big.encode());
1742 transcript.commit(o_big.encode());
1743 transcript.commit(m_big_tilde.encode());
1744 let padded_vars = circuit.internal_vars.next_power_of_two();
1745 let y = F::random(transcript.noise(b"y"));
1746 let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1747 let y_inv = y.inv();
1748 let y_inv_powers = powers(F::one(), &y_inv)
1749 .take(padded_vars)
1750 .collect::<Vec<_>>();
1751 let z = F::random(transcript.noise(b"z"));
1752 let z_powers = powers(z.clone(), &z)
1753 .take(circuit.weights.height())
1754 .collect::<Vec<_>>();
1755 let (kappa, theta, lambda, rho, omega) = {
1756 let mut kappa = F::zero();
1757 let mut theta = vec![F::zero(); circuit.committed_vars];
1758 let mut lambda = vec![F::zero(); circuit.internal_vars];
1759 let mut rho = vec![F::zero(); circuit.internal_vars];
1760 let mut omega = vec![F::zero(); circuit.internal_vars];
1761 let theta_start = 1;
1762 let lambda_start = theta_start + circuit.committed_vars;
1763 let rho_start = lambda_start + circuit.internal_vars;
1764 let omega_start = rho_start + circuit.internal_vars;
1765 for (&(i, j), w_ij) in &circuit.weights.weights {
1766 let w_ij = w_ij.clone();
1767 if j >= omega_start {
1768 omega[j - omega_start] += &(w_ij * &z_powers[i]);
1769 } else if j >= rho_start {
1770 rho[j - rho_start] += &(w_ij * &z_powers[i]);
1771 } else if j >= lambda_start {
1772 lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1773 } else if j >= theta_start {
1774 theta[j - theta_start] += &(w_ij * &z_powers[i]);
1775 } else {
1776 kappa += &(w_ij * &z_powers[i]);
1777 }
1778 }
1779 (kappa, theta, lambda, rho, omega)
1780 };
1781
1782 let mut omega_minus_y = omega
1784 .iter()
1785 .cloned()
1786 .zip(&y_powers)
1787 .map(|(omega_i, y_i)| omega_i - y_i)
1788 .collect::<Vec<_>>();
1789 omega_minus_y.extend(
1790 y_powers
1791 .iter()
1792 .skip(circuit.internal_vars)
1793 .cloned()
1794 .map(|y_i| -y_i),
1795 );
1796 let y_inv_rho = y_inv_powers
1797 .iter()
1798 .cloned()
1799 .zip(&rho)
1800 .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1801 .collect::<Vec<_>>();
1802 let y_inv_lambda = y_inv_powers
1803 .iter()
1804 .cloned()
1805 .zip(&lambda)
1806 .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1807 .collect::<Vec<_>>();
1808 let y_inv_omega_minus_y = y_inv_powers
1809 .iter()
1810 .cloned()
1811 .zip(&omega_minus_y)
1812 .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1813 .collect::<Vec<_>>();
1814
1815 let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1816
1817 for t_big_i in &t_big {
1818 transcript.commit(t_big_i.encode());
1819 }
1820 let x = F::random(transcript.noise(b"x"));
1821 let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1822
1823 let ipa_g = setup.ipa.g();
1824 let ipa_h = setup.ipa.h();
1825
1826 let value_generator = &setup.value_generator;
1827 let blinding_generator = &setup.blinding_generator;
1828
1829 let t_check = Synthetic::msm(
1830 &[value_generator.clone(), blinding_generator.clone()],
1831 &[t_x.clone(), t_tilde_x],
1832 &Sequential,
1833 ) - &(value_generator.clone() * &((-kappa + &delta_y_z) * &x[1]))
1834 + &(Synthetic::concrete(theta.iter().cloned().zip(claim.commitments.iter().cloned()))
1835 * &x[1])
1836 - &Synthetic::concrete(std::iter::once(&x[0]).chain(&x[2..]).cloned().zip(t_big));
1837
1838 let p_check = {
1839 let p_0 = Synthetic::msm(&ipa_h[..padded_vars], &y_inv_omega_minus_y, &Sequential);
1841 let p_1 = Synthetic::msm(&ipa_g[..circuit.internal_vars], &y_inv_rho, &Sequential)
1842 + &Synthetic::msm(&ipa_h[..circuit.internal_vars], &y_inv_lambda, &Sequential);
1843 Synthetic::concrete([
1844 (F::one(), p.clone()),
1845 (-x[0].clone(), m_big),
1846 (-x[1].clone(), o_big),
1847 (-x[2].clone(), m_big_tilde),
1848 ]) - &p_0
1849 - &(p_1 * &x[0])
1850 + &(blinding_generator.clone() * &s_tilde)
1851 };
1852
1853 let ipa_claim = ipa::Claim {
1854 commitment: p,
1855 product: t_x,
1856 y: y_inv,
1857 log_len: padded_vars
1858 .ilog2()
1859 .try_into()
1860 .expect("should be less than 2^256 rows"),
1861 };
1862
1863 let ipa_check = ipa::verify(transcript, &setup.ipa, &ipa_claim, ipa_proof)?;
1864
1865 let final_check =
1866 ipa_check + &(p_check * &F::random(&mut *rng)) + &(t_check * &F::random(&mut *rng));
1867 Some(final_check)
1868}
1869
1870#[commonware_macros::stability(ALPHA)]
1871#[cfg(any(test, feature = "fuzz"))]
1872pub mod fuzz {
1873 use super::*;
1874 use arbitrary::{Arbitrary, Unstructured};
1875 use commonware_math::{
1876 algebra::{Additive, Ring},
1877 test::{F, G},
1878 };
1879 use commonware_parallel::Sequential;
1880 use commonware_utils::test_rng;
1881 use std::sync::OnceLock;
1882
1883 const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_CIRCUIT";
1884
1885 const TEST_SETUP_PAIRS: usize = 64;
1889
1890 pub(super) fn test_setup() -> &'static Setup<G> {
1891 static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
1892 TEST_SETUP.get_or_init(|| {
1893 let count = 2 * TEST_SETUP_PAIRS + 3;
1894 let gens = (1..=count)
1895 .map(|i| G::generator() * &F::from(i as u64))
1896 .collect::<Vec<_>>();
1897 Setup::new(
1898 ipa::Setup::new(
1899 gens[2 * TEST_SETUP_PAIRS],
1900 gens[..2 * TEST_SETUP_PAIRS]
1901 .chunks_exact(2)
1902 .map(|c| (c[0], c[1])),
1903 ),
1904 gens[2 * TEST_SETUP_PAIRS + 1],
1905 gens[2 * TEST_SETUP_PAIRS + 2],
1906 )
1907 })
1908 }
1909
1910 fn quadratic_value(a: F, b: F, c: F, x: F) -> F {
1911 a * &x * &x + &(b * &x) + &c
1912 }
1913
1914 pub(super) fn quadratic_circuit(a: F, b: F, c: F) -> Circuit<F> {
1915 let mut weights = SparseMatrix::default();
1916
1917 weights[(0, 1)] = F::one();
1919 weights[(0, 3)] = -F::one();
1920
1921 weights[(1, 1)] = F::one();
1923 weights[(1, 4)] = -F::one();
1924
1925 weights[(2, 0)] = c;
1927 weights[(2, 1)] = b;
1928 weights[(2, 2)] = -F::one();
1929 weights[(2, 5)] = a;
1930
1931 Circuit::new(2, weights).expect("quadratic circuit layout should be valid")
1932 }
1933
1934 pub struct Case {
1936 circuit: Circuit<F>,
1937 witness: Witness<F>,
1938 }
1939
1940 impl Case {
1941 fn is_satisfied(&self) -> bool {
1942 self.circuit.is_satisfied(
1943 &self.witness.values,
1944 &self.witness.left,
1945 &self.witness.right,
1946 )
1947 }
1948
1949 fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
1950 let a = u.arbitrary::<F>()?;
1951 let b = u.arbitrary::<F>()?;
1952 let c = u.arbitrary::<F>()?;
1953 let x = u.arbitrary::<F>()?;
1954 let valid = u.arbitrary::<bool>()?;
1955 let mut y = quadratic_value(a, b, c, x);
1956 if !valid {
1957 let mut tweak = u.arbitrary::<F>()?;
1958 if tweak == F::zero() {
1959 tweak = F::one()
1960 }
1961 y += &tweak;
1962 }
1963
1964 let x_sq = x * &x;
1965 let witness = Witness::new(
1966 vec![x, y],
1967 vec![u.arbitrary::<F>()?, u.arbitrary::<F>()?],
1968 vec![x],
1969 vec![x],
1970 vec![x_sq],
1971 )
1972 .expect("quadratic witness should have matching vector lengths");
1973 let circuit = quadratic_circuit(a, b, c);
1974 let out = Self { circuit, witness };
1975 assert_eq!(
1976 out.is_satisfied(),
1977 valid,
1978 "quadratic case should match requested validity",
1979 );
1980 Ok(out)
1981 }
1982 }
1983
1984 pub enum Plan {
1985 ProveAndVerify(Case),
1986 ZkcConversion(crate::zk::circuit::fuzz::Plan),
1987 }
1988
1989 impl<'a> Arbitrary<'a> for Plan {
1990 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1991 match u.int_in_range(0..=1)? {
1992 0 => Ok(Self::ProveAndVerify(Case::arbitrary(u)?)),
1993 1 => Ok(Self::ZkcConversion(u.arbitrary()?)),
1994 _ => unreachable!("plan variant out of range"),
1995 }
1996 }
1997 }
1998
1999 fn assert_verify_matches_satisfaction(case: &Case) {
2000 let setup = test_setup();
2001 let claim = case.witness.claim(setup);
2002 let verified = prove_and_verify(setup, &case.circuit, &claim, &case.witness);
2003 assert_eq!(verified, case.is_satisfied());
2004 }
2005
2006 fn prove_and_verify(
2009 setup: &Setup<G>,
2010 circuit: &Circuit<F>,
2011 claim: &Claim<G>,
2012 witness: &Witness<F>,
2013 ) -> bool {
2014 let mut rng = test_rng();
2015 let mut prover_transcript = Transcript::new(NAMESPACE);
2016 let Some(proof) = super::prove(
2017 &mut rng,
2018 &mut prover_transcript,
2019 setup,
2020 circuit,
2021 claim,
2022 witness,
2023 &Sequential,
2024 ) else {
2025 return false;
2026 };
2027 let mut verifier_transcript = Transcript::new(NAMESPACE);
2028 setup
2029 .eval(
2030 |vs| {
2031 verify(
2032 &mut rng,
2033 &mut verifier_transcript,
2034 vs,
2035 circuit,
2036 claim,
2037 proof,
2038 &Sequential,
2039 )
2040 },
2041 &Sequential,
2042 )
2043 .map(|g| g == G::zero())
2044 .unwrap_or(false)
2045 }
2046
2047 pub(super) fn assert_zkc_conversion_preserves_satisfaction(
2058 plan: &crate::zk::circuit::fuzz::Plan,
2059 u: &mut Unstructured<'_>,
2060 ) -> arbitrary::Result<()> {
2061 let valued = plan.build();
2062 let mut committed = Vec::new();
2063 for i in 0..valued.circuit.witnesses {
2064 if u.arbitrary()? {
2065 committed.push(crate::zk::circuit::CircuitIdx::Witness(i));
2066 }
2067 }
2068 let (circuit, witness) =
2069 zkc_to_circuit_and_witness(Some(&mut test_rng()), valued, &committed);
2070 let satisfied = witness.is_satisfied(&circuit);
2071 assert_eq!(satisfied, plan.satisfied(), "plan: {plan:?}");
2072
2073 if satisfied {
2074 let setup = test_setup();
2075 assert!(
2076 circuit.internal_vars() <= TEST_SETUP_PAIRS,
2077 "circuit too large for test setup ({} > {TEST_SETUP_PAIRS}); plan: {plan:?}",
2078 circuit.internal_vars()
2079 );
2080 let honest = witness.claim(setup);
2081 assert!(
2082 prove_and_verify(setup, &circuit, &honest, &witness),
2083 "honest claim must verify; plan: {plan:?}"
2084 );
2085 if !committed.is_empty() {
2086 let j = u.choose_index(committed.len())?;
2087 let mut tampered = witness.claim(setup);
2088 tampered.commitments[j] += setup.value_generator();
2089 assert!(
2090 !prove_and_verify(setup, &circuit, &tampered, &witness),
2091 "tampering committed value {j} must break verification; plan: {plan:?}"
2092 );
2093 }
2094 }
2095 Ok(())
2096 }
2097
2098 impl Plan {
2099 pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
2100 match self {
2101 Self::ProveAndVerify(case) => assert_verify_matches_satisfaction(&case),
2102 Self::ZkcConversion(plan) => {
2103 assert_zkc_conversion_preserves_satisfaction(&plan, u)?
2104 }
2105 }
2106 Ok(())
2107 }
2108 }
2109}
2110
2111#[cfg(test)]
2112mod test {
2113 use super::{fuzz, prove, verify, Circuit, Setup, SparseMatrix, Witness};
2114 use crate::{transcript::Transcript, zk::circuit as zk};
2115 use commonware_codec::{Decode, Encode};
2116 use commonware_invariants::minifuzz;
2117 use commonware_math::{
2118 algebra::{Additive, CryptoGroup, Ring},
2119 test::{F, G},
2120 };
2121 use commonware_parallel::Sequential;
2122 use commonware_utils::test_rng;
2123
2124 #[test]
2125 fn test_zkc_conversion_preserves_satisfaction_minifuzz() {
2126 minifuzz::test(|u| {
2127 let plan = u.arbitrary::<zk::fuzz::Plan>()?;
2128 fuzz::assert_zkc_conversion_preserves_satisfaction(&plan, u)
2129 });
2130 }
2131
2132 #[test]
2133 fn test_zkc_conversion_preserves_committed_order() {
2134 let (valued, _) = zk::build_with_values(|ctx| {
2135 let a = zk::Var::witness(ctx, |_| F::from(1u64));
2136 let b = zk::Var::witness(ctx, |_| F::from(2u64));
2137 let c = a * &b;
2138 c.assert_eq(&zk::Var::constant(ctx, F::from(2u64)));
2139 Vec::new()
2140 });
2141 let (circuit, witness) = super::zkc_to_circuit_and_witness(
2142 Some(&mut test_rng()),
2143 valued,
2144 &[
2145 zk::CircuitIdx::Witness(1),
2146 zk::CircuitIdx::Witness(0),
2147 zk::CircuitIdx::Witness(1),
2148 ],
2149 );
2150 assert!(witness.is_satisfied(&circuit));
2151 assert_eq!(
2152 witness.values,
2153 vec![F::from(2u64), F::from(1u64), F::from(2u64)]
2154 );
2155 }
2156
2157 #[test]
2158 fn test_zkc_conversion_add_doubling_chain() {
2159 const DEPTH: usize = 64;
2162 let mut expected = F::one();
2163 for _ in 0..DEPTH {
2164 expected = expected + &expected;
2165 }
2166 let (valued, _) = zk::build_with_values(|ctx| {
2167 let mut x = zk::Var::witness(ctx, |_| F::one());
2168 for _ in 0..DEPTH {
2169 x = x.clone() + &x;
2170 }
2171 x.assert_eq(&zk::Var::constant(ctx, expected));
2172 Vec::new()
2173 });
2174 let (circuit, witness) = super::zkc_to_circuit_and_witness(
2175 Some(&mut test_rng()),
2176 valued,
2177 &[zk::CircuitIdx::Witness(0)],
2178 );
2179 assert!(witness.is_satisfied(&circuit));
2180 }
2181
2182 #[test]
2183 fn test_random_r1cs_minifuzz() {
2184 const N: usize = 2;
2185 const M: usize = 4;
2186
2187 minifuzz::test(|u| {
2188 let a = u.arbitrary::<[[F; N]; M]>()?;
2189 let b = u.arbitrary::<[[F; N]; M]>()?;
2190 let c = u.arbitrary::<[[F; N]; M]>()?;
2191 let z = u.arbitrary::<[F; N]>()?;
2192 let mut left = [F::zero(); M];
2193 let mut right = [F::zero(); M];
2194 let mut satisfied = true;
2195 for i in 0..M {
2196 let mut acc = F::zero();
2197 for j in 0..N {
2198 left[i] += &(a[i][j] * &z[j]);
2199 right[i] += &(b[i][j] * &z[j]);
2200 acc += &(c[i][j] * &z[j]);
2201 }
2202 satisfied = satisfied && acc == left[i] * &right[i];
2203 }
2204 let mut k = 0;
2205 let mut weights = SparseMatrix::default();
2206
2207 for i in 0..M {
2209 weights[(k, 1 + N + i)] = -F::one();
2210 for j in 0..N {
2211 weights[(k, 1 + j)] = a[i][j];
2212 }
2213 k += 1;
2214 }
2215 for i in 0..M {
2217 weights[(k, 1 + N + M + i)] = -F::one();
2218 for j in 0..N {
2219 weights[(k, 1 + j)] = b[i][j];
2220 }
2221 k += 1;
2222 }
2223 for i in 0..M {
2225 weights[(k, 1 + N + 2 * M + i)] = -F::one();
2226 for j in 0..N {
2227 weights[(k, 1 + j)] = c[i][j];
2228 }
2229 k += 1;
2230 }
2231 assert_eq!(
2232 satisfied,
2233 Circuit::new(N, weights)
2234 .expect("should be able to make circuit")
2235 .is_satisfied(&z, &left, &right)
2236 );
2237 Ok(())
2238 });
2239 }
2240
2241 #[test]
2242 fn test_setup_roundtrip() {
2243 let setup = fuzz::test_setup();
2244 let encoded = setup.encode();
2245 let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.ipa.g().len(), ()))
2246 .expect("setup should decode with its own length bound");
2247 assert!(setup == &decoded);
2248 assert_eq!(decoded.encode(), encoded);
2249 }
2250
2251 #[test]
2252 fn test_fuzz() {
2253 minifuzz::test(|u| {
2254 u.arbitrary::<fuzz::Plan>()?.run(u)?;
2255 Ok(())
2256 });
2257 }
2258
2259 #[test]
2266 fn verify_rejects_over_long_claim() {
2267 let setup = fuzz::test_setup();
2268
2269 let a = F::one();
2272 let b = F::one();
2273 let c = F::one();
2274 let x = F::from(3u8);
2275 let y = a * &x * &x + &(b * &x) + &c;
2276 let circuit = fuzz::quadratic_circuit(a, b, c);
2277
2278 let witness = Witness::new(
2279 vec![x, y],
2280 vec![F::zero(), F::zero()],
2281 vec![x],
2282 vec![x],
2283 vec![x * &x],
2284 )
2285 .expect("witness vector lengths must be consistent");
2286
2287 let mut claim = witness.claim(setup);
2290 claim.commitments.push(G::generator() * &F::from(9u8));
2291
2292 let mut rng = test_rng();
2293 let mut prover_transcript = Transcript::new(b"verify-rejects-over-long-claim");
2294 let proof = prove(
2295 &mut rng,
2296 &mut prover_transcript,
2297 setup,
2298 &circuit,
2299 &claim,
2300 &witness,
2301 &Sequential,
2302 )
2303 .expect("prove still produces a proof against the malformed claim");
2304
2305 let mut verifier_transcript = Transcript::new(b"verify-rejects-over-long-claim");
2306 let verified = setup.eval(
2307 |vs| {
2308 verify(
2309 &mut rng,
2310 &mut verifier_transcript,
2311 vs,
2312 &circuit,
2313 &claim,
2314 proof,
2315 &Sequential,
2316 )
2317 },
2318 &Sequential,
2319 );
2320 assert!(
2321 verified.is_none(),
2322 "verify must reject a claim whose commitment arity does not match the circuit"
2323 );
2324 }
2325}