Skip to main content

commonware_cryptography/zk/bulletproofs/
circuit.rs

1//! This module provides a Bulletproofs circuit proof built on top of the
2//! [inner product argument](super::ipa).
3//!
4//! # Background
5//!
6//! We start with Pedersen commitments to committed values `v_i`:
7//!
8//! `V_i = v_i * B + v_blind_i * B_blind`.
9//!
10//! A [`Circuit`] then constrains these committed values using:
11//!
12//! - multiplication gates `l_i * r_i = o_i`, and
13//! - linear constraints over the concatenated vector
14//!   `1 | committed values | left wires | right wires | output wires`.
15//!
16//! Concretely, the circuit stores a sparse weight matrix `W`, where each row
17//! enforces a linear relation over that concatenated vector.
18//!
19//! Given a set of commitments, the prover wants to convince the verifier that
20//! the committed values satisfy the circuit, without revealing the committed
21//! values, their Pedersen blindings, or the internal wire values.
22//!
23//! # Usage
24//!
25//! First construct a [`Setup`]. This wraps an IPA [`super::ipa::Setup`] and
26//! adds two generators used for Pedersen commitments.
27//!
28//! Next, describe the constraint system as a [`SparseMatrix`], and turn it
29//! into a [`Circuit`] with [`Circuit::new`]. The circuit fixes the column
30//! layout to:
31//!
32//! `1 | committed values | left wires | right wires | output wires`
33//!
34//! A prover-side assignment is represented by [`Witness`]. [`Witness::new`]
35//! checks that the value vectors have compatible lengths, and
36//! [`Witness::claim`] derives the public [`Claim`] for those committed values.
37//!
38//! Given a [`Setup`], [`Circuit`], [`Claim`], and [`Witness`], create a
39//! [`Proof`] with [`prove`].
40//!
41//! The proof is bound to the current [`Transcript`] state. The verifier must
42//! replay the same transcript history before calling [`verify`].
43//!
44//! Use [`verify`] to construct the returned [`Synthetic`] verification equation
45//! and [`Setup::eval`] to evaluate it against the concrete setup.
46//!
47//! ## Example
48//!
49//! ```rust
50//! # use commonware_cryptography::{
51//! #     bls12381::primitives::group::{G1, Scalar},
52//! #     transcript::Transcript,
53//! #     zk::bulletproofs::{
54//! #         circuit::{prove, verify, Circuit, Setup, SparseMatrix, Witness},
55//! #         ipa,
56//! #     },
57//! # };
58//! # use commonware_math::algebra::{Additive, CryptoGroup, Random, Ring};
59//! # use commonware_parallel::Sequential;
60//! # use commonware_utils::test_rng;
61//! # type F = Scalar;
62//! # type G = G1;
63//! # let generators: [G; 5] =
64//! #     core::array::from_fn(|i| G::generator() * &F::from(i as u64 + 1));
65//!
66//! // This is a toy setup for documentation. Real generators must not have
67//! // known discrete-log relationships.
68//! let setup = Setup::new(
69//!     ipa::Setup::new(
70//!         generators[0].clone(),
71//!         [(generators[1].clone(), generators[2].clone())],
72//!     ),
73//!     generators[3].clone(),
74//!     generators[4].clone(),
75//! );
76//!
77//! // Build a one-gate circuit proving that the committed values are 3 and 4,
78//! // with a product wire fixed to 12.
79//! let mut weights = SparseMatrix::default();
80//! weights[(0, 1)] = F::one();
81//! weights[(0, 3)] = -F::one();
82//! weights[(1, 2)] = F::one();
83//! weights[(1, 4)] = -F::one();
84//! weights[(2, 0)] = F::from(12u64);
85//! weights[(2, 5)] = -F::one();
86//! let circuit = Circuit::new(2, weights).expect("matrix width should fit");
87//!
88//! let mut prover_rng = test_rng();
89//! let witness = Witness::new(
90//!     vec![F::from(3u64), F::from(4u64)],
91//!     vec![F::random(&mut prover_rng), F::random(&mut prover_rng)],
92//!     vec![F::from(3u64)],
93//!     vec![F::from(4u64)],
94//!     vec![F::from(12u64)],
95//! )
96//! .expect("witness lengths should match");
97//! let claim = witness.claim(&setup);
98//!
99//! let mut prover_transcript = Transcript::new(b"circuit-example");
100//! prover_transcript.commit(b"context".as_slice());
101//! let proof = prove(
102//!     &mut prover_rng,
103//!     &mut prover_transcript,
104//!     &setup,
105//!     &circuit,
106//!     &claim,
107//!     &witness,
108//!     &Sequential,
109//! )
110//! .expect("witness should satisfy the claim and circuit");
111//!
112//! let mut verifier_rng = test_rng();
113//! let mut verifier_transcript = Transcript::new(b"circuit-example");
114//! verifier_transcript.commit(b"context".as_slice());
115//! let valid = setup
116//!     .eval(
117//!         |vs| verify(&mut verifier_rng, &mut verifier_transcript, vs, &circuit, &claim, proof, &Sequential),
118//!         &Sequential,
119//!     )
120//!     .map(|g| g == G::zero())
121//!     .unwrap_or(false);
122//! assert!(valid);
123//! ```
124//!
125//! # References
126//!
127//! The [Dalek crate notes](https://doc-internal.dalek.rs/bulletproofs/notes/inner_product_proof/index.html)
128//! were useful prior art when implementing and documenting the IPA layer used by
129//! this module.
130//!
131//! The original [Bulletproofs paper](https://eprint.iacr.org/2017/1066) and the
132//! implementation notes from the IPA module are also useful background for this file.
133
134use 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
149/// A sparse matrix indexed by `(row, column)`.
150///
151/// Missing entries are treated as 0.
152pub struct SparseMatrix<F> {
153    width: usize,
154    height: usize,
155    weights: BTreeMap<(usize, usize), F>,
156    /// This exists so that we can return a reference when indexing.
157    zero: F,
158}
159
160impl<F> SparseMatrix<F> {
161    /// The width of this matrix.
162    ///
163    /// This is determined solely by the highest column with a non-zero entry.
164    pub const fn width(&self) -> usize {
165        self.width
166    }
167
168    /// The height of this matrix.
169    ///
170    /// This is determined solely by the highest row with a non-zero entry.
171    pub const fn height(&self) -> usize {
172        self.height
173    }
174
175    /// Pad this matrix to have at least these dimensions.
176    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
250/// A circuit describing the constraints the prover must satisfy.
251pub 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    /// Create a new circuit from a committed-value count and a weight matrix.
278    ///
279    /// The circuit enforces:
280    ///
281    /// - `l_i * r_i = o_i`, and
282    /// - one linear constraint per row of `weights`.
283    ///
284    /// The columns are interpreted as:
285    ///
286    /// `1 | committed values | left wires | right wires | output wires`
287    ///
288    /// This returns `None` if the matrix width is incompatible with that layout.
289    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    /// Number of left/right/output internal wires.
303    pub const fn internal_vars(&self) -> usize {
304        self.internal_vars
305    }
306
307    /// Number of committed values.
308    pub const fn committed_vars(&self) -> usize {
309        self.committed_vars
310    }
311
312    /// Checks whether a certain assignment to committed variables satisfies this circuit.
313    ///
314    /// This returns false if the assignment has the wrong length, rather than
315    /// implicitly truncating or padding the assignment.
316    #[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
349/// Conversion from `zk::Circuit`s into bulletproofs circuits.
350///
351/// The conversion linearizes: every circuit value is expressed as a linear
352/// combination of columns of the weight matrix. Additions and
353/// multiplications by constants combine linearly; each multiplication of two
354/// non-constant values becomes a multiplication gate, whose output wire is a
355/// new column. Each assertion `(l, r)` then becomes a matrix row enforcing
356/// `l - r = 0`.
357mod 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    /// A column of the bulletproofs weight matrix.
365    ///
366    /// The column layout is `1 | committed values | left wires | right wires
367    /// | output wires`. `Witness` is a provisional location for a witness
368    /// not yet pinned to a column; `location_to_col` resolves it through
369    /// `witness_locations`.
370    #[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    /// A circuit value, expressed as a linear combination of locations.
381    ///
382    /// `Location` is a single location with weight one, and `Constant` is a
383    /// multiple of the constant `1` column. `General` holds arbitrary
384    /// weighted sums, keyed by location so that duplicates merge.
385    #[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        /// Multiply two combinations, if at least one of them is a constant.
394        ///
395        /// Returns `None` otherwise, in which case the product needs a
396        /// multiplication gate.
397        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        /// Add two linear combinations, merging the weights of duplicate
421        /// locations so the result stays bounded by the number of distinct
422        /// locations rather than the number of terms.
423        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            // Merging adjacent duplicates above leaves every location unique.
438            Self::General(Map::try_from(merged).expect("merged locations should be unique"))
439        }
440
441        /// Iterate over the `(weight, location)` terms of this combination.
442        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        /// The witness index, if this combination is a single witness not
457        /// yet pinned to a column.
458        const fn witness(&self) -> Option<usize> {
459            match self {
460                Self::Location(Location::Witness(w)) => Some(*w),
461                _ => None,
462            }
463        }
464    }
465
466    /// State for converting a `zk::Circuit` into a bulletproofs circuit.
467    ///
468    /// `circuit` performs the verifier-side conversion, and
469    /// `circuit_and_witness` the prover-side one. Both run the same
470    /// conversion, so they produce the same circuit.
471    pub struct ZKCConverter<F> {
472        /// Scratch stack of indices waiting to be linearized.
473        linearize_queue: Vec<zk::CircuitIdx>,
474        /// The linear combination computed for each visited index.
475        linearize_cache: BTreeMap<zk::CircuitIdx, LinComb<F>>,
476        /// The assertions of the source circuit.
477        assertions: Vec<(zk::CircuitIdx, zk::CircuitIdx)>,
478        /// Assertions pinning a value to a specific location, used to bind
479        /// gate wires and committed values.
480        extra_assertions: Vec<(zk::CircuitIdx, Location)>,
481        /// The location assigned to each witness.
482        witness_locations: BTreeMap<usize, Location>,
483        /// The indices to commit, in caller order.
484        committed_indices: Vec<zk::CircuitIdx>,
485        /// The committed slot for each committed index (the first slot, if
486        /// the index is repeated).
487        committed_positions: BTreeMap<zk::CircuitIdx, usize>,
488        /// One entry per multiplication gate: the indices whose values feed
489        /// the left and right wires, and the index whose value is the
490        /// output wire. Padding gates holding leftover witnesses leave the
491        /// right wire, and then the output, unset.
492        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        /// Create a converter committing the values at `committed_indices`,
501        /// in order.
502        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                // Duplicates keep the first position, so every occurrence of
506                // the index resolves to the same column, and the extra
507                // assertions constrain the remaining slots to match it.
508                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        /// Convert a circuit without an assignment (verifier mode).
523        pub fn circuit(mut self, zkc: zk::Circuit<F>) -> super::Circuit<F> {
524            self.populate(&zkc);
525            self.reckon_circuit()
526        }
527
528        /// Convert a circuit and its assignment (prover mode).
529        ///
530        /// Without a `blinding_rng`, the blinding factors are zero.
531        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        /// Linearize every assertion and assign every witness a location.
581        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            // Now, assign a non-witness location to any discovered witnesses.
588            {
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            // Add extra assertions for each committed index, linearizing so
609            // that committed values not referenced by any assertion still
610            // resolve to a location.
611            let committed = self.committed_indices.clone();
612            for (i, c_pos) in committed.into_iter().enumerate() {
613                self.linearize(zkc, c_pos);
614                // If a committed witness still resolves to its own `Committed`
615                // column, its binding row would collapse to
616                // `Committed(i) - Committed(i) = 0`. That happens whenever no
617                // non-tautological constraint references it (it is used
618                // nowhere, or only in `assert(w == w)`), leaving the commitment
619                // with an all-zero column: a prover could then swap it for an
620                // arbitrary group element and still verify. Anchor it to a fresh
621                // padding wire and redirect every reference there, so its
622                // binding row reads `Left(k) - Committed(i) = 0`, tying the
623                // column to a value genuinely committed in `M`. A wire-anchored
624                // entry (e.g. a duplicate of an already-anchored index) is left
625                // as is.
626                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        /// Resolve a location to its column in the weight matrix.
640        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        /// Build the bulletproofs circuit from the accumulated state.
666        ///
667        /// Each assertion contributes a row applying the left side's
668        /// combination positively and the right side's negatively, so the
669        /// row reads `l - r = 0`.
670        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        /// Assign `loc` to `witness` unless it already has a non-provisional
716        /// location, returning the location in effect.
717        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        /// Compute and cache the linear combination for `i` and everything
730        /// it depends on.
731        ///
732        /// Uses an explicit work stack rather than recursion, so deep
733        /// circuits cannot overflow the call stack.
734        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                                    // Borrow checker shenanigans
788                                    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
808/// Convert a ZK circuit into a bulletproofs circuit, treating the witness
809/// positions named by `committed_indices` as committed values.
810///
811/// Committed values keep the order of `committed_indices`. A duplicate index
812/// produces a separate commitment to the same value, constrained to match.
813/// Every index must name a witness allocated by the circuit; other indices
814/// are unsupported, and may panic or leave a commitment unconstrained.
815///
816/// `committed_indices` must match what the prover passed to
817/// [`zkc_to_circuit_and_witness`], otherwise the resulting [`Circuit`] will
818/// not match the prover's and proofs against it will fail.
819pub 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
826/// Convert a ZK circuit and witness assignment into a bulletproofs circuit and
827/// witness.
828///
829/// `committed_indices` names the witness positions that should become
830/// bulletproofs committed values, in the order they should appear as
831/// committed values. A duplicate index produces a separate commitment to the
832/// same value, constrained to match. Every index must name a witness
833/// allocated by the circuit; other indices are unsupported, and may panic
834/// or leave a commitment unconstrained.
835pub 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/// Generators used by the circuit proof system.
844///
845/// This wraps the underlying IPA setup and adds two Pedersen generators used
846/// for commitments to committed values and blindings.
847#[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    /// Create a new [`Setup`] from an [`ipa::Setup`] and two Pedersen generators.
856    ///
857    /// You MUST ensure that all generators are unique.
858    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    /// Check if this setup supports claims of a given length.
875    pub const fn supports(&self, lg_len: u8) -> bool {
876        self.ipa.supports(lg_len)
877    }
878
879    /// Construct a [`Setup`] of size `2^lg_len`, deterministically deriving
880    /// the IPA generators, the product generator, and the blinding generator
881    /// from `domain_separator` via [`HashToGroup`]. The caller supplies the
882    /// `value_generator` so external commitments (e.g. `value * G1::generator()`)
883    /// can line up with the Pedersen commitments produced by
884    /// [`Witness::claim`].
885    ///
886    /// Each hashed generator is derived with a unique label, so the
887    /// discrete-log relations between them are unknown (assuming a properly
888    /// modelled hash-to-curve). The caller is responsible for ensuring the
889    /// supplied `value_generator` has unknown discrete log relative to the
890    /// blinding generator (e.g. by using a system-fixed generator distinct
891    /// from any hashed point).
892    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    /// Build the virtual setup and the flat array of concrete generators
920    /// used to evaluate any [`Synthetic`] produced against this setup.
921    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    /// Build a virtual setup, call `f` to obtain a verification equation,
948    /// and evaluate it against the concrete generators in `self`.
949    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    /// Build a virtual setup, call `f` to obtain a list of per-item
962    /// verification equations, and check each of them in a way that batches
963    /// MSMs as much as possible.
964    ///
965    /// The strategy is:
966    ///
967    /// 1. Pre-scale every per-item equation by an independent random scalar
968    ///    so that scalar zero-ness is preserved with overwhelming
969    ///    probability and any subset sum is sound on its own.
970    /// 2. Sum every (scaled) equation into a single [`Synthetic`] and
971    ///    evaluate it with **one** MSM. If the result is the group
972    ///    identity, every item is valid.
973    /// 3. Otherwise, recursively split the failing range in half and
974    ///    re-evaluate the sum on each half (still one MSM per check). The
975    ///    recursion bottoms out at a single item, at which point any
976    ///    remaining failure is attributed to that item.
977    ///
978    /// This costs one MSM in the all-valid case and `O(k log n)` MSMs when
979    /// `k` items are invalid (vs. `n` MSMs for naive per-item checking).
980    ///
981    /// Returning `None` from `f` indicates that the whole batch is malformed
982    /// and produces an outer `None`. Individual `None` entries within the
983    /// returned `Vec` indicate that the corresponding item is structurally
984    /// invalid; they are reported as `false` in the result without ever
985    /// being included in any subset sum.
986    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        // Pre-scale each present synthetic by an independent random scalar.
1000        // None entries stay None; they are reported as `false` and never
1001        // contribute to any subset sum.
1002        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        // Indices of items eligible for batched checking.
1008        let active: Vec<usize> = (0..n).filter(|&i| scaled[i].is_some()).collect();
1009
1010        // Sum the scaled synthetics for `range` and evaluate to a single MSM.
1011        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        // Iterative DFS over contiguous index ranges. A range that checks
1020        // out marks every contained item as valid; a failing range of
1021        // length > 1 splits in half; a failing range of length 1 leaves
1022        // its (single) item marked invalid.
1023        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/// A prover-side assignment for a circuit proof.
1075///
1076/// This contains the committed values, their Pedersen blindings, and the
1077/// internal left, right, and output wire values.
1078#[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    /// Create a new witness, given all committed values, and internal values.
1089    ///
1090    /// This is a very low level method, with the only safety guard being to check
1091    /// that certain vectors have matching lengths. Beyond that, we don't check
1092    /// that the values satisfy a circuit relationship, or match the commitments.
1093    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    /// Check whether this witness's wires satisfy the given [`Circuit`].
1120    ///
1121    /// Useful as a debugging aid: if this returns `false`, the prover and
1122    /// circuit are inconsistent and any [`prove`] result will not verify.
1123    #[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    /// Create the public claim corresponding to this witness for the given setup.
1132    ///
1133    /// The resulting claim contains Pedersen commitments to the witness's
1134    /// committed values and blindings.
1135    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
1150/// The public claim for the protocol.
1151///
1152/// The claim consists of Pedersen commitments to values, which the prover claims
1153/// satisfy a [`Circuit`].
1154///
1155/// The claim does not contain the [`Circuit`] itself, so that the verifier is
1156/// in control of what properties they want the committed values to satisfy.
1157pub 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/// A proof demonstrating knowledge of a [`Witness`] satisfying a [`Claim`] relative
1174/// to a [`Circuit`].
1175///
1176/// See [`prove`] and [`verify`].
1177#[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    /// `(max_len, (g_cfg, f_cfg))` where `max_len` bounds the IPA round count.
1227    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
1259/// Prove that a given [`Witness`] satisfies a [`Circuit`] and matches a [`Claim`].
1260///
1261/// The proof is bound to the transcript state at the time of the call, so the
1262/// verifier must replay the same transcript history before verification.
1263///
1264/// This returns `None` if the setup does not support the circuit size, if the
1265/// witness lengths are inconsistent with the circuit, or if the claim does not
1266/// match the witness.
1267pub 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    // To set the stage, we're trying to convince the verifier that:
1277    //
1278    //   - we know v_i, ~v_i, l_i, r_i, o_i such that...
1279    //   - v_i B + ~v_i ~B = V_i,
1280    //   - l_i r_i = o_i,
1281    //   - c_i + <Θ_ij, v_j> + <Λ_ij, l_j> + <Ρ_ij, r_j> + <Ω_ij, o_j> = 0.
1282    //
1283    // Before we get back any challenges from the verifier, we need to commit to
1284    // the circuit, our claim, and the internal variables we're using. We create a commitment:
1285    //
1286    //   M := <l_i, G_i> + <r_i, H_i> + m ~B
1287    //   O := <o_i, G_i> + ~o ~B
1288    //
1289    // we'll also be introducing some blinding factors ~l_i, ~r_i later, and we need
1290    // to commit to these now as well:
1291    //
1292    //   ~M := <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1293    //
1294    // After sending all of these to the verifier, we get:
1295    // y, and z, which we use to reduce the constraints to:
1296    //
1297    //   <y^i, l_i r_i - o_i> +
1298    //   <z z^i, c_i + <Θ_ij, v_j> + <Λ_ij, l_j> + <Ρ_ij, r_j> + <Ω_ij, o_j>> = 0
1299    //
1300    // (By y^i, we mean a vector whose ith entry is y to the power of i. For small fields,
1301    // generating more challenges is needed instead, but for large fields, using powers lets us
1302    // sample less randomness.)
1303    //
1304    // At this point, it's convenient to fold these challenges into the weights:
1305    //
1306    //   θ_j := <Θ_ij, z z^i>
1307    //   λ_j := <Λ_ij, z z^i>
1308    //   ρ_j := <Ρ_ij, z z^i>
1309    //   ω_j := <Ω_ij, z z^i>
1310    //   κ := <c_i, z z^i>
1311    //
1312    // giving us:
1313    //
1314    //   <y^i, l_i r_i - o_i> + κ + <θ_i, v_i> + <λ_i, l_i> + <ρ_i, r_i> + <ω_i, o_i> = 0
1315    //
1316    // It's useful to have the terms concerning the committed variables on one side,
1317    // and the internal variables on the other:
1318    //
1319    //   -κ - <θ_i, v_i> = <y^i, l_i r_i - o_i> + <λ_i, l_i> + <ρ_i, r_i> + <ω_i, o_i>
1320    //
1321    // next, merge the terms with o_i:
1322    //
1323    //  ... = <y^i, l_i r_i> + ... + <ω_i - y^i, o_i>
1324    //
1325    // next, we can move one part of the l_i r_i term to the other side:
1326    //
1327    //   ... = <y^i r_i, l_i> + ...
1328    //
1329    // then, we can create another y^i r_i term:
1330    //
1331    //   ... = ... + <y^-i ρ_i, y^i r_i> + ...
1332    //
1333    // merging these terms we get:
1334    //
1335    //   -κ - <θ_i, v_i> = <l_i + y^-i ρ_i, y^i r_i> + <λ_i, l_i> + <ω_i - y^i, o_i>
1336    //
1337    // if we define:
1338    //
1339    //   δ(y, z) := <y^-i ρ_i, λ_i>
1340    //
1341    // we can add this to both sides, and merge the λ_i terms, giving us:
1342    //
1343    //  -κ - <θ_i, v_i> + δ(y, z) =
1344    //  <l_i + y^-i ρ_i, y^i r_i> + <l_i + y^-i ρ_i, λ_i> + <ω_i - y^i, o_i> =
1345    //  <l_i + y^-i ρ_i, y^i r_i + λ_i> + <ω_i - y^i, o_i>
1346    //
1347    // Now, we deploy a trick, in order to turn a statement about a sum:
1348    //
1349    //   <a_i, b_i> + <c_i, d_i>
1350    //
1351    // into a single inner product. The trick is that if we create polynomials:
1352    //
1353    //   f_i(X) := a_i X + c_i X^2
1354    //   g_i(X) := b_i X + d_i
1355    //
1356    // then the 2nd degree of <f_i(X), g_i(X)> is <a_i, b_i> + <c_i, d_i>.
1357    //
1358    // So, we can check that:
1359    //
1360    //   t X^2 = <f_i(X), g_i(X)>
1361    //
1362    // as polynomials. To check equality of polynomials, we can commit to them,
1363    // and then have the verifier send us a random evaluation point.
1364    //
1365    // Let's apply that to our situation.
1366    //
1367    //   f_i(X) := (l_i + y^-i ρ_i) X + o_i X^2
1368    //   g_i(X) := (y^i r_i + λ_i) X + (ω_i - y^i)
1369    //   t(X) := <f_i(X), g_i(X)>
1370    //   deg2(t(X)) = -κ - <θ_i, v_i> + δ(y, z)
1371    //
1372    // Our goal at this point is to convince the verifier that:
1373    //
1374    //   - deg2(t(X)) = -κ - <θ_i, v_i> + δ(y, z),
1375    //   - f_i(X) and g_i(X) are correctly constructed,
1376    //   - t(X) = <f_i(X), g_i(X)>.
1377    //
1378    // We want to make sure that our proof is still zero-knowledge, so we can't just
1379    // send a commitment to the polynomial as is, because it leaks information about
1380    // the l_i, r_i, and o_i values. To get around this, we introduce blinding factors
1381    // ~l_i, ~r_i:
1382    //
1383    //   f_i(X) := ((l_i + ~l_i X^2) + y^-i ρ_i) X + o_i X^2
1384    //   g_i(X) := (y^i (r_i + ~r_i X^2) + λ_i) X + (ω_i - y^i)
1385    //
1386    // we use a factor of X^2 so that this blinding doesn't interfere with the
1387    // second degree of <f_i(X), g_i(X)>. When the verifier sees f_i(x) and g_i(x)
1388    // for a random challenge point, they will have a masking factor of ~l_i x^3
1389    // (respectively, y^i ~r_i x^3), hiding things completely.
1390    //
1391    // Expanding this out, we get:
1392    //
1393    //   t(X) := <f_i(X), g_i(X)> =
1394    //   <l_i + y^-i ρ_i, ω_i - y^i> X +
1395    //   (<l_i + y^-i ρ_i, y^i r_i + λ_i> + <o_i, ω_i - y^i>) X^2 +
1396    //   (<~l_i, ω_i - y^i> + <o_i, y^i r_i + λ_i>) X^3 +
1397    //   (<~l_i, y^i r_i + λ_i> + <l_i + y^-i ρ_i, y^i ~r_i>) X^4 +
1398    //   <o_i, y^i ~r_i> X^5 +
1399    //   <~l_i, y^i ~r_i> X^6
1400    //
1401    // thus, we can create commitments T_1, T_3, T_4, T_5, T_6 to these elements,
1402    // (skipping the X^2 factor), using blinding factors ~t_i.
1403    //
1404    // Then, for a random challenge, x, the verifier can check that the second degree is correct:
1405    //
1406    //  t(x) B + ~t(x) ~B =?
1407    //  (-κ + δ(y, z)) x^2 B - x^2 <θ_i, V_i> + Σ_{i != 2} x^i T_i
1408    //
1409    // for ~t(x), we use the synthetic blinding factors ~t_i for x^1, x^3, ...
1410    // and for x^2, we use -<θ_i, ~v_i>, so that the equation above works.
1411    //
1412    // The right hand side is checking the second degree in the exponent, behind
1413    // the Pedersen commitments, and the left hand side is our opening of the polynomial,
1414    // at a random point.
1415    //
1416    // Before getting this challenge, we also want to provide the necessary commitments
1417    // to f_i(X) and g_i(X) as well, so that those can be checked.
1418    //
1419    // Eventually, we want to prove the inner product <f_i(x), g_i(x)>, and the IPA
1420    // protocol expects to see <f_i(x), G_i> + <g_i(x), H_i>. Expanding that, out,
1421    // using the indeterminate X (rather than the challenge x), we get:
1422    //
1423    //   <f_i(X), G_i> = <l_i + y^-i ρ_i, G_i> X + <o_i, G_i> X^2 + <~l_i, G_i> X^3
1424    //   <g_i(X), H_i> = <ω_i - y^i, H_i> + <y^i r_i + λ_i, H_i> X + <y^i ~r_i, H_i> X^3
1425    //
1426    // The natural commitments involve grouping things by coefficient, and by public
1427    // vs secret values:
1428    //
1429    //   P_0 := <ω_i - y^i, H_i>
1430    //   P_1 := <y^-i ρ_i, G_i> + <λ_i, H_i>
1431    //   S_1 := <l_i, G_i> + <y^i r_i, H_i>
1432    //   S_2 := <o_i, G_i>
1433    //   S_3 := <~l_i, G_i> + <y^i ~r_i, H_i>
1434    //
1435    // Recall that we've already sent the verifier:
1436    //
1437    //   M := <l_i, G_i> + <r_i, H_i> + m ~B
1438    //   O := <o_i, G_i> + ~o ~B
1439    //  ~M := <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1440    //
1441    // It seems like we're stuck here, because <r_i, H_i> doesn't match the <y^i r_i, H_i>
1442    // that we need inside of S_1. However, the IPA already lets us treat the right-side
1443    // basis as y^-i H_i by setting claim.y = y^-1. In terms of the original H_i that this
1444    // implementation uses in its MSMs, the public pieces become:
1445    //
1446    //   P_0 = <y^-i ω_i - 1, H_i>
1447    //   P_1 = <y^-i ρ_i, G_i> + <y^-i λ_i, H_i>
1448    //   M   = <l_i, G_i> + <r_i, H_i> + m ~B
1449    //   O   = <o_i, G_i> + ~o ~B
1450    //  ~M   = <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1451    //
1452    // but, the bottom three are equal to:
1453    //
1454    //   M = S_1 +  m ~B
1455    //   O = S_2 + ~o ~B
1456    //  ~M = S_3 + ~m ~B
1457    //
1458    // Thus, we can reveal ~s := x m + x^2 ~o + x^3 ~m, and have the verifier calculate
1459    //
1460    //   P := -~s ~B + P_0 + x (P_1 + M) + x^2 O + x^3 ~M
1461    //      = <f_i(x), G_i> + <g_i(x), y^-i H_i>
1462    //
1463    // (Rather than the verifier calculating this, the prover can provide it, and the verifier
1464    // can check this equation. This turns it into an MSM check, which can be more efficiently
1465    // combined with other such checks).
1466    //
1467    // Finally, we run the IPA protocol, using t(x) as the claimed inner product,
1468    // and P as the commitment to the vectors, and G_i, H'_i as the generators
1469    // for this commitment.
1470    //
1471    // Concretely, we reuse the ordinary IPA setup and set ipa_claim.y = y^-1.
1472    // This keeps the right-side basis change inside the IPA, while the public MSM
1473    // checks above stay written against the original H_i.
1474    //
1475    // # Padding
1476    //
1477    // The IPA protocol requires the input vectors to be padded to a power of 2.
1478    // To do this, we'll pad the l_i, r_i, ~l_i, ~r_i with 0s. This forces the
1479    // o_i to be padded with 0 as well. In order to explicitly not consider these
1480    // values, we make sure that the weights are padded with columns of 0s.
1481    // Because we compress the weight matrices into vectors by taking a combination
1482    // of rows, we can pad the resulting vectors with 0s.
1483    //
1484    // Looking at t(X), the value doesn't change with the padding, because we always
1485    // have a zero value on one side of each inner product for the new indices.
1486    //
1487    // P_0 on the other hand, will end up with some extra -1 values we'll have
1488    // to take into account. Because this is the only changed value, we can handle
1489    // this one as a special case.
1490    //
1491    // Now, let's write some Rust.
1492    //
1493    // First, let's commit to our internal variables, and to our masks:
1494    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    // Now, commit to all the this information.
1514    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    // We cache a few quantities, which we'll need for MSMs later anyways.
1558    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    // t_1, t_2, t_3, t_4, t_5, t_6
1605    let t = {
1606        let mut t = std::array::from_fn::<_, 6, _>(|_| F::zero());
1607        // t_1
1608        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_2
1612        t[1] = delta_y_z - &kappa - &<F as Space<F>>::msm(&theta, &witness.values, strategy);
1613        // t_3
1614        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        // t_4
1619        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_5
1624        t[4] = <F as Space<F>>::msm(&witness.out, &y_r_tilde, strategy);
1625        // t_6
1626        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        // Skip the second element
1638        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    // The IPA generators may be larger than `padded_vars` for setups that
1643    // support multiple circuit sizes. Restrict to the prefix actually used.
1644    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    // Now, we can commit the t commitments, along with the secret commitments.
1654    // The public commitments will be recomputed by the verifier.
1655    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
1705/// Construct the verification equation for a circuit proof.
1706///
1707/// The returned [`Synthetic`] should evaluate to zero for a correct proof.
1708/// Use [`Setup::eval`] to create the virtual setup and evaluate the result.
1709///
1710/// The extra randomness is used to compress the circuit-specific checks into a
1711/// single equation before combining them with the inner product argument.
1712pub 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    // Reject malformed claims whose commitment arity does not match the
1733    // circuit. Without this check, an over-long claim could verify because
1734    // commitments past `committed_vars` are bound to the transcript but
1735    // never enter the algebraic commitment relation.
1736    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    // We cache a few quantities, which we'll need for MSMs later anyways.
1783    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        // Match the prover: only the first `padded_vars` generators are used.
1840        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    /// Number of IPA generator pairs in the test setup. Large enough to prove
1886    /// and verify any circuit produced by the fuzz plans, whose op count
1887    /// bounds `internal_vars` well below this.
1888    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        // Bind l_0 = x.
1918        weights[(0, 1)] = F::one();
1919        weights[(0, 3)] = -F::one();
1920
1921        // Bind r_0 = x.
1922        weights[(1, 1)] = F::one();
1923        weights[(1, 4)] = -F::one();
1924
1925        // Enforce y = a x^2 + b x + c.
1926        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    /// A quadratic circuit paired with a witness that may or may not satisfy it.
1935    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    /// Prove `claim` against `circuit` with `witness`, then verify, returning
2007    /// whether verification accepted. A `prove` failure counts as rejection.
2008    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    /// Check that converting a ZK circuit to a bulletproofs circuit and
2048    /// witness preserves satisfaction, committing a random subset of the
2049    /// witnesses.
2050    ///
2051    /// For satisfied circuits this also runs a full prove/verify roundtrip and
2052    /// checks that tampering with a committed commitment is rejected. The
2053    /// latter is the binding property: every committed value (including a
2054    /// witness constrained by nothing) must enter the verification equation
2055    /// with a nonzero coefficient, so it cannot be swapped for an arbitrary
2056    /// group element.
2057    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        // A shared Add chain (x = x + x, repeated) must convert in time and
2160        // memory proportional to the chain length, not 2^length.
2161        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            // Bind the left values:
2208            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            // Bind the right values:
2216            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            // Bind the product values:
2224            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    /// Regression test for an arity bug in `verify`: an over-long claim
2260    /// (with more commitments than `Circuit::committed_vars()`) must be
2261    /// rejected. Without the explicit arity check, the extra commitments
2262    /// would be bound to the transcript but ignored by the algebraic
2263    /// commitment relation, letting a malformed claim verify against a
2264    /// proof generated against that same malformed claim.
2265    #[test]
2266    fn verify_rejects_over_long_claim() {
2267        let setup = fuzz::test_setup();
2268
2269        // Use the existing 2-committed-value quadratic circuit:
2270        // y = a x^2 + b x + c, with x = 3, a = b = c = 1, so y = 13.
2271        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        // Build an honest claim, then append a junk commitment so that
2288        // `claim.commitments.len() == 3` while `circuit.committed_vars() == 2`.
2289        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}