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, Version},
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", Version::V1);
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", Version::V1);
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::{Additive, CryptoGroup, Field, HashToGroup, Random, Ring, Space, powers},
140    synthetic::Synthetic,
141};
142use commonware_parallel::{Sequential, Strategy};
143use rand_core::CryptoRng;
144use std::{
145    collections::BTreeMap,
146    ops::{Index, IndexMut, Mul},
147};
148
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.width.write(buf);
241        self.height.write(buf);
242        self.weights.write(buf);
243    }
244}
245
246impl<F: EncodeSize> EncodeSize for SparseMatrix<F> {
247    fn encode_size(&self) -> usize {
248        self.width.encode_size() + self.height.encode_size() + self.weights.encode_size()
249    }
250}
251
252/// A circuit describing the constraints the prover must satisfy.
253pub struct Circuit<F> {
254    committed_vars: usize,
255    internal_vars: usize,
256    weights: SparseMatrix<F>,
257}
258
259impl<F: Write> Write for Circuit<F> {
260    fn write(&self, buf: &mut impl BufMut) {
261        self.committed_vars.write(buf);
262        self.internal_vars.write(buf);
263        self.weights.write(buf);
264    }
265}
266
267impl<F: Encode> Circuit<F> {
268    fn commit(&self, transcript: &mut Transcript) {
269        transcript.commit(self.encode());
270    }
271}
272
273impl<F: EncodeSize> EncodeSize for Circuit<F> {
274    fn encode_size(&self) -> usize {
275        self.committed_vars.encode_size()
276            + self.internal_vars.encode_size()
277            + self.weights.encode_size()
278    }
279}
280
281impl<F: Ring> Circuit<F> {
282    /// Create a new circuit from a committed-value count and a weight matrix.
283    ///
284    /// The circuit enforces:
285    ///
286    /// - `l_i * r_i = o_i`, and
287    /// - one linear constraint per row of `weights`.
288    ///
289    /// The columns are interpreted as:
290    ///
291    /// `1 | committed values | left wires | right wires | output wires`
292    ///
293    /// This returns `None` if the matrix width is incompatible with that layout.
294    pub fn new(committed_vars: usize, weights: SparseMatrix<F>) -> Option<Self> {
295        let remaining_vars = weights.width.checked_sub(committed_vars.checked_add(1)?)?;
296        if remaining_vars % 3 != 0 {
297            return None;
298        }
299        let internal_vars = remaining_vars / 3;
300        Some(Self {
301            committed_vars,
302            internal_vars,
303            weights,
304        })
305    }
306
307    /// Number of left/right/output internal wires.
308    pub const fn internal_vars(&self) -> usize {
309        self.internal_vars
310    }
311
312    /// Number of committed values.
313    pub const fn committed_vars(&self) -> usize {
314        self.committed_vars
315    }
316
317    /// Checks whether a certain assignment to committed variables satisfies this circuit.
318    ///
319    /// This returns false if the assignment has the wrong length, rather than
320    /// implicitly truncating or padding the assignment.
321    #[must_use]
322    pub fn is_satisfied(
323        &self,
324        committed_values: &[F],
325        left_values: &[F],
326        right_values: &[F],
327    ) -> bool {
328        if committed_values.len() != self.committed_vars
329            || left_values.len() != self.internal_vars
330            || right_values.len() != self.internal_vars
331        {
332            return false;
333        }
334        let mut output = Vec::with_capacity(1 + self.committed_vars + 3 * self.internal_vars);
335        output.push(F::one());
336        output.extend_from_slice(committed_values);
337        output.extend_from_slice(left_values);
338        output.extend_from_slice(right_values);
339        output.extend(
340            left_values
341                .iter()
342                .zip(right_values)
343                .map(|(l_i, r_i)| l_i.clone() * r_i),
344        );
345        let mut res = vec![F::zero(); self.weights.height];
346        for (&(i, j), w_ij) in &self.weights.weights {
347            res[i] += &(output[j].clone() * w_ij);
348        }
349        let zero = F::zero();
350        res.iter().all(|r_i| r_i == &zero)
351    }
352}
353
354/// Conversion from `zk::Circuit`s into bulletproofs circuits.
355///
356/// The conversion linearizes: every circuit value is expressed as a linear
357/// combination of columns of the weight matrix. Additions and
358/// multiplications by constants combine linearly; each multiplication of two
359/// non-constant values becomes a multiplication gate, whose output wire is a
360/// new column. Each assertion `(l, r)` then becomes a matrix row enforcing
361/// `l - r = 0`.
362mod zkc {
363    use crate::zk::circuit as zk;
364    use commonware_math::algebra::{Field, Random, Ring};
365    use commonware_utils::ordered::Map;
366    use rand_core::CryptoRng;
367    use std::{borrow::Cow, collections::BTreeMap};
368
369    /// A column of the bulletproofs weight matrix.
370    ///
371    /// The column layout is `1 | committed values | left wires | right wires
372    /// | output wires`. `Witness` is a provisional location for a witness
373    /// not yet pinned to a column; `location_to_col` resolves it through
374    /// `witness_locations`.
375    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
376    enum Location {
377        One,
378        Witness(usize),
379        Left(usize),
380        Right(usize),
381        Output(usize),
382        Committed(usize),
383    }
384
385    /// A circuit value, expressed as a linear combination of locations.
386    ///
387    /// `Location` is a single location with weight one, and `Constant` is a
388    /// multiple of the constant `1` column. `General` holds arbitrary
389    /// weighted sums, keyed by location so that duplicates merge.
390    #[derive(Clone)]
391    enum LinComb<F> {
392        Location(Location),
393        Constant(F),
394        General(Map<Location, F>),
395    }
396
397    impl<F: Ring> LinComb<F> {
398        /// Multiply two combinations, if at least one of them is a constant.
399        ///
400        /// Returns `None` otherwise, in which case the product needs a
401        /// multiplication gate.
402        fn mul(&self, other: &Self) -> Option<Self> {
403            let (constant, other) = match (self, other) {
404                (Self::Constant(c), other) => (c.clone(), other),
405                (other, Self::Constant(c)) => (c.clone(), other),
406                _ => return None,
407            };
408            let out = match other {
409                Self::Location(location) => Self::General(
410                    Map::try_from([(*location, constant)])
411                        .expect("single entry cannot duplicate keys"),
412                ),
413                Self::Constant(other_constant) => Self::Constant(constant * other_constant),
414                Self::General(items) => {
415                    let mut items = items.clone();
416                    for w in items.values_mut() {
417                        *w = w.clone() * &constant;
418                    }
419                    Self::General(items)
420                }
421            };
422            Some(out)
423        }
424
425        /// Add two linear combinations, merging the weights of duplicate
426        /// locations so the result stays bounded by the number of distinct
427        /// locations rather than the number of terms.
428        fn sum(&self, other: &Self) -> Self {
429            let mut terms: Vec<(Location, F)> = self
430                .iter()
431                .chain(other.iter())
432                .map(|(w, loc)| (loc, w.into_owned()))
433                .collect();
434            terms.sort_by_key(|&(loc, _)| loc);
435            let mut merged: Vec<(Location, F)> = Vec::with_capacity(terms.len());
436            for (loc, w) in terms {
437                match merged.last_mut() {
438                    Some((last, acc)) if *last == loc => *acc += &w,
439                    _ => merged.push((loc, w)),
440                }
441            }
442            // Merging adjacent duplicates above leaves every location unique.
443            Self::General(Map::try_from(merged).expect("merged locations should be unique"))
444        }
445
446        /// Iterate over the `(weight, location)` terms of this combination.
447        fn iter(&self) -> impl Iterator<Item = (Cow<'_, F>, Location)> {
448            let (single, general) = match self {
449                Self::Location(loc) => (Some((Cow::Owned(F::one()), *loc)), None),
450                Self::Constant(w) => (Some((Cow::Borrowed(w), Location::One)), None),
451                Self::General(items) => (None, Some(items.iter_pairs())),
452            };
453            single.into_iter().chain(
454                general
455                    .into_iter()
456                    .flatten()
457                    .map(|(loc, w)| (Cow::Borrowed(w), *loc)),
458            )
459        }
460
461        /// The witness index, if this combination is a single witness not
462        /// yet pinned to a column.
463        const fn witness(&self) -> Option<usize> {
464            match self {
465                Self::Location(Location::Witness(w)) => Some(*w),
466                _ => None,
467            }
468        }
469    }
470
471    /// State for converting a `zk::Circuit` into a bulletproofs circuit.
472    ///
473    /// `circuit` performs the verifier-side conversion, and
474    /// `circuit_and_witness` the prover-side one. Both run the same
475    /// conversion, so they produce the same circuit.
476    pub struct ZKCConverter<F> {
477        /// Scratch stack of indices waiting to be linearized.
478        linearize_queue: Vec<zk::CircuitIdx>,
479        /// The linear combination computed for each visited index.
480        linearize_cache: BTreeMap<zk::CircuitIdx, LinComb<F>>,
481        /// The assertions of the source circuit.
482        assertions: Vec<(zk::CircuitIdx, zk::CircuitIdx)>,
483        /// Assertions pinning a value to a specific location, used to bind
484        /// gate wires and committed values.
485        extra_assertions: Vec<(zk::CircuitIdx, Location)>,
486        /// The location assigned to each witness.
487        witness_locations: BTreeMap<usize, Location>,
488        /// The indices to commit, in caller order.
489        committed_indices: Vec<zk::CircuitIdx>,
490        /// The committed slot for each committed index (the first slot, if
491        /// the index is repeated).
492        committed_positions: BTreeMap<zk::CircuitIdx, usize>,
493        /// One entry per multiplication gate: the indices whose values feed
494        /// the left and right wires, and the index whose value is the
495        /// output wire. Padding gates holding leftover witnesses leave the
496        /// right wire, and then the output, unset.
497        internal_vars: Vec<(
498            zk::CircuitIdx,
499            Option<zk::CircuitIdx>,
500            Option<zk::CircuitIdx>,
501        )>,
502    }
503
504    impl<F: Field + Random> ZKCConverter<F> {
505        /// Create a converter committing the values at `committed_indices`,
506        /// in order.
507        pub fn new(committed_indices: Vec<zk::CircuitIdx>) -> Self {
508            let mut committed_positions = BTreeMap::new();
509            for (i, &idx) in committed_indices.iter().enumerate() {
510                // Duplicates keep the first position, so every occurrence of
511                // the index resolves to the same column, and the extra
512                // assertions constrain the remaining slots to match it.
513                committed_positions.entry(idx).or_insert(i);
514            }
515            Self {
516                linearize_queue: Vec::new(),
517                linearize_cache: BTreeMap::new(),
518                assertions: Vec::new(),
519                extra_assertions: Vec::new(),
520                witness_locations: BTreeMap::new(),
521                committed_indices,
522                committed_positions,
523                internal_vars: Default::default(),
524            }
525        }
526
527        /// Convert a circuit without an assignment (verifier mode).
528        pub fn circuit(mut self, zkc: zk::Circuit<F>) -> super::Circuit<F> {
529            self.populate(&zkc);
530            self.reckon_circuit()
531        }
532
533        /// Convert a circuit and its assignment (prover mode).
534        ///
535        /// Without a `blinding_rng`, the blinding factors are zero.
536        pub fn circuit_and_witness(
537            mut self,
538            blinding_rng: Option<&mut impl CryptoRng>,
539            zkc: zk::ValuedCircuit<F>,
540        ) -> (super::Circuit<F>, super::Witness<F>) {
541            self.populate(&zkc.circuit);
542            let blinding = blinding_rng.map_or_else(
543                || vec![F::zero(); self.committed_indices.len()],
544                |rng| {
545                    (0..self.committed_indices.len())
546                        .map(|_| F::random(&mut *rng))
547                        .collect::<Vec<_>>()
548                },
549            );
550            let values = self
551                .committed_indices
552                .iter()
553                .map(|&i| zkc[i].clone())
554                .collect::<Vec<_>>();
555            let mut left = Vec::with_capacity(self.internal_vars.len());
556            let mut right = Vec::with_capacity(self.internal_vars.len());
557            let mut out = Vec::with_capacity(self.internal_vars.len());
558            for &(l_i, r_i, o_i) in &self.internal_vars {
559                left.push(zkc[l_i].clone());
560                match (r_i, o_i) {
561                    (None, _) => {
562                        right.push(F::zero());
563                        out.push(F::zero());
564                    }
565                    (Some(r_i), None) => {
566                        right.push(zkc[r_i].clone());
567                        out.push(zkc[l_i].clone() * &zkc[r_i]);
568                    }
569                    (Some(r_i), Some(o_i)) => {
570                        right.push(zkc[r_i].clone());
571                        out.push(zkc[o_i].clone());
572                    }
573                }
574            }
575            let witness = super::Witness {
576                values,
577                blinding,
578                left,
579                right,
580                out,
581            };
582            (self.reckon_circuit(), witness)
583        }
584
585        /// Linearize every assertion and assign every witness a location.
586        fn populate(&mut self, zkc: &zk::Circuit<F>) {
587            for &(l, r) in &zkc.assertions {
588                self.assertions.push((l, r));
589                self.linearize(zkc, l);
590                self.linearize(zkc, r);
591            }
592            // Now, assign a non-witness location to any discovered witnesses.
593            {
594                let mut left = true;
595
596                for loc in self.witness_locations.values_mut() {
597                    let &mut Location::Witness(w) = loc else {
598                        continue;
599                    };
600                    if left {
601                        *loc = Location::Left(self.internal_vars.len());
602                        self.internal_vars
603                            .push((zk::CircuitIdx::Witness(w as u32), None, None));
604                        left = false;
605                    } else {
606                        let i = self.internal_vars.len() - 1;
607                        *loc = Location::Right(i);
608                        self.internal_vars[i].1 = Some(zk::CircuitIdx::Witness(w as u32));
609                        left = true;
610                    }
611                }
612            }
613            // Add extra assertions for each committed index, linearizing so
614            // that committed values not referenced by any assertion still
615            // resolve to a location.
616            let committed = self.committed_indices.clone();
617            for (i, c_pos) in committed.into_iter().enumerate() {
618                self.linearize(zkc, c_pos);
619                // If a committed witness still resolves to its own `Committed`
620                // column, its binding row would collapse to
621                // `Committed(i) - Committed(i) = 0`. That happens whenever no
622                // non-tautological constraint references it (it is used
623                // nowhere, or only in `assert(w == w)`), leaving the commitment
624                // with an all-zero column: a prover could then swap it for an
625                // arbitrary group element and still verify. Anchor it to a fresh
626                // padding wire and redirect every reference there, so its
627                // binding row reads `Left(k) - Committed(i) = 0`, tying the
628                // column to a value genuinely committed in `M`. A wire-anchored
629                // entry (e.g. a duplicate of an already-anchored index) is left
630                // as is.
631                if matches!(
632                    self.linearize_cache.get(&c_pos),
633                    Some(LinComb::Location(Location::Committed(_)))
634                ) {
635                    let k = self.internal_vars.len();
636                    self.internal_vars.push((c_pos, None, None));
637                    self.linearize_cache
638                        .insert(c_pos, LinComb::Location(Location::Left(k)));
639                }
640                self.extra_assertions.push((c_pos, Location::Committed(i)));
641            }
642        }
643
644        /// Resolve a location to its column in the weight matrix.
645        fn location_to_col(&self, loc: Location) -> usize {
646            fn inner<F>(this: &ZKCConverter<F>, loc: Location, no_witness: bool) -> usize {
647                match loc {
648                    Location::One => 0,
649                    Location::Left(i) => 1 + this.committed_indices.len() + i,
650                    Location::Right(i) => {
651                        1 + this.committed_indices.len() + this.internal_vars.len() + i
652                    }
653                    Location::Output(i) => {
654                        1 + this.committed_indices.len() + 2 * this.internal_vars.len() + i
655                    }
656                    Location::Committed(i) => 1 + i,
657                    Location::Witness(i) => {
658                        if no_witness {
659                            unreachable!("unexpected witness location")
660                        } else {
661                            inner(this, this.witness_locations[&i], true)
662                        }
663                    }
664                }
665            }
666
667            inner(self, loc, false)
668        }
669
670        /// Build the bulletproofs circuit from the accumulated state.
671        ///
672        /// Each assertion contributes a row applying the left side's
673        /// combination positively and the right side's negatively, so the
674        /// row reads `l - r = 0`.
675        fn reckon_circuit(&self) -> super::Circuit<F> {
676            let mut weights = super::SparseMatrix::default();
677            for (row, (l, r)) in self
678                .assertions
679                .iter()
680                .map(|(l, r)| {
681                    (
682                        Cow::Borrowed(
683                            self.linearize_cache
684                                .get(l)
685                                .expect("linearize_cache_should_be_populated"),
686                        ),
687                        Cow::Borrowed(
688                            self.linearize_cache
689                                .get(r)
690                                .expect("linearize_cache should be populated"),
691                        ),
692                    )
693                })
694                .chain(self.extra_assertions.iter().map(|(cidx, loc)| {
695                    (
696                        Cow::Borrowed(
697                            self.linearize_cache
698                                .get(cidx)
699                                .expect("linearize_cache should be populated"),
700                        ),
701                        Cow::Owned(LinComb::Location(*loc)),
702                    )
703                }))
704                .enumerate()
705            {
706                for (w, loc) in l.iter() {
707                    weights[(row, self.location_to_col(loc))] += w.as_ref();
708                }
709                for (w, loc) in r.iter() {
710                    weights[(row, self.location_to_col(loc))] -= w.as_ref();
711                }
712            }
713            super::Circuit {
714                committed_vars: self.committed_indices.len(),
715                internal_vars: self.internal_vars.len(),
716                weights,
717            }
718        }
719
720        /// Assign `loc` to `witness` unless it already has a non-provisional
721        /// location, returning the location in effect.
722        fn assign_witness_location(&mut self, witness: usize, loc: Location) -> Location {
723            *self
724                .witness_locations
725                .entry(witness)
726                .and_modify(|current_loc| {
727                    if let Location::Witness(_) = *current_loc {
728                        *current_loc = loc;
729                    }
730                })
731                .or_insert(loc)
732        }
733
734        /// Compute and cache the linear combination for `i` and everything
735        /// it depends on.
736        ///
737        /// Uses an explicit work stack rather than recursion, so deep
738        /// circuits cannot overflow the call stack.
739        fn linearize(&mut self, zkc: &zk::Circuit<F>, i: zk::CircuitIdx) {
740            self.linearize_queue.clear();
741            self.linearize_queue.push(i);
742            while let Some(i) = self.linearize_queue.pop() {
743                if self.linearize_cache.contains_key(&i) {
744                    continue;
745                }
746                let comb = match i {
747                    zk::CircuitIdx::Constant(i) => {
748                        LinComb::Constant(zkc.constants[i as usize].clone())
749                    }
750                    this @ zk::CircuitIdx::Witness(i) => {
751                        let i_usize = i as usize;
752                        let w_loc = self
753                            .committed_positions
754                            .get(&this)
755                            .map_or(Location::Witness(i_usize), |&i| Location::Committed(i));
756                        let loc = self.assign_witness_location(i_usize, w_loc);
757                        LinComb::Location(loc)
758                    }
759                    this @ zk::CircuitIdx::Node(i) => {
760                        let this_node = &zkc.nodes[i as usize];
761                        let (l, r) = match *this_node {
762                            zk::CircuitNode::Add(l, r) => (l, r),
763                            zk::CircuitNode::Mul(l, r) => (l, r),
764                        };
765                        let (l_comb, r_comb) =
766                            match (self.linearize_cache.get(&l), self.linearize_cache.get(&r)) {
767                                (None, None) => {
768                                    self.linearize_queue.extend([this, r, l]);
769                                    continue;
770                                }
771                                (Some(_), None) => {
772                                    self.linearize_queue.extend([this, r]);
773                                    continue;
774                                }
775                                (None, Some(_)) => {
776                                    self.linearize_queue.extend([this, l]);
777                                    continue;
778                                }
779                                (Some(l_comb), Some(r_comb)) => (l_comb, r_comb),
780                            };
781                        match *this_node {
782                            zk::CircuitNode::Add(_, _) => l_comb.sum(r_comb),
783                            zk::CircuitNode::Mul(l, r) => {
784                                if let Some(out) = l_comb.mul(r_comb) {
785                                    out
786                                } else {
787                                    let i = self.internal_vars.len();
788                                    self.internal_vars.push((l, Some(r), Some(this)));
789                                    self.extra_assertions.push((l, Location::Left(i)));
790                                    self.extra_assertions.push((r, Location::Right(i)));
791
792                                    // Borrow checker shenanigans
793                                    let w_l = l_comb.witness();
794                                    let w_r = r_comb.witness();
795                                    if let Some(w) = w_l {
796                                        self.assign_witness_location(w, Location::Left(i));
797                                    }
798                                    if let Some(w) = w_r {
799                                        self.assign_witness_location(w, Location::Right(i));
800                                    }
801                                    LinComb::Location(Location::Output(i))
802                                }
803                            }
804                        }
805                    }
806                };
807                self.linearize_cache.insert(i, comb);
808            }
809        }
810    }
811}
812
813/// Convert a ZK circuit into a bulletproofs circuit, treating the witness
814/// positions named by `committed_indices` as committed values.
815///
816/// Committed values keep the order of `committed_indices`. A duplicate index
817/// produces a separate commitment to the same value, constrained to match.
818/// Every index must name a witness allocated by the circuit; other indices
819/// are unsupported, and may panic or leave a commitment unconstrained.
820///
821/// `committed_indices` must match what the prover passed to
822/// [`zkc_to_circuit_and_witness`], otherwise the resulting [`Circuit`] will
823/// not match the prover's and proofs against it will fail.
824pub fn zkc_to_circuit<F: Field + Random>(
825    zkc: crate::zk::circuit::Circuit<F>,
826    committed_indices: &[crate::zk::circuit::CircuitIdx],
827) -> Circuit<F> {
828    zkc::ZKCConverter::new(committed_indices.to_vec()).circuit(zkc)
829}
830
831/// Convert a ZK circuit and witness assignment into a bulletproofs circuit and
832/// witness.
833///
834/// `committed_indices` names the witness positions that should become
835/// bulletproofs committed values, in the order they should appear as
836/// committed values. A duplicate index produces a separate commitment to the
837/// same value, constrained to match. Every index must name a witness
838/// allocated by the circuit; other indices are unsupported, and may panic
839/// or leave a commitment unconstrained.
840pub fn zkc_to_circuit_and_witness<F: Field + Random>(
841    blinding_rng: Option<&mut impl CryptoRng>,
842    zkc: crate::zk::circuit::ValuedCircuit<F>,
843    committed_indices: &[crate::zk::circuit::CircuitIdx],
844) -> (Circuit<F>, Witness<F>) {
845    zkc::ZKCConverter::new(committed_indices.to_vec()).circuit_and_witness(blinding_rng, zkc)
846}
847
848/// Generators used by the circuit proof system.
849///
850/// This wraps the underlying IPA setup and adds two Pedersen generators used
851/// for commitments to committed values and blindings.
852#[derive(PartialEq)]
853pub struct Setup<G> {
854    ipa: ipa::Setup<G>,
855    value_generator: G,
856    blinding_generator: G,
857}
858
859impl<G> Setup<G> {
860    /// Create a new [`Setup`] from an [`ipa::Setup`] and two Pedersen generators.
861    ///
862    /// You MUST ensure that all generators are unique.
863    pub const fn new(ipa: ipa::Setup<G>, value_generator: G, blinding_generator: G) -> Self {
864        Self {
865            ipa,
866            value_generator,
867            blinding_generator,
868        }
869    }
870
871    pub const fn value_generator(&self) -> &G {
872        &self.value_generator
873    }
874
875    pub const fn blinding_generator(&self) -> &G {
876        &self.blinding_generator
877    }
878
879    /// Check if this setup supports claims of a given length.
880    pub const fn supports(&self, lg_len: u8) -> bool {
881        self.ipa.supports(lg_len)
882    }
883
884    /// Construct a [`Setup`] of size `2^lg_len`, deterministically deriving
885    /// the IPA generators, the product generator, and the blinding generator
886    /// from `domain_separator` via [`HashToGroup`]. The caller supplies the
887    /// `value_generator` so external commitments (e.g. `value * G1::generator()`)
888    /// can line up with the Pedersen commitments produced by
889    /// [`Witness::claim`].
890    ///
891    /// Each hashed generator is derived with a unique label, so the
892    /// discrete-log relations between them are unknown (assuming a properly
893    /// modelled hash-to-curve). The caller is responsible for ensuring the
894    /// supplied `value_generator` has unknown discrete log relative to the
895    /// blinding generator (e.g. by using a system-fixed generator distinct
896    /// from any hashed point).
897    pub fn hashed(domain_separator: &[u8], lg_len: u8, value_generator: G) -> Self
898    where
899        G: HashToGroup,
900    {
901        let n: usize = 1usize << lg_len;
902        let product_generator = G::hash_to_group(domain_separator, b"product");
903        let blinding_generator = G::hash_to_group(domain_separator, b"blinding");
904        let g_and_h = (0..n).map(|i| {
905            let i_bytes = (i as u64).to_le_bytes();
906            let mut g_msg = Vec::with_capacity(2 + i_bytes.len());
907            g_msg.extend_from_slice(b"g/");
908            g_msg.extend_from_slice(&i_bytes);
909            let mut h_msg = Vec::with_capacity(2 + i_bytes.len());
910            h_msg.extend_from_slice(b"h/");
911            h_msg.extend_from_slice(&i_bytes);
912            (
913                G::hash_to_group(domain_separator, &g_msg),
914                G::hash_to_group(domain_separator, &h_msg),
915            )
916        });
917        Self::new(
918            ipa::Setup::new(product_generator, g_and_h),
919            value_generator,
920            blinding_generator,
921        )
922    }
923
924    /// Build the virtual setup and the flat array of concrete generators
925    /// used to evaluate any [`Synthetic`] produced against this setup.
926    fn build_virtual<F: Field>(&self) -> (Setup<Synthetic<F, G>>, Vec<G>)
927    where
928        G: Clone,
929    {
930        let n = self.ipa.g().len();
931        let mut gens = Synthetic::<F, G>::generators();
932        let vg: Vec<_> = (0..n)
933            .map(|_| gens.next().expect("generators is infinite"))
934            .collect();
935        let vh: Vec<_> = (0..n)
936            .map(|_| gens.next().expect("generators is infinite"))
937            .collect();
938        let vq = gens.next().expect("generators is infinite");
939        let ipa_vs = ipa::Setup::new(vq, vg.into_iter().zip(vh));
940        let pv = gens.next().expect("generators is infinite");
941        let pb = gens.next().expect("generators is infinite");
942        let vs = Setup::new(ipa_vs, pv, pb);
943        let mut flat = Vec::with_capacity(2 * n + 3);
944        flat.extend_from_slice(self.ipa.g());
945        flat.extend_from_slice(self.ipa.h());
946        flat.push(self.ipa.product_generator().clone());
947        flat.push(self.value_generator.clone());
948        flat.push(self.blinding_generator.clone());
949        (vs, flat)
950    }
951
952    /// Build a virtual setup, call `f` to obtain a verification equation,
953    /// and evaluate it against the concrete generators in `self`.
954    pub fn eval<F: Field>(
955        &self,
956        f: impl FnOnce(&Setup<Synthetic<F, G>>) -> Option<Synthetic<F, G>>,
957        strategy: &impl Strategy,
958    ) -> Option<G>
959    where
960        G: Space<F>,
961    {
962        let (vs, flat) = self.build_virtual::<F>();
963        f(&vs).map(|v| v.eval(&flat, strategy))
964    }
965
966    /// Build a virtual setup, call `f` to obtain a list of per-item
967    /// verification equations, and check each of them in a way that batches
968    /// MSMs as much as possible.
969    ///
970    /// The strategy is:
971    ///
972    /// 1. Pre-scale every per-item equation by an independent random scalar
973    ///    so that scalar zero-ness is preserved with overwhelming
974    ///    probability and any subset sum is sound on its own.
975    /// 2. Sum every (scaled) equation into a single [`Synthetic`] and
976    ///    evaluate it with **one** MSM. If the result is the group
977    ///    identity, every item is valid.
978    /// 3. Otherwise, recursively split the failing range in half and
979    ///    re-evaluate the sum on each half (still one MSM per check). The
980    ///    recursion bottoms out at a single item, at which point any
981    ///    remaining failure is attributed to that item.
982    ///
983    /// This costs one MSM in the all-valid case and `O(k log n)` MSMs when
984    /// `k` items are invalid (vs. `n` MSMs for naive per-item checking).
985    ///
986    /// Returning `None` from `f` indicates that the whole batch is malformed
987    /// and produces an outer `None`. Individual `None` entries within the
988    /// returned `Vec` indicate that the corresponding item is structurally
989    /// invalid; they are reported as `false` in the result without ever
990    /// being included in any subset sum.
991    pub fn eval_check_batched<F: Field + Random, R: CryptoRng>(
992        &self,
993        rng: &mut R,
994        f: impl FnOnce(&Setup<Synthetic<F, G>>, &mut R) -> Option<Vec<Option<Synthetic<F, G>>>>,
995        strategy: &impl Strategy,
996    ) -> Option<Vec<bool>>
997    where
998        G: Space<F> + PartialEq,
999    {
1000        let (vs, flat) = self.build_virtual::<F>();
1001        let synths = f(&vs, &mut *rng)?;
1002        let n = synths.len();
1003
1004        // Pre-scale each present synthetic by an independent random scalar.
1005        // None entries stay None; they are reported as `false` and never
1006        // contribute to any subset sum.
1007        let scaled: Vec<Option<Synthetic<F, G>>> = synths
1008            .into_iter()
1009            .map(|opt| opt.map(|s| s * &F::random(&mut *rng)))
1010            .collect();
1011
1012        // Indices of items eligible for batched checking.
1013        let active: Vec<usize> = (0..n).filter(|&i| scaled[i].is_some()).collect();
1014
1015        // Sum the scaled synthetics for `range` and evaluate to a single MSM.
1016        let check = |range: &[usize]| -> bool {
1017            let mut acc = Synthetic::<F, G>::default();
1018            for &i in range {
1019                acc += scaled[i].as_ref().expect("active indices are Some");
1020            }
1021            acc.eval(&flat, strategy) == G::zero()
1022        };
1023
1024        // Iterative DFS over contiguous index ranges. A range that checks
1025        // out marks every contained item as valid; a failing range of
1026        // length > 1 splits in half; a failing range of length 1 leaves
1027        // its (single) item marked invalid.
1028        let mut valid = vec![false; n];
1029        let mut stack: Vec<&[usize]> = Vec::new();
1030        if !active.is_empty() {
1031            stack.push(&active);
1032        }
1033        while let Some(range) = stack.pop() {
1034            if check(range) {
1035                for &i in range {
1036                    valid[i] = true;
1037                }
1038            } else if range.len() > 1 {
1039                let mid = range.len() / 2;
1040                let (left, right) = range.split_at(mid);
1041                stack.push(right);
1042                stack.push(left);
1043            }
1044        }
1045        Some(valid)
1046    }
1047}
1048
1049impl<G: Write> Write for Setup<G> {
1050    fn write(&self, buf: &mut impl BufMut) {
1051        self.ipa.write(buf);
1052        self.value_generator.write(buf);
1053        self.blinding_generator.write(buf);
1054    }
1055}
1056
1057impl<G: EncodeSize> EncodeSize for Setup<G> {
1058    fn encode_size(&self) -> usize {
1059        self.ipa.encode_size()
1060            + self.value_generator.encode_size()
1061            + self.blinding_generator.encode_size()
1062    }
1063}
1064
1065impl<G: Read> Read for Setup<G>
1066where
1067    G::Cfg: Clone,
1068{
1069    type Cfg = (usize, G::Cfg);
1070
1071    fn read_cfg(buf: &mut impl Buf, (max_len, cfg): &Self::Cfg) -> Result<Self, Error> {
1072        let ipa = ipa::Setup::read_cfg(buf, &(*max_len, cfg.clone()))?;
1073        let value_generator = G::read_cfg(buf, cfg)?;
1074        let blinding_generator = G::read_cfg(buf, cfg)?;
1075        Ok(Self::new(ipa, value_generator, blinding_generator))
1076    }
1077}
1078
1079/// A prover-side assignment for a circuit proof.
1080///
1081/// This contains the committed values, their Pedersen blindings, and the
1082/// internal left, right, and output wire values.
1083#[allow(dead_code)]
1084pub struct Witness<F> {
1085    values: Vec<F>,
1086    blinding: Vec<F>,
1087    left: Vec<F>,
1088    right: Vec<F>,
1089    out: Vec<F>,
1090}
1091
1092impl<F> Witness<F> {
1093    /// Create a new witness, given all committed values, and internal values.
1094    ///
1095    /// This is a very low level method, with the only safety guard being to check
1096    /// that certain vectors have matching lengths. Beyond that, we don't check
1097    /// that the values satisfy a circuit relationship, or match the commitments.
1098    pub fn new(
1099        values: Vec<F>,
1100        blinding: Vec<F>,
1101        left: Vec<F>,
1102        right: Vec<F>,
1103        out: Vec<F>,
1104    ) -> Option<Self> {
1105        if values.len() != blinding.len() {
1106            return None;
1107        }
1108        if left.len() != right.len() || right.len() != out.len() {
1109            return None;
1110        }
1111        Some(Self {
1112            values,
1113            blinding,
1114            left,
1115            right,
1116            out,
1117        })
1118    }
1119
1120    pub fn values(&self) -> &[F] {
1121        &self.values
1122    }
1123
1124    /// Check whether this witness's wires satisfy the given [`Circuit`].
1125    ///
1126    /// Useful as a debugging aid: if this returns `false`, the prover and
1127    /// circuit are inconsistent and any [`prove`] result will not verify.
1128    #[must_use]
1129    pub fn is_satisfied(&self, circuit: &Circuit<F>) -> bool
1130    where
1131        F: Ring,
1132    {
1133        circuit.is_satisfied(&self.values, &self.left, &self.right)
1134    }
1135
1136    /// Create the public claim corresponding to this witness for the given setup.
1137    ///
1138    /// The resulting claim contains Pedersen commitments to the witness's
1139    /// committed values and blindings.
1140    pub fn claim<G: Space<F>>(&self, setup: &Setup<G>) -> Claim<G> {
1141        Claim {
1142            commitments: self
1143                .values
1144                .iter()
1145                .zip(&self.blinding)
1146                .map(|(value, blind)| {
1147                    setup.value_generator.clone() * value
1148                        + &(setup.blinding_generator.clone() * blind)
1149                })
1150                .collect(),
1151        }
1152    }
1153}
1154
1155/// The public claim for the protocol.
1156///
1157/// The claim consists of Pedersen commitments to values, which the prover claims
1158/// satisfy a [`Circuit`].
1159///
1160/// The claim does not contain the [`Circuit`] itself, so that the verifier is
1161/// in control of what properties they want the committed values to satisfy.
1162pub struct Claim<G> {
1163    pub commitments: Vec<G>,
1164}
1165
1166impl<G: Write> Write for Claim<G> {
1167    fn write(&self, buf: &mut impl BufMut) {
1168        self.commitments.write(buf);
1169    }
1170}
1171
1172impl<G: EncodeSize> EncodeSize for Claim<G> {
1173    fn encode_size(&self) -> usize {
1174        self.commitments.encode_size()
1175    }
1176}
1177
1178/// A proof demonstrating knowledge of a [`Witness`] satisfying a [`Claim`] relative
1179/// to a [`Circuit`].
1180///
1181/// See [`prove`] and [`verify`].
1182#[allow(dead_code)]
1183#[derive(Clone)]
1184pub struct Proof<F, G> {
1185    m_big: G,
1186    o_big: G,
1187    m_big_tilde: G,
1188    t_big: [G; 5],
1189    s_tilde: F,
1190    t_x: F,
1191    t_tilde_x: F,
1192    p_big: G,
1193    ipa_proof: ipa::Proof<F, G>,
1194}
1195
1196impl<F: Write, G: Write> Write for Proof<F, G> {
1197    fn write(&self, buf: &mut impl BufMut) {
1198        self.m_big.write(buf);
1199        self.o_big.write(buf);
1200        self.m_big_tilde.write(buf);
1201        for t in &self.t_big {
1202            t.write(buf);
1203        }
1204        self.s_tilde.write(buf);
1205        self.t_x.write(buf);
1206        self.t_tilde_x.write(buf);
1207        self.p_big.write(buf);
1208        self.ipa_proof.write(buf);
1209    }
1210}
1211
1212impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
1213    fn encode_size(&self) -> usize {
1214        self.m_big.encode_size()
1215            + self.o_big.encode_size()
1216            + self.m_big_tilde.encode_size()
1217            + self.t_big.iter().map(|t| t.encode_size()).sum::<usize>()
1218            + self.s_tilde.encode_size()
1219            + self.t_x.encode_size()
1220            + self.t_tilde_x.encode_size()
1221            + self.p_big.encode_size()
1222            + self.ipa_proof.encode_size()
1223    }
1224}
1225
1226impl<F: Read, G: Read> Read for Proof<F, G>
1227where
1228    F::Cfg: Clone,
1229    G::Cfg: Clone,
1230{
1231    /// `(max_len, (g_cfg, f_cfg))` where `max_len` bounds the IPA round count.
1232    type Cfg = (usize, (G::Cfg, F::Cfg));
1233
1234    fn read_cfg(buf: &mut impl Buf, cfg @ (_, (g_cfg, f_cfg)): &Self::Cfg) -> Result<Self, Error> {
1235        let m_big = G::read_cfg(buf, g_cfg)?;
1236        let o_big = G::read_cfg(buf, g_cfg)?;
1237        let m_big_tilde = G::read_cfg(buf, g_cfg)?;
1238        let t_big = [
1239            G::read_cfg(buf, g_cfg)?,
1240            G::read_cfg(buf, g_cfg)?,
1241            G::read_cfg(buf, g_cfg)?,
1242            G::read_cfg(buf, g_cfg)?,
1243            G::read_cfg(buf, g_cfg)?,
1244        ];
1245        let s_tilde = F::read_cfg(buf, f_cfg)?;
1246        let t_x = F::read_cfg(buf, f_cfg)?;
1247        let t_tilde_x = F::read_cfg(buf, f_cfg)?;
1248        let p_big = G::read_cfg(buf, g_cfg)?;
1249        let ipa_proof = ipa::Proof::read_cfg(buf, cfg)?;
1250        Ok(Self {
1251            m_big,
1252            o_big,
1253            m_big_tilde,
1254            t_big,
1255            s_tilde,
1256            t_x,
1257            t_tilde_x,
1258            p_big,
1259            ipa_proof,
1260        })
1261    }
1262}
1263
1264/// Prove that a given [`Witness`] satisfies a [`Circuit`] and matches a [`Claim`].
1265///
1266/// The proof is bound to the transcript state at the time of the call, so the
1267/// verifier must replay the same transcript history before verification.
1268///
1269/// This returns `None` if the setup does not support the circuit size, if the
1270/// witness lengths are inconsistent with the circuit, or if the claim does not
1271/// match the witness.
1272pub fn prove<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1273    rng: &mut impl CryptoRng,
1274    transcript: &mut Transcript,
1275    setup: &Setup<G>,
1276    circuit: &Circuit<F>,
1277    claim: &Claim<G>,
1278    witness: &Witness<F>,
1279    strategy: &impl Strategy,
1280) -> Option<Proof<F, G>> {
1281    // To set the stage, we're trying to convince the verifier that:
1282    //
1283    //   - we know v_i, ~v_i, l_i, r_i, o_i such that...
1284    //   - v_i B + ~v_i ~B = V_i,
1285    //   - l_i r_i = o_i,
1286    //   - c_i + <Θ_ij, v_j> + <Λ_ij, l_j> + <Ρ_ij, r_j> + <Ω_ij, o_j> = 0.
1287    //
1288    // Before we get back any challenges from the verifier, we need to commit to
1289    // the circuit, our claim, and the internal variables we're using. We create a commitment:
1290    //
1291    //   M := <l_i, G_i> + <r_i, H_i> + m ~B
1292    //   O := <o_i, G_i> + ~o ~B
1293    //
1294    // we'll also be introducing some blinding factors ~l_i, ~r_i later, and we need
1295    // to commit to these now as well:
1296    //
1297    //   ~M := <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1298    //
1299    // After sending all of these to the verifier, we get:
1300    // y, and z, which we use to reduce the constraints to:
1301    //
1302    //   <y^i, l_i r_i - o_i> +
1303    //   <z z^i, c_i + <Θ_ij, v_j> + <Λ_ij, l_j> + <Ρ_ij, r_j> + <Ω_ij, o_j>> = 0
1304    //
1305    // (By y^i, we mean a vector whose ith entry is y to the power of i. For small fields,
1306    // generating more challenges is needed instead, but for large fields, using powers lets us
1307    // sample less randomness.)
1308    //
1309    // At this point, it's convenient to fold these challenges into the weights:
1310    //
1311    //   θ_j := <Θ_ij, z z^i>
1312    //   λ_j := <Λ_ij, z z^i>
1313    //   ρ_j := <Ρ_ij, z z^i>
1314    //   ω_j := <Ω_ij, z z^i>
1315    //   κ := <c_i, z z^i>
1316    //
1317    // giving us:
1318    //
1319    //   <y^i, l_i r_i - o_i> + κ + <θ_i, v_i> + <λ_i, l_i> + <ρ_i, r_i> + <ω_i, o_i> = 0
1320    //
1321    // It's useful to have the terms concerning the committed variables on one side,
1322    // and the internal variables on the other:
1323    //
1324    //   -κ - <θ_i, v_i> = <y^i, l_i r_i - o_i> + <λ_i, l_i> + <ρ_i, r_i> + <ω_i, o_i>
1325    //
1326    // next, merge the terms with o_i:
1327    //
1328    //  ... = <y^i, l_i r_i> + ... + <ω_i - y^i, o_i>
1329    //
1330    // next, we can move one part of the l_i r_i term to the other side:
1331    //
1332    //   ... = <y^i r_i, l_i> + ...
1333    //
1334    // then, we can create another y^i r_i term:
1335    //
1336    //   ... = ... + <y^-i ρ_i, y^i r_i> + ...
1337    //
1338    // merging these terms we get:
1339    //
1340    //   -κ - <θ_i, v_i> = <l_i + y^-i ρ_i, y^i r_i> + <λ_i, l_i> + <ω_i - y^i, o_i>
1341    //
1342    // if we define:
1343    //
1344    //   δ(y, z) := <y^-i ρ_i, λ_i>
1345    //
1346    // we can add this to both sides, and merge the λ_i terms, giving us:
1347    //
1348    //  -κ - <θ_i, v_i> + δ(y, z) =
1349    //  <l_i + y^-i ρ_i, y^i r_i> + <l_i + y^-i ρ_i, λ_i> + <ω_i - y^i, o_i> =
1350    //  <l_i + y^-i ρ_i, y^i r_i + λ_i> + <ω_i - y^i, o_i>
1351    //
1352    // Now, we deploy a trick, in order to turn a statement about a sum:
1353    //
1354    //   <a_i, b_i> + <c_i, d_i>
1355    //
1356    // into a single inner product. The trick is that if we create polynomials:
1357    //
1358    //   f_i(X) := a_i X + c_i X^2
1359    //   g_i(X) := b_i X + d_i
1360    //
1361    // then the 2nd degree of <f_i(X), g_i(X)> is <a_i, b_i> + <c_i, d_i>.
1362    //
1363    // So, we can check that:
1364    //
1365    //   t X^2 = <f_i(X), g_i(X)>
1366    //
1367    // as polynomials. To check equality of polynomials, we can commit to them,
1368    // and then have the verifier send us a random evaluation point.
1369    //
1370    // Let's apply that to our situation.
1371    //
1372    //   f_i(X) := (l_i + y^-i ρ_i) X + o_i X^2
1373    //   g_i(X) := (y^i r_i + λ_i) X + (ω_i - y^i)
1374    //   t(X) := <f_i(X), g_i(X)>
1375    //   deg2(t(X)) = -κ - <θ_i, v_i> + δ(y, z)
1376    //
1377    // Our goal at this point is to convince the verifier that:
1378    //
1379    //   - deg2(t(X)) = -κ - <θ_i, v_i> + δ(y, z),
1380    //   - f_i(X) and g_i(X) are correctly constructed,
1381    //   - t(X) = <f_i(X), g_i(X)>.
1382    //
1383    // We want to make sure that our proof is still zero-knowledge, so we can't just
1384    // send a commitment to the polynomial as is, because it leaks information about
1385    // the l_i, r_i, and o_i values. To get around this, we introduce blinding factors
1386    // ~l_i, ~r_i:
1387    //
1388    //   f_i(X) := ((l_i + ~l_i X^2) + y^-i ρ_i) X + o_i X^2
1389    //   g_i(X) := (y^i (r_i + ~r_i X^2) + λ_i) X + (ω_i - y^i)
1390    //
1391    // we use a factor of X^2 so that this blinding doesn't interfere with the
1392    // second degree of <f_i(X), g_i(X)>. When the verifier sees f_i(x) and g_i(x)
1393    // for a random challenge point, they will have a masking factor of ~l_i x^3
1394    // (respectively, y^i ~r_i x^3), hiding things completely.
1395    //
1396    // Expanding this out, we get:
1397    //
1398    //   t(X) := <f_i(X), g_i(X)> =
1399    //   <l_i + y^-i ρ_i, ω_i - y^i> X +
1400    //   (<l_i + y^-i ρ_i, y^i r_i + λ_i> + <o_i, ω_i - y^i>) X^2 +
1401    //   (<~l_i, ω_i - y^i> + <o_i, y^i r_i + λ_i>) X^3 +
1402    //   (<~l_i, y^i r_i + λ_i> + <l_i + y^-i ρ_i, y^i ~r_i>) X^4 +
1403    //   <o_i, y^i ~r_i> X^5 +
1404    //   <~l_i, y^i ~r_i> X^6
1405    //
1406    // thus, we can create commitments T_1, T_3, T_4, T_5, T_6 to these elements,
1407    // (skipping the X^2 factor), using blinding factors ~t_i.
1408    //
1409    // Then, for a random challenge, x, the verifier can check that the second degree is correct:
1410    //
1411    //  t(x) B + ~t(x) ~B =?
1412    //  (-κ + δ(y, z)) x^2 B - x^2 <θ_i, V_i> + Σ_{i != 2} x^i T_i
1413    //
1414    // for ~t(x), we use the synthetic blinding factors ~t_i for x^1, x^3, ...
1415    // and for x^2, we use -<θ_i, ~v_i>, so that the equation above works.
1416    //
1417    // The right hand side is checking the second degree in the exponent, behind
1418    // the Pedersen commitments, and the left hand side is our opening of the polynomial,
1419    // at a random point.
1420    //
1421    // Before getting this challenge, we also want to provide the necessary commitments
1422    // to f_i(X) and g_i(X) as well, so that those can be checked.
1423    //
1424    // Eventually, we want to prove the inner product <f_i(x), g_i(x)>, and the IPA
1425    // protocol expects to see <f_i(x), G_i> + <g_i(x), H_i>. Expanding that, out,
1426    // using the indeterminate X (rather than the challenge x), we get:
1427    //
1428    //   <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
1429    //   <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
1430    //
1431    // The natural commitments involve grouping things by coefficient, and by public
1432    // vs secret values:
1433    //
1434    //   P_0 := <ω_i - y^i, H_i>
1435    //   P_1 := <y^-i ρ_i, G_i> + <λ_i, H_i>
1436    //   S_1 := <l_i, G_i> + <y^i r_i, H_i>
1437    //   S_2 := <o_i, G_i>
1438    //   S_3 := <~l_i, G_i> + <y^i ~r_i, H_i>
1439    //
1440    // Recall that we've already sent the verifier:
1441    //
1442    //   M := <l_i, G_i> + <r_i, H_i> + m ~B
1443    //   O := <o_i, G_i> + ~o ~B
1444    //  ~M := <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1445    //
1446    // It seems like we're stuck here, because <r_i, H_i> doesn't match the <y^i r_i, H_i>
1447    // that we need inside of S_1. However, the IPA already lets us treat the right-side
1448    // basis as y^-i H_i by setting claim.y = y^-1. In terms of the original H_i that this
1449    // implementation uses in its MSMs, the public pieces become:
1450    //
1451    //   P_0 = <y^-i ω_i - 1, H_i>
1452    //   P_1 = <y^-i ρ_i, G_i> + <y^-i λ_i, H_i>
1453    //   M   = <l_i, G_i> + <r_i, H_i> + m ~B
1454    //   O   = <o_i, G_i> + ~o ~B
1455    //  ~M   = <~l_i, G_i> + <~r_i, H_i> + ~m ~B
1456    //
1457    // but, the bottom three are equal to:
1458    //
1459    //   M = S_1 +  m ~B
1460    //   O = S_2 + ~o ~B
1461    //  ~M = S_3 + ~m ~B
1462    //
1463    // Thus, we can reveal ~s := x m + x^2 ~o + x^3 ~m, and have the verifier calculate
1464    //
1465    //   P := -~s ~B + P_0 + x (P_1 + M) + x^2 O + x^3 ~M
1466    //      = <f_i(x), G_i> + <g_i(x), y^-i H_i>
1467    //
1468    // (Rather than the verifier calculating this, the prover can provide it, and the verifier
1469    // can check this equation. This turns it into an MSM check, which can be more efficiently
1470    // combined with other such checks).
1471    //
1472    // Finally, we run the IPA protocol, using t(x) as the claimed inner product,
1473    // and P as the commitment to the vectors, and G_i, H'_i as the generators
1474    // for this commitment.
1475    //
1476    // Concretely, we reuse the ordinary IPA setup and set ipa_claim.y = y^-1.
1477    // This keeps the right-side basis change inside the IPA, while the public MSM
1478    // checks above stay written against the original H_i.
1479    //
1480    // # Padding
1481    //
1482    // The IPA protocol requires the input vectors to be padded to a power of 2.
1483    // To do this, we'll pad the l_i, r_i, ~l_i, ~r_i with 0s. This forces the
1484    // o_i to be padded with 0 as well. In order to explicitly not consider these
1485    // values, we make sure that the weights are padded with columns of 0s.
1486    // Because we compress the weight matrices into vectors by taking a combination
1487    // of rows, we can pad the resulting vectors with 0s.
1488    //
1489    // Looking at t(X), the value doesn't change with the padding, because we always
1490    // have a zero value on one side of each inner product for the new indices.
1491    //
1492    // P_0 on the other hand, will end up with some extra -1 values we'll have
1493    // to take into account. Because this is the only changed value, we can handle
1494    // this one as a special case.
1495    //
1496    // Now, let's write some Rust.
1497    //
1498    // First, let's commit to our internal variables, and to our masks:
1499    let l_tilde = (0..circuit.internal_vars)
1500        .map(|_| F::random(&mut *rng))
1501        .collect::<Vec<_>>();
1502    let r_tilde = (0..circuit.internal_vars)
1503        .map(|_| F::random(&mut *rng))
1504        .collect::<Vec<_>>();
1505    let m = F::random(&mut *rng);
1506    let o_tilde = F::random(&mut *rng);
1507    let m_tilde = F::random(&mut *rng);
1508    let g_internal = &setup.ipa.g()[..circuit.internal_vars];
1509    let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1510    let m_big = G::msm(g_internal, &witness.left, strategy)
1511        + &G::msm(h_internal, &witness.right, strategy)
1512        + &(setup.blinding_generator.clone() * &m);
1513    let o_big =
1514        G::msm(g_internal, &witness.out, strategy) + &(setup.blinding_generator.clone() * &o_tilde);
1515    let m_big_tilde = G::msm(g_internal, &l_tilde, strategy)
1516        + &G::msm(h_internal, &r_tilde, strategy)
1517        + &(setup.blinding_generator.clone() * &m_tilde);
1518    // Now, commit to all the this information.
1519    circuit.commit(transcript);
1520    transcript.commit(claim.encode());
1521    transcript.commit(m_big.encode());
1522    transcript.commit(o_big.encode());
1523    transcript.commit(m_big_tilde.encode());
1524    let padded_vars = circuit.internal_vars.next_power_of_two();
1525    let y = F::random(transcript.noise(b"y"));
1526    let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1527    let y_inv = y.inv();
1528    let y_inv_powers = powers(F::one(), &y_inv)
1529        .take(padded_vars)
1530        .collect::<Vec<_>>();
1531    let z = F::random(transcript.noise(b"z"));
1532    let z_powers = powers(z.clone(), &z)
1533        .take(circuit.weights.height())
1534        .collect::<Vec<_>>();
1535    let (kappa, theta, lambda, rho, omega) = {
1536        let mut kappa = F::zero();
1537        let mut theta = vec![F::zero(); circuit.committed_vars];
1538        let mut lambda = vec![F::zero(); circuit.internal_vars];
1539        let mut rho = vec![F::zero(); circuit.internal_vars];
1540        let mut omega = vec![F::zero(); circuit.internal_vars];
1541        let theta_start = 1;
1542        let lambda_start = theta_start + circuit.committed_vars;
1543        let rho_start = lambda_start + circuit.internal_vars;
1544        let omega_start = rho_start + circuit.internal_vars;
1545        for (&(i, j), w_ij) in &circuit.weights.weights {
1546            let w_ij = w_ij.clone();
1547            if j >= omega_start {
1548                omega[j - omega_start] += &(w_ij * &z_powers[i]);
1549            } else if j >= rho_start {
1550                rho[j - rho_start] += &(w_ij * &z_powers[i]);
1551            } else if j >= lambda_start {
1552                lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1553            } else if j >= theta_start {
1554                theta[j - theta_start] += &(w_ij * &z_powers[i]);
1555            } else {
1556                kappa += &(w_ij * &z_powers[i]);
1557            }
1558        }
1559        (kappa, theta, lambda, rho, omega)
1560    };
1561
1562    // We cache a few quantities, which we'll need for MSMs later anyways.
1563    let mut omega_minus_y = omega
1564        .iter()
1565        .cloned()
1566        .zip(&y_powers)
1567        .map(|(omega_i, y_i)| omega_i - y_i)
1568        .collect::<Vec<_>>();
1569    omega_minus_y.extend(
1570        y_powers
1571            .iter()
1572            .skip(circuit.internal_vars)
1573            .cloned()
1574            .map(|y_i| -y_i),
1575    );
1576    let y_inv_rho = y_inv_powers
1577        .iter()
1578        .cloned()
1579        .zip(&rho)
1580        .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1581        .collect::<Vec<_>>();
1582    let y_inv_lambda = y_inv_powers
1583        .iter()
1584        .cloned()
1585        .zip(&lambda)
1586        .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1587        .collect::<Vec<_>>();
1588    let y_inv_omega_minus_y = y_inv_powers
1589        .iter()
1590        .cloned()
1591        .zip(&omega_minus_y)
1592        .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1593        .collect::<Vec<_>>();
1594    let y_r = y_powers
1595        .iter()
1596        .cloned()
1597        .zip(&witness.right)
1598        .map(|(y_i, r_i)| y_i * r_i)
1599        .collect::<Vec<_>>();
1600    let y_r_tilde = y_powers
1601        .iter()
1602        .cloned()
1603        .zip(&r_tilde)
1604        .map(|(y_i, r_i)| y_i * r_i)
1605        .collect::<Vec<_>>();
1606
1607    let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1608
1609    // t_1, t_2, t_3, t_4, t_5, t_6
1610    let t = {
1611        let mut t = std::array::from_fn::<_, 6, _>(|_| F::zero());
1612        // t_1
1613        for i in 0..circuit.internal_vars {
1614            t[0] += &((witness.left[i].clone() + &y_inv_rho[i]) * &omega_minus_y[i]);
1615        }
1616        // t_2
1617        t[1] = delta_y_z - &kappa - &<F as Space<F>>::msm(&theta, &witness.values, strategy);
1618        // t_3
1619        for i in 0..circuit.internal_vars {
1620            t[2] += &(l_tilde[i].clone() * &omega_minus_y[i]);
1621            t[2] += &(witness.out[i].clone() * &(y_r[i].clone() + &lambda[i]));
1622        }
1623        // t_4
1624        for i in 0..circuit.internal_vars {
1625            t[3] += &(l_tilde[i].clone() * &(y_r[i].clone() + &lambda[i]));
1626            t[3] += &((witness.left[i].clone() + &y_inv_rho[i]) * &y_r_tilde[i]);
1627        }
1628        // t_5
1629        t[4] = <F as Space<F>>::msm(&witness.out, &y_r_tilde, strategy);
1630        // t_6
1631        t[5] = <F as Space<F>>::msm(&l_tilde, &y_r_tilde, strategy);
1632        t
1633    };
1634    let t_tilde = std::array::from_fn::<_, 6, _>(|i| {
1635        if i == 1 {
1636            -<F as Space<F>>::msm(&theta, &witness.blinding, strategy)
1637        } else {
1638            F::random(&mut *rng)
1639        }
1640    });
1641    let t_big = std::array::from_fn::<_, 5, _>(|i| {
1642        // Skip the second element
1643        let i = if i >= 1 { i + 1 } else { i };
1644        setup.value_generator.clone() * &t[i] + &(setup.blinding_generator.clone() * &t_tilde[i])
1645    });
1646
1647    // The IPA generators may be larger than `padded_vars` for setups that
1648    // support multiple circuit sizes. Restrict to the prefix actually used.
1649    let p_0 = G::msm(
1650        &setup.ipa.h()[..padded_vars],
1651        &y_inv_omega_minus_y,
1652        strategy,
1653    );
1654    let h_internal = &setup.ipa.h()[..circuit.internal_vars];
1655    let p_1 =
1656        G::msm(g_internal, &y_inv_rho, strategy) + &G::msm(h_internal, &y_inv_lambda, strategy);
1657
1658    // Now, we can commit the t commitments, along with the secret commitments.
1659    // The public commitments will be recomputed by the verifier.
1660    for t_big_i in &t_big {
1661        transcript.commit(t_big_i.encode());
1662    }
1663    let x = F::random(transcript.noise(b"x"));
1664    let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1665    let s_tilde = m * &x[0] + &(o_tilde * &x[1]) + &(m_tilde * &x[2]);
1666    let p = setup.blinding_generator.clone() * &(-s_tilde.clone())
1667        + &p_0
1668        + &((p_1 + &m_big) * &x[0])
1669        + &(o_big.clone() * &x[1])
1670        + &(m_big_tilde.clone() * &x[2]);
1671    let t_x = <F as Space<F>>::msm(&t, &x, strategy);
1672    let t_tilde_x = <F as Space<F>>::msm(&t_tilde, &x, strategy);
1673    let ipa_claim = ipa::Claim {
1674        commitment: p.clone(),
1675        product: t_x.clone(),
1676        y: y_inv,
1677        log_len: padded_vars.ilog2().try_into().ok()?,
1678    };
1679    let mut f_x = (0..circuit.internal_vars)
1680        .map(|i| {
1681            (witness.left[i].clone() + &y_inv_rho[i]) * &x[0]
1682                + &(witness.out[i].clone() * &x[1])
1683                + &(l_tilde[i].clone() * &x[2])
1684        })
1685        .collect::<Vec<_>>();
1686    f_x.resize(padded_vars, F::zero());
1687    let mut g_x = (0..circuit.internal_vars)
1688        .map(|i| {
1689            (y_r[i].clone() + &lambda[i]) * &x[0]
1690                + &omega_minus_y[i]
1691                + &(y_r_tilde[i].clone() * &x[2])
1692        })
1693        .collect::<Vec<_>>();
1694    g_x.extend_from_slice(&omega_minus_y[circuit.internal_vars..]);
1695    let witness = ipa::Witness::new(f_x.into_iter().zip(g_x))?;
1696    let ipa_proof = ipa::prove(transcript, &setup.ipa, &ipa_claim, witness, strategy)?;
1697    Some(Proof {
1698        m_big,
1699        o_big,
1700        m_big_tilde,
1701        t_big,
1702        s_tilde,
1703        t_x,
1704        t_tilde_x,
1705        p_big: p,
1706        ipa_proof,
1707    })
1708}
1709
1710/// Construct the verification equation for a circuit proof.
1711///
1712/// The returned [`Synthetic`] should evaluate to zero for a correct proof.
1713/// Use [`Setup::eval`] to create the virtual setup and evaluate the result.
1714///
1715/// The extra randomness is used to compress the circuit-specific checks into a
1716/// single equation before combining them with the inner product argument.
1717pub fn verify<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
1718    rng: &mut impl CryptoRng,
1719    transcript: &mut Transcript,
1720    setup: &Setup<Synthetic<F, G>>,
1721    circuit: &Circuit<F>,
1722    claim: &Claim<G>,
1723    proof: Proof<F, G>,
1724    strategy: &impl Strategy,
1725) -> Option<Synthetic<F, G>> {
1726    let Proof {
1727        m_big,
1728        o_big,
1729        m_big_tilde,
1730        t_big,
1731        s_tilde,
1732        t_x,
1733        t_tilde_x,
1734        ipa_proof,
1735        p_big: p,
1736    } = proof;
1737    // Reject malformed claims whose commitment arity does not match the
1738    // circuit. Without this check, an over-long claim could verify because
1739    // commitments past `committed_vars` are bound to the transcript but
1740    // never enter the algebraic commitment relation.
1741    if claim.commitments.len() != circuit.committed_vars {
1742        return None;
1743    }
1744    circuit.commit(transcript);
1745    transcript.commit(claim.encode());
1746    transcript.commit(m_big.encode());
1747    transcript.commit(o_big.encode());
1748    transcript.commit(m_big_tilde.encode());
1749    let padded_vars = circuit.internal_vars.next_power_of_two();
1750    let y = F::random(transcript.noise(b"y"));
1751    let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
1752    let y_inv = y.inv();
1753    let y_inv_powers = powers(F::one(), &y_inv)
1754        .take(padded_vars)
1755        .collect::<Vec<_>>();
1756    let z = F::random(transcript.noise(b"z"));
1757    let z_powers = powers(z.clone(), &z)
1758        .take(circuit.weights.height())
1759        .collect::<Vec<_>>();
1760    let (kappa, theta, lambda, rho, omega) = {
1761        let mut kappa = F::zero();
1762        let mut theta = vec![F::zero(); circuit.committed_vars];
1763        let mut lambda = vec![F::zero(); circuit.internal_vars];
1764        let mut rho = vec![F::zero(); circuit.internal_vars];
1765        let mut omega = vec![F::zero(); circuit.internal_vars];
1766        let theta_start = 1;
1767        let lambda_start = theta_start + circuit.committed_vars;
1768        let rho_start = lambda_start + circuit.internal_vars;
1769        let omega_start = rho_start + circuit.internal_vars;
1770        for (&(i, j), w_ij) in &circuit.weights.weights {
1771            let w_ij = w_ij.clone();
1772            if j >= omega_start {
1773                omega[j - omega_start] += &(w_ij * &z_powers[i]);
1774            } else if j >= rho_start {
1775                rho[j - rho_start] += &(w_ij * &z_powers[i]);
1776            } else if j >= lambda_start {
1777                lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
1778            } else if j >= theta_start {
1779                theta[j - theta_start] += &(w_ij * &z_powers[i]);
1780            } else {
1781                kappa += &(w_ij * &z_powers[i]);
1782            }
1783        }
1784        (kappa, theta, lambda, rho, omega)
1785    };
1786
1787    // We cache a few quantities, which we'll need for MSMs later anyways.
1788    let mut omega_minus_y = omega
1789        .iter()
1790        .cloned()
1791        .zip(&y_powers)
1792        .map(|(omega_i, y_i)| omega_i - y_i)
1793        .collect::<Vec<_>>();
1794    omega_minus_y.extend(
1795        y_powers
1796            .iter()
1797            .skip(circuit.internal_vars)
1798            .cloned()
1799            .map(|y_i| -y_i),
1800    );
1801    let y_inv_rho = y_inv_powers
1802        .iter()
1803        .cloned()
1804        .zip(&rho)
1805        .map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
1806        .collect::<Vec<_>>();
1807    let y_inv_lambda = y_inv_powers
1808        .iter()
1809        .cloned()
1810        .zip(&lambda)
1811        .map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
1812        .collect::<Vec<_>>();
1813    let y_inv_omega_minus_y = y_inv_powers
1814        .iter()
1815        .cloned()
1816        .zip(&omega_minus_y)
1817        .map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
1818        .collect::<Vec<_>>();
1819
1820    let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
1821
1822    for t_big_i in &t_big {
1823        transcript.commit(t_big_i.encode());
1824    }
1825    let x = F::random(transcript.noise(b"x"));
1826    let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
1827
1828    let ipa_g = setup.ipa.g();
1829    let ipa_h = setup.ipa.h();
1830
1831    let value_generator = &setup.value_generator;
1832    let blinding_generator = &setup.blinding_generator;
1833
1834    let t_check = Synthetic::msm(
1835        &[value_generator.clone(), blinding_generator.clone()],
1836        &[t_x.clone(), t_tilde_x],
1837        &Sequential,
1838    ) - &(value_generator.clone() * &((-kappa + &delta_y_z) * &x[1]))
1839        + &(Synthetic::concrete(theta.iter().cloned().zip(claim.commitments.iter().cloned()))
1840            * &x[1])
1841        - &Synthetic::concrete(std::iter::once(&x[0]).chain(&x[2..]).cloned().zip(t_big));
1842
1843    let p_check = {
1844        // Match the prover: only the first `padded_vars` generators are used.
1845        let p_0 = Synthetic::msm(&ipa_h[..padded_vars], &y_inv_omega_minus_y, &Sequential);
1846        let p_1 = Synthetic::msm(&ipa_g[..circuit.internal_vars], &y_inv_rho, &Sequential)
1847            + &Synthetic::msm(&ipa_h[..circuit.internal_vars], &y_inv_lambda, &Sequential);
1848        Synthetic::concrete([
1849            (F::one(), p.clone()),
1850            (-x[0].clone(), m_big),
1851            (-x[1].clone(), o_big),
1852            (-x[2].clone(), m_big_tilde),
1853        ]) - &p_0
1854            - &(p_1 * &x[0])
1855            + &(blinding_generator.clone() * &s_tilde)
1856    };
1857
1858    let ipa_claim = ipa::Claim {
1859        commitment: p,
1860        product: t_x,
1861        y: y_inv,
1862        log_len: padded_vars
1863            .ilog2()
1864            .try_into()
1865            .expect("should be less than 2^256 rows"),
1866    };
1867
1868    let ipa_check = ipa::verify(transcript, &setup.ipa, &ipa_claim, ipa_proof)?;
1869
1870    let final_check =
1871        ipa_check + &(p_check * &F::random(&mut *rng)) + &(t_check * &F::random(&mut *rng));
1872    Some(final_check)
1873}
1874
1875#[commonware_macros::stability(ALPHA)]
1876#[cfg(any(test, feature = "fuzz"))]
1877pub mod fuzz {
1878    use super::*;
1879    use crate::transcript::Version;
1880    use arbitrary::{Arbitrary, Unstructured};
1881    use commonware_math::{
1882        algebra::{Additive, Ring},
1883        test::{F, G},
1884    };
1885    use commonware_parallel::Sequential;
1886    use commonware_utils::test_rng;
1887    use std::sync::OnceLock;
1888
1889    const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_CIRCUIT";
1890
1891    /// Number of IPA generator pairs in the test setup. Large enough to prove
1892    /// and verify any circuit produced by the fuzz plans, whose op count
1893    /// bounds `internal_vars` well below this.
1894    const TEST_SETUP_PAIRS: usize = 64;
1895
1896    pub(super) fn test_setup() -> &'static Setup<G> {
1897        static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
1898        TEST_SETUP.get_or_init(|| {
1899            let count = 2 * TEST_SETUP_PAIRS + 3;
1900            let gens = (1..=count)
1901                .map(|i| G::generator() * &F::from(i as u64))
1902                .collect::<Vec<_>>();
1903            Setup::new(
1904                ipa::Setup::new(
1905                    gens[2 * TEST_SETUP_PAIRS],
1906                    gens[..2 * TEST_SETUP_PAIRS]
1907                        .as_chunks::<2>()
1908                        .0
1909                        .iter()
1910                        .map(|c| (c[0], c[1])),
1911                ),
1912                gens[2 * TEST_SETUP_PAIRS + 1],
1913                gens[2 * TEST_SETUP_PAIRS + 2],
1914            )
1915        })
1916    }
1917
1918    fn quadratic_value(a: F, b: F, c: F, x: F) -> F {
1919        a * &x * &x + &(b * &x) + &c
1920    }
1921
1922    pub(super) fn quadratic_circuit(a: F, b: F, c: F) -> Circuit<F> {
1923        let mut weights = SparseMatrix::default();
1924
1925        // Bind l_0 = x.
1926        weights[(0, 1)] = F::one();
1927        weights[(0, 3)] = -F::one();
1928
1929        // Bind r_0 = x.
1930        weights[(1, 1)] = F::one();
1931        weights[(1, 4)] = -F::one();
1932
1933        // Enforce y = a x^2 + b x + c.
1934        weights[(2, 0)] = c;
1935        weights[(2, 1)] = b;
1936        weights[(2, 2)] = -F::one();
1937        weights[(2, 5)] = a;
1938
1939        Circuit::new(2, weights).expect("quadratic circuit layout should be valid")
1940    }
1941
1942    /// A quadratic circuit paired with a witness that may or may not satisfy it.
1943    pub struct Case {
1944        circuit: Circuit<F>,
1945        witness: Witness<F>,
1946    }
1947
1948    impl Case {
1949        fn is_satisfied(&self) -> bool {
1950            self.circuit.is_satisfied(
1951                &self.witness.values,
1952                &self.witness.left,
1953                &self.witness.right,
1954            )
1955        }
1956
1957        fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
1958            let a = u.arbitrary::<F>()?;
1959            let b = u.arbitrary::<F>()?;
1960            let c = u.arbitrary::<F>()?;
1961            let x = u.arbitrary::<F>()?;
1962            let valid = u.arbitrary::<bool>()?;
1963            let mut y = quadratic_value(a, b, c, x);
1964            if !valid {
1965                let mut tweak = u.arbitrary::<F>()?;
1966                if tweak == F::zero() {
1967                    tweak = F::one()
1968                }
1969                y += &tweak;
1970            }
1971
1972            let x_sq = x * &x;
1973            let witness = Witness::new(
1974                vec![x, y],
1975                vec![u.arbitrary::<F>()?, u.arbitrary::<F>()?],
1976                vec![x],
1977                vec![x],
1978                vec![x_sq],
1979            )
1980            .expect("quadratic witness should have matching vector lengths");
1981            let circuit = quadratic_circuit(a, b, c);
1982            let out = Self { circuit, witness };
1983            assert_eq!(
1984                out.is_satisfied(),
1985                valid,
1986                "quadratic case should match requested validity",
1987            );
1988            Ok(out)
1989        }
1990    }
1991
1992    pub enum Plan {
1993        ProveAndVerify(Case),
1994        ZkcConversion(crate::zk::circuit::fuzz::Plan),
1995    }
1996
1997    impl<'a> Arbitrary<'a> for Plan {
1998        fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1999            match u.int_in_range(0..=1)? {
2000                0 => Ok(Self::ProveAndVerify(Case::arbitrary(u)?)),
2001                1 => Ok(Self::ZkcConversion(u.arbitrary()?)),
2002                _ => unreachable!("plan variant out of range"),
2003            }
2004        }
2005    }
2006
2007    fn assert_verify_matches_satisfaction(case: &Case) {
2008        let setup = test_setup();
2009        let claim = case.witness.claim(setup);
2010        let verified = prove_and_verify(setup, &case.circuit, &claim, &case.witness);
2011        assert_eq!(verified, case.is_satisfied());
2012    }
2013
2014    /// Prove `claim` against `circuit` with `witness`, then verify, returning
2015    /// whether verification accepted. A `prove` failure counts as rejection.
2016    fn prove_and_verify(
2017        setup: &Setup<G>,
2018        circuit: &Circuit<F>,
2019        claim: &Claim<G>,
2020        witness: &Witness<F>,
2021    ) -> bool {
2022        let mut rng = test_rng();
2023        let mut prover_transcript = Transcript::new(NAMESPACE, Version::V1);
2024        let Some(proof) = super::prove(
2025            &mut rng,
2026            &mut prover_transcript,
2027            setup,
2028            circuit,
2029            claim,
2030            witness,
2031            &Sequential,
2032        ) else {
2033            return false;
2034        };
2035        let mut verifier_transcript = Transcript::new(NAMESPACE, Version::V1);
2036        setup
2037            .eval(
2038                |vs| {
2039                    verify(
2040                        &mut rng,
2041                        &mut verifier_transcript,
2042                        vs,
2043                        circuit,
2044                        claim,
2045                        proof,
2046                        &Sequential,
2047                    )
2048                },
2049                &Sequential,
2050            )
2051            .map(|g| g == G::zero())
2052            .unwrap_or(false)
2053    }
2054
2055    /// Check that converting a ZK circuit to a bulletproofs circuit and
2056    /// witness preserves satisfaction, committing a random subset of the
2057    /// witnesses.
2058    ///
2059    /// For satisfied circuits this also runs a full prove/verify roundtrip and
2060    /// checks that tampering with a committed commitment is rejected. The
2061    /// latter is the binding property: every committed value (including a
2062    /// witness constrained by nothing) must enter the verification equation
2063    /// with a nonzero coefficient, so it cannot be swapped for an arbitrary
2064    /// group element.
2065    pub(super) fn assert_zkc_conversion_preserves_satisfaction(
2066        plan: &crate::zk::circuit::fuzz::Plan,
2067        u: &mut Unstructured<'_>,
2068    ) -> arbitrary::Result<()> {
2069        let valued = plan.build();
2070        let mut committed = Vec::new();
2071        for i in 0..valued.circuit.witnesses {
2072            if u.arbitrary()? {
2073                committed.push(crate::zk::circuit::CircuitIdx::Witness(i));
2074            }
2075        }
2076        let (circuit, witness) =
2077            zkc_to_circuit_and_witness(Some(&mut test_rng()), valued, &committed);
2078        let satisfied = witness.is_satisfied(&circuit);
2079        assert_eq!(satisfied, plan.satisfied(), "plan: {plan:?}");
2080
2081        if satisfied {
2082            let setup = test_setup();
2083            assert!(
2084                circuit.internal_vars() <= TEST_SETUP_PAIRS,
2085                "circuit too large for test setup ({} > {TEST_SETUP_PAIRS}); plan: {plan:?}",
2086                circuit.internal_vars()
2087            );
2088            let honest = witness.claim(setup);
2089            assert!(
2090                prove_and_verify(setup, &circuit, &honest, &witness),
2091                "honest claim must verify; plan: {plan:?}"
2092            );
2093            if !committed.is_empty() {
2094                let j = u.choose_index(committed.len())?;
2095                let mut tampered = witness.claim(setup);
2096                tampered.commitments[j] += setup.value_generator();
2097                assert!(
2098                    !prove_and_verify(setup, &circuit, &tampered, &witness),
2099                    "tampering committed value {j} must break verification; plan: {plan:?}"
2100                );
2101            }
2102        }
2103        Ok(())
2104    }
2105
2106    impl Plan {
2107        pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
2108            match self {
2109                Self::ProveAndVerify(case) => assert_verify_matches_satisfaction(&case),
2110                Self::ZkcConversion(plan) => {
2111                    assert_zkc_conversion_preserves_satisfaction(&plan, u)?
2112                }
2113            }
2114            Ok(())
2115        }
2116    }
2117}
2118
2119#[cfg(test)]
2120mod test {
2121    use super::{Circuit, Setup, SparseMatrix, Witness, fuzz, prove, verify};
2122    use crate::{
2123        transcript::{Transcript, Version},
2124        zk::circuit as zk,
2125    };
2126    use commonware_codec::{Decode, Encode};
2127    use commonware_invariants::minifuzz;
2128    use commonware_math::{
2129        algebra::{Additive, CryptoGroup, Ring},
2130        test::{F, G},
2131    };
2132    use commonware_parallel::Sequential;
2133    use commonware_utils::test_rng;
2134
2135    #[test]
2136    fn test_sparse_matrix_encoding_binds_dimensions() {
2137        let matrix = SparseMatrix::<F>::default();
2138        let mut wider = SparseMatrix::<F>::default();
2139        wider.pad(1, 0);
2140        let mut taller = SparseMatrix::<F>::default();
2141        taller.pad(0, 1);
2142
2143        assert_eq!(matrix.weights, wider.weights);
2144        assert_eq!(matrix.weights, taller.weights);
2145        assert_ne!(matrix.encode(), wider.encode());
2146        assert_ne!(matrix.encode(), taller.encode());
2147        assert_ne!(wider.encode(), taller.encode());
2148    }
2149
2150    #[test]
2151    fn test_distinct_valid_circuits_encode_differently() {
2152        let mut no_internal_vars = SparseMatrix::<F>::default();
2153        no_internal_vars.pad(1, 0);
2154        let no_internal_vars =
2155            Circuit::new(0, no_internal_vars).expect("width 1 is a valid circuit layout");
2156
2157        let mut one_internal_var = SparseMatrix::<F>::default();
2158        one_internal_var.pad(4, 0);
2159        let one_internal_var =
2160            Circuit::new(0, one_internal_var).expect("width 4 is a valid circuit layout");
2161
2162        assert_eq!(no_internal_vars.internal_vars(), 0);
2163        assert_eq!(one_internal_var.internal_vars(), 1);
2164        assert!(no_internal_vars.is_satisfied(&[], &[], &[]));
2165        assert!(!one_internal_var.is_satisfied(&[], &[], &[]));
2166        assert!(one_internal_var.is_satisfied(&[], &[F::zero()], &[F::zero()]));
2167        assert_ne!(no_internal_vars.encode(), one_internal_var.encode());
2168    }
2169
2170    #[test]
2171    fn test_converted_circuits_bind_internal_vars() {
2172        let (one_internal_var, _) = zk::build::<F>(|ctx| {
2173            let a = zk::Var::witness(ctx, |_| F::zero());
2174            let b = zk::Var::witness(ctx, |_| F::zero());
2175            let product = a * &b;
2176            product.assert_eq(&product);
2177            Vec::new()
2178        });
2179        let one_internal_var = super::zkc_to_circuit(one_internal_var, &[]);
2180
2181        let (two_internal_vars, _) = zk::build::<F>(|ctx| {
2182            let w0 = zk::Var::witness(ctx, |_| F::zero());
2183            let w1 = zk::Var::witness(ctx, |_| F::zero());
2184            let w2 = zk::Var::witness(ctx, |_| F::zero());
2185            w1.assert_eq(&w1);
2186            w0.assert_eq(&w0);
2187            w2.assert_eq(&w2);
2188            Vec::new()
2189        });
2190        let two_internal_vars = super::zkc_to_circuit(two_internal_vars, &[]);
2191
2192        assert_eq!(one_internal_var.internal_vars(), 1);
2193        assert_eq!(two_internal_vars.internal_vars(), 2);
2194        assert_eq!(
2195            one_internal_var.weights.encode(),
2196            two_internal_vars.weights.encode()
2197        );
2198        assert_ne!(one_internal_var.encode(), two_internal_vars.encode());
2199    }
2200
2201    #[test]
2202    fn test_zkc_conversion_preserves_satisfaction_minifuzz() {
2203        minifuzz::test(|u| {
2204            let plan = u.arbitrary::<zk::fuzz::Plan>()?;
2205            fuzz::assert_zkc_conversion_preserves_satisfaction(&plan, u)
2206        });
2207    }
2208
2209    #[test]
2210    fn test_zkc_conversion_preserves_committed_order() {
2211        let (valued, _) = zk::build_with_values(|ctx| {
2212            let a = zk::Var::witness(ctx, |_| F::from(1u64));
2213            let b = zk::Var::witness(ctx, |_| F::from(2u64));
2214            let c = a * &b;
2215            c.assert_eq(&zk::Var::constant(ctx, F::from(2u64)));
2216            Vec::new()
2217        });
2218        let (circuit, witness) = super::zkc_to_circuit_and_witness(
2219            Some(&mut test_rng()),
2220            valued,
2221            &[
2222                zk::CircuitIdx::Witness(1),
2223                zk::CircuitIdx::Witness(0),
2224                zk::CircuitIdx::Witness(1),
2225            ],
2226        );
2227        assert!(witness.is_satisfied(&circuit));
2228        assert_eq!(
2229            witness.values,
2230            vec![F::from(2u64), F::from(1u64), F::from(2u64)]
2231        );
2232    }
2233
2234    #[test]
2235    fn test_zkc_conversion_add_doubling_chain() {
2236        // A shared Add chain (x = x + x, repeated) must convert in time and
2237        // memory proportional to the chain length, not 2^length.
2238        const DEPTH: usize = 64;
2239        let mut expected = F::one();
2240        for _ in 0..DEPTH {
2241            expected = expected + &expected;
2242        }
2243        let (valued, _) = zk::build_with_values(|ctx| {
2244            let mut x = zk::Var::witness(ctx, |_| F::one());
2245            for _ in 0..DEPTH {
2246                x = x.clone() + &x;
2247            }
2248            x.assert_eq(&zk::Var::constant(ctx, expected));
2249            Vec::new()
2250        });
2251        let (circuit, witness) = super::zkc_to_circuit_and_witness(
2252            Some(&mut test_rng()),
2253            valued,
2254            &[zk::CircuitIdx::Witness(0)],
2255        );
2256        assert!(witness.is_satisfied(&circuit));
2257    }
2258
2259    #[test]
2260    fn test_random_r1cs_minifuzz() {
2261        const N: usize = 2;
2262        const M: usize = 4;
2263
2264        minifuzz::test(|u| {
2265            let a = u.arbitrary::<[[F; N]; M]>()?;
2266            let b = u.arbitrary::<[[F; N]; M]>()?;
2267            let c = u.arbitrary::<[[F; N]; M]>()?;
2268            let z = u.arbitrary::<[F; N]>()?;
2269            let mut left = [F::zero(); M];
2270            let mut right = [F::zero(); M];
2271            let mut satisfied = true;
2272            for i in 0..M {
2273                let mut acc = F::zero();
2274                for j in 0..N {
2275                    left[i] += &(a[i][j] * &z[j]);
2276                    right[i] += &(b[i][j] * &z[j]);
2277                    acc += &(c[i][j] * &z[j]);
2278                }
2279                satisfied = satisfied && acc == left[i] * &right[i];
2280            }
2281            let mut k = 0;
2282            let mut weights = SparseMatrix::default();
2283
2284            // Bind the left values:
2285            for i in 0..M {
2286                weights[(k, 1 + N + i)] = -F::one();
2287                for j in 0..N {
2288                    weights[(k, 1 + j)] = a[i][j];
2289                }
2290                k += 1;
2291            }
2292            // Bind the right values:
2293            for i in 0..M {
2294                weights[(k, 1 + N + M + i)] = -F::one();
2295                for j in 0..N {
2296                    weights[(k, 1 + j)] = b[i][j];
2297                }
2298                k += 1;
2299            }
2300            // Bind the product values:
2301            for i in 0..M {
2302                weights[(k, 1 + N + 2 * M + i)] = -F::one();
2303                for j in 0..N {
2304                    weights[(k, 1 + j)] = c[i][j];
2305                }
2306                k += 1;
2307            }
2308            assert_eq!(
2309                satisfied,
2310                Circuit::new(N, weights)
2311                    .expect("should be able to make circuit")
2312                    .is_satisfied(&z, &left, &right)
2313            );
2314            Ok(())
2315        });
2316    }
2317
2318    #[test]
2319    fn test_setup_roundtrip() {
2320        let setup = fuzz::test_setup();
2321        let encoded = setup.encode();
2322        let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.ipa.g().len(), ()))
2323            .expect("setup should decode with its own length bound");
2324        assert!(setup == &decoded);
2325        assert_eq!(decoded.encode(), encoded);
2326    }
2327
2328    #[test]
2329    fn test_fuzz() {
2330        minifuzz::test(|u| {
2331            u.arbitrary::<fuzz::Plan>()?.run(u)?;
2332            Ok(())
2333        });
2334    }
2335
2336    /// Regression test for an arity bug in `verify`: an over-long claim
2337    /// (with more commitments than `Circuit::committed_vars()`) must be
2338    /// rejected. Without the explicit arity check, the extra commitments
2339    /// would be bound to the transcript but ignored by the algebraic
2340    /// commitment relation, letting a malformed claim verify against a
2341    /// proof generated against that same malformed claim.
2342    #[test]
2343    fn verify_rejects_over_long_claim() {
2344        let setup = fuzz::test_setup();
2345
2346        // Use the existing 2-committed-value quadratic circuit:
2347        // y = a x^2 + b x + c, with x = 3, a = b = c = 1, so y = 13.
2348        let a = F::one();
2349        let b = F::one();
2350        let c = F::one();
2351        let x = F::from(3u8);
2352        let y = a * &x * &x + &(b * &x) + &c;
2353        let circuit = fuzz::quadratic_circuit(a, b, c);
2354
2355        let witness = Witness::new(
2356            vec![x, y],
2357            vec![F::zero(), F::zero()],
2358            vec![x],
2359            vec![x],
2360            vec![x * &x],
2361        )
2362        .expect("witness vector lengths must be consistent");
2363
2364        // Build an honest claim, then append a junk commitment so that
2365        // `claim.commitments.len() == 3` while `circuit.committed_vars() == 2`.
2366        let mut claim = witness.claim(setup);
2367        claim.commitments.push(G::generator() * &F::from(9u8));
2368
2369        let mut rng = test_rng();
2370        let mut prover_transcript = Transcript::new(b"verify-rejects-over-long-claim", Version::V1);
2371        let proof = prove(
2372            &mut rng,
2373            &mut prover_transcript,
2374            setup,
2375            &circuit,
2376            &claim,
2377            &witness,
2378            &Sequential,
2379        )
2380        .expect("prove still produces a proof against the malformed claim");
2381
2382        let mut verifier_transcript =
2383            Transcript::new(b"verify-rejects-over-long-claim", Version::V1);
2384        let verified = setup.eval(
2385            |vs| {
2386                verify(
2387                    &mut rng,
2388                    &mut verifier_transcript,
2389                    vs,
2390                    &circuit,
2391                    &claim,
2392                    proof,
2393                    &Sequential,
2394                )
2395            },
2396            &Sequential,
2397        );
2398        assert!(
2399            verified.is_none(),
2400            "verify must reject a claim whose commitment arity does not match the circuit"
2401        );
2402    }
2403}