Skip to main content

commonware_cryptography/zk/bulletproofs/
ipa.rs

1//! This module provides an "Inner Product Argument", using [Bulletproofs](https://eprint.iacr.org/2017/1066).
2//!
3//! # Background
4//!
5//! We have a cryptographic group `G`, with associated scalar field `F`.
6//!
7//! Prior to this, we have agreed on distinct group elements `G_i`, `H_i`, and `Q`.
8//!
9//! A prover has two vectors of field elements `a_i` and `b_i`. They have created
10//! a commitment `P` to these vectors, defined as `P = <a_i, G_i> + <b_i, y^i H_i>`.
11//! They want to convince the verifier that the product of the vectors that `P`
12//! commits to is equal to `c = <a_i, b_i>`.
13//!
14//! One way to do this is to simply send the vectors `a_i` and `b_i`. The goal
15//! of the Bulletproofs IPA is to convince the verifier while sending less information.
16//! The result is that we can convince the verifier while sending `O(lg N)` group elements
17//! rather than `O(N)` field elements.
18//!
19//! Importantly, this argument is NOT zero-knowledge. The verifier learns information
20//! about the vectors. This argument can be used as part of a broader zero-knowledge
21//! protocol though, but this step by itself does not provide that property.
22//!
23//! The prover's work is mostly `O(N)` scalar multiplications, and the verifier's
24//! work is mostly an MSM of size `O(N)`. The verifier gets to be a bit faster because
25//! they can do a large MSM rather than many individual scalar multiplications,
26//! but the asymptotic complexity is the same.
27//!
28//! # Usage
29//!
30//! Let's look at the concrete API now.
31//!
32//! We need group elements we can use to create commitments. This is what [`Setup`]
33//! is for. A single [`Setup`] can support arguments for vectors of various sizes.
34//! [`Setup::new`] creates a setup by explicitly providing all of the generators
35//! we need.
36//!
37//! Next, we need to actual vectors we want to make a proof over. This is the
38//! [`Witness`] type. This can be constructed with [`Witness::new`], which enforces
39//! that the vectors have the same length, and that this length is a power of two.
40//! This is a technical requirement for the Bulletproofs IPA. Padding should be
41//! handled at the layer above.
42//!
43//! Next, we need the public statement, represented by [`Claim`]. This contains
44//! the claimed product `c`, and the commitment `P`. For a honest prover that
45//! wants to generate a claim from a witness, you can use [`Witness::new_with_claim`],
46//! which makes sure that the witness satisfies the same conditions as [`Witness::new`],
47//! while also calculating the claim.
48//!
49//! This is not necessarily what you want to do in all situations. When using
50//! the Bulletproofs IPA as a step in a larger proof system, you might have a claim
51//! and witness which come from previous steps. Because of that, you can construct
52//! a [`Claim`] directly, using its public fields.
53//!
54//! Because a single [`Setup`] can support vectors of different lengths, the claim
55//! also needs to contain information about the length of these vectors.
56//!
57//! Given a [`Setup`], [`Witness`], and [`Claim`], you can create a [`Proof`]
58//! with [`prove`].
59//!
60//! Both [`prove`] and [`verify`] take a [`Transcript`]. The proof is only valid
61//! for the transcript state used to produce it, so the verifier must replay the
62//! same transcript history before calling [`verify`].
63//!
64//! On the verifier side, we don't have a [`Witness`], and can instead check
65//! that the prover had a valid witness, using their [`Proof`], through [`verify`].
66//! The result is a [`Synthetic`] verification equation that can be evaluated
67//! against the setup's generators, or combined with other equations for batching.
68//!
69//! ## Example
70//!
71//! ```rust
72//! # use commonware_cryptography::{
73//! #     bls12381::primitives::group::{G1, Scalar},
74//! #     transcript::Transcript,
75//! #     zk::bulletproofs::ipa::{prove, verify, Setup, Witness},
76//! # };
77//! # use commonware_math::algebra::{Additive, CryptoGroup, Ring};
78//! # use commonware_parallel::Sequential;
79//! # type F = Scalar;
80//! # type G = G1;
81//! # #[allow(non_snake_case)]
82//! # let GENERATORS: [G; 9] = core::array::from_fn(|i| G::generator() * &F::from(i as u64 + 1));
83//!
84//! // It's important that these generators have no known discrete logarithm
85//! // relationships relative to each other. For example, multipying a single
86//! // generator would be insecure!
87//! let setup = Setup::new(
88//!     GENERATORS[0].clone(),
89//!     GENERATORS[1..]
90//!         .chunks_exact(2)
91//!         .map(|chunk| (chunk[0].clone(), chunk[1].clone())),
92//! );
93//!
94//! // Witness vectors must have the same power-of-two length.
95//! let (witness, claim) = Witness::new_with_claim(
96//!     &setup,
97//!     F::one(),
98//!     [
99//!         (F::from(3u64), F::from(4u64)),
100//!         (F::from(5u64), F::from(6u64)),
101//!         (F::from(7u64), F::from(8u64)),
102//!         (F::from(9u64), F::from(10u64)),
103//!     ],
104//! )
105//! .expect("witness should fit the setup");
106//!
107//! // The proof is bound to this transcript state.
108//! let mut prover_transcript = Transcript::new(b"ipa-example");
109//! prover_transcript.commit(b"context".as_slice());
110//!
111//! // Any Strategy works here. Sequential is simplest; a parallel strategy can
112//! // reduce wall-clock time on larger inputs without changing the proof.
113//! let strategy = Sequential;
114//! let proof = prove(&mut prover_transcript, &setup, &claim, witness, &strategy)
115//!     .expect("claim should match the witness and setup");
116//!
117//! // Verification must replay the same transcript state.
118//! let mut verifier_transcript = Transcript::new(b"ipa-example");
119//! verifier_transcript.commit(b"context".as_slice());
120//! let valid = setup
121//!     .eval(|vs| verify(&mut verifier_transcript, vs, &claim, proof), &strategy)
122//!     .map(|g| g == G::zero())
123//!     .unwrap_or(false);
124//! assert!(valid);
125//! ```
126//!
127//! # References
128//!
129//! The [Dalek crate](https://doc-internal.dalek.rs/bulletproofs/notes/inner_product_proof/index.html)
130//! was an invaluable reference when implementing and documenting this module.
131
132use crate::transcript::{Summary, Transcript};
133use bytes::{Buf, BufMut};
134use commonware_codec::{Encode, EncodeSize, Error, RangeCfg, Read, ReadExt, Write};
135use commonware_math::{
136    algebra::{powers, CryptoGroup, Field, Random, Space},
137    synthetic::Synthetic,
138};
139use commonware_parallel::{Sequential, Strategy};
140
141/// A setup decides on what group elements we use to commit to vectors and their product.
142///
143/// A setup for an inner product argument for `c = <a_i, b_i>` needs generators
144/// to commit to `a_i`, which we call `G_i`, generators for `b_i`, which we call
145/// `H_i`, and a generator for the product, `c`, which we call `Q`, or "the product generator".
146///
147/// We can support inner products of different sizes, as long as we have enough generators.
148///
149/// To construct this type, see [`Self::new`].
150#[derive(Debug, PartialEq)]
151pub struct Setup<G> {
152    g: Vec<G>,
153    h: Vec<G>,
154    product_generator: G,
155}
156
157impl<G: Write> Write for Setup<G> {
158    fn write(&self, buf: &mut impl BufMut) {
159        self.product_generator.write(buf);
160        self.g.len().write(buf);
161        for (g_i, h_i) in self.g.iter().zip(&self.h) {
162            g_i.write(buf);
163            h_i.write(buf);
164        }
165    }
166}
167
168impl<G: EncodeSize> EncodeSize for Setup<G> {
169    fn encode_size(&self) -> usize {
170        self.product_generator.encode_size()
171            + self.g.len().encode_size()
172            + self
173                .g
174                .iter()
175                .zip(&self.h)
176                .map(|(g_i, h_i)| g_i.encode_size() + h_i.encode_size())
177                .sum::<usize>()
178    }
179}
180
181impl<G: Read> Read for Setup<G> {
182    type Cfg = (usize, G::Cfg);
183
184    fn read_cfg(buf: &mut impl Buf, (max_len, cfg): &Self::Cfg) -> Result<Self, Error> {
185        let product_generator = G::read_cfg(buf, cfg)?;
186        let len = usize::read_cfg(buf, &RangeCfg::new(..=*max_len))?;
187        let mut g = Vec::with_capacity(len);
188        let mut h = Vec::with_capacity(len);
189        for _ in 0..len {
190            g.push(G::read_cfg(buf, cfg)?);
191            h.push(G::read_cfg(buf, cfg)?);
192        }
193        Ok(Self {
194            g,
195            h,
196            product_generator,
197        })
198    }
199}
200
201#[cfg(any(test, feature = "arbitrary"))]
202impl<G> arbitrary::Arbitrary<'_> for Setup<G>
203where
204    G: for<'a> arbitrary::Arbitrary<'a>,
205{
206    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
207        let g_and_h = u.arbitrary::<Vec<(G, G)>>()?;
208        Ok(Self::new(u.arbitrary()?, g_and_h))
209    }
210}
211
212impl<G> Setup<G> {
213    /// Create a new [`Setup`], given specific choices of the generator.
214    ///
215    /// You MUST ensure that all of the values provided to this function are unique.
216    pub fn new(product_generator: G, g_and_h: impl IntoIterator<Item = (G, G)>) -> Self {
217        let (g, h): (Vec<G>, Vec<G>) = g_and_h.into_iter().collect();
218        Self {
219            g,
220            h,
221            product_generator,
222        }
223    }
224
225    /// The left-side generators `G_i`.
226    pub fn g(&self) -> &[G] {
227        &self.g
228    }
229
230    /// The right-side generators `H_i`.
231    pub fn h(&self) -> &[G] {
232        &self.h
233    }
234
235    /// The product generator `Q`.
236    pub const fn product_generator(&self) -> &G {
237        &self.product_generator
238    }
239
240    /// Check if this setup supports claims of a given length.
241    pub const fn supports(&self, lg_len: u8) -> bool {
242        self.g.len() >> lg_len > 0
243    }
244
245    /// Build a virtual setup, call `f` to obtain a verification equation,
246    /// and evaluate it against the concrete generators in `self`.
247    ///
248    /// Returns `None` when `f` returns `None` (malformed proof).
249    /// Otherwise returns the evaluated group element, which should be
250    /// zero for a valid proof.
251    pub fn eval<F: Field>(
252        &self,
253        f: impl FnOnce(&Setup<Synthetic<F, G>>) -> Option<Synthetic<F, G>>,
254        strategy: &impl Strategy,
255    ) -> Option<G>
256    where
257        G: Space<F>,
258    {
259        let n = self.g.len();
260        let mut gens = Synthetic::<F, G>::generators();
261        let vg: Vec<_> = (0..n)
262            .map(|_| gens.next().expect("generators is infinite"))
263            .collect();
264        let vh: Vec<_> = (0..n)
265            .map(|_| gens.next().expect("generators is infinite"))
266            .collect();
267        let vq = gens.next().expect("generators is infinite");
268        let vs = Setup::new(vq, vg.into_iter().zip(vh));
269        let mut flat = Vec::with_capacity(2 * n + 1);
270        flat.extend_from_slice(&self.g);
271        flat.extend_from_slice(&self.h);
272        flat.push(self.product_generator.clone());
273        f(&vs).map(|v| v.eval(&flat, strategy))
274    }
275}
276
277/// The public claim we're making about the inner product.
278///
279/// We claim that our commitment `P` is equal to `<a_i, G_i> + <b_i, y^i H_i>`,
280/// and that our product `c` is equal to `<a_i, b_i>`.
281#[derive(Debug, PartialEq)]
282pub struct Claim<F, G> {
283    pub commitment: G,
284    pub product: F,
285    pub y: F,
286    /// The claimed vector length, stored as `log2(len)`.
287    ///
288    /// Inner product arguments require power-of-two vector lengths, so storing
289    /// the logarithm is enough to recover the full claimed length.
290    pub log_len: u8,
291}
292
293impl<F: Write, G: Write> Write for Claim<F, G> {
294    fn write(&self, buf: &mut impl BufMut) {
295        self.commitment.write(buf);
296        self.product.write(buf);
297        self.y.write(buf);
298        self.log_len.write(buf);
299    }
300}
301
302impl<F: EncodeSize, G: EncodeSize> EncodeSize for Claim<F, G> {
303    fn encode_size(&self) -> usize {
304        self.commitment.encode_size()
305            + self.product.encode_size()
306            + self.y.encode_size()
307            + self.log_len.encode_size()
308    }
309}
310
311impl<F: Read, G: Read> Read for Claim<F, G> {
312    type Cfg = (G::Cfg, F::Cfg);
313
314    fn read_cfg(buf: &mut impl Buf, (g_cfg, f_cfg): &Self::Cfg) -> Result<Self, Error> {
315        Ok(Self {
316            commitment: G::read_cfg(buf, g_cfg)?,
317            product: F::read_cfg(buf, f_cfg)?,
318            y: F::read_cfg(buf, f_cfg)?,
319            log_len: u8::read(buf)?,
320        })
321    }
322}
323
324#[cfg(any(test, feature = "arbitrary"))]
325impl<F, G> arbitrary::Arbitrary<'_> for Claim<F, G>
326where
327    F: for<'a> arbitrary::Arbitrary<'a>,
328    G: for<'a> arbitrary::Arbitrary<'a>,
329{
330    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
331        Ok(Self {
332            commitment: u.arbitrary()?,
333            product: u.arbitrary()?,
334            y: u.arbitrary()?,
335            log_len: u.arbitrary()?,
336        })
337    }
338}
339
340/// The witness contains the actual vectors `a_i` and `b_i` for the inner product argument.
341///
342/// This struct guarantees that their lengths are equal, and a power of two.
343#[derive(Clone)]
344pub struct Witness<F> {
345    a: Vec<F>,
346    b: Vec<F>,
347}
348
349impl<F> Witness<F> {
350    /// Create a new witness, from the two vectors whose product we're taking.
351    ///
352    /// This function returns `None` if the iterator does not produce a power of
353    /// two number of elements.
354    pub fn new(elements: impl IntoIterator<Item = (F, F)>) -> Option<Self> {
355        let (a, b): (Vec<F>, Vec<F>) = elements.into_iter().collect();
356        if !a.len().is_power_of_two() {
357            return None;
358        }
359        Some(Self { a, b })
360    }
361}
362
363impl<F: Field> Witness<F> {
364    /// Like [`Self::new`], but also produces a [`Claim`], for convenience.
365    ///
366    /// In some situations, you have a claim from somewhere else, using the
367    /// proof system in this module as just one step in some larger proof.
368    ///
369    /// If you don't have a claim, this lets you compute a valid one.
370    ///
371    /// To do so, you need a [`Setup`], which can be reused across different
372    /// witnesses.
373    pub fn new_with_claim<G: Space<F>>(
374        setup: &Setup<G>,
375        y: F,
376        elements: impl IntoIterator<Item = (F, F)>,
377    ) -> Option<(Self, Claim<F, G>)> {
378        let witness = Self::new(elements)?;
379        // By invariant, h has the same len as g, and b has the same len as a,
380        // so we can just check this.
381        if setup.g.len() < witness.a.len() {
382            return None;
383        }
384        let claim = {
385            let mut commitment = G::zero();
386            let mut product = F::zero();
387            for ((((a_i, b_i), g_i), h_i), y_i) in witness
388                .a
389                .iter()
390                .zip(&witness.b)
391                .zip(&setup.g)
392                .zip(&setup.h)
393                .zip(powers(F::one(), &y))
394            {
395                commitment += &(g_i.clone() * a_i + &(h_i.clone() * &(b_i.clone() * &y_i)));
396                product += &(a_i.clone() * b_i);
397            }
398            Claim {
399                commitment,
400                product,
401                y,
402                log_len: witness.a.len().ilog2() as u8,
403            }
404        };
405        Some((witness, claim))
406    }
407}
408
409/// A proof for the inner product argument.
410#[derive(Clone, Debug, PartialEq)]
411pub struct Proof<F, G> {
412    l_r_coms: Vec<(G, G)>,
413    /// Summary of the transcript after the public statement and all proof messages.
414    ///
415    /// This binds even zero-round exchanges to the transcript.
416    transcript_summary: Summary,
417    a_final: F,
418    b_final: F,
419}
420
421impl<F: Write, G: Write> Write for Proof<F, G> {
422    fn write(&self, buf: &mut impl BufMut) {
423        self.l_r_coms.write(buf);
424        self.transcript_summary.write(buf);
425        self.a_final.write(buf);
426        self.b_final.write(buf);
427    }
428}
429
430impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
431    fn encode_size(&self) -> usize {
432        self.l_r_coms.encode_size()
433            + self.transcript_summary.encode_size()
434            + self.a_final.encode_size()
435            + self.b_final.encode_size()
436    }
437}
438
439impl<F: Read, G: Read> Read for Proof<F, G> {
440    type Cfg = (usize, (G::Cfg, F::Cfg));
441
442    fn read_cfg(buf: &mut impl Buf, (max_len, (g_cfg, f_cfg)): &Self::Cfg) -> Result<Self, Error> {
443        let max_rounds = if *max_len == 0 {
444            0
445        } else {
446            max_len.ilog2() as usize
447        };
448        Ok(Self {
449            l_r_coms: Vec::<(G, G)>::read_cfg(
450                buf,
451                &(RangeCfg::new(..=max_rounds), (g_cfg.clone(), g_cfg.clone())),
452            )?,
453            transcript_summary: Summary::read(buf)?,
454            a_final: F::read_cfg(buf, f_cfg)?,
455            b_final: F::read_cfg(buf, f_cfg)?,
456        })
457    }
458}
459
460#[cfg(any(test, feature = "arbitrary"))]
461impl<F, G> arbitrary::Arbitrary<'_> for Proof<F, G>
462where
463    F: for<'a> arbitrary::Arbitrary<'a>,
464    G: for<'a> arbitrary::Arbitrary<'a>,
465{
466    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
467        let rounds = u.int_in_range(0..=usize::BITS as usize - 1)?;
468        let l_r_coms = (0..rounds)
469            .map(|_| u.arbitrary())
470            .collect::<arbitrary::Result<Vec<_>>>()?;
471        Ok(Self {
472            l_r_coms,
473            transcript_summary: u.arbitrary()?,
474            a_final: u.arbitrary()?,
475            b_final: u.arbitrary()?,
476        })
477    }
478}
479
480/// Prove that a given [`Witness`] is valid, relative to a [`Claim`] and [`Setup`].
481///
482/// We also take in a transcript. The proof is bound to the transcript state at
483/// the time of this call, so the verifier must replay the same transcript
484/// history before calling [`verify`].
485///
486/// This returns `None` if the setup is too short for the witness, or if the
487/// claim's vector length does not match the witness length.
488pub fn prove<F: Field + Random, G: CryptoGroup<Scalar = F> + Encode>(
489    transcript: &mut Transcript,
490    setup: &Setup<G>,
491    claim: &Claim<F, G>,
492    witness: Witness<F>,
493    strategy: &impl Strategy,
494) -> Option<Proof<F, G>>
495where
496    Claim<F, G>: Encode,
497{
498    // Okay, let's explain the math behind how this proof system works.
499    //
500    // (Once again, https://doc-internal.dalek.rs/bulletproofs/notes/inner_product_proof/index.html,
501    // is a useful reference, inspiring much of this documentation).
502    //
503    // We'll describe the protocol as if it were interactive. We turn it into
504    // a non-interactive protocol using the venerable Fiat-Shamir transform.
505    // The Transcript abstraction helps us with that.
506    //
507    // We have vectors a_i and b_i, in our claim, we have:
508    //
509    //   P = <a_i, G_i> + <b_i, y^i H_i>
510    //   c = <a_i, b_i>
511    //
512    // for recursion, it's convenient to have a statement about one commitment
513    // instead. We can also run the protocol over H'_i := y^i H_i instead.
514    //
515    // We can have the verifier give us a challenge w, compressing this into:
516    //
517    //  P = <a_i, G_i> + <b_i, H'_i> + c * w * Q
518    //
519    // where Q is the additional generator from our setup.
520    //
521    // For the recursion, the idea is that at each round, we have:
522    //
523    //   P_k = <a_k_i, G_k_i> + <b_k_i, H_k_i> + <a_k_i, b_k_i> w Q
524    //
525    // and our goal is to turn (P_k, a_k_i, b_k_i) into (P_(k-1), a_(k-1)_i, b_(k-1)_i)
526    // at each round, with the vectors halving in size. Eventually, we'll just
527    // have a single element, which is trivial to prove just by sending it over.
528    //
529    // Not having a good explanation for why the following trick works, let's shut
530    // up and calculate. Assume we have some folding coefficient u_k:
531    //
532    //   a_(k-1)_i := u_k    a_i + u_k^-1 a_(mid + i)
533    //   b_(k-1)_i := u_k^-1 b_i + u_k    b_(mid + i)
534    //   G_(k-1)_i := u_k^-1 G_i + u_k    G_(mid + i)
535    //   H_(k-1)_i := u_k    H_i + u_k^-1 H_(mid + i)
536    //
537    // (the new vectors are half the size, and mid is the new midpoint)
538    //
539    // then, we get:
540    //
541    //   P_(k-1) =
542    //     <u_k    a_i + u_k^-1 a_(mid + i), u_k^-1 G_i + u_k    G_(mid + i)> +
543    //     <u_k^-1 b_i + u_k    b_(mid + i), u_k    H_i + u_k^-1 H_(mid + i)> +
544    //     <u_k    a_i + u_k^-1 a_(mid + i), u_k^-1 b_i + u_k    b_(mid + i)>
545    //
546    // shutting up and calculating, we get:
547    //
548    //   <a_i, G_i> + <u_k^2  a_i, G_(mid + i)> + <u_k^-2 a_(mid + i), G_i> + <a_(mid + i), G_(mid + i)> +
549    //   <b_i, H_i> + <u_k^-2 b_i, H_(mid + i)> + <u_k^2  b_(mid + i), H_i> + <b_(mid + i), H_(mid + i)> +
550    //   <a_i, b_i> + <u_k^2  a_i, b_(mid + i)> + <u_k^-2 a_(mid + i), b_i> + <a_(mid + i), b_(mid + i)>
551    //
552    // we can group terms by coefficient, and notice that we have:
553    //
554    //           <a_i, G_i> + <a_(mid + i), G_(mid + i)> +
555    //           <b_i, H_i> + <b_(mid + i), H_(mid + i)> +
556    //           <a_i, b_i> + <a_(mid + i), b_(mid + i)> +
557    //   u_k^2  (<a_i, G_(mid + i)> + <b_(mid + i), H_i> + <a_i, b_(mid + i)>) +
558    //   u_k^-2 (<a_(mid + i), G_i> + <b_i, H_(mid + i)> + <a_(mid + i), b_i>)
559    //
560    // However, the first few lines of this are just P_k, so we have:
561    //
562    //   P_(k-1) = P_k + u_k^2 L_k + u_k^-2 R_k
563    //
564    // defining L_k and R_k as shorthand to the terms above.
565    //
566    // How do we use this fact? We have the prover calculate L_k and R_k, send
567    // them over to the verifier, who responds with a challenge u_k. We can then
568    // use that challenge to calculate the new vectors a_(k-1)_i,...
569    //
570    // The verifier can also check the provers work, by verifying:
571    //
572    //   P_k + u_k^2 L_k + u_k^-2 R_k =? P_(k-1)
573    //
574    // In fact, we don't even need to send P_(k-1) either. The prover
575    // knows what P_(k-1) needs to equal, thus determining what P_(k-2) should
576    // be, and so on, until we reach a final value P_0.
577    //
578    // For that final value, we have vectors of size 1, so we can send them over,
579    // and have the verifier check:
580    //
581    //   P_0 =? a_0 G_0 + b_0 H_0 + a_0 b_0 w B
582    //
583    // with P_0 being calculated by the verifier, from the initial generators,
584    // claim, and the challenges.
585    let witness_len = witness.a.len();
586    let claimed_len = 1usize.checked_shl(u32::from(claim.log_len))?;
587    if claimed_len != witness_len || setup.g.len() < witness_len {
588        return None;
589    }
590    // At this point, we've committed to the claim we're trying to prove, so
591    // we can't pull any shenanigans by modifying the claim based on the challenges.
592    transcript.commit(claim.encode());
593    let w = F::random(transcript.noise(b"w challenge"));
594    let w_q = setup.product_generator.clone() * &w;
595
596    let mut l_r_coms = Vec::<(G, G)>::new();
597    let mut a = witness.a;
598    let mut b = witness.b;
599    let mut g = setup.g[..witness_len].to_vec();
600    let mut h = setup.h[..witness_len].to_vec();
601    for (h_i, y_i) in h.iter_mut().zip(powers(F::one(), &claim.y)) {
602        *h_i *= &y_i;
603    }
604    while a.len() > 1 {
605        let mid = a.len() / 2;
606        let (a_lo, a_hi) = a.split_at_mut(mid);
607        let (b_lo, b_hi) = b.split_at_mut(mid);
608        let (g_lo, g_hi) = g.split_at(mid);
609        let (h_lo, h_hi) = h.split_at(mid);
610        let l = G::msm(g_hi, a_lo, strategy)
611            + &G::msm(h_lo, b_hi, strategy)
612            + &(w_q.clone() * &F::msm(a_lo, b_hi, strategy));
613        let r = G::msm(g_lo, a_hi, strategy)
614            + &G::msm(h_hi, b_lo, strategy)
615            + &(w_q.clone() * &F::msm(a_hi, b_lo, strategy));
616        l_r_coms.push((l.clone(), r.clone()));
617        transcript.commit(l.encode());
618        transcript.commit(r.encode());
619        let u = F::random(transcript.noise(b"u challenge"));
620        let u_inv = u.inv();
621
622        for (a_lo_i, a_hi_i) in a_lo.iter_mut().zip(a_hi.iter_mut()) {
623            *a_lo_i *= &u;
624            *a_lo_i += &(u_inv.clone() * a_hi_i);
625        }
626        a.truncate(mid);
627
628        for (b_lo_i, b_hi_i) in b_lo.iter_mut().zip(b_hi.iter_mut()) {
629            *b_lo_i *= &u_inv;
630            *b_lo_i += &(u.clone() * b_hi_i);
631        }
632        b.truncate(mid);
633
634        let u_u_inv = [u.clone(), u_inv.clone()];
635        let (new_g, new_h) = strategy.join(
636            || {
637                strategy.map_collect_vec(g_lo.iter().zip(g_hi), |(g_lo_i, g_hi_i)| {
638                    G::msm(&[g_hi_i.clone(), g_lo_i.clone()], &u_u_inv, strategy)
639                })
640            },
641            || {
642                strategy.map_collect_vec(h_lo.iter().zip(h_hi), |(h_lo_i, h_hi_i)| {
643                    G::msm(&[h_lo_i.clone(), h_hi_i.clone()], &u_u_inv, strategy)
644                })
645            },
646        );
647        g = new_g;
648        h = new_h;
649    }
650    let a_final = a.pop().expect("a should not be empty");
651    let b_final = b.pop().expect("b should not be empty");
652    Some(Proof {
653        l_r_coms,
654        transcript_summary: transcript.summarize(),
655        a_final,
656        b_final,
657    })
658}
659
660/// Construct the verification equation for a [`Proof`], relative to a
661/// [`Claim`] and a virtual [`Setup`].
662///
663/// If the check succeeds, we are convinced that the prover knows a valid
664/// [`Witness`] to this particular [`Claim`].
665///
666/// The returned [`Synthetic`] should evaluate to zero for a correct proof.
667/// Use [`Setup::eval`] to create the virtual setup and evaluate the result.
668///
669/// The return will be `None` if the proof is incorrect in an obvious way.
670pub fn verify<F: Field + Random, G: CryptoGroup<Scalar = F> + Encode>(
671    transcript: &mut Transcript,
672    setup: &Setup<Synthetic<F, G>>,
673    claim: &Claim<F, G>,
674    proof: Proof<F, G>,
675) -> Option<Synthetic<F, G>>
676where
677    Claim<F, G>: Encode,
678{
679    // See the prove function for some more explanation of the math.
680    // If you read that function's documentation naively, you might come under
681    // the impression that we have to naively follow the prover, folding the
682    // generators at each step, in order to produce the final value P_0, which
683    // we can then use to check that final a_0 and b_0. This is not ideal,
684    // because it's more efficient to do scalar multiplications as a batch, using
685    // an MSM. Our goal will thus be to reduce all of our work to hashing, in order
686    // to get the challenges, and a single large MSM.
687    //
688    // The final check we have is:
689    //
690    //   P_0 =? a_0 G_0 + b_0 H_0 + a_0 b_0 w Q
691    //
692    // What is P_0? Well, it must be equal to:
693    //
694    //   P_1 - u_1^2 L_1 - u_1^-2 R_1
695    //
696    // we can unravel P_1, and so, on, to get:
697    //
698    //   P_0 = P + c w Q - <u_k^2, L_k> - <u_k^-2, R_k>
699    //
700    // and that's nice and ready for an MSM. The issue is now how to figure out
701    // G_0 and H_0. Intuitively, this should be possible to do as a large MSM
702    // of the original G_i and H_i. This is because each folding step is just a linear
703    // transformation of the prior vectors. Composing these will still result in
704    // a linear transformation. We just need to figure out the weights for this.
705    //
706    // For vectors of size 1, this is trivial, the weights are just 1.
707    //
708    // Let's say we've figured out the weights for G_(k-1), what should the weights
709    // for G_k be? We want:
710    //
711    //   <g_(k-1)_j, G_(k-1)_j> = <g_k_i, G_k_i>
712    //
713    // i.e. the weights we want should produce the same result as folding, and then
714    // using the weights we know exist by induction. (If this is not easy to understand,
715    // imagine that the next layer beneath us is just the trivial layer, with one element,
716    // and a single weight equal to 1).
717    //
718    // We can expand the result of folding, to get:
719    //
720    //   <g_(k-1)_j, u_k^-1 G_k_j + u_k G_k_(mid + j)>
721    //
722    // but, this gives us the weights we need, defining:
723    //
724    //   g_k_i := u_k^{if i < mid { -1 } else { 1 }} g_(k - 1)_(i % mid)
725    //
726    // Another way of visualizing what's happening here: at each iteration, as
727    // we double the size of the weights, what we're doing is copying the existing
728    // weights, and then multiplying the left side by u_k^-1, and the right side
729    // by u_k.
730    //
731    // Here's an example progression:
732    //
733    // 1
734    //
735    // 1, 1
736    // u_1^-1, u_1
737    //
738    // u_1^-1, u_1, u_1^-1, u_1
739    // u_1^-1 u_2^-1, u_1 u_2^-1, u_1^-1 u_2, u_1 u_2
740    //
741    // Now, we don't actually need to do anything special for H, because it turns
742    // out that the weights we need are just the ones we've calculated for G, just
743    // in reverse order! To see why, note that the only difference with H is that
744    // we need to use u_k on the left, and u_k^-1 on the right. The vector we
745    // have at each step is the result of copying the previous vector, doubling its size,
746    // and then multiplying with one value and the left, and the other on the right.
747    // If we reverse this vector, the result we get is the same as if we had reversed
748    // the previous step's vector, copied it, and then multiplied with u_k on the left,
749    // and u_k^-1 on the right, which is exactly what we need to do.
750    //
751    // Recall also that the prover starts by turning H_i -> H'_i = y^i H_i. We can
752    // accomplish this by multiplying our weights for those values by y^i as well.
753    let rounds = usize::from(claim.log_len);
754    let claimed_len = 1usize.checked_shl(u32::from(claim.log_len))?;
755    let Proof {
756        l_r_coms,
757        transcript_summary,
758        a_final,
759        b_final,
760    } = proof;
761    if l_r_coms.len() != rounds {
762        return None;
763    }
764    transcript.commit(claim.encode());
765
766    let w = F::random(transcript.noise(b"w challenge"));
767
768    // We reduce verification down to one MSM which needs to equal 0:
769    // commitment + product * U + sum(u_i^2 * L_i + u_i^-2 * R_i)
770    // - a_final * g_final - b_final * h_final - a_final * b_final * U = 0.
771    let mut us = Vec::<(F, F)>::with_capacity(rounds);
772    let mut out = Synthetic::concrete(l_r_coms.into_iter().flat_map(|(l, r)| {
773        transcript.commit(l.encode());
774        transcript.commit(r.encode());
775        let u = F::random(transcript.noise(b"u challenge"));
776        let u_inv = u.inv();
777        us.push((u.clone(), u_inv.clone()));
778        let u2 = {
779            let mut out = u;
780            out.square();
781            out
782        };
783        let u_inv2 = {
784            let mut out = u_inv;
785            out.square();
786            out
787        };
788        [(u2, l), (u_inv2, r)]
789    }));
790    if transcript.summarize() != transcript_summary {
791        return None;
792    }
793    out += &Synthetic::concrete([(F::one(), claim.commitment.clone())]);
794    out += &(setup.product_generator().clone() * &(claim.product.clone() * &w));
795    let g_weights = {
796        let mut weights = Vec::<F>::with_capacity(claimed_len);
797        weights.push(F::one());
798        for (u, u_inv) in us.into_iter().rev() {
799            let end = weights.len();
800            weights.extend_from_within(..);
801            for left_i in &mut weights[..end] {
802                *left_i *= &u_inv;
803            }
804            for right_i in &mut weights[end..] {
805                *right_i *= &u;
806            }
807        }
808        weights
809    };
810    let h_weights: Vec<F> = g_weights
811        .iter()
812        .rev()
813        .zip(powers(F::one(), &claim.y))
814        .map(|(w_i, y_i)| y_i * w_i)
815        .collect();
816    let g = &setup.g()[..claimed_len];
817    let h = &setup.h()[..claimed_len];
818    out -= &(Synthetic::msm(h, &h_weights, &Sequential) * &b_final);
819    out -= &(Synthetic::msm(g, &g_weights, &Sequential) * &a_final);
820    out -= &(setup.product_generator().clone() * &(a_final * &b_final * &w));
821    Some(out)
822}
823
824#[cfg(all(test, feature = "arbitrary"))]
825mod conformance {
826    use super::{Claim, Proof, Setup};
827    use commonware_codec::conformance::CodecConformance;
828    use commonware_math::test::{F as TestF, G as TestG};
829
830    commonware_conformance::conformance_tests! {
831        CodecConformance<Setup<TestG>>,
832        CodecConformance<Claim<TestF, TestG>>,
833        CodecConformance<Proof<TestF, TestG>>,
834    }
835}
836
837#[commonware_macros::stability(ALPHA)]
838#[cfg(any(test, feature = "fuzz"))]
839pub mod fuzz {
840    use super::*;
841    use arbitrary::{Arbitrary, Unstructured};
842    #[cfg(test)]
843    use commonware_codec::Decode;
844    use commonware_math::{
845        algebra::Additive,
846        test::{F, G},
847    };
848    use commonware_parallel::Sequential;
849    use std::sync::OnceLock;
850
851    const MAX_VECTOR_LG: u8 = 5;
852    const MAX_VECTOR_LEN: usize = 1 << MAX_VECTOR_LG;
853    const MAX_SETUP_VECTOR_LEN: usize = 2 * MAX_VECTOR_LEN;
854    const NUM_GENERATORS: usize = 2 * MAX_SETUP_VECTOR_LEN + 1;
855    const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_IPA";
856    const BAD_NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_IPA_BUT_DIFFERENT";
857
858    fn test_setup() -> &'static Setup<G> {
859        static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
860        TEST_SETUP.get_or_init(|| {
861            let generators = (1..=NUM_GENERATORS)
862                .map(|i| G::generator() * &F::from(i as u8))
863                .collect::<Vec<_>>();
864            Setup::new(
865                generators[0],
866                generators[1..]
867                    .chunks_exact(2)
868                    .map(|chunk| (chunk[0], chunk[1])),
869            )
870        })
871    }
872
873    struct Prover<'a> {
874        setup: &'a Setup<G>,
875        witness: Witness<F>,
876        claim: Claim<F, G>,
877        proof: Proof<F, G>,
878        bad_namespace: bool,
879        honest: bool,
880    }
881
882    impl<'a> Prover<'a> {
883        fn new(setup: &'a Setup<G>, y: F, a: &[F], b: &[F]) -> Self {
884            let (witness, claim) =
885                Witness::new_with_claim(setup, y, a.iter().zip(b).map(|(&a, &b)| (a, b)))
886                    .expect("prover expects arguments to match setup");
887            let proof = prove(
888                &mut Transcript::new(NAMESPACE),
889                setup,
890                &claim,
891                witness.clone(),
892                &Sequential,
893            )
894            .expect("proving should work");
895            Self {
896                setup,
897                witness,
898                claim,
899                proof,
900                bad_namespace: false,
901                honest: true,
902            }
903        }
904
905        #[allow(clippy::missing_const_for_fn)]
906        fn bad_namespace(&mut self) {
907            self.honest = false;
908            self.bad_namespace = true;
909        }
910
911        fn tweak_product(&mut self, delta: F) {
912            if delta == F::zero() {
913                return;
914            }
915            self.honest = false;
916            // Normally, we compress the separate product by doing:
917            //
918            //   c w Q + P
919            //
920            // but, if you know w in advance, you can do:
921            //
922            //   (c + d) w Q + (P - d w Q)
923            //
924            // i.e. tweak your commitment to change the product, without changing
925            // the actual vectors that make up your witness.
926            //
927            // One simple case where you know w is if the implementor forgets to multiply
928            // the product generator by this challenge. (I made this mistake myself).
929            self.claim.product -= &delta;
930            self.claim.commitment += &(*self.setup.product_generator() * &delta);
931            self.proof = prove(
932                &mut Transcript::new(NAMESPACE),
933                self.setup,
934                &self.claim,
935                self.witness.clone(),
936                &Sequential,
937            )
938            .expect("proving should work after tweaking the public claim");
939        }
940
941        fn increase_length(&mut self) {
942            self.honest = false;
943            let longer_log_len = self
944                .claim
945                .log_len
946                .checked_add(1)
947                .expect("test vectors should support doubling the witness length");
948            let longer_len = 1usize
949                .checked_shl(u32::from(longer_log_len))
950                .expect("witness length should fit into usize");
951            self.witness.a.resize_with(longer_len, F::zero);
952            self.witness.b.resize_with(longer_len, F::zero);
953
954            // Padding with zeros preserves the commitment and product, but the
955            // regenerated proof is now bound to a different claimed length.
956            let longer_claim = Claim {
957                log_len: longer_log_len,
958                ..self.claim
959            };
960            self.proof = prove(
961                &mut Transcript::new(NAMESPACE),
962                self.setup,
963                &longer_claim,
964                self.witness.clone(),
965                &Sequential,
966            )
967            .expect("proving should work after increasing the witness length");
968        }
969
970        fn tweak_l_r_coms<'b>(&mut self, u: &mut Unstructured<'b>) -> arbitrary::Result<()> {
971            let Some(last_round) = self.proof.l_r_coms.len().checked_sub(1) else {
972                return Ok(());
973            };
974            let round = u.int_in_range(0..=last_round)?;
975            let tweak_left = u.arbitrary::<bool>()?;
976            let delta = u.arbitrary::<G>()?;
977            if delta == G::zero() {
978                return Ok(());
979            }
980
981            self.honest = false;
982            let (l, r) = &mut self.proof.l_r_coms[round];
983            if tweak_left {
984                *l += &delta;
985            } else {
986                *r += &delta;
987            }
988            Ok(())
989        }
990
991        #[allow(clippy::missing_const_for_fn)]
992        fn honest(&self) -> bool {
993            self.honest
994        }
995
996        fn verify(self) -> bool {
997            let ns = if self.bad_namespace {
998                BAD_NAMESPACE
999            } else {
1000                NAMESPACE
1001            };
1002            let setup = self.setup;
1003            let claim = self.claim;
1004            let proof = self.proof;
1005            setup
1006                .eval(
1007                    |vs| super::verify(&mut Transcript::new(ns), vs, &claim, proof),
1008                    &Sequential,
1009                )
1010                .map(|g| g == G::zero())
1011                .unwrap_or(false)
1012        }
1013    }
1014
1015    #[derive(Debug)]
1016    pub struct Plan {
1017        y: F,
1018        a: Vec<F>,
1019        b: Vec<F>,
1020    }
1021
1022    impl<'a> Arbitrary<'a> for Plan {
1023        fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1024            let lg_len = u.int_in_range(0..=MAX_VECTOR_LG)?;
1025            let len = 1usize << lg_len;
1026            let y = u.arbitrary()?;
1027            let a = (0..len)
1028                .map(|_| u.arbitrary())
1029                .collect::<arbitrary::Result<Vec<_>>>()?;
1030            let b = (0..len)
1031                .map(|_| u.arbitrary())
1032                .collect::<arbitrary::Result<Vec<_>>>()?;
1033            Ok(Self { y, a, b })
1034        }
1035    }
1036
1037    impl Plan {
1038        pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
1039            let setup = test_setup();
1040            let mut prover = Prover::new(setup, self.y, &self.a, &self.b);
1041            // is the prover going to be malicious at all?
1042            if u.arbitrary::<bool>()? {
1043                match u.arbitrary::<u8>()? {
1044                    x if x < 64 => prover.tweak_product(u.arbitrary::<F>()?),
1045                    x if x < 128 => prover.increase_length(),
1046                    x if x < 192 => prover.tweak_l_r_coms(u)?,
1047                    _ => prover.bad_namespace(),
1048                }
1049            }
1050            match (prover.honest(), prover.verify()) {
1051                (true, true) | (false, false) => {}
1052                (true, false) => panic!("prover honest, but proof didn't verify"),
1053                (false, true) => panic!("prover malicious, but proof verifies!!!"),
1054            }
1055            Ok(())
1056        }
1057    }
1058
1059    #[cfg(test)]
1060    mod test {
1061        use super::*;
1062
1063        fn assert_setup_roundtrip(setup: &Setup<G>) {
1064            let encoded = setup.encode();
1065            let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.g.len(), ()))
1066                .expect("setup should decode with its own length bound");
1067            assert_eq!(setup, &decoded);
1068            assert_eq!(decoded.encode(), encoded);
1069        }
1070
1071        fn assert_claim_roundtrip(claim: &Claim<F, G>) {
1072            let encoded = claim.encode();
1073            let decoded: Claim<F, G> = Claim::decode_cfg(encoded.clone(), &((), ()))
1074                .expect("claim should decode with unit cfg");
1075            assert_eq!(claim, &decoded);
1076            assert_eq!(decoded.encode(), encoded);
1077        }
1078
1079        fn assert_proof_roundtrip(proof: &Proof<F, G>) {
1080            let max_len = if proof.l_r_coms.is_empty() {
1081                0
1082            } else {
1083                1usize
1084                    .checked_shl(proof.l_r_coms.len() as u32)
1085                    .expect("proof arbitrary bounds rounds to fit in usize")
1086            };
1087            let encoded = proof.encode();
1088            let decoded: Proof<F, G> = Proof::decode_cfg(encoded.clone(), &(max_len, ((), ())))
1089                .expect("proof should decode with a matching round bound");
1090            assert_eq!(proof, &decoded);
1091            assert_eq!(decoded.encode(), encoded);
1092        }
1093
1094        #[test]
1095        fn test_codec_roundtrip() {
1096            commonware_invariants::minifuzz::test(|u| {
1097                assert_setup_roundtrip(&u.arbitrary::<Setup<G>>()?);
1098                assert_claim_roundtrip(&u.arbitrary::<Claim<F, G>>()?);
1099                assert_proof_roundtrip(&u.arbitrary::<Proof<F, G>>()?);
1100                Ok(())
1101            });
1102        }
1103
1104        #[test]
1105        fn test_fuzz() {
1106            commonware_invariants::minifuzz::test(|u| u.arbitrary::<Plan>()?.run(u));
1107        }
1108
1109        #[test]
1110        fn prover_tweaks_cover_edge_paths() {
1111            let setup = test_setup();
1112
1113            let mut honest = Prover::new(setup, F::from(1u8), &[F::from(3u8)], &[F::from(4u8)]);
1114            honest.tweak_product(F::zero());
1115            honest
1116                .tweak_l_r_coms(&mut Unstructured::new(&[]))
1117                .expect("single-round proof should no-op before reading fuzz input");
1118            assert!(honest.honest());
1119            assert!(honest.verify());
1120
1121            type Tweak = Box<dyn FnOnce(&mut Prover<'static>)>;
1122            let failures: [Tweak; 4] = [
1123                Box::new(|p| p.tweak_product(F::from(1u8))),
1124                Box::new(|p| p.increase_length()),
1125                Box::new(|p| {
1126                    p.tweak_l_r_coms(&mut Unstructured::new(&[1, 1, 0, 0, 0, 0, 0, 0, 0]))
1127                        .expect("structured fuzz input should mutate a proof round");
1128                }),
1129                Box::new(|p| p.bad_namespace()),
1130            ];
1131            for tweak in failures {
1132                let mut prover = Prover::new(
1133                    setup,
1134                    F::from(1u8),
1135                    &[F::from(3u8), F::from(5u8)],
1136                    &[F::from(4u8), F::from(6u8)],
1137                );
1138                tweak(&mut prover);
1139                assert!(!prover.honest());
1140                assert!(!prover.verify());
1141            }
1142        }
1143    }
1144}