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