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(&mut 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_mut(mid);
609        let (h_lo, h_hi) = h.split_at_mut(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(&mut 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        for (g_lo_i, g_hi_i) in g_lo.iter_mut().zip(g_hi.iter_mut()) {
635            *g_lo_i *= &u_inv;
636            *g_lo_i += &(g_hi_i.clone() * &u);
637        }
638        g.truncate(mid);
639
640        for (h_lo_i, h_hi_i) in h_lo.iter_mut().zip(h_hi.iter_mut()) {
641            *h_lo_i *= &u;
642            *h_lo_i += &(h_hi_i.clone() * &u_inv);
643        }
644        h.truncate(mid);
645    }
646    let a_final = a.pop().expect("a should not be empty");
647    let b_final = b.pop().expect("b should not be empty");
648    Some(Proof {
649        l_r_coms,
650        transcript_summary: transcript.summarize(),
651        a_final,
652        b_final,
653    })
654}
655
656/// Construct the verification equation for a [`Proof`], relative to a
657/// [`Claim`] and a virtual [`Setup`].
658///
659/// If the check succeeds, we are convinced that the prover knows a valid
660/// [`Witness`] to this particular [`Claim`].
661///
662/// The returned [`Synthetic`] should evaluate to zero for a correct proof.
663/// Use [`Setup::eval`] to create the virtual setup and evaluate the result.
664///
665/// The return will be `None` if the proof is incorrect in an obvious way.
666pub fn verify<F: Field + Random, G: CryptoGroup<Scalar = F> + Encode>(
667    transcript: &mut Transcript,
668    setup: &Setup<Synthetic<F, G>>,
669    claim: &Claim<F, G>,
670    proof: Proof<F, G>,
671) -> Option<Synthetic<F, G>>
672where
673    Claim<F, G>: Encode,
674{
675    // See the prove function for some more explanation of the math.
676    // If you read that function's documentation naively, you might come under
677    // the impression that we have to naively follow the prover, folding the
678    // generators at each step, in order to produce the final value P_0, which
679    // we can then use to check that final a_0 and b_0. This is not ideal,
680    // because it's more efficient to do scalar multiplications as a batch, using
681    // an MSM. Our goal will thus be to reduce all of our work to hashing, in order
682    // to get the challenges, and a single large MSM.
683    //
684    // The final check we have is:
685    //
686    //   P_0 =? a_0 G_0 + b_0 H_0 + a_0 b_0 w Q
687    //
688    // What is P_0? Well, it must be equal to:
689    //
690    //   P_1 - u_1^2 L_1 - u_1^-2 R_1
691    //
692    // we can unravel P_1, and so, on, to get:
693    //
694    //   P_0 = P + c w Q - <u_k^2, L_k> - <u_k^-2, R_k>
695    //
696    // and that's nice and ready for an MSM. The issue is now how to figure out
697    // G_0 and H_0. Intuitively, this should be possible to do as a large MSM
698    // of the original G_i and H_i. This is because each folding step is just a linear
699    // transformation of the prior vectors. Composing these will still result in
700    // a linear transformation. We just need to figure out the weights for this.
701    //
702    // For vectors of size 1, this is trivial, the weights are just 1.
703    //
704    // Let's say we've figured out the weights for G_(k-1), what should the weights
705    // for G_k be? We want:
706    //
707    //   <g_(k-1)_j, G_(k-1)_j> = <g_k_i, G_k_i>
708    //
709    // i.e. the weights we want should produce the same result as folding, and then
710    // using the weights we know exist by induction. (If this is not easy to understand,
711    // imagine that the next layer beneath us is just the trivial layer, with one element,
712    // and a single weight equal to 1).
713    //
714    // We can expand the result of folding, to get:
715    //
716    //   <g_(k-1)_j, u_k^-1 G_k_j + u_k G_k_(mid + j)>
717    //
718    // but, this gives us the weights we need, defining:
719    //
720    //   g_k_i := u_k^{if i < mid { -1 } else { 1 }} g_(k - 1)_(i % mid)
721    //
722    // Another way of visualizing what's happening here: at each iteration, as
723    // we double the size of the weights, what we're doing is copying the existing
724    // weights, and then multiplying the left side by u_k^-1, and the right side
725    // by u_k.
726    //
727    // Here's an example progression:
728    //
729    // 1
730    //
731    // 1, 1
732    // u_1^-1, u_1
733    //
734    // u_1^-1, u_1, u_1^-1, u_1
735    // u_1^-1 u_2^-1, u_1 u_2^-1, u_1^-1 u_2, u_1 u_2
736    //
737    // Now, we don't actually need to do anything special for H, because it turns
738    // out that the weights we need are just the ones we've calculated for G, just
739    // in reverse order! To see why, note that the only difference with H is that
740    // we need to use u_k on the left, and u_k^-1 on the right. The vector we
741    // have at each step is the result of copying the previous vector, doubling its size,
742    // and then multiplying with one value and the left, and the other on the right.
743    // If we reverse this vector, the result we get is the same as if we had reversed
744    // the previous step's vector, copied it, and then multiplied with u_k on the left,
745    // and u_k^-1 on the right, which is exactly what we need to do.
746    //
747    // Recall also that the prover starts by turning H_i -> H'_i = y^i H_i. We can
748    // accomplish this by multiplying our weights for those values by y^i as well.
749    let rounds = usize::from(claim.log_len);
750    let claimed_len = 1usize.checked_shl(u32::from(claim.log_len))?;
751    let Proof {
752        l_r_coms,
753        transcript_summary,
754        a_final,
755        b_final,
756    } = proof;
757    if l_r_coms.len() != rounds {
758        return None;
759    }
760    transcript.commit(claim.encode());
761
762    let w = F::random(&mut transcript.noise(b"w challenge"));
763
764    // We reduce verification down to one MSM which needs to equal 0:
765    // commitment + product * U + sum(u_i^2 * L_i + u_i^-2 * R_i)
766    // - a_final * g_final - b_final * h_final - a_final * b_final * U = 0.
767    let mut us = Vec::<(F, F)>::with_capacity(rounds);
768    let mut out = Synthetic::concrete(l_r_coms.into_iter().flat_map(|(l, r)| {
769        transcript.commit(l.encode());
770        transcript.commit(r.encode());
771        let u = F::random(transcript.noise(b"u challenge"));
772        let u_inv = u.inv();
773        us.push((u.clone(), u_inv.clone()));
774        let u2 = {
775            let mut out = u;
776            out.square();
777            out
778        };
779        let u_inv2 = {
780            let mut out = u_inv;
781            out.square();
782            out
783        };
784        [(u2, l), (u_inv2, r)]
785    }));
786    if transcript.summarize() != transcript_summary {
787        return None;
788    }
789    out += &Synthetic::concrete([(F::one(), claim.commitment.clone())]);
790    out += &(setup.product_generator().clone() * &(claim.product.clone() * &w));
791    let g_weights = {
792        let mut weights = Vec::<F>::with_capacity(claimed_len);
793        weights.push(F::one());
794        for (u, u_inv) in us.into_iter().rev() {
795            let end = weights.len();
796            weights.extend_from_within(..);
797            for left_i in &mut weights[..end] {
798                *left_i *= &u_inv;
799            }
800            for right_i in &mut weights[end..] {
801                *right_i *= &u;
802            }
803        }
804        weights
805    };
806    let h_weights: Vec<F> = g_weights
807        .iter()
808        .rev()
809        .zip(powers(F::one(), &claim.y))
810        .map(|(w_i, y_i)| y_i * w_i)
811        .collect();
812    let g = &setup.g()[..claimed_len];
813    let h = &setup.h()[..claimed_len];
814    out -= &(Synthetic::msm(h, &h_weights, &Sequential) * &b_final);
815    out -= &(Synthetic::msm(g, &g_weights, &Sequential) * &a_final);
816    out -= &(setup.product_generator().clone() * &(a_final * &b_final * &w));
817    Some(out)
818}
819
820#[cfg(all(test, feature = "arbitrary"))]
821mod conformance {
822    use super::{Claim, Proof, Setup};
823    use commonware_codec::conformance::CodecConformance;
824    use commonware_math::test::{F as TestF, G as TestG};
825
826    commonware_conformance::conformance_tests! {
827        CodecConformance<Setup<TestG>>,
828        CodecConformance<Claim<TestF, TestG>>,
829        CodecConformance<Proof<TestF, TestG>>,
830    }
831}
832
833#[commonware_macros::stability(ALPHA)]
834#[cfg(any(test, feature = "fuzz"))]
835pub mod fuzz {
836    use super::*;
837    use arbitrary::{Arbitrary, Unstructured};
838    #[cfg(test)]
839    use commonware_codec::Decode;
840    use commonware_math::{
841        algebra::Additive,
842        test::{F, G},
843    };
844    use commonware_parallel::Sequential;
845    use std::sync::OnceLock;
846
847    const MAX_VECTOR_LG: u8 = 5;
848    const MAX_VECTOR_LEN: usize = 1 << MAX_VECTOR_LG;
849    const MAX_SETUP_VECTOR_LEN: usize = 2 * MAX_VECTOR_LEN;
850    const NUM_GENERATORS: usize = 2 * MAX_SETUP_VECTOR_LEN + 1;
851    const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_IPA";
852    const BAD_NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_IPA_BUT_DIFFERENT";
853
854    fn test_setup() -> &'static Setup<G> {
855        static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
856        TEST_SETUP.get_or_init(|| {
857            let generators = (1..=NUM_GENERATORS)
858                .map(|i| G::generator() * &F::from(i as u8))
859                .collect::<Vec<_>>();
860            Setup::new(
861                generators[0],
862                generators[1..]
863                    .chunks_exact(2)
864                    .map(|chunk| (chunk[0], chunk[1])),
865            )
866        })
867    }
868
869    struct Prover<'a> {
870        setup: &'a Setup<G>,
871        witness: Witness<F>,
872        claim: Claim<F, G>,
873        proof: Proof<F, G>,
874        bad_namespace: bool,
875        honest: bool,
876    }
877
878    impl<'a> Prover<'a> {
879        fn new(setup: &'a Setup<G>, y: F, a: &[F], b: &[F]) -> Self {
880            let (witness, claim) =
881                Witness::new_with_claim(setup, y, a.iter().zip(b).map(|(&a, &b)| (a, b)))
882                    .expect("prover expects arguments to match setup");
883            let proof = prove(
884                &mut Transcript::new(NAMESPACE),
885                setup,
886                &claim,
887                witness.clone(),
888                &Sequential,
889            )
890            .expect("proving should work");
891            Self {
892                setup,
893                witness,
894                claim,
895                proof,
896                bad_namespace: false,
897                honest: true,
898            }
899        }
900
901        #[allow(clippy::missing_const_for_fn)]
902        fn bad_namespace(&mut self) {
903            self.honest = false;
904            self.bad_namespace = true;
905        }
906
907        fn tweak_product(&mut self, delta: F) {
908            if delta == F::zero() {
909                return;
910            }
911            self.honest = false;
912            // Normally, we compress the separate product by doing:
913            //
914            //   c w Q + P
915            //
916            // but, if you know w in advance, you can do:
917            //
918            //   (c + d) w Q + (P - d w Q)
919            //
920            // i.e. tweak your commitment to change the product, without changing
921            // the actual vectors that make up your witness.
922            //
923            // One simple case where you know w is if the implementor forgets to multiply
924            // the product generator by this challenge. (I made this mistake myself).
925            self.claim.product -= &delta;
926            self.claim.commitment += &(*self.setup.product_generator() * &delta);
927            self.proof = prove(
928                &mut Transcript::new(NAMESPACE),
929                self.setup,
930                &self.claim,
931                self.witness.clone(),
932                &Sequential,
933            )
934            .expect("proving should work after tweaking the public claim");
935        }
936
937        fn increase_length(&mut self) {
938            self.honest = false;
939            let longer_log_len = self
940                .claim
941                .log_len
942                .checked_add(1)
943                .expect("test vectors should support doubling the witness length");
944            let longer_len = 1usize
945                .checked_shl(u32::from(longer_log_len))
946                .expect("witness length should fit into usize");
947            self.witness.a.resize_with(longer_len, F::zero);
948            self.witness.b.resize_with(longer_len, F::zero);
949
950            // Padding with zeros preserves the commitment and product, but the
951            // regenerated proof is now bound to a different claimed length.
952            let longer_claim = Claim {
953                log_len: longer_log_len,
954                ..self.claim
955            };
956            self.proof = prove(
957                &mut Transcript::new(NAMESPACE),
958                self.setup,
959                &longer_claim,
960                self.witness.clone(),
961                &Sequential,
962            )
963            .expect("proving should work after increasing the witness length");
964        }
965
966        fn tweak_l_r_coms<'b>(&mut self, u: &mut Unstructured<'b>) -> arbitrary::Result<()> {
967            let Some(last_round) = self.proof.l_r_coms.len().checked_sub(1) else {
968                return Ok(());
969            };
970            let round = u.int_in_range(0..=last_round)?;
971            let tweak_left = u.arbitrary::<bool>()?;
972            let delta = u.arbitrary::<G>()?;
973            if delta == G::zero() {
974                return Ok(());
975            }
976
977            self.honest = false;
978            let (l, r) = &mut self.proof.l_r_coms[round];
979            if tweak_left {
980                *l += &delta;
981            } else {
982                *r += &delta;
983            }
984            Ok(())
985        }
986
987        #[allow(clippy::missing_const_for_fn)]
988        fn honest(&self) -> bool {
989            self.honest
990        }
991
992        fn verify(self) -> bool {
993            let ns = if self.bad_namespace {
994                BAD_NAMESPACE
995            } else {
996                NAMESPACE
997            };
998            let setup = self.setup;
999            let claim = self.claim;
1000            let proof = self.proof;
1001            setup
1002                .eval(
1003                    |vs| super::verify(&mut Transcript::new(ns), vs, &claim, proof),
1004                    &Sequential,
1005                )
1006                .map(|g| g == G::zero())
1007                .unwrap_or(false)
1008        }
1009    }
1010
1011    #[derive(Debug)]
1012    pub struct Plan {
1013        y: F,
1014        a: Vec<F>,
1015        b: Vec<F>,
1016    }
1017
1018    impl<'a> Arbitrary<'a> for Plan {
1019        fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1020            let lg_len = u.int_in_range(0..=MAX_VECTOR_LG)?;
1021            let len = 1usize << lg_len;
1022            let y = u.arbitrary()?;
1023            let a = (0..len)
1024                .map(|_| u.arbitrary())
1025                .collect::<arbitrary::Result<Vec<_>>>()?;
1026            let b = (0..len)
1027                .map(|_| u.arbitrary())
1028                .collect::<arbitrary::Result<Vec<_>>>()?;
1029            Ok(Self { y, a, b })
1030        }
1031    }
1032
1033    impl Plan {
1034        pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
1035            let setup = test_setup();
1036            let mut prover = Prover::new(setup, self.y, &self.a, &self.b);
1037            // is the prover going to be malicious at all?
1038            if u.arbitrary::<bool>()? {
1039                match u.arbitrary::<u8>()? {
1040                    x if x < 64 => prover.tweak_product(u.arbitrary::<F>()?),
1041                    x if x < 128 => prover.increase_length(),
1042                    x if x < 192 => prover.tweak_l_r_coms(u)?,
1043                    _ => prover.bad_namespace(),
1044                }
1045            }
1046            match (prover.honest(), prover.verify()) {
1047                (true, true) | (false, false) => {}
1048                (true, false) => panic!("prover honest, but proof didn't verify"),
1049                (false, true) => panic!("prover malicious, but proof verifies!!!"),
1050            }
1051            Ok(())
1052        }
1053    }
1054
1055    #[cfg(test)]
1056    mod test {
1057        use super::*;
1058
1059        fn assert_setup_roundtrip(setup: &Setup<G>) {
1060            let encoded = setup.encode();
1061            let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.g.len(), ()))
1062                .expect("setup should decode with its own length bound");
1063            assert_eq!(setup, &decoded);
1064            assert_eq!(decoded.encode(), encoded);
1065        }
1066
1067        fn assert_claim_roundtrip(claim: &Claim<F, G>) {
1068            let encoded = claim.encode();
1069            let decoded: Claim<F, G> = Claim::decode_cfg(encoded.clone(), &((), ()))
1070                .expect("claim should decode with unit cfg");
1071            assert_eq!(claim, &decoded);
1072            assert_eq!(decoded.encode(), encoded);
1073        }
1074
1075        fn assert_proof_roundtrip(proof: &Proof<F, G>) {
1076            let max_len = if proof.l_r_coms.is_empty() {
1077                0
1078            } else {
1079                1usize
1080                    .checked_shl(proof.l_r_coms.len() as u32)
1081                    .expect("proof arbitrary bounds rounds to fit in usize")
1082            };
1083            let encoded = proof.encode();
1084            let decoded: Proof<F, G> = Proof::decode_cfg(encoded.clone(), &(max_len, ((), ())))
1085                .expect("proof should decode with a matching round bound");
1086            assert_eq!(proof, &decoded);
1087            assert_eq!(decoded.encode(), encoded);
1088        }
1089
1090        #[test]
1091        fn test_codec_roundtrip() {
1092            commonware_invariants::minifuzz::test(|u| {
1093                assert_setup_roundtrip(&u.arbitrary::<Setup<G>>()?);
1094                assert_claim_roundtrip(&u.arbitrary::<Claim<F, G>>()?);
1095                assert_proof_roundtrip(&u.arbitrary::<Proof<F, G>>()?);
1096                Ok(())
1097            });
1098        }
1099
1100        #[test]
1101        fn test_fuzz() {
1102            commonware_invariants::minifuzz::test(|u| u.arbitrary::<Plan>()?.run(u));
1103        }
1104
1105        #[test]
1106        fn prover_tweaks_cover_edge_paths() {
1107            let setup = test_setup();
1108
1109            let mut honest = Prover::new(setup, F::from(1u8), &[F::from(3u8)], &[F::from(4u8)]);
1110            honest.tweak_product(F::zero());
1111            honest
1112                .tweak_l_r_coms(&mut Unstructured::new(&[]))
1113                .expect("single-round proof should no-op before reading fuzz input");
1114            assert!(honest.honest());
1115            assert!(honest.verify());
1116
1117            type Tweak = Box<dyn FnOnce(&mut Prover<'static>)>;
1118            let failures: [Tweak; 4] = [
1119                Box::new(|p| p.tweak_product(F::from(1u8))),
1120                Box::new(|p| p.increase_length()),
1121                Box::new(|p| {
1122                    p.tweak_l_r_coms(&mut Unstructured::new(&[1, 1, 0, 0, 0, 0, 0, 0, 0]))
1123                        .expect("structured fuzz input should mutate a proof round");
1124                }),
1125                Box::new(|p| p.bad_namespace()),
1126            ];
1127            for tweak in failures {
1128                let mut prover = Prover::new(
1129                    setup,
1130                    F::from(1u8),
1131                    &[F::from(3u8), F::from(5u8)],
1132                    &[F::from(4u8), F::from(6u8)],
1133                );
1134                tweak(&mut prover);
1135                assert!(!prover.honest());
1136                assert!(!prover.verify());
1137            }
1138        }
1139    }
1140}