//! This module provides an implementation of a variant of (Turbo)[PLONK][plonk] //! that is designed specifically for the polynomial commitment scheme described //! in the [Halo][halo] paper. //! //! [halo]: https://eprint.iacr.org/2019/1021 //! [plonk]: https://eprint.iacr.org/2019/953 use blake2b_simd::Params as Blake2bParams; use crate::arithmetic::{CurveAffine, FieldExt}; use crate::poly::{ commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, }; use crate::transcript::{ChallengeScalar, Transcript}; mod circuit; mod keygen; mod lookup; pub(crate) mod permutation; mod vanishing; mod prover; mod verifier; pub use circuit::*; pub use keygen::*; pub use prover::*; pub use verifier::*; use std::io; /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. #[derive(Debug)] pub struct VerifyingKey { domain: EvaluationDomain, fixed_commitments: Vec, permutations: Vec>, cs: ConstraintSystem, } impl VerifyingKey { /// Writes a verifying key to a buffer. pub fn write(&self, writer: &mut W) -> io::Result<()> { for commitment in &self.fixed_commitments { writer.write_all(commitment.to_bytes().as_ref())?; } for permutation in &self.permutations { permutation.write(writer)?; } Ok(()) } /// Reads a verification key from a buffer. pub fn read>( reader: &mut R, params: &Params, ) -> io::Result { let (domain, cs, _) = keygen::create_domain::(params); let fixed_commitments: Vec<_> = (0..cs.num_fixed_columns) .map(|_| C::read(reader)) .collect::>()?; let permutations: Vec<_> = cs .permutations .iter() .map(|argument| permutation::VerifyingKey::read(reader, argument)) .collect::>()?; Ok(VerifyingKey { domain, fixed_commitments, permutations, cs, }) } /// Hashes a verification key into a transcript. pub fn hash_into>(&self, transcript: &mut T) -> io::Result<()> { let mut hasher = Blake2bParams::new() .hash_length(64) .personal(b"Halo2-Verify-Key") .to_state(); let s = format!("{:?}", self.pinned()); hasher.update(&(s.len() as u64).to_le_bytes()); hasher.update(s.as_bytes()); // Hash in final Blake2bState transcript.common_scalar(C::Scalar::from_bytes_wide(hasher.finalize().as_array()))?; Ok(()) } /// Obtains a pinned representation of this verification key that contains /// the minimal information necessary to reconstruct the verification key. pub fn pinned(&self) -> PinnedVerificationKey<'_, C> { PinnedVerificationKey { base_modulus: C::Base::MODULUS, scalar_modulus: C::Scalar::MODULUS, domain: self.domain.pinned(), fixed_commitments: &self.fixed_commitments, permutations: &self.permutations, cs: self.cs.pinned(), } } } /// Minimal representation of a verification key that can be used to identify /// its active contents. #[derive(Debug)] pub struct PinnedVerificationKey<'a, C: CurveAffine> { base_modulus: &'static str, scalar_modulus: &'static str, domain: PinnedEvaluationDomain<'a, C::Scalar>, cs: PinnedConstraintSystem<'a, C::Scalar>, fixed_commitments: &'a Vec, permutations: &'a Vec>, } /// This is a proving key which allows for the creation of proofs for a /// particular circuit. #[derive(Debug)] pub struct ProvingKey { vk: VerifyingKey, // TODO: get rid of this? l0: Polynomial, fixed_values: Vec>, fixed_polys: Vec>, fixed_cosets: Vec>, permutations: Vec>, } /// This is an error that could occur during proving or circuit synthesis. // TODO: these errors need to be cleaned up #[derive(Debug)] pub enum Error { /// This is an error that can occur during synthesis of the circuit, for /// example, when the witness is not present. SynthesisError, /// The structured reference string or the parameters are not compatible /// with the circuit being synthesized. IncompatibleParams, /// The constraint system is not satisfied. ConstraintSystemFailure, /// Out of bounds index passed to a backend BoundsFailure, /// Opening error OpeningError, /// Transcript error TranscriptError, } impl ProvingKey { /// Get the underlying [`VerifyingKey`]. pub fn get_vk(&self) -> &VerifyingKey { &self.vk } } impl VerifyingKey { /// Get the underlying [`EvaluationDomain`]. pub fn get_domain(&self) -> &EvaluationDomain { &self.domain } } #[derive(Clone, Copy, Debug)] struct Theta; type ChallengeTheta = ChallengeScalar; #[derive(Clone, Copy, Debug)] struct Beta; type ChallengeBeta = ChallengeScalar; #[derive(Clone, Copy, Debug)] struct Gamma; type ChallengeGamma = ChallengeScalar; #[derive(Clone, Copy, Debug)] struct Y; type ChallengeY = ChallengeScalar; #[derive(Clone, Copy, Debug)] struct X; type ChallengeX = ChallengeScalar; #[test] fn test_proving() { use crate::arithmetic::FieldExt; use crate::dev::MockProver; use crate::pasta::{EqAffine, Fp}; use crate::poly::{ commitment::{Blind, Params}, Rotation, }; use crate::transcript::{Blake2bRead, Blake2bWrite}; use circuit::{Advice, Column, Fixed}; use group::Curve; use std::marker::PhantomData; const K: u32 = 5; /// This represents an advice column at a certain row in the ConstraintSystem #[derive(Copy, Clone, Debug)] pub struct Variable(Column, usize); // Initialize the polynomial commitment parameters let params: Params = Params::new(K); #[derive(Copy, Clone)] struct PLONKConfig { a: Column, b: Column, c: Column, d: Column, e: Column, sa: Column, sb: Column, sc: Column, sm: Column, sp: Column, sl: Column, sl2: Column, perm: usize, perm2: usize, } trait StandardCS { fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where F: FnOnce() -> Result<(FF, FF, FF), Error>; fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where F: FnOnce() -> Result<(FF, FF, FF), Error>; fn copy(&mut self, a: Variable, b: Variable) -> Result<(), Error>; fn public_input(&mut self, f: F) -> Result where F: FnOnce() -> Result; fn lookup_table(&mut self, values: &[Vec]) -> Result<(), Error>; } #[derive(Clone)] struct MyCircuit { a: Option, lookup_tables: Vec>, } struct StandardPLONK<'a, F: FieldExt, CS: Assignment + 'a> { cs: &'a mut CS, config: PLONKConfig, current_gate: usize, _marker: PhantomData, } impl<'a, FF: FieldExt, CS: Assignment> StandardPLONK<'a, FF, CS> { fn new(cs: &'a mut CS, config: PLONKConfig) -> Self { StandardPLONK { cs, config, current_gate: 0, _marker: PhantomData, } } } impl<'a, FF: FieldExt, CS: Assignment> StandardCS for StandardPLONK<'a, FF, CS> { fn raw_multiply(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where F: FnOnce() -> Result<(FF, FF, FF), Error>, { let index = self.current_gate; self.current_gate += 1; let mut value = None; self.cs.assign_advice( || "lhs", self.config.a, index, || { value = Some(f()?); Ok(value.ok_or(Error::SynthesisError)?.0) }, )?; self.cs.assign_advice( || "lhs^4", self.config.d, index, || Ok(value.ok_or(Error::SynthesisError)?.0.square().square()), )?; self.cs.assign_advice( || "rhs", self.config.b, index, || Ok(value.ok_or(Error::SynthesisError)?.1), )?; self.cs.assign_advice( || "rhs^4", self.config.e, index, || Ok(value.ok_or(Error::SynthesisError)?.1.square().square()), )?; self.cs.assign_advice( || "out", self.config.c, index, || Ok(value.ok_or(Error::SynthesisError)?.2), )?; self.cs .assign_fixed(|| "a", self.config.sa, index, || Ok(FF::zero()))?; self.cs .assign_fixed(|| "b", self.config.sb, index, || Ok(FF::zero()))?; self.cs .assign_fixed(|| "c", self.config.sc, index, || Ok(FF::one()))?; self.cs .assign_fixed(|| "a * b", self.config.sm, index, || Ok(FF::one()))?; Ok(( Variable(self.config.a, index), Variable(self.config.b, index), Variable(self.config.c, index), )) } fn raw_add(&mut self, f: F) -> Result<(Variable, Variable, Variable), Error> where F: FnOnce() -> Result<(FF, FF, FF), Error>, { let index = self.current_gate; self.current_gate += 1; let mut value = None; self.cs.assign_advice( || "lhs", self.config.a, index, || { value = Some(f()?); Ok(value.ok_or(Error::SynthesisError)?.0) }, )?; self.cs.assign_advice( || "lhs^4", self.config.d, index, || Ok(value.ok_or(Error::SynthesisError)?.0.square().square()), )?; self.cs.assign_advice( || "rhs", self.config.b, index, || Ok(value.ok_or(Error::SynthesisError)?.1), )?; self.cs.assign_advice( || "rhs^4", self.config.e, index, || Ok(value.ok_or(Error::SynthesisError)?.1.square().square()), )?; self.cs.assign_advice( || "out", self.config.c, index, || Ok(value.ok_or(Error::SynthesisError)?.2), )?; self.cs .assign_fixed(|| "a", self.config.sa, index, || Ok(FF::one()))?; self.cs .assign_fixed(|| "b", self.config.sb, index, || Ok(FF::one()))?; self.cs .assign_fixed(|| "c", self.config.sc, index, || Ok(FF::one()))?; self.cs .assign_fixed(|| "a * b", self.config.sm, index, || Ok(FF::zero()))?; Ok(( Variable(self.config.a, index), Variable(self.config.b, index), Variable(self.config.c, index), )) } fn copy(&mut self, left: Variable, right: Variable) -> Result<(), Error> { let left_column = match left.0 { x if x == self.config.a => 0, x if x == self.config.b => 1, x if x == self.config.c => 2, _ => unreachable!(), }; let right_column = match right.0 { x if x == self.config.a => 0, x if x == self.config.b => 1, x if x == self.config.c => 2, _ => unreachable!(), }; self.cs .copy(self.config.perm, left_column, left.1, right_column, right.1)?; self.cs.copy( self.config.perm2, left_column, left.1, right_column, right.1, ) } fn public_input(&mut self, f: F) -> Result where F: FnOnce() -> Result, { let index = self.current_gate; self.current_gate += 1; self.cs .assign_advice(|| "value", self.config.a, index, || f())?; self.cs .assign_fixed(|| "public", self.config.sp, index, || Ok(FF::one()))?; Ok(Variable(self.config.a, index)) } fn lookup_table(&mut self, values: &[Vec]) -> Result<(), Error> { for (&value_0, &value_1) in values[0].iter().zip(values[1].iter()) { let index = self.current_gate; self.current_gate += 1; self.cs .assign_fixed(|| "table col 1", self.config.sl, index, || Ok(value_0))?; self.cs .assign_fixed(|| "table col 2", self.config.sl2, index, || Ok(value_1))?; } Ok(()) } } impl Circuit for MyCircuit { type Config = PLONKConfig; fn configure(meta: &mut ConstraintSystem) -> PLONKConfig { let e = meta.advice_column(); let a = meta.advice_column(); let b = meta.advice_column(); let sf = meta.fixed_column(); let c = meta.advice_column(); let d = meta.advice_column(); let p = meta.instance_column(); let perm = meta.permutation(&[a.into(), b.into(), c.into()]); let perm2 = meta.permutation(&[a.into(), b.into(), c.into()]); let sm = meta.fixed_column(); let sa = meta.fixed_column(); let sb = meta.fixed_column(); let sc = meta.fixed_column(); let sp = meta.fixed_column(); let sl = meta.fixed_column(); let sl2 = meta.fixed_column(); /* * A B ... sl sl2 * [ * instance 0 ... 0 0 * a a ... 0 0 * a a^2 ... 0 0 * a a ... 0 0 * a a^2 ... 0 0 * ... ... ... ... ... * ... ... ... instance 0 * ... ... ... a a * ... ... ... a a^2 * ... ... ... 0 0 * ] */ let a_ = meta.query_any(a.into(), Rotation::cur()); let b_ = meta.query_any(b.into(), Rotation::cur()); let sl_ = meta.query_any(sl.into(), Rotation::cur()); let sl2_ = meta.query_any(sl2.into(), Rotation::cur()); meta.lookup(&[a_.clone()], &[sl_.clone()]); meta.lookup(&[a_ * b_], &[sl_ * sl2_]); meta.create_gate("Combined add-mult", |meta| { let d = meta.query_advice(d, Rotation::next()); let a = meta.query_advice(a, Rotation::cur()); let sf = meta.query_fixed(sf, Rotation::cur()); let e = meta.query_advice(e, Rotation::prev()); let b = meta.query_advice(b, Rotation::cur()); let c = meta.query_advice(c, Rotation::cur()); let sa = meta.query_fixed(sa, Rotation::cur()); let sb = meta.query_fixed(sb, Rotation::cur()); let sc = meta.query_fixed(sc, Rotation::cur()); let sm = meta.query_fixed(sm, Rotation::cur()); a.clone() * sa + b.clone() * sb + a * b * sm + (c * sc * (-F::one())) + sf * (d * e) }); meta.create_gate("Public input", |meta| { let a = meta.query_advice(a, Rotation::cur()); let p = meta.query_instance(p, Rotation::cur()); let sp = meta.query_fixed(sp, Rotation::cur()); sp * (a + p * (-F::one())) }); PLONKConfig { a, b, c, d, e, sa, sb, sc, sm, sp, sl, sl2, perm, perm2, } } fn synthesize( &self, cs: &mut impl Assignment, config: PLONKConfig, ) -> Result<(), Error> { let mut cs = StandardPLONK::new(cs, config); let _ = cs.public_input(|| Ok(F::one() + F::one()))?; for _ in 0..10 { let mut a_squared = None; let (a0, _, c0) = cs.raw_multiply(|| { a_squared = self.a.map(|a| a.square()); Ok(( self.a.ok_or(Error::SynthesisError)?, self.a.ok_or(Error::SynthesisError)?, a_squared.ok_or(Error::SynthesisError)?, )) })?; let (a1, b1, _) = cs.raw_add(|| { let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); Ok(( self.a.ok_or(Error::SynthesisError)?, a_squared.ok_or(Error::SynthesisError)?, fin.ok_or(Error::SynthesisError)?, )) })?; cs.copy(a0, a1)?; cs.copy(b1, c0)?; } cs.lookup_table(&self.lookup_tables)?; Ok(()) } } let a = Fp::from_u64(2834758237) * Fp::ZETA; let a_squared = a * &a; let instance = Fp::one() + Fp::one(); let lookup_table = vec![instance, a, a, Fp::zero()]; let lookup_table_2 = vec![Fp::zero(), a, a_squared, Fp::zero()]; let empty_circuit: MyCircuit = MyCircuit { a: None, lookup_tables: vec![lookup_table.clone(), lookup_table_2.clone()], }; let circuit: MyCircuit = MyCircuit { a: Some(a), lookup_tables: vec![lookup_table, lookup_table_2], }; // Initialize the proving key let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); let mut pubinputs = pk.get_vk().get_domain().empty_lagrange(); pubinputs[0] = instance; let pubinput = params .commit_lagrange(&pubinputs, Blind::default()) .to_affine(); // Check this circuit is satisfied. let prover = match MockProver::run(K, &circuit, vec![pubinputs.to_vec()]) { Ok(prover) => prover, Err(e) => panic!("{:?}", e), }; assert_eq!(prover.verify(), Ok(())); for _ in 0..100 { let mut transcript = Blake2bWrite::init(vec![]); // Create a proof create_proof( ¶ms, &pk, &[circuit.clone(), circuit.clone()], &[&[pubinputs.clone()], &[pubinputs.clone()]], &mut transcript, ) .expect("proof generation should not fail"); let proof: Vec = transcript.finalize(); let pubinput_slice = &[pubinput]; let pubinput_slice_copy = &[pubinput]; let msm = params.empty_msm(); let mut transcript = Blake2bRead::init(&proof[..]); let guard = verify_proof( ¶ms, pk.get_vk(), msm, &[pubinput_slice, pubinput_slice_copy], &mut transcript, ) .unwrap(); { let msm = guard.clone().use_challenges(); assert!(msm.eval()); } { let g = guard.compute_g(); let (msm, _) = guard.clone().use_g(g); assert!(msm.eval()); } let msm = guard.clone().use_challenges(); assert!(msm.clone().eval()); let mut transcript = Blake2bRead::init(&proof[..]); let mut vk_buffer = vec![]; pk.get_vk().write(&mut vk_buffer).unwrap(); let vk = VerifyingKey::::read::<_, MyCircuit>(&mut &vk_buffer[..], ¶ms) .unwrap(); let guard = verify_proof( ¶ms, &vk, msm, &[pubinput_slice, pubinput_slice_copy], &mut transcript, ) .unwrap(); { let msm = guard.clone().use_challenges(); assert!(msm.eval()); } { let g = guard.compute_g(); let (msm, _) = guard.clone().use_g(g); assert!(msm.eval()); } } // Check that the verification key has not changed unexpectedly { assert_eq!( format!("{:#?}", pk.vk.pinned()), r#####"PinnedVerificationKey { base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", domain: PinnedEvaluationDomain { k: 5, extended_k: 7, omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc, }, cs: PinnedConstraintSystem { num_fixed_columns: 8, num_advice_columns: 5, num_instance_columns: 1, gates: [ Sum( Sum( Sum( Sum( Product( Advice( 0, ), Fixed( 3, ), ), Product( Advice( 1, ), Fixed( 4, ), ), ), Product( Product( Advice( 0, ), Advice( 1, ), ), Fixed( 6, ), ), ), Scaled( Product( Advice( 2, ), Fixed( 5, ), ), 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000000, ), ), Product( Fixed( 2, ), Product( Advice( 3, ), Advice( 4, ), ), ), ), Product( Fixed( 7, ), Sum( Advice( 0, ), Scaled( Instance( 0, ), 0x40000000000000000000000000000000224698fc094cf91b992d30ed00000000, ), ), ), ], advice_queries: [ ( Column { index: 1, column_type: Advice, }, Rotation( 0, ), ), ( Column { index: 2, column_type: Advice, }, Rotation( 0, ), ), ( Column { index: 3, column_type: Advice, }, Rotation( 0, ), ), ( Column { index: 4, column_type: Advice, }, Rotation( 1, ), ), ( Column { index: 0, column_type: Advice, }, Rotation( -1, ), ), ], instance_queries: [ ( Column { index: 0, column_type: Instance, }, Rotation( 0, ), ), ], fixed_queries: [ ( Column { index: 6, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 7, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 0, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 2, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 3, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 4, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 1, column_type: Fixed, }, Rotation( 0, ), ), ( Column { index: 5, column_type: Fixed, }, Rotation( 0, ), ), ], permutations: [ Argument { columns: [ Column { index: 1, column_type: Advice, }, Column { index: 2, column_type: Advice, }, Column { index: 3, column_type: Advice, }, ], }, Argument { columns: [ Column { index: 1, column_type: Advice, }, Column { index: 2, column_type: Advice, }, Column { index: 3, column_type: Advice, }, ], }, ], lookups: [ Argument { input_expressions: [ Advice( 0, ), ], table_expressions: [ Fixed( 0, ), ], }, Argument { input_expressions: [ Product( Advice( 0, ), Advice( 1, ), ), ], table_expressions: [ Product( Fixed( 0, ), Fixed( 1, ), ), ], }, ], }, fixed_commitments: [ (0x2792aa28c75a1516a39a4637de0775e5ec6b6530f516c08d68a5bbb46e84a1de, 0x0281cd4163b0ee3ab6bf8f2268cd54cf9bea66af5cad9dc36e606141943ba936), (0x3cdb6aad229646e50f3f51c2011c771db056b05b1c59082b8281efd84c8a2ff3, 0x1af67237dfbf81ec144a8ec6eb894138ae74f42b2d42e5d004b16a604079f243), (0x30929fec22a98cfc1643aca048fbfa2347df388499de0e491c0aef036615e41a, 0x369f53ee6b760e2d1636e31aef89a9a8a8abcf8eae7749468b0a1a0c9f14c65a), (0x30929fec22a98cfc1643aca048fbfa2347df388499de0e491c0aef036615e41a, 0x369f53ee6b760e2d1636e31aef89a9a8a8abcf8eae7749468b0a1a0c9f14c65a), (0x2dc1809e1a657ba12ddf038a75600ce2ee002400e5dd4ee5818dcb2f72225b81, 0x0e2cf3c5b0865a3433b0c9ffca1d9af9f5cc9ea1c746c6bb8137f43a146a64af), (0x009891864ebb1288d28749f2ec16554b8f11fb0d73b024fb6fbb6bdbf9370716, 0x140268076a1d9e17e6332a3846208a1693046ba6d3fafe36987e418b6ab4cbb6), (0x220abc4c01a23a50aba33a9b725adc1ced28d4aeec2adb852da0783e6b11f086, 0x2f888fc3d5253867cb2374e44a04ae6a4a301b000c12c080a856d777b30c93ea), (0x0477d75521867d384452ee0883397838dbd576614ab8fe0019a65f0c570dbc26, 0x33835efa7bc0855a8c2e0644051b75b2404568ef44cebd4accdbcdfec042839b), ], permutations: [ VerifyingKey { commitments: [ (0x31e37d7bdde8c02fb8a3b84d1dc30b730bc5ee4fda7973f00cbaa5ecb3d1b3ae, 0x1af12066de65c315fe51c44459bef9624e74f2b2d92c9ee1bf07715038dad56a), (0x3a77fc054e01378e69fc4bc01417600ad8adce317ea572b24e978353e93466c8, 0x0e68c78cc93a71ba2dcd2c8d0f38d5b60333a29db6ed238e83641504f54f218d), (0x2a4a0739f4cb19c2a3316dc8e1e8bc86bc0a7f218cfa0af78788802e93a3b683, 0x08052016a9c440afea08a5b4f78c92e09f52d642be4a9013605ec2f4f199c69e), ], }, VerifyingKey { commitments: [ (0x31e37d7bdde8c02fb8a3b84d1dc30b730bc5ee4fda7973f00cbaa5ecb3d1b3ae, 0x1af12066de65c315fe51c44459bef9624e74f2b2d92c9ee1bf07715038dad56a), (0x3a77fc054e01378e69fc4bc01417600ad8adce317ea572b24e978353e93466c8, 0x0e68c78cc93a71ba2dcd2c8d0f38d5b60333a29db6ed238e83641504f54f218d), (0x2a4a0739f4cb19c2a3316dc8e1e8bc86bc0a7f218cfa0af78788802e93a3b683, 0x08052016a9c440afea08a5b4f78c92e09f52d642be4a9013605ec2f4f199c69e), ], }, ], }"##### ); } }