Skip to main content

bellman/
lib.rs

1//! `bellman` is a crate for building zk-SNARK circuits. It provides circuit
2//! traits and and primitive structures, as well as basic gadget implementations
3//! such as booleans and number abstractions.
4//!
5//! # Example circuit
6//!
7//! Say we want to write a circuit that proves we know the preimage to some hash
8//! computed using SHA-256d (calling SHA-256 twice). The preimage must have a
9//! fixed length known in advance (because the circuit parameters will depend on
10//! it), but can otherwise have any value. We take the following strategy:
11//!
12//! - Witness each bit of the preimage.
13//! - Compute `hash = SHA-256d(preimage)` inside the circuit.
14//! - Expose `hash` as a public input using multiscalar packing.
15//!
16//! ```
17//! use bellman::{
18//!     gadgets::{
19//!         boolean::{AllocatedBit, Boolean},
20//!         multipack,
21//!         sha256::sha256,
22//!     },
23//!     groth16, Circuit, ConstraintSystem, SynthesisError,
24//! };
25//! use bls12_381::Bls12;
26//! use ff::PrimeField;
27//! use pairing::Engine;
28//! use rand::rng;
29//! use sha2::{Digest, Sha256};
30//!
31//! /// Our own SHA-256d gadget. Input and output are in little-endian bit order.
32//! fn sha256d<Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
33//!     mut cs: CS,
34//!     data: &[Boolean],
35//! ) -> Result<Vec<Boolean>, SynthesisError> {
36//!     // Flip endianness of each input byte
37//!     let input: Vec<_> = data
38//!         .chunks(8)
39//!         .map(|c| c.iter().rev())
40//!         .flatten()
41//!         .cloned()
42//!         .collect();
43//!
44//!     let mid = sha256(cs.namespace(|| "SHA-256(input)"), &input)?;
45//!     let res = sha256(cs.namespace(|| "SHA-256(mid)"), &mid)?;
46//!
47//!     // Flip endianness of each output byte
48//!     Ok(res
49//!         .chunks(8)
50//!         .map(|c| c.iter().rev())
51//!         .flatten()
52//!         .cloned()
53//!         .collect())
54//! }
55//!
56//! struct MyCircuit {
57//!     /// The input to SHA-256d we are proving that we know. Set to `None` when we
58//!     /// are verifying a proof (and do not have the witness data).
59//!     preimage: Option<[u8; 80]>,
60//! }
61//!
62//! impl<Scalar: PrimeField> Circuit<Scalar> for MyCircuit {
63//!     fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
64//!         // Compute the values for the bits of the preimage. If we are verifying a proof,
65//!         // we still need to create the same constraints, so we return an equivalent-size
66//!         // Vec of None (indicating that the value of each bit is unknown).
67//!         let bit_values = if let Some(preimage) = self.preimage {
68//!             preimage
69//!                 .into_iter()
70//!                 .map(|byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8))
71//!                 .flatten()
72//!                 .map(|b| Some(b))
73//!                 .collect()
74//!         } else {
75//!             vec![None; 80 * 8]
76//!         };
77//!         assert_eq!(bit_values.len(), 80 * 8);
78//!
79//!         // Witness the bits of the preimage.
80//!         let preimage_bits = bit_values
81//!             .into_iter()
82//!             .enumerate()
83//!             // Allocate each bit.
84//!             .map(|(i, b)| {
85//!                 AllocatedBit::alloc(cs.namespace(|| format!("preimage bit {}", i)), b)
86//!             })
87//!             // Convert the AllocatedBits into Booleans (required for the sha256 gadget).
88//!             .map(|b| b.map(Boolean::from))
89//!             .collect::<Result<Vec<_>, _>>()?;
90//!
91//!         // Compute hash = SHA-256d(preimage).
92//!         let hash = sha256d(cs.namespace(|| "SHA-256d(preimage)"), &preimage_bits)?;
93//!
94//!         // Expose the vector of 32 boolean variables as compact public inputs.
95//!         multipack::pack_into_inputs(cs.namespace(|| "pack hash"), &hash)
96//!     }
97//! }
98//!
99//! // Create parameters for our circuit. In a production deployment these would
100//! // be generated securely using a multiparty computation.
101//! let mut rng = rng();
102//! let params = {
103//!     let c = MyCircuit { preimage: None };
104//!     groth16::generate_random_parameters::<Bls12, _, _>(c, &mut rng).unwrap()
105//! };
106//!
107//! // Prepare the verification key (for proof verification).
108//! let pvk = groth16::prepare_verifying_key(&params.vk);
109//!
110//! // Pick a preimage and compute its hash.
111//! let preimage = [42; 80];
112//! let hash = Sha256::digest(&Sha256::digest(&preimage));
113//!
114//! // Create an instance of our circuit (with the preimage as a witness).
115//! let c = MyCircuit {
116//!     preimage: Some(preimage),
117//! };
118//!
119//! // Create a Groth16 proof with our parameters.
120//! let proof = groth16::create_random_proof(c, &params, &mut rng).unwrap();
121//!
122//! // Pack the hash as inputs for proof verification.
123//! let hash_bits = multipack::bytes_to_bits_le(&hash);
124//! let inputs = multipack::compute_multipacking(&hash_bits);
125//!
126//! // Check the proof!
127//! assert!(groth16::verify_proof(&pvk, &proof, &inputs).is_ok());
128//! ```
129//!
130//! # Roadmap
131//!
132//! `bellman` is being refactored into a generic proving library. Currently it
133//! is pairing-specific, and different types of proving systems need to be
134//! implemented as sub-modules. After the refactor, `bellman` will be generic
135//! using the [`ff`] and [`group`] crates, while specific proving systems will
136//! be separate crates that pull in the dependencies they require.
137
138// Catch documentation errors caused by code changes.
139#![deny(rustdoc::broken_intra_doc_links)]
140
141pub mod domain;
142pub mod gadgets;
143#[cfg(feature = "groth16")]
144pub mod groth16;
145pub mod multicore;
146pub mod multiexp;
147
148use ff::PrimeField;
149
150use std::error::Error;
151use std::fmt;
152use std::io;
153use std::marker::PhantomData;
154use std::ops::{Add, Sub};
155
156/// Computations are expressed in terms of arithmetic circuits, in particular
157/// rank-1 quadratic constraint systems. The `Circuit` trait represents a
158/// circuit that can be synthesized. The `synthesize` method is called during
159/// CRS generation and during proving.
160pub trait Circuit<Scalar: PrimeField> {
161    /// Synthesize the circuit into a rank-1 quadratic constraint system
162    fn synthesize<CS: ConstraintSystem<Scalar>>(self, cs: &mut CS) -> Result<(), SynthesisError>;
163}
164
165/// Represents a variable in our constraint system.
166#[derive(Copy, Clone, Debug)]
167pub struct Variable(Index);
168
169impl Variable {
170    /// This constructs a variable with an arbitrary index.
171    /// Circuit implementations are not recommended to use this.
172    pub fn new_unchecked(idx: Index) -> Variable {
173        Variable(idx)
174    }
175
176    /// This returns the index underlying the variable.
177    /// Circuit implementations are not recommended to use this.
178    pub fn get_unchecked(&self) -> Index {
179        self.0
180    }
181}
182
183/// Represents the index of either an input variable or
184/// auxiliary variable.
185#[derive(Copy, Clone, PartialEq, Debug)]
186pub enum Index {
187    Input(usize),
188    Aux(usize),
189}
190
191/// This represents a linear combination of some variables, with coefficients
192/// in the scalar field of a pairing-friendly elliptic curve group.
193#[derive(Clone)]
194pub struct LinearCombination<Scalar: PrimeField>(Vec<(Variable, Scalar)>);
195
196impl<Scalar: PrimeField> AsRef<[(Variable, Scalar)]> for LinearCombination<Scalar> {
197    fn as_ref(&self) -> &[(Variable, Scalar)] {
198        &self.0
199    }
200}
201
202impl<Scalar: PrimeField> LinearCombination<Scalar> {
203    pub fn zero() -> LinearCombination<Scalar> {
204        LinearCombination(vec![])
205    }
206}
207
208impl<Scalar: PrimeField> Add<(Scalar, Variable)> for LinearCombination<Scalar> {
209    type Output = LinearCombination<Scalar>;
210
211    fn add(mut self, (coeff, var): (Scalar, Variable)) -> LinearCombination<Scalar> {
212        self.0.push((var, coeff));
213
214        self
215    }
216}
217
218impl<Scalar: PrimeField> Sub<(Scalar, Variable)> for LinearCombination<Scalar> {
219    type Output = LinearCombination<Scalar>;
220
221    #[allow(clippy::suspicious_arithmetic_impl)]
222    fn sub(self, (coeff, var): (Scalar, Variable)) -> LinearCombination<Scalar> {
223        self + (coeff.neg(), var)
224    }
225}
226
227impl<Scalar: PrimeField> Add<Variable> for LinearCombination<Scalar> {
228    type Output = LinearCombination<Scalar>;
229
230    fn add(self, other: Variable) -> LinearCombination<Scalar> {
231        self + (Scalar::ONE, other)
232    }
233}
234
235impl<Scalar: PrimeField> Sub<Variable> for LinearCombination<Scalar> {
236    type Output = LinearCombination<Scalar>;
237
238    fn sub(self, other: Variable) -> LinearCombination<Scalar> {
239        self - (Scalar::ONE, other)
240    }
241}
242
243impl<'a, Scalar: PrimeField> Add<&'a LinearCombination<Scalar>> for LinearCombination<Scalar> {
244    type Output = LinearCombination<Scalar>;
245
246    fn add(mut self, other: &'a LinearCombination<Scalar>) -> LinearCombination<Scalar> {
247        for s in &other.0 {
248            self = self + (s.1, s.0);
249        }
250
251        self
252    }
253}
254
255impl<'a, Scalar: PrimeField> Sub<&'a LinearCombination<Scalar>> for LinearCombination<Scalar> {
256    type Output = LinearCombination<Scalar>;
257
258    fn sub(mut self, other: &'a LinearCombination<Scalar>) -> LinearCombination<Scalar> {
259        for s in &other.0 {
260            self = self - (s.1, s.0);
261        }
262
263        self
264    }
265}
266
267impl<'a, Scalar: PrimeField> Add<(Scalar, &'a LinearCombination<Scalar>)>
268    for LinearCombination<Scalar>
269{
270    type Output = LinearCombination<Scalar>;
271
272    fn add(
273        mut self,
274        (coeff, other): (Scalar, &'a LinearCombination<Scalar>),
275    ) -> LinearCombination<Scalar> {
276        for s in &other.0 {
277            let mut tmp = s.1;
278            tmp.mul_assign(&coeff);
279            self = self + (tmp, s.0);
280        }
281
282        self
283    }
284}
285
286impl<'a, Scalar: PrimeField> Sub<(Scalar, &'a LinearCombination<Scalar>)>
287    for LinearCombination<Scalar>
288{
289    type Output = LinearCombination<Scalar>;
290
291    fn sub(
292        mut self,
293        (coeff, other): (Scalar, &'a LinearCombination<Scalar>),
294    ) -> LinearCombination<Scalar> {
295        for s in &other.0 {
296            let mut tmp = s.1;
297            tmp.mul_assign(&coeff);
298            self = self - (tmp, s.0);
299        }
300
301        self
302    }
303}
304
305/// This is an error that could occur during circuit synthesis contexts,
306/// such as CRS generation or proving.
307#[derive(Debug)]
308pub enum SynthesisError {
309    /// During synthesis, we lacked knowledge of a variable assignment.
310    AssignmentMissing,
311    /// During synthesis, we divided by zero.
312    DivisionByZero,
313    /// During synthesis, we constructed an unsatisfiable constraint system.
314    Unsatisfiable,
315    /// During synthesis, our polynomials ended up being too high of degree
316    PolynomialDegreeTooLarge,
317    /// During proof generation, we encountered an identity in the CRS
318    UnexpectedIdentity,
319    /// During proof generation, we encountered an I/O error with the CRS
320    IoError(io::Error),
321    /// During CRS generation, we observed an unconstrained auxiliary variable
322    UnconstrainedVariable,
323}
324
325impl From<io::Error> for SynthesisError {
326    fn from(e: io::Error) -> SynthesisError {
327        SynthesisError::IoError(e)
328    }
329}
330
331impl Error for SynthesisError {}
332
333impl fmt::Display for SynthesisError {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
335        let msg = match *self {
336            SynthesisError::AssignmentMissing => {
337                "an assignment for a variable could not be computed"
338            }
339            SynthesisError::DivisionByZero => "division by zero",
340            SynthesisError::Unsatisfiable => "unsatisfiable constraint system",
341            SynthesisError::PolynomialDegreeTooLarge => "polynomial degree is too large",
342            SynthesisError::UnexpectedIdentity => "encountered an identity element in the CRS",
343            SynthesisError::IoError(_) => "encountered an I/O error",
344            SynthesisError::UnconstrainedVariable => "auxiliary variable was unconstrained",
345        };
346        if let SynthesisError::IoError(ref e) = *self {
347            write!(f, "I/O error: ")?;
348            e.fmt(f)
349        } else {
350            write!(f, "{}", msg)
351        }
352    }
353}
354
355/// An error during verification.
356#[derive(Debug, Clone)]
357pub enum VerificationError {
358    /// Verification was attempted with a malformed verifying key.
359    InvalidVerifyingKey,
360    /// Proof verification failed.
361    InvalidProof,
362}
363
364impl Error for VerificationError {}
365
366impl fmt::Display for VerificationError {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
368        let msg = match *self {
369            VerificationError::InvalidVerifyingKey => "malformed verifying key",
370            VerificationError::InvalidProof => "proof verification failed",
371        };
372        write!(f, "{}", msg)
373    }
374}
375
376/// Represents a constraint system which can have new variables
377/// allocated and constrains between them formed.
378pub trait ConstraintSystem<Scalar: PrimeField>: Sized {
379    /// Represents the type of the "root" of this constraint system
380    /// so that nested namespaces can minimize indirection.
381    type Root: ConstraintSystem<Scalar>;
382
383    /// Return the "one" input variable
384    fn one() -> Variable {
385        Variable::new_unchecked(Index::Input(0))
386    }
387
388    /// Allocate a private variable in the constraint system. The provided function is used to
389    /// determine the assignment of the variable. The given `annotation` function is invoked
390    /// in testing contexts in order to derive a unique name for this variable in the current
391    /// namespace.
392    fn alloc<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
393    where
394        F: FnOnce() -> Result<Scalar, SynthesisError>,
395        A: FnOnce() -> AR,
396        AR: Into<String>;
397
398    /// Allocate a public variable in the constraint system. The provided function is used to
399    /// determine the assignment of the variable.
400    fn alloc_input<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
401    where
402        F: FnOnce() -> Result<Scalar, SynthesisError>,
403        A: FnOnce() -> AR,
404        AR: Into<String>;
405
406    /// Enforce that `A` * `B` = `C`. The `annotation` function is invoked in testing contexts
407    /// in order to derive a unique name for the constraint in the current namespace.
408    fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
409    where
410        A: FnOnce() -> AR,
411        AR: Into<String>,
412        LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
413        LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
414        LC: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>;
415
416    /// Create a new (sub)namespace and enter into it. Not intended
417    /// for downstream use; use `namespace` instead.
418    fn push_namespace<NR, N>(&mut self, name_fn: N)
419    where
420        NR: Into<String>,
421        N: FnOnce() -> NR;
422
423    /// Exit out of the existing namespace. Not intended for
424    /// downstream use; use `namespace` instead.
425    fn pop_namespace(&mut self);
426
427    /// Gets the "root" constraint system, bypassing the namespacing.
428    /// Not intended for downstream use; use `namespace` instead.
429    fn get_root(&mut self) -> &mut Self::Root;
430
431    /// Begin a namespace for this constraint system.
432    fn namespace<NR, N>(&mut self, name_fn: N) -> Namespace<'_, Scalar, Self::Root>
433    where
434        NR: Into<String>,
435        N: FnOnce() -> NR,
436    {
437        self.get_root().push_namespace(name_fn);
438
439        Namespace(self.get_root(), PhantomData)
440    }
441}
442
443/// This is a "namespaced" constraint system which borrows a constraint system (pushing
444/// a namespace context) and, when dropped, pops out of the namespace context.
445pub struct Namespace<'a, Scalar: PrimeField, CS: ConstraintSystem<Scalar>>(
446    &'a mut CS,
447    PhantomData<Scalar>,
448);
449
450impl<'cs, Scalar: PrimeField, CS: ConstraintSystem<Scalar>> ConstraintSystem<Scalar>
451    for Namespace<'cs, Scalar, CS>
452{
453    type Root = CS::Root;
454
455    fn one() -> Variable {
456        CS::one()
457    }
458
459    fn alloc<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
460    where
461        F: FnOnce() -> Result<Scalar, SynthesisError>,
462        A: FnOnce() -> AR,
463        AR: Into<String>,
464    {
465        self.0.alloc(annotation, f)
466    }
467
468    fn alloc_input<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
469    where
470        F: FnOnce() -> Result<Scalar, SynthesisError>,
471        A: FnOnce() -> AR,
472        AR: Into<String>,
473    {
474        self.0.alloc_input(annotation, f)
475    }
476
477    fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
478    where
479        A: FnOnce() -> AR,
480        AR: Into<String>,
481        LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
482        LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
483        LC: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
484    {
485        self.0.enforce(annotation, a, b, c)
486    }
487
488    // Downstream users who use `namespace` will never interact with these
489    // functions and they will never be invoked because the namespace is
490    // never a root constraint system.
491
492    fn push_namespace<NR, N>(&mut self, _: N)
493    where
494        NR: Into<String>,
495        N: FnOnce() -> NR,
496    {
497        panic!("only the root's push_namespace should be called");
498    }
499
500    fn pop_namespace(&mut self) {
501        panic!("only the root's pop_namespace should be called");
502    }
503
504    fn get_root(&mut self) -> &mut Self::Root {
505        self.0.get_root()
506    }
507}
508
509impl<'a, Scalar: PrimeField, CS: ConstraintSystem<Scalar>> Drop for Namespace<'a, Scalar, CS> {
510    fn drop(&mut self) {
511        self.get_root().pop_namespace()
512    }
513}
514
515/// Convenience implementation of ConstraintSystem<Scalar> for mutable references to
516/// constraint systems.
517impl<'cs, Scalar: PrimeField, CS: ConstraintSystem<Scalar>> ConstraintSystem<Scalar>
518    for &'cs mut CS
519{
520    type Root = CS::Root;
521
522    fn one() -> Variable {
523        CS::one()
524    }
525
526    fn alloc<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
527    where
528        F: FnOnce() -> Result<Scalar, SynthesisError>,
529        A: FnOnce() -> AR,
530        AR: Into<String>,
531    {
532        (**self).alloc(annotation, f)
533    }
534
535    fn alloc_input<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
536    where
537        F: FnOnce() -> Result<Scalar, SynthesisError>,
538        A: FnOnce() -> AR,
539        AR: Into<String>,
540    {
541        (**self).alloc_input(annotation, f)
542    }
543
544    fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
545    where
546        A: FnOnce() -> AR,
547        AR: Into<String>,
548        LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
549        LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
550        LC: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
551    {
552        (**self).enforce(annotation, a, b, c)
553    }
554
555    fn push_namespace<NR, N>(&mut self, name_fn: N)
556    where
557        NR: Into<String>,
558        N: FnOnce() -> NR,
559    {
560        (**self).push_namespace(name_fn)
561    }
562
563    fn pop_namespace(&mut self) {
564        (**self).pop_namespace()
565    }
566
567    fn get_root(&mut self) -> &mut Self::Root {
568        (**self).get_root()
569    }
570}
571
572#[cfg(test)]
573mod test {
574    use super::*;
575
576    #[test]
577    fn verification_error_string() {
578        let err = VerificationError::InvalidProof;
579
580        // Make sure it correctly returns something (i.e. it's not an endless loop)
581        assert!(!err.to_string().is_empty());
582    }
583
584    #[test]
585    fn synthesis_error_string() {
586        let err = SynthesisError::PolynomialDegreeTooLarge;
587
588        // Make sure it correctly returns something (i.e. it's not an endless loop)
589        assert!(!err.to_string().is_empty());
590
591        let err = SynthesisError::IoError(io::Error::new(io::ErrorKind::Other, "other"));
592        assert!(
593            err.to_string().contains("other"),
594            "\"{}\" does not contain the underlying error",
595            err
596        );
597    }
598}