1use super::ipa;
135use crate::transcript::Transcript;
136use bytes::{Buf, BufMut};
137use commonware_codec::{Encode, EncodeSize, Error, Read, Write};
138use commonware_math::{
139 algebra::{Additive, CryptoGroup, Field, HashToGroup, Random, Ring, Space, powers},
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.width.write(buf);
241 self.height.write(buf);
242 self.weights.write(buf);
243 }
244}
245
246impl<F: EncodeSize> EncodeSize for SparseMatrix<F> {
247 fn encode_size(&self) -> usize {
248 self.width.encode_size() + self.height.encode_size() + self.weights.encode_size()
249 }
250}
251
252pub struct Circuit<F> {
254 committed_vars: usize,
255 internal_vars: usize,
256 weights: SparseMatrix<F>,
257}
258
259impl<F: Write> Write for Circuit<F> {
260 fn write(&self, buf: &mut impl BufMut) {
261 self.committed_vars.write(buf);
262 self.internal_vars.write(buf);
263 self.weights.write(buf);
264 }
265}
266
267impl<F: Encode> Circuit<F> {
268 fn commit(&self, transcript: &mut Transcript) {
269 transcript.commit(self.encode());
270 }
271}
272
273impl<F: EncodeSize> EncodeSize for Circuit<F> {
274 fn encode_size(&self) -> usize {
275 self.committed_vars.encode_size()
276 + self.internal_vars.encode_size()
277 + self.weights.encode_size()
278 }
279}
280
281impl<F: Ring> Circuit<F> {
282 pub fn new(committed_vars: usize, weights: SparseMatrix<F>) -> Option<Self> {
295 let remaining_vars = weights.width.checked_sub(committed_vars.checked_add(1)?)?;
296 if remaining_vars % 3 != 0 {
297 return None;
298 }
299 let internal_vars = remaining_vars / 3;
300 Some(Self {
301 committed_vars,
302 internal_vars,
303 weights,
304 })
305 }
306
307 pub const fn internal_vars(&self) -> usize {
309 self.internal_vars
310 }
311
312 pub const fn committed_vars(&self) -> usize {
314 self.committed_vars
315 }
316
317 #[must_use]
322 pub fn is_satisfied(
323 &self,
324 committed_values: &[F],
325 left_values: &[F],
326 right_values: &[F],
327 ) -> bool {
328 if committed_values.len() != self.committed_vars
329 || left_values.len() != self.internal_vars
330 || right_values.len() != self.internal_vars
331 {
332 return false;
333 }
334 let mut output = Vec::with_capacity(1 + self.committed_vars + 3 * self.internal_vars);
335 output.push(F::one());
336 output.extend_from_slice(committed_values);
337 output.extend_from_slice(left_values);
338 output.extend_from_slice(right_values);
339 output.extend(
340 left_values
341 .iter()
342 .zip(right_values)
343 .map(|(l_i, r_i)| l_i.clone() * r_i),
344 );
345 let mut res = vec![F::zero(); self.weights.height];
346 for (&(i, j), w_ij) in &self.weights.weights {
347 res[i] += &(output[j].clone() * w_ij);
348 }
349 let zero = F::zero();
350 res.iter().all(|r_i| r_i == &zero)
351 }
352}
353
354mod zkc {
363 use crate::zk::circuit as zk;
364 use commonware_math::algebra::{Field, Random, Ring};
365 use commonware_utils::ordered::Map;
366 use rand_core::CryptoRng;
367 use std::{borrow::Cow, collections::BTreeMap};
368
369 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
376 enum Location {
377 One,
378 Witness(usize),
379 Left(usize),
380 Right(usize),
381 Output(usize),
382 Committed(usize),
383 }
384
385 #[derive(Clone)]
391 enum LinComb<F> {
392 Location(Location),
393 Constant(F),
394 General(Map<Location, F>),
395 }
396
397 impl<F: Ring> LinComb<F> {
398 fn mul(&self, other: &Self) -> Option<Self> {
403 let (constant, other) = match (self, other) {
404 (Self::Constant(c), other) => (c.clone(), other),
405 (other, Self::Constant(c)) => (c.clone(), other),
406 _ => return None,
407 };
408 let out = match other {
409 Self::Location(location) => Self::General(
410 Map::try_from([(*location, constant)])
411 .expect("single entry cannot duplicate keys"),
412 ),
413 Self::Constant(other_constant) => Self::Constant(constant * other_constant),
414 Self::General(items) => {
415 let mut items = items.clone();
416 for w in items.values_mut() {
417 *w = w.clone() * &constant;
418 }
419 Self::General(items)
420 }
421 };
422 Some(out)
423 }
424
425 fn sum(&self, other: &Self) -> Self {
429 let mut terms: Vec<(Location, F)> = self
430 .iter()
431 .chain(other.iter())
432 .map(|(w, loc)| (loc, w.into_owned()))
433 .collect();
434 terms.sort_by_key(|&(loc, _)| loc);
435 let mut merged: Vec<(Location, F)> = Vec::with_capacity(terms.len());
436 for (loc, w) in terms {
437 match merged.last_mut() {
438 Some((last, acc)) if *last == loc => *acc += &w,
439 _ => merged.push((loc, w)),
440 }
441 }
442 Self::General(Map::try_from(merged).expect("merged locations should be unique"))
444 }
445
446 fn iter(&self) -> impl Iterator<Item = (Cow<'_, F>, Location)> {
448 let (single, general) = match self {
449 Self::Location(loc) => (Some((Cow::Owned(F::one()), *loc)), None),
450 Self::Constant(w) => (Some((Cow::Borrowed(w), Location::One)), None),
451 Self::General(items) => (None, Some(items.iter_pairs())),
452 };
453 single.into_iter().chain(
454 general
455 .into_iter()
456 .flatten()
457 .map(|(loc, w)| (Cow::Borrowed(w), *loc)),
458 )
459 }
460
461 const fn witness(&self) -> Option<usize> {
464 match self {
465 Self::Location(Location::Witness(w)) => Some(*w),
466 _ => None,
467 }
468 }
469 }
470
471 pub struct ZKCConverter<F> {
477 linearize_queue: Vec<zk::CircuitIdx>,
479 linearize_cache: BTreeMap<zk::CircuitIdx, LinComb<F>>,
481 assertions: Vec<(zk::CircuitIdx, zk::CircuitIdx)>,
483 extra_assertions: Vec<(zk::CircuitIdx, Location)>,
486 witness_locations: BTreeMap<usize, Location>,
488 committed_indices: Vec<zk::CircuitIdx>,
490 committed_positions: BTreeMap<zk::CircuitIdx, usize>,
493 internal_vars: Vec<(
498 zk::CircuitIdx,
499 Option<zk::CircuitIdx>,
500 Option<zk::CircuitIdx>,
501 )>,
502 }
503
504 impl<F: Field + Random> ZKCConverter<F> {
505 pub fn new(committed_indices: Vec<zk::CircuitIdx>) -> Self {
508 let mut committed_positions = BTreeMap::new();
509 for (i, &idx) in committed_indices.iter().enumerate() {
510 committed_positions.entry(idx).or_insert(i);
514 }
515 Self {
516 linearize_queue: Vec::new(),
517 linearize_cache: BTreeMap::new(),
518 assertions: Vec::new(),
519 extra_assertions: Vec::new(),
520 witness_locations: BTreeMap::new(),
521 committed_indices,
522 committed_positions,
523 internal_vars: Default::default(),
524 }
525 }
526
527 pub fn circuit(mut self, zkc: zk::Circuit<F>) -> super::Circuit<F> {
529 self.populate(&zkc);
530 self.reckon_circuit()
531 }
532
533 pub fn circuit_and_witness(
537 mut self,
538 blinding_rng: Option<&mut impl CryptoRng>,
539 zkc: zk::ValuedCircuit<F>,
540 ) -> (super::Circuit<F>, super::Witness<F>) {
541 self.populate(&zkc.circuit);
542 let blinding = blinding_rng.map_or_else(
543 || vec![F::zero(); self.committed_indices.len()],
544 |rng| {
545 (0..self.committed_indices.len())
546 .map(|_| F::random(&mut *rng))
547 .collect::<Vec<_>>()
548 },
549 );
550 let values = self
551 .committed_indices
552 .iter()
553 .map(|&i| zkc[i].clone())
554 .collect::<Vec<_>>();
555 let mut left = Vec::with_capacity(self.internal_vars.len());
556 let mut right = Vec::with_capacity(self.internal_vars.len());
557 let mut out = Vec::with_capacity(self.internal_vars.len());
558 for &(l_i, r_i, o_i) in &self.internal_vars {
559 left.push(zkc[l_i].clone());
560 match (r_i, o_i) {
561 (None, _) => {
562 right.push(F::zero());
563 out.push(F::zero());
564 }
565 (Some(r_i), None) => {
566 right.push(zkc[r_i].clone());
567 out.push(zkc[l_i].clone() * &zkc[r_i]);
568 }
569 (Some(r_i), Some(o_i)) => {
570 right.push(zkc[r_i].clone());
571 out.push(zkc[o_i].clone());
572 }
573 }
574 }
575 let witness = super::Witness {
576 values,
577 blinding,
578 left,
579 right,
580 out,
581 };
582 (self.reckon_circuit(), witness)
583 }
584
585 fn populate(&mut self, zkc: &zk::Circuit<F>) {
587 for &(l, r) in &zkc.assertions {
588 self.assertions.push((l, r));
589 self.linearize(zkc, l);
590 self.linearize(zkc, r);
591 }
592 {
594 let mut left = true;
595
596 for loc in self.witness_locations.values_mut() {
597 let &mut Location::Witness(w) = loc else {
598 continue;
599 };
600 if left {
601 *loc = Location::Left(self.internal_vars.len());
602 self.internal_vars
603 .push((zk::CircuitIdx::Witness(w as u32), None, None));
604 left = false;
605 } else {
606 let i = self.internal_vars.len() - 1;
607 *loc = Location::Right(i);
608 self.internal_vars[i].1 = Some(zk::CircuitIdx::Witness(w as u32));
609 left = true;
610 }
611 }
612 }
613 let committed = self.committed_indices.clone();
617 for (i, c_pos) in committed.into_iter().enumerate() {
618 self.linearize(zkc, c_pos);
619 if matches!(
632 self.linearize_cache.get(&c_pos),
633 Some(LinComb::Location(Location::Committed(_)))
634 ) {
635 let k = self.internal_vars.len();
636 self.internal_vars.push((c_pos, None, None));
637 self.linearize_cache
638 .insert(c_pos, LinComb::Location(Location::Left(k)));
639 }
640 self.extra_assertions.push((c_pos, Location::Committed(i)));
641 }
642 }
643
644 fn location_to_col(&self, loc: Location) -> usize {
646 fn inner<F>(this: &ZKCConverter<F>, loc: Location, no_witness: bool) -> usize {
647 match loc {
648 Location::One => 0,
649 Location::Left(i) => 1 + this.committed_indices.len() + i,
650 Location::Right(i) => {
651 1 + this.committed_indices.len() + this.internal_vars.len() + i
652 }
653 Location::Output(i) => {
654 1 + this.committed_indices.len() + 2 * this.internal_vars.len() + i
655 }
656 Location::Committed(i) => 1 + i,
657 Location::Witness(i) => {
658 if no_witness {
659 unreachable!("unexpected witness location")
660 } else {
661 inner(this, this.witness_locations[&i], true)
662 }
663 }
664 }
665 }
666
667 inner(self, loc, false)
668 }
669
670 fn reckon_circuit(&self) -> super::Circuit<F> {
676 let mut weights = super::SparseMatrix::default();
677 for (row, (l, r)) in self
678 .assertions
679 .iter()
680 .map(|(l, r)| {
681 (
682 Cow::Borrowed(
683 self.linearize_cache
684 .get(l)
685 .expect("linearize_cache_should_be_populated"),
686 ),
687 Cow::Borrowed(
688 self.linearize_cache
689 .get(r)
690 .expect("linearize_cache should be populated"),
691 ),
692 )
693 })
694 .chain(self.extra_assertions.iter().map(|(cidx, loc)| {
695 (
696 Cow::Borrowed(
697 self.linearize_cache
698 .get(cidx)
699 .expect("linearize_cache should be populated"),
700 ),
701 Cow::Owned(LinComb::Location(*loc)),
702 )
703 }))
704 .enumerate()
705 {
706 for (w, loc) in l.iter() {
707 weights[(row, self.location_to_col(loc))] += w.as_ref();
708 }
709 for (w, loc) in r.iter() {
710 weights[(row, self.location_to_col(loc))] -= w.as_ref();
711 }
712 }
713 super::Circuit {
714 committed_vars: self.committed_indices.len(),
715 internal_vars: self.internal_vars.len(),
716 weights,
717 }
718 }
719
720 fn assign_witness_location(&mut self, witness: usize, loc: Location) -> Location {
723 *self
724 .witness_locations
725 .entry(witness)
726 .and_modify(|current_loc| {
727 if let Location::Witness(_) = *current_loc {
728 *current_loc = loc;
729 }
730 })
731 .or_insert(loc)
732 }
733
734 fn linearize(&mut self, zkc: &zk::Circuit<F>, i: zk::CircuitIdx) {
740 self.linearize_queue.clear();
741 self.linearize_queue.push(i);
742 while let Some(i) = self.linearize_queue.pop() {
743 if self.linearize_cache.contains_key(&i) {
744 continue;
745 }
746 let comb = match i {
747 zk::CircuitIdx::Constant(i) => {
748 LinComb::Constant(zkc.constants[i as usize].clone())
749 }
750 this @ zk::CircuitIdx::Witness(i) => {
751 let i_usize = i as usize;
752 let w_loc = self
753 .committed_positions
754 .get(&this)
755 .map_or(Location::Witness(i_usize), |&i| Location::Committed(i));
756 let loc = self.assign_witness_location(i_usize, w_loc);
757 LinComb::Location(loc)
758 }
759 this @ zk::CircuitIdx::Node(i) => {
760 let this_node = &zkc.nodes[i as usize];
761 let (l, r) = match *this_node {
762 zk::CircuitNode::Add(l, r) => (l, r),
763 zk::CircuitNode::Mul(l, r) => (l, r),
764 };
765 let (l_comb, r_comb) =
766 match (self.linearize_cache.get(&l), self.linearize_cache.get(&r)) {
767 (None, None) => {
768 self.linearize_queue.extend([this, r, l]);
769 continue;
770 }
771 (Some(_), None) => {
772 self.linearize_queue.extend([this, r]);
773 continue;
774 }
775 (None, Some(_)) => {
776 self.linearize_queue.extend([this, l]);
777 continue;
778 }
779 (Some(l_comb), Some(r_comb)) => (l_comb, r_comb),
780 };
781 match *this_node {
782 zk::CircuitNode::Add(_, _) => l_comb.sum(r_comb),
783 zk::CircuitNode::Mul(l, r) => {
784 if let Some(out) = l_comb.mul(r_comb) {
785 out
786 } else {
787 let i = self.internal_vars.len();
788 self.internal_vars.push((l, Some(r), Some(this)));
789 self.extra_assertions.push((l, Location::Left(i)));
790 self.extra_assertions.push((r, Location::Right(i)));
791
792 let w_l = l_comb.witness();
794 let w_r = r_comb.witness();
795 if let Some(w) = w_l {
796 self.assign_witness_location(w, Location::Left(i));
797 }
798 if let Some(w) = w_r {
799 self.assign_witness_location(w, Location::Right(i));
800 }
801 LinComb::Location(Location::Output(i))
802 }
803 }
804 }
805 }
806 };
807 self.linearize_cache.insert(i, comb);
808 }
809 }
810 }
811}
812
813pub fn zkc_to_circuit<F: Field + Random>(
825 zkc: crate::zk::circuit::Circuit<F>,
826 committed_indices: &[crate::zk::circuit::CircuitIdx],
827) -> Circuit<F> {
828 zkc::ZKCConverter::new(committed_indices.to_vec()).circuit(zkc)
829}
830
831pub fn zkc_to_circuit_and_witness<F: Field + Random>(
841 blinding_rng: Option<&mut impl CryptoRng>,
842 zkc: crate::zk::circuit::ValuedCircuit<F>,
843 committed_indices: &[crate::zk::circuit::CircuitIdx],
844) -> (Circuit<F>, Witness<F>) {
845 zkc::ZKCConverter::new(committed_indices.to_vec()).circuit_and_witness(blinding_rng, zkc)
846}
847
848#[derive(PartialEq)]
853pub struct Setup<G> {
854 ipa: ipa::Setup<G>,
855 value_generator: G,
856 blinding_generator: G,
857}
858
859impl<G> Setup<G> {
860 pub const fn new(ipa: ipa::Setup<G>, value_generator: G, blinding_generator: G) -> Self {
864 Self {
865 ipa,
866 value_generator,
867 blinding_generator,
868 }
869 }
870
871 pub const fn value_generator(&self) -> &G {
872 &self.value_generator
873 }
874
875 pub const fn blinding_generator(&self) -> &G {
876 &self.blinding_generator
877 }
878
879 pub const fn supports(&self, lg_len: u8) -> bool {
881 self.ipa.supports(lg_len)
882 }
883
884 pub fn hashed(domain_separator: &[u8], lg_len: u8, value_generator: G) -> Self
898 where
899 G: HashToGroup,
900 {
901 let n: usize = 1usize << lg_len;
902 let product_generator = G::hash_to_group(domain_separator, b"product");
903 let blinding_generator = G::hash_to_group(domain_separator, b"blinding");
904 let g_and_h = (0..n).map(|i| {
905 let i_bytes = (i as u64).to_le_bytes();
906 let mut g_msg = Vec::with_capacity(2 + i_bytes.len());
907 g_msg.extend_from_slice(b"g/");
908 g_msg.extend_from_slice(&i_bytes);
909 let mut h_msg = Vec::with_capacity(2 + i_bytes.len());
910 h_msg.extend_from_slice(b"h/");
911 h_msg.extend_from_slice(&i_bytes);
912 (
913 G::hash_to_group(domain_separator, &g_msg),
914 G::hash_to_group(domain_separator, &h_msg),
915 )
916 });
917 Self::new(
918 ipa::Setup::new(product_generator, g_and_h),
919 value_generator,
920 blinding_generator,
921 )
922 }
923
924 fn build_virtual<F: Field>(&self) -> (Setup<Synthetic<F, G>>, Vec<G>)
927 where
928 G: Clone,
929 {
930 let n = self.ipa.g().len();
931 let mut gens = Synthetic::<F, G>::generators();
932 let vg: Vec<_> = (0..n)
933 .map(|_| gens.next().expect("generators is infinite"))
934 .collect();
935 let vh: Vec<_> = (0..n)
936 .map(|_| gens.next().expect("generators is infinite"))
937 .collect();
938 let vq = gens.next().expect("generators is infinite");
939 let ipa_vs = ipa::Setup::new(vq, vg.into_iter().zip(vh));
940 let pv = gens.next().expect("generators is infinite");
941 let pb = gens.next().expect("generators is infinite");
942 let vs = Setup::new(ipa_vs, pv, pb);
943 let mut flat = Vec::with_capacity(2 * n + 3);
944 flat.extend_from_slice(self.ipa.g());
945 flat.extend_from_slice(self.ipa.h());
946 flat.push(self.ipa.product_generator().clone());
947 flat.push(self.value_generator.clone());
948 flat.push(self.blinding_generator.clone());
949 (vs, flat)
950 }
951
952 pub fn eval<F: Field>(
955 &self,
956 f: impl FnOnce(&Setup<Synthetic<F, G>>) -> Option<Synthetic<F, G>>,
957 strategy: &impl Strategy,
958 ) -> Option<G>
959 where
960 G: Space<F>,
961 {
962 let (vs, flat) = self.build_virtual::<F>();
963 f(&vs).map(|v| v.eval(&flat, strategy))
964 }
965
966 pub fn eval_check_batched<F: Field + Random, R: CryptoRng>(
992 &self,
993 rng: &mut R,
994 f: impl FnOnce(&Setup<Synthetic<F, G>>, &mut R) -> Option<Vec<Option<Synthetic<F, G>>>>,
995 strategy: &impl Strategy,
996 ) -> Option<Vec<bool>>
997 where
998 G: Space<F> + PartialEq,
999 {
1000 let (vs, flat) = self.build_virtual::<F>();
1001 let synths = f(&vs, &mut *rng)?;
1002 let n = synths.len();
1003
1004 let scaled: Vec<Option<Synthetic<F, G>>> = synths
1008 .into_iter()
1009 .map(|opt| opt.map(|s| s * &F::random(&mut *rng)))
1010 .collect();
1011
1012 let active: Vec<usize> = (0..n).filter(|&i| scaled[i].is_some()).collect();
1014
1015 let check = |range: &[usize]| -> bool {
1017 let mut acc = Synthetic::<F, G>::default();
1018 for &i in range {
1019 acc += scaled[i].as_ref().expect("active indices are Some");
1020 }
1021 acc.eval(&flat, strategy) == G::zero()
1022 };
1023
1024 let mut valid = vec![false; n];
1029 let mut stack: Vec<&[usize]> = Vec::new();
1030 if !active.is_empty() {
1031 stack.push(&active);
1032 }
1033 while let Some(range) = stack.pop() {
1034 if check(range) {
1035 for &i in range {
1036 valid[i] = true;
1037 }
1038 } else if range.len() > 1 {
1039 let mid = range.len() / 2;
1040 let (left, right) = range.split_at(mid);
1041 stack.push(right);
1042 stack.push(left);
1043 }
1044 }
1045 Some(valid)
1046 }
1047}
1048
1049impl<G: Write> Write for Setup<G> {
1050 fn write(&self, buf: &mut impl BufMut) {
1051 self.ipa.write(buf);
1052 self.value_generator.write(buf);
1053 self.blinding_generator.write(buf);
1054 }
1055}
1056
1057impl<G: EncodeSize> EncodeSize for Setup<G> {
1058 fn encode_size(&self) -> usize {
1059 self.ipa.encode_size()
1060 + self.value_generator.encode_size()
1061 + self.blinding_generator.encode_size()
1062 }
1063}
1064
1065impl<G: Read> Read for Setup<G>
1066where
1067 G::Cfg: Clone,
1068{
1069 type Cfg = (usize, G::Cfg);
1070
1071 fn read_cfg(buf: &mut impl Buf, (max_len, cfg): &Self::Cfg) -> Result<Self, Error> {
1072 let ipa = ipa::Setup::read_cfg(buf, &(*max_len, cfg.clone()))?;
1073 let value_generator = G::read_cfg(buf, cfg)?;
1074 let blinding_generator = G::read_cfg(buf, cfg)?;
1075 Ok(Self::new(ipa, value_generator, blinding_generator))
1076 }
1077}
1078
1079#[allow(dead_code)]
1084pub struct Witness<F> {
1085 values: Vec<F>,
1086 blinding: Vec<F>,
1087 left: Vec<F>,
1088 right: Vec<F>,
1089 out: Vec<F>,
1090}
1091
1092impl<F> Witness<F> {
1093 pub fn new(
1099 values: Vec<F>,
1100 blinding: Vec<F>,
1101 left: Vec<F>,
1102 right: Vec<F>,
1103 out: Vec<F>,
1104 ) -> Option<Self> {
1105 if values.len() != blinding.len() {
1106 return None;
1107 }
1108 if left.len() != right.len() || right.len() != out.len() {
1109 return None;
1110 }
1111 Some(Self {
1112 values,
1113 blinding,
1114 left,
1115 right,
1116 out,
1117 })
1118 }
1119
1120 pub fn values(&self) -> &[F] {
1121 &self.values
1122 }
1123
1124 #[must_use]
1129 pub fn is_satisfied(&self, circuit: &Circuit<F>) -> bool
1130 where
1131 F: Ring,
1132 {
1133 circuit.is_satisfied(&self.values, &self.left, &self.right)
1134 }
1135
1136 pub fn claim<G: Space<F>>(&self, setup: &Setup<G>) -> Claim<G> {
1141 Claim {
1142 commitments: self
1143 .values
1144 .iter()
1145 .zip(&self.blinding)
1146 .map(|(value, blind)| {
1147 setup.value_generator.clone() * value
1148 + &(setup.blinding_generator.clone() * blind)
1149 })
1150 .collect(),
1151 }
1152 }
1153}
1154
1155pub struct Claim<G> {
1163 pub commitments: Vec<G>,
1164}
1165
1166impl<G: Write> Write for Claim<G> {
1167 fn write(&self, buf: &mut impl BufMut) {
1168 self.commitments.write(buf);
1169 }
1170}
1171
1172impl<G: EncodeSize> EncodeSize for Claim<G> {
1173 fn encode_size(&self) -> usize {
1174 self.commitments.encode_size()
1175 }
1176}
1177
1178#[allow(dead_code)]
1183#[derive(Clone)]
1184pub struct Proof<F, G> {
1185 m_big: G,
1186 o_big: G,
1187 m_big_tilde: G,
1188 t_big: [G; 5],
1189 s_tilde: F,
1190 t_x: F,
1191 t_tilde_x: F,
1192 p_big: G,
1193 ipa_proof: ipa::Proof<F, G>,
1194}
1195
1196impl<F: Write, G: Write> Write for Proof<F, G> {
1197 fn write(&self, buf: &mut impl BufMut) {
1198 self.m_big.write(buf);
1199 self.o_big.write(buf);
1200 self.m_big_tilde.write(buf);
1201 for t in &self.t_big {
1202 t.write(buf);
1203 }
1204 self.s_tilde.write(buf);
1205 self.t_x.write(buf);
1206 self.t_tilde_x.write(buf);
1207 self.p_big.write(buf);
1208 self.ipa_proof.write(buf);
1209 }
1210}
1211
1212impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
1213 fn encode_size(&self) -> usize {
1214 self.m_big.encode_size()
1215 + self.o_big.encode_size()
1216 + self.m_big_tilde.encode_size()
1217 + self.t_big.iter().map(|t| t.encode_size()).sum::<usize>()
1218 + self.s_tilde.encode_size()
1219 + self.t_x.encode_size()
1220 + self.t_tilde_x.encode_size()
1221 + self.p_big.encode_size()
1222 + self.ipa_proof.encode_size()
1223 }
1224}
1225
1226impl<F: Read, G: Read> Read for Proof<F, G>
1227where
1228 F::Cfg: Clone,
1229 G::Cfg: Clone,
1230{
1231 type Cfg = (usize, (G::Cfg, F::Cfg));
1233
1234 fn read_cfg(buf: &mut impl Buf, cfg @ (_, (g_cfg, f_cfg)): &Self::Cfg) -> Result<Self, Error> {
1235 let m_big = G::read_cfg(buf, g_cfg)?;
1236 let o_big = G::read_cfg(buf, g_cfg)?;
1237 let m_big_tilde = G::read_cfg(buf, g_cfg)?;
1238 let t_big = [
1239 G::read_cfg(buf, g_cfg)?,
1240 G::read_cfg(buf, g_cfg)?,
1241 G::read_cfg(buf, g_cfg)?,
1242 G::read_cfg(buf, g_cfg)?,
1243 G::read_cfg(buf, g_cfg)?,
1244 ];
1245 let s_tilde = F::read_cfg(buf, f_cfg)?;
1246 let t_x = F::read_cfg(buf, f_cfg)?;
1247 let t_tilde_x = F::read_cfg(buf, f_cfg)?;
1248 let p_big = G::read_cfg(buf, g_cfg)?;
1249 let ipa_proof = ipa::Proof::read_cfg(buf, cfg)?;
1250 Ok(Self {
1251 m_big,
1252 o_big,
1253 m_big_tilde,
1254 t_big,
1255 s_tilde,
1256 t_x,
1257 t_tilde_x,
1258 p_big,
1259 ipa_proof,
1260 })
1261 }
1262}
1263
1264pub fn prove<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1273 rng: &mut impl CryptoRng,
1274 transcript: &mut Transcript,
1275 setup: &Setup<G>,
1276 circuit: &Circuit<F>,
1277 claim: &Claim<G>,
1278 witness: &Witness<F>,
1279 strategy: &impl Strategy,
1280) -> Option<Proof<F, G>> {
1281 let l_tilde = (0..circuit.internal_vars)
1500 .map(|_| F::random(&mut *rng))
1501 .collect::<Vec<_>>();
1502 let r_tilde = (0..circuit.internal_vars)
1503 .map(|_| F::random(&mut *rng))
1504 .collect::<Vec<_>>();
1505 let m = F::random(&mut *rng);
1506 let o_tilde = F::random(&mut *rng);
1507 let m_tilde = F::random(&mut *rng);
1508 let g_internal = &setup.ipa.g()[..circuit.internal_vars];
1509 let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1510 let m_big = G::msm(g_internal, &witness.left, strategy)
1511 + &G::msm(h_internal, &witness.right, strategy)
1512 + &(setup.blinding_generator.clone() * &m);
1513 let o_big =
1514 G::msm(g_internal, &witness.out, strategy) + &(setup.blinding_generator.clone() * &o_tilde);
1515 let m_big_tilde = G::msm(g_internal, &l_tilde, strategy)
1516 + &G::msm(h_internal, &r_tilde, strategy)
1517 + &(setup.blinding_generator.clone() * &m_tilde);
1518 circuit.commit(transcript);
1520 transcript.commit(claim.encode());
1521 transcript.commit(m_big.encode());
1522 transcript.commit(o_big.encode());
1523 transcript.commit(m_big_tilde.encode());
1524 let padded_vars = circuit.internal_vars.next_power_of_two();
1525 let y = F::random(transcript.noise(b"y"));
1526 let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1527 let y_inv = y.inv();
1528 let y_inv_powers = powers(F::one(), &y_inv)
1529 .take(padded_vars)
1530 .collect::<Vec<_>>();
1531 let z = F::random(transcript.noise(b"z"));
1532 let z_powers = powers(z.clone(), &z)
1533 .take(circuit.weights.height())
1534 .collect::<Vec<_>>();
1535 let (kappa, theta, lambda, rho, omega) = {
1536 let mut kappa = F::zero();
1537 let mut theta = vec![F::zero(); circuit.committed_vars];
1538 let mut lambda = vec![F::zero(); circuit.internal_vars];
1539 let mut rho = vec![F::zero(); circuit.internal_vars];
1540 let mut omega = vec![F::zero(); circuit.internal_vars];
1541 let theta_start = 1;
1542 let lambda_start = theta_start + circuit.committed_vars;
1543 let rho_start = lambda_start + circuit.internal_vars;
1544 let omega_start = rho_start + circuit.internal_vars;
1545 for (&(i, j), w_ij) in &circuit.weights.weights {
1546 let w_ij = w_ij.clone();
1547 if j >= omega_start {
1548 omega[j - omega_start] += &(w_ij * &z_powers[i]);
1549 } else if j >= rho_start {
1550 rho[j - rho_start] += &(w_ij * &z_powers[i]);
1551 } else if j >= lambda_start {
1552 lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1553 } else if j >= theta_start {
1554 theta[j - theta_start] += &(w_ij * &z_powers[i]);
1555 } else {
1556 kappa += &(w_ij * &z_powers[i]);
1557 }
1558 }
1559 (kappa, theta, lambda, rho, omega)
1560 };
1561
1562 let mut omega_minus_y = omega
1564 .iter()
1565 .cloned()
1566 .zip(&y_powers)
1567 .map(|(omega_i, y_i)| omega_i - y_i)
1568 .collect::<Vec<_>>();
1569 omega_minus_y.extend(
1570 y_powers
1571 .iter()
1572 .skip(circuit.internal_vars)
1573 .cloned()
1574 .map(|y_i| -y_i),
1575 );
1576 let y_inv_rho = y_inv_powers
1577 .iter()
1578 .cloned()
1579 .zip(&rho)
1580 .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1581 .collect::<Vec<_>>();
1582 let y_inv_lambda = y_inv_powers
1583 .iter()
1584 .cloned()
1585 .zip(&lambda)
1586 .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1587 .collect::<Vec<_>>();
1588 let y_inv_omega_minus_y = y_inv_powers
1589 .iter()
1590 .cloned()
1591 .zip(&omega_minus_y)
1592 .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1593 .collect::<Vec<_>>();
1594 let y_r = y_powers
1595 .iter()
1596 .cloned()
1597 .zip(&witness.right)
1598 .map(|(y_i, r_i)| y_i * r_i)
1599 .collect::<Vec<_>>();
1600 let y_r_tilde = y_powers
1601 .iter()
1602 .cloned()
1603 .zip(&r_tilde)
1604 .map(|(y_i, r_i)| y_i * r_i)
1605 .collect::<Vec<_>>();
1606
1607 let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1608
1609 let t = {
1611 let mut t = std::array::from_fn::<_, 6, _>(|_| F::zero());
1612 for i in 0..circuit.internal_vars {
1614 t[0] += &((witness.left[i].clone() + &y_inv_rho[i]) * &omega_minus_y[i]);
1615 }
1616 t[1] = delta_y_z - &kappa - &<F as Space<F>>::msm(&theta, &witness.values, strategy);
1618 for i in 0..circuit.internal_vars {
1620 t[2] += &(l_tilde[i].clone() * &omega_minus_y[i]);
1621 t[2] += &(witness.out[i].clone() * &(y_r[i].clone() + &lambda[i]));
1622 }
1623 for i in 0..circuit.internal_vars {
1625 t[3] += &(l_tilde[i].clone() * &(y_r[i].clone() + &lambda[i]));
1626 t[3] += &((witness.left[i].clone() + &y_inv_rho[i]) * &y_r_tilde[i]);
1627 }
1628 t[4] = <F as Space<F>>::msm(&witness.out, &y_r_tilde, strategy);
1630 t[5] = <F as Space<F>>::msm(&l_tilde, &y_r_tilde, strategy);
1632 t
1633 };
1634 let t_tilde = std::array::from_fn::<_, 6, _>(|i| {
1635 if i == 1 {
1636 -<F as Space<F>>::msm(&theta, &witness.blinding, strategy)
1637 } else {
1638 F::random(&mut *rng)
1639 }
1640 });
1641 let t_big = std::array::from_fn::<_, 5, _>(|i| {
1642 let i = if i >= 1 { i + 1 } else { i };
1644 setup.value_generator.clone() * &t[i] + &(setup.blinding_generator.clone() * &t_tilde[i])
1645 });
1646
1647 let p_0 = G::msm(
1650 &setup.ipa.h()[..padded_vars],
1651 &y_inv_omega_minus_y,
1652 strategy,
1653 );
1654 let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1655 let p_1 =
1656 G::msm(g_internal, &y_inv_rho, strategy) + &G::msm(h_internal, &y_inv_lambda, strategy);
1657
1658 for t_big_i in &t_big {
1661 transcript.commit(t_big_i.encode());
1662 }
1663 let x = F::random(transcript.noise(b"x"));
1664 let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1665 let s_tilde = m * &x[0] + &(o_tilde * &x[1]) + &(m_tilde * &x[2]);
1666 let p = setup.blinding_generator.clone() * &(-s_tilde.clone())
1667 + &p_0
1668 + &((p_1 + &m_big) * &x[0])
1669 + &(o_big.clone() * &x[1])
1670 + &(m_big_tilde.clone() * &x[2]);
1671 let t_x = <F as Space<F>>::msm(&t, &x, strategy);
1672 let t_tilde_x = <F as Space<F>>::msm(&t_tilde, &x, strategy);
1673 let ipa_claim = ipa::Claim {
1674 commitment: p.clone(),
1675 product: t_x.clone(),
1676 y: y_inv,
1677 log_len: padded_vars.ilog2().try_into().ok()?,
1678 };
1679 let mut f_x = (0..circuit.internal_vars)
1680 .map(|i| {
1681 (witness.left[i].clone() + &y_inv_rho[i]) * &x[0]
1682 + &(witness.out[i].clone() * &x[1])
1683 + &(l_tilde[i].clone() * &x[2])
1684 })
1685 .collect::<Vec<_>>();
1686 f_x.resize(padded_vars, F::zero());
1687 let mut g_x = (0..circuit.internal_vars)
1688 .map(|i| {
1689 (y_r[i].clone() + &lambda[i]) * &x[0]
1690 + &omega_minus_y[i]
1691 + &(y_r_tilde[i].clone() * &x[2])
1692 })
1693 .collect::<Vec<_>>();
1694 g_x.extend_from_slice(&omega_minus_y[circuit.internal_vars..]);
1695 let witness = ipa::Witness::new(f_x.into_iter().zip(g_x))?;
1696 let ipa_proof = ipa::prove(transcript, &setup.ipa, &ipa_claim, witness, strategy)?;
1697 Some(Proof {
1698 m_big,
1699 o_big,
1700 m_big_tilde,
1701 t_big,
1702 s_tilde,
1703 t_x,
1704 t_tilde_x,
1705 p_big: p,
1706 ipa_proof,
1707 })
1708}
1709
1710pub fn verify<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1718 rng: &mut impl CryptoRng,
1719 transcript: &mut Transcript,
1720 setup: &Setup<Synthetic<F, G>>,
1721 circuit: &Circuit<F>,
1722 claim: &Claim<G>,
1723 proof: Proof<F, G>,
1724 strategy: &impl Strategy,
1725) -> Option<Synthetic<F, G>> {
1726 let Proof {
1727 m_big,
1728 o_big,
1729 m_big_tilde,
1730 t_big,
1731 s_tilde,
1732 t_x,
1733 t_tilde_x,
1734 ipa_proof,
1735 p_big: p,
1736 } = proof;
1737 if claim.commitments.len() != circuit.committed_vars {
1742 return None;
1743 }
1744 circuit.commit(transcript);
1745 transcript.commit(claim.encode());
1746 transcript.commit(m_big.encode());
1747 transcript.commit(o_big.encode());
1748 transcript.commit(m_big_tilde.encode());
1749 let padded_vars = circuit.internal_vars.next_power_of_two();
1750 let y = F::random(transcript.noise(b"y"));
1751 let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1752 let y_inv = y.inv();
1753 let y_inv_powers = powers(F::one(), &y_inv)
1754 .take(padded_vars)
1755 .collect::<Vec<_>>();
1756 let z = F::random(transcript.noise(b"z"));
1757 let z_powers = powers(z.clone(), &z)
1758 .take(circuit.weights.height())
1759 .collect::<Vec<_>>();
1760 let (kappa, theta, lambda, rho, omega) = {
1761 let mut kappa = F::zero();
1762 let mut theta = vec![F::zero(); circuit.committed_vars];
1763 let mut lambda = vec![F::zero(); circuit.internal_vars];
1764 let mut rho = vec![F::zero(); circuit.internal_vars];
1765 let mut omega = vec![F::zero(); circuit.internal_vars];
1766 let theta_start = 1;
1767 let lambda_start = theta_start + circuit.committed_vars;
1768 let rho_start = lambda_start + circuit.internal_vars;
1769 let omega_start = rho_start + circuit.internal_vars;
1770 for (&(i, j), w_ij) in &circuit.weights.weights {
1771 let w_ij = w_ij.clone();
1772 if j >= omega_start {
1773 omega[j - omega_start] += &(w_ij * &z_powers[i]);
1774 } else if j >= rho_start {
1775 rho[j - rho_start] += &(w_ij * &z_powers[i]);
1776 } else if j >= lambda_start {
1777 lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1778 } else if j >= theta_start {
1779 theta[j - theta_start] += &(w_ij * &z_powers[i]);
1780 } else {
1781 kappa += &(w_ij * &z_powers[i]);
1782 }
1783 }
1784 (kappa, theta, lambda, rho, omega)
1785 };
1786
1787 let mut omega_minus_y = omega
1789 .iter()
1790 .cloned()
1791 .zip(&y_powers)
1792 .map(|(omega_i, y_i)| omega_i - y_i)
1793 .collect::<Vec<_>>();
1794 omega_minus_y.extend(
1795 y_powers
1796 .iter()
1797 .skip(circuit.internal_vars)
1798 .cloned()
1799 .map(|y_i| -y_i),
1800 );
1801 let y_inv_rho = y_inv_powers
1802 .iter()
1803 .cloned()
1804 .zip(&rho)
1805 .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1806 .collect::<Vec<_>>();
1807 let y_inv_lambda = y_inv_powers
1808 .iter()
1809 .cloned()
1810 .zip(&lambda)
1811 .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1812 .collect::<Vec<_>>();
1813 let y_inv_omega_minus_y = y_inv_powers
1814 .iter()
1815 .cloned()
1816 .zip(&omega_minus_y)
1817 .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1818 .collect::<Vec<_>>();
1819
1820 let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1821
1822 for t_big_i in &t_big {
1823 transcript.commit(t_big_i.encode());
1824 }
1825 let x = F::random(transcript.noise(b"x"));
1826 let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1827
1828 let ipa_g = setup.ipa.g();
1829 let ipa_h = setup.ipa.h();
1830
1831 let value_generator = &setup.value_generator;
1832 let blinding_generator = &setup.blinding_generator;
1833
1834 let t_check = Synthetic::msm(
1835 &[value_generator.clone(), blinding_generator.clone()],
1836 &[t_x.clone(), t_tilde_x],
1837 &Sequential,
1838 ) - &(value_generator.clone() * &((-kappa + &delta_y_z) * &x[1]))
1839 + &(Synthetic::concrete(theta.iter().cloned().zip(claim.commitments.iter().cloned()))
1840 * &x[1])
1841 - &Synthetic::concrete(std::iter::once(&x[0]).chain(&x[2..]).cloned().zip(t_big));
1842
1843 let p_check = {
1844 let p_0 = Synthetic::msm(&ipa_h[..padded_vars], &y_inv_omega_minus_y, &Sequential);
1846 let p_1 = Synthetic::msm(&ipa_g[..circuit.internal_vars], &y_inv_rho, &Sequential)
1847 + &Synthetic::msm(&ipa_h[..circuit.internal_vars], &y_inv_lambda, &Sequential);
1848 Synthetic::concrete([
1849 (F::one(), p.clone()),
1850 (-x[0].clone(), m_big),
1851 (-x[1].clone(), o_big),
1852 (-x[2].clone(), m_big_tilde),
1853 ]) - &p_0
1854 - &(p_1 * &x[0])
1855 + &(blinding_generator.clone() * &s_tilde)
1856 };
1857
1858 let ipa_claim = ipa::Claim {
1859 commitment: p,
1860 product: t_x,
1861 y: y_inv,
1862 log_len: padded_vars
1863 .ilog2()
1864 .try_into()
1865 .expect("should be less than 2^256 rows"),
1866 };
1867
1868 let ipa_check = ipa::verify(transcript, &setup.ipa, &ipa_claim, ipa_proof)?;
1869
1870 let final_check =
1871 ipa_check + &(p_check * &F::random(&mut *rng)) + &(t_check * &F::random(&mut *rng));
1872 Some(final_check)
1873}
1874
1875#[commonware_macros::stability(ALPHA)]
1876#[cfg(any(test, feature = "fuzz"))]
1877pub mod fuzz {
1878 use super::*;
1879 use crate::transcript::Version;
1880 use arbitrary::{Arbitrary, Unstructured};
1881 use commonware_math::{
1882 algebra::{Additive, Ring},
1883 test::{F, G},
1884 };
1885 use commonware_parallel::Sequential;
1886 use commonware_utils::test_rng;
1887 use std::sync::OnceLock;
1888
1889 const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_CIRCUIT";
1890
1891 const TEST_SETUP_PAIRS: usize = 64;
1895
1896 pub(super) fn test_setup() -> &'static Setup<G> {
1897 static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
1898 TEST_SETUP.get_or_init(|| {
1899 let count = 2 * TEST_SETUP_PAIRS + 3;
1900 let gens = (1..=count)
1901 .map(|i| G::generator() * &F::from(i as u64))
1902 .collect::<Vec<_>>();
1903 Setup::new(
1904 ipa::Setup::new(
1905 gens[2 * TEST_SETUP_PAIRS],
1906 gens[..2 * TEST_SETUP_PAIRS]
1907 .as_chunks::<2>()
1908 .0
1909 .iter()
1910 .map(|c| (c[0], c[1])),
1911 ),
1912 gens[2 * TEST_SETUP_PAIRS + 1],
1913 gens[2 * TEST_SETUP_PAIRS + 2],
1914 )
1915 })
1916 }
1917
1918 fn quadratic_value(a: F, b: F, c: F, x: F) -> F {
1919 a * &x * &x + &(b * &x) + &c
1920 }
1921
1922 pub(super) fn quadratic_circuit(a: F, b: F, c: F) -> Circuit<F> {
1923 let mut weights = SparseMatrix::default();
1924
1925 weights[(0, 1)] = F::one();
1927 weights[(0, 3)] = -F::one();
1928
1929 weights[(1, 1)] = F::one();
1931 weights[(1, 4)] = -F::one();
1932
1933 weights[(2, 0)] = c;
1935 weights[(2, 1)] = b;
1936 weights[(2, 2)] = -F::one();
1937 weights[(2, 5)] = a;
1938
1939 Circuit::new(2, weights).expect("quadratic circuit layout should be valid")
1940 }
1941
1942 pub struct Case {
1944 circuit: Circuit<F>,
1945 witness: Witness<F>,
1946 }
1947
1948 impl Case {
1949 fn is_satisfied(&self) -> bool {
1950 self.circuit.is_satisfied(
1951 &self.witness.values,
1952 &self.witness.left,
1953 &self.witness.right,
1954 )
1955 }
1956
1957 fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
1958 let a = u.arbitrary::<F>()?;
1959 let b = u.arbitrary::<F>()?;
1960 let c = u.arbitrary::<F>()?;
1961 let x = u.arbitrary::<F>()?;
1962 let valid = u.arbitrary::<bool>()?;
1963 let mut y = quadratic_value(a, b, c, x);
1964 if !valid {
1965 let mut tweak = u.arbitrary::<F>()?;
1966 if tweak == F::zero() {
1967 tweak = F::one()
1968 }
1969 y += &tweak;
1970 }
1971
1972 let x_sq = x * &x;
1973 let witness = Witness::new(
1974 vec![x, y],
1975 vec![u.arbitrary::<F>()?, u.arbitrary::<F>()?],
1976 vec![x],
1977 vec![x],
1978 vec![x_sq],
1979 )
1980 .expect("quadratic witness should have matching vector lengths");
1981 let circuit = quadratic_circuit(a, b, c);
1982 let out = Self { circuit, witness };
1983 assert_eq!(
1984 out.is_satisfied(),
1985 valid,
1986 "quadratic case should match requested validity",
1987 );
1988 Ok(out)
1989 }
1990 }
1991
1992 pub enum Plan {
1993 ProveAndVerify(Case),
1994 ZkcConversion(crate::zk::circuit::fuzz::Plan),
1995 }
1996
1997 impl<'a> Arbitrary<'a> for Plan {
1998 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1999 match u.int_in_range(0..=1)? {
2000 0 => Ok(Self::ProveAndVerify(Case::arbitrary(u)?)),
2001 1 => Ok(Self::ZkcConversion(u.arbitrary()?)),
2002 _ => unreachable!("plan variant out of range"),
2003 }
2004 }
2005 }
2006
2007 fn assert_verify_matches_satisfaction(case: &Case) {
2008 let setup = test_setup();
2009 let claim = case.witness.claim(setup);
2010 let verified = prove_and_verify(setup, &case.circuit, &claim, &case.witness);
2011 assert_eq!(verified, case.is_satisfied());
2012 }
2013
2014 fn prove_and_verify(
2017 setup: &Setup<G>,
2018 circuit: &Circuit<F>,
2019 claim: &Claim<G>,
2020 witness: &Witness<F>,
2021 ) -> bool {
2022 let mut rng = test_rng();
2023 let mut prover_transcript = Transcript::new(NAMESPACE, Version::V1);
2024 let Some(proof) = super::prove(
2025 &mut rng,
2026 &mut prover_transcript,
2027 setup,
2028 circuit,
2029 claim,
2030 witness,
2031 &Sequential,
2032 ) else {
2033 return false;
2034 };
2035 let mut verifier_transcript = Transcript::new(NAMESPACE, Version::V1);
2036 setup
2037 .eval(
2038 |vs| {
2039 verify(
2040 &mut rng,
2041 &mut verifier_transcript,
2042 vs,
2043 circuit,
2044 claim,
2045 proof,
2046 &Sequential,
2047 )
2048 },
2049 &Sequential,
2050 )
2051 .map(|g| g == G::zero())
2052 .unwrap_or(false)
2053 }
2054
2055 pub(super) fn assert_zkc_conversion_preserves_satisfaction(
2066 plan: &crate::zk::circuit::fuzz::Plan,
2067 u: &mut Unstructured<'_>,
2068 ) -> arbitrary::Result<()> {
2069 let valued = plan.build();
2070 let mut committed = Vec::new();
2071 for i in 0..valued.circuit.witnesses {
2072 if u.arbitrary()? {
2073 committed.push(crate::zk::circuit::CircuitIdx::Witness(i));
2074 }
2075 }
2076 let (circuit, witness) =
2077 zkc_to_circuit_and_witness(Some(&mut test_rng()), valued, &committed);
2078 let satisfied = witness.is_satisfied(&circuit);
2079 assert_eq!(satisfied, plan.satisfied(), "plan: {plan:?}");
2080
2081 if satisfied {
2082 let setup = test_setup();
2083 assert!(
2084 circuit.internal_vars() <= TEST_SETUP_PAIRS,
2085 "circuit too large for test setup ({} > {TEST_SETUP_PAIRS}); plan: {plan:?}",
2086 circuit.internal_vars()
2087 );
2088 let honest = witness.claim(setup);
2089 assert!(
2090 prove_and_verify(setup, &circuit, &honest, &witness),
2091 "honest claim must verify; plan: {plan:?}"
2092 );
2093 if !committed.is_empty() {
2094 let j = u.choose_index(committed.len())?;
2095 let mut tampered = witness.claim(setup);
2096 tampered.commitments[j] += setup.value_generator();
2097 assert!(
2098 !prove_and_verify(setup, &circuit, &tampered, &witness),
2099 "tampering committed value {j} must break verification; plan: {plan:?}"
2100 );
2101 }
2102 }
2103 Ok(())
2104 }
2105
2106 impl Plan {
2107 pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
2108 match self {
2109 Self::ProveAndVerify(case) => assert_verify_matches_satisfaction(&case),
2110 Self::ZkcConversion(plan) => {
2111 assert_zkc_conversion_preserves_satisfaction(&plan, u)?
2112 }
2113 }
2114 Ok(())
2115 }
2116 }
2117}
2118
2119#[cfg(test)]
2120mod test {
2121 use super::{Circuit, Setup, SparseMatrix, Witness, fuzz, prove, verify};
2122 use crate::{
2123 transcript::{Transcript, Version},
2124 zk::circuit as zk,
2125 };
2126 use commonware_codec::{Decode, Encode};
2127 use commonware_invariants::minifuzz;
2128 use commonware_math::{
2129 algebra::{Additive, CryptoGroup, Ring},
2130 test::{F, G},
2131 };
2132 use commonware_parallel::Sequential;
2133 use commonware_utils::test_rng;
2134
2135 #[test]
2136 fn test_sparse_matrix_encoding_binds_dimensions() {
2137 let matrix = SparseMatrix::<F>::default();
2138 let mut wider = SparseMatrix::<F>::default();
2139 wider.pad(1, 0);
2140 let mut taller = SparseMatrix::<F>::default();
2141 taller.pad(0, 1);
2142
2143 assert_eq!(matrix.weights, wider.weights);
2144 assert_eq!(matrix.weights, taller.weights);
2145 assert_ne!(matrix.encode(), wider.encode());
2146 assert_ne!(matrix.encode(), taller.encode());
2147 assert_ne!(wider.encode(), taller.encode());
2148 }
2149
2150 #[test]
2151 fn test_distinct_valid_circuits_encode_differently() {
2152 let mut no_internal_vars = SparseMatrix::<F>::default();
2153 no_internal_vars.pad(1, 0);
2154 let no_internal_vars =
2155 Circuit::new(0, no_internal_vars).expect("width 1 is a valid circuit layout");
2156
2157 let mut one_internal_var = SparseMatrix::<F>::default();
2158 one_internal_var.pad(4, 0);
2159 let one_internal_var =
2160 Circuit::new(0, one_internal_var).expect("width 4 is a valid circuit layout");
2161
2162 assert_eq!(no_internal_vars.internal_vars(), 0);
2163 assert_eq!(one_internal_var.internal_vars(), 1);
2164 assert!(no_internal_vars.is_satisfied(&[], &[], &[]));
2165 assert!(!one_internal_var.is_satisfied(&[], &[], &[]));
2166 assert!(one_internal_var.is_satisfied(&[], &[F::zero()], &[F::zero()]));
2167 assert_ne!(no_internal_vars.encode(), one_internal_var.encode());
2168 }
2169
2170 #[test]
2171 fn test_converted_circuits_bind_internal_vars() {
2172 let (one_internal_var, _) = zk::build::<F>(|ctx| {
2173 let a = zk::Var::witness(ctx, |_| F::zero());
2174 let b = zk::Var::witness(ctx, |_| F::zero());
2175 let product = a * &b;
2176 product.assert_eq(&product);
2177 Vec::new()
2178 });
2179 let one_internal_var = super::zkc_to_circuit(one_internal_var, &[]);
2180
2181 let (two_internal_vars, _) = zk::build::<F>(|ctx| {
2182 let w0 = zk::Var::witness(ctx, |_| F::zero());
2183 let w1 = zk::Var::witness(ctx, |_| F::zero());
2184 let w2 = zk::Var::witness(ctx, |_| F::zero());
2185 w1.assert_eq(&w1);
2186 w0.assert_eq(&w0);
2187 w2.assert_eq(&w2);
2188 Vec::new()
2189 });
2190 let two_internal_vars = super::zkc_to_circuit(two_internal_vars, &[]);
2191
2192 assert_eq!(one_internal_var.internal_vars(), 1);
2193 assert_eq!(two_internal_vars.internal_vars(), 2);
2194 assert_eq!(
2195 one_internal_var.weights.encode(),
2196 two_internal_vars.weights.encode()
2197 );
2198 assert_ne!(one_internal_var.encode(), two_internal_vars.encode());
2199 }
2200
2201 #[test]
2202 fn test_zkc_conversion_preserves_satisfaction_minifuzz() {
2203 minifuzz::test(|u| {
2204 let plan = u.arbitrary::<zk::fuzz::Plan>()?;
2205 fuzz::assert_zkc_conversion_preserves_satisfaction(&plan, u)
2206 });
2207 }
2208
2209 #[test]
2210 fn test_zkc_conversion_preserves_committed_order() {
2211 let (valued, _) = zk::build_with_values(|ctx| {
2212 let a = zk::Var::witness(ctx, |_| F::from(1u64));
2213 let b = zk::Var::witness(ctx, |_| F::from(2u64));
2214 let c = a * &b;
2215 c.assert_eq(&zk::Var::constant(ctx, F::from(2u64)));
2216 Vec::new()
2217 });
2218 let (circuit, witness) = super::zkc_to_circuit_and_witness(
2219 Some(&mut test_rng()),
2220 valued,
2221 &[
2222 zk::CircuitIdx::Witness(1),
2223 zk::CircuitIdx::Witness(0),
2224 zk::CircuitIdx::Witness(1),
2225 ],
2226 );
2227 assert!(witness.is_satisfied(&circuit));
2228 assert_eq!(
2229 witness.values,
2230 vec![F::from(2u64), F::from(1u64), F::from(2u64)]
2231 );
2232 }
2233
2234 #[test]
2235 fn test_zkc_conversion_add_doubling_chain() {
2236 const DEPTH: usize = 64;
2239 let mut expected = F::one();
2240 for _ in 0..DEPTH {
2241 expected = expected + &expected;
2242 }
2243 let (valued, _) = zk::build_with_values(|ctx| {
2244 let mut x = zk::Var::witness(ctx, |_| F::one());
2245 for _ in 0..DEPTH {
2246 x = x.clone() + &x;
2247 }
2248 x.assert_eq(&zk::Var::constant(ctx, expected));
2249 Vec::new()
2250 });
2251 let (circuit, witness) = super::zkc_to_circuit_and_witness(
2252 Some(&mut test_rng()),
2253 valued,
2254 &[zk::CircuitIdx::Witness(0)],
2255 );
2256 assert!(witness.is_satisfied(&circuit));
2257 }
2258
2259 #[test]
2260 fn test_random_r1cs_minifuzz() {
2261 const N: usize = 2;
2262 const M: usize = 4;
2263
2264 minifuzz::test(|u| {
2265 let a = u.arbitrary::<[[F; N]; M]>()?;
2266 let b = u.arbitrary::<[[F; N]; M]>()?;
2267 let c = u.arbitrary::<[[F; N]; M]>()?;
2268 let z = u.arbitrary::<[F; N]>()?;
2269 let mut left = [F::zero(); M];
2270 let mut right = [F::zero(); M];
2271 let mut satisfied = true;
2272 for i in 0..M {
2273 let mut acc = F::zero();
2274 for j in 0..N {
2275 left[i] += &(a[i][j] * &z[j]);
2276 right[i] += &(b[i][j] * &z[j]);
2277 acc += &(c[i][j] * &z[j]);
2278 }
2279 satisfied = satisfied && acc == left[i] * &right[i];
2280 }
2281 let mut k = 0;
2282 let mut weights = SparseMatrix::default();
2283
2284 for i in 0..M {
2286 weights[(k, 1 + N + i)] = -F::one();
2287 for j in 0..N {
2288 weights[(k, 1 + j)] = a[i][j];
2289 }
2290 k += 1;
2291 }
2292 for i in 0..M {
2294 weights[(k, 1 + N + M + i)] = -F::one();
2295 for j in 0..N {
2296 weights[(k, 1 + j)] = b[i][j];
2297 }
2298 k += 1;
2299 }
2300 for i in 0..M {
2302 weights[(k, 1 + N + 2 * M + i)] = -F::one();
2303 for j in 0..N {
2304 weights[(k, 1 + j)] = c[i][j];
2305 }
2306 k += 1;
2307 }
2308 assert_eq!(
2309 satisfied,
2310 Circuit::new(N, weights)
2311 .expect("should be able to make circuit")
2312 .is_satisfied(&z, &left, &right)
2313 );
2314 Ok(())
2315 });
2316 }
2317
2318 #[test]
2319 fn test_setup_roundtrip() {
2320 let setup = fuzz::test_setup();
2321 let encoded = setup.encode();
2322 let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.ipa.g().len(), ()))
2323 .expect("setup should decode with its own length bound");
2324 assert!(setup == &decoded);
2325 assert_eq!(decoded.encode(), encoded);
2326 }
2327
2328 #[test]
2329 fn test_fuzz() {
2330 minifuzz::test(|u| {
2331 u.arbitrary::<fuzz::Plan>()?.run(u)?;
2332 Ok(())
2333 });
2334 }
2335
2336 #[test]
2343 fn verify_rejects_over_long_claim() {
2344 let setup = fuzz::test_setup();
2345
2346 let a = F::one();
2349 let b = F::one();
2350 let c = F::one();
2351 let x = F::from(3u8);
2352 let y = a * &x * &x + &(b * &x) + &c;
2353 let circuit = fuzz::quadratic_circuit(a, b, c);
2354
2355 let witness = Witness::new(
2356 vec![x, y],
2357 vec![F::zero(), F::zero()],
2358 vec![x],
2359 vec![x],
2360 vec![x * &x],
2361 )
2362 .expect("witness vector lengths must be consistent");
2363
2364 let mut claim = witness.claim(setup);
2367 claim.commitments.push(G::generator() * &F::from(9u8));
2368
2369 let mut rng = test_rng();
2370 let mut prover_transcript = Transcript::new(b"verify-rejects-over-long-claim", Version::V1);
2371 let proof = prove(
2372 &mut rng,
2373 &mut prover_transcript,
2374 setup,
2375 &circuit,
2376 &claim,
2377 &witness,
2378 &Sequential,
2379 )
2380 .expect("prove still produces a proof against the malformed claim");
2381
2382 let mut verifier_transcript =
2383 Transcript::new(b"verify-rejects-over-long-claim", Version::V1);
2384 let verified = setup.eval(
2385 |vs| {
2386 verify(
2387 &mut rng,
2388 &mut verifier_transcript,
2389 vs,
2390 &circuit,
2391 &claim,
2392 proof,
2393 &Sequential,
2394 )
2395 },
2396 &Sequential,
2397 );
2398 assert!(
2399 verified.is_none(),
2400 "verify must reject a claim whose commitment arity does not match the circuit"
2401 );
2402 }
2403}