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(¶ms.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, ¶ms, &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}