From 284c985f1af7f46746c7a90675dc35c2f33c1fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= <4142+huitseeker@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:05:12 -0400 Subject: [PATCH] Add DigestBuilder. (#40) (#229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add DigestBuilder. * Make digest and claims private. * refactor: Refactor DigestBuilder - Refactored `src/digest.rs` to replace `Vec` storage with dedicated Write I/O. - Removed optional `hasher` and introduced dedicated factory method. - Reworked digest computation and mapping into separate functions. - Merged build and digest computation to enhance coherence. - Improved type safety with Result error propagation. * Propagate DigestBuilder changes. * Fix tests. * Correct assertion for OutputSize scale. * Remove commented. * Remove dbg!. * Fixup rebase. --------- Co-authored-by: porcuquine Co-authored-by: François Garillot feat: add a digest to R1CSShape (#49) * refactor: Refactor Digestible trait - Removed `to_bytes` method from the `Digestible` trait in `src/digest.rs` file. * fix: Make bincode serialization in digest.rs more rigorous - Updated `bincode::serialize_into(byte_sink, self)` with a configurable version to enable "little endian" and "fixint encoding" options. - Added a comment in `src/digest.rs` about `bincode`'s recursive length-prefixing during serialization. * refactor: Refactor digest computation using `OnceCell` and `DigestComputer` This gives up on a generic builder and instead uses an idempotent `OnceCell` + a generic digest computer to populate the digest of a structure. - this shows how to set up digest computation so it doesn't depend on the digest field, - the digest can't be set twice, - an erroneous digest can't be inferred from the serialized data. In Details: - Overhauled digest functionality in multiple files by replacing `DigestBuilder` with `DigestComputer`, significantly altering the handling of hashes. - Incorporated `once_cell::sync::OnceCell` and `ff::PrimeField` dependencies to improve performance and simplify code. - Modified `VerifierKey` and `RunningClaims` structures to include a `OnceCell` for digest, leading to a change in function calls and procedures. - Simplified `setup_running_claims` by removing error handling and directly returning `RunningClaims` type. - Adapted test functions according to the changes including the removal of unnecessary unwrapping in certain scenarios. - Updated Cargo.toml with the new dependency `once_cell` version `1.18.0`. * refactor: rename pp digest in VerifierKey to pp_digest * feat: add a digest to R1CSShape * fix: Small issues - Introduced a new assertion within the `write_bytes` method of `src/supernova/mod.rs` for validating whether the `claims` are empty - Improved code comment clarity regarding the creation of a running claim in `src/supernova/mod.rs`. Co-authored-by: porcuquine <1746729+porcuquine@users.noreply.github.com> --- Cargo.toml | 1 + src/digest.rs | 166 +++++++++++++++++++++++++++++++++++++++++ src/errors.rs | 3 + src/lib.rs | 113 +++++++++++++--------------- src/r1cs.rs | 18 +++++ src/spartan/ppsnark.rs | 53 +++++++++---- src/spartan/snark.rs | 44 +++++++---- 7 files changed, 308 insertions(+), 90 deletions(-) create mode 100644 src/digest.rs diff --git a/Cargo.toml b/Cargo.toml index b33efebf..185d3187 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ byteorder = "1.4.3" thiserror = "1.0" halo2curves = { version = "0.4.0", features = ["derive_serde"] } group = "0.13.0" +once_cell = "1.18.0" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { version = "0.1.4" } diff --git a/src/digest.rs b/src/digest.rs new file mode 100644 index 00000000..1ec0ad0c --- /dev/null +++ b/src/digest.rs @@ -0,0 +1,166 @@ +use bincode::Options; +use ff::PrimeField; +use serde::Serialize; +use sha3::{Digest, Sha3_256}; +use std::io; +use std::marker::PhantomData; + +use crate::constants::NUM_HASH_BITS; + +/// Trait for components with potentially discrete digests to be included in their container's digest. +pub trait Digestible { + /// Write the byte representation of Self in a byte buffer + fn write_bytes(&self, byte_sink: &mut W) -> Result<(), io::Error>; +} + +/// Marker trait to be implemented for types that implement `Digestible` and `Serialize`. +/// Their instances will be serialized to bytes then digested. +pub trait SimpleDigestible: Serialize {} + +impl Digestible for T { + fn write_bytes(&self, byte_sink: &mut W) -> Result<(), io::Error> { + let config = bincode::DefaultOptions::new() + .with_little_endian() + .with_fixint_encoding(); + // Note: bincode recursively length-prefixes every field! + config + .serialize_into(byte_sink, self) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } +} + +pub struct DigestComputer<'a, F: PrimeField, T> { + inner: &'a T, + _phantom: PhantomData, +} + +impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> { + fn hasher() -> Sha3_256 { + Sha3_256::new() + } + + fn map_to_field(digest: &mut [u8]) -> F { + let bv = (0..NUM_HASH_BITS).map(|i| { + let (byte_pos, bit_pos) = (i / 8, i % 8); + let bit = (digest[byte_pos] >> bit_pos) & 1; + bit == 1 + }); + + // turn the bit vector into a scalar + let mut digest = F::ZERO; + let mut coeff = F::ONE; + for bit in bv { + if bit { + digest += coeff; + } + coeff += coeff; + } + digest + } + + /// Create a new DigestComputer + pub fn new(inner: &'a T) -> Self { + DigestComputer { + inner, + _phantom: PhantomData, + } + } + + /// Compute the digest of a `Digestible` instance. + pub fn digest(&self) -> Result { + let mut hasher = Self::hasher(); + self + .inner + .write_bytes(&mut hasher) + .expect("Serialization error"); + let mut bytes: [u8; 32] = hasher.finalize().into(); + Ok(Self::map_to_field(&mut bytes)) + } +} + +#[cfg(test)] +mod tests { + use ff::Field; + use once_cell::sync::OnceCell; + use pasta_curves::pallas; + use serde::{Deserialize, Serialize}; + + use crate::traits::Group; + + use super::{DigestComputer, SimpleDigestible}; + + #[derive(Serialize, Deserialize)] + struct S { + i: usize, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, + } + + impl SimpleDigestible for S {} + + impl S { + fn new(i: usize) -> Self { + S { + i, + digest: OnceCell::new(), + } + } + + fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .unwrap() + } + } + + type G = pallas::Point; + + #[test] + fn test_digest_field_not_ingested_in_computation() { + let s1 = S::::new(42); + + // let's set up a struct with a weird digest field to make sure the digest computation does not depend of it + let oc = OnceCell::new(); + oc.set(::Scalar::ONE).unwrap(); + + let s2: S = S { i: 42, digest: oc }; + + assert_eq!( + DigestComputer::<::Scalar, _>::new(&s1) + .digest() + .unwrap(), + DigestComputer::<::Scalar, _>::new(&s2) + .digest() + .unwrap() + ); + + // note: because of the semantics of `OnceCell::get_or_try_init`, the above + // equality will not result in `s1.digest() == s2.digest` + assert_ne!( + s2.digest(), + DigestComputer::<::Scalar, _>::new(&s2) + .digest() + .unwrap() + ); + } + + #[test] + fn test_digest_impervious_to_serialization() { + let good_s = S::::new(42); + + // let's set up a struct with a weird digest field to confuse deserializers + let oc = OnceCell::new(); + oc.set(::Scalar::ONE).unwrap(); + + let bad_s: S = S { i: 42, digest: oc }; + // this justifies the adjective "bad" + assert_ne!(good_s.digest(), bad_s.digest()); + + let naughty_bytes = bincode::serialize(&bad_s).unwrap(); + + let retrieved_s: S = bincode::deserialize(&naughty_bytes).unwrap(); + assert_eq!(good_s.digest(), retrieved_s.digest()) + } +} diff --git a/src/errors.rs b/src/errors.rs index 1cfc5c0e..95385ad6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -56,4 +56,7 @@ pub enum NovaError { /// return when error during synthesis #[error("SynthesisError")] SynthesisError, + /// returned when there is an error creating a digest + #[error("DigestError")] + DigestError, } diff --git a/src/lib.rs b/src/lib.rs index b4b50e4f..dd1541ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ mod bellpepper; mod circuit; mod constants; +mod digest; mod nifs; mod r1cs; @@ -25,6 +26,8 @@ pub mod provider; pub mod spartan; pub mod traits; +use once_cell::sync::OnceCell; + use crate::bellpepper::{ r1cs::{NovaShape, NovaWitness}, shape_cs::ShapeCS, @@ -40,7 +43,8 @@ use gadgets::utils::scalar_as_base; use nifs::NIFS; use r1cs::{R1CSInstance, R1CSShape, R1CSWitness, RelaxedR1CSInstance, RelaxedR1CSWitness}; use serde::{Deserialize, Serialize}; -use sha3::{Digest, Sha3_256}; + +use crate::digest::{DigestComputer, SimpleDigestible}; use traits::{ circuit::StepCircuit, commitment::{CommitmentEngineTrait, CommitmentTrait}, @@ -70,9 +74,18 @@ where r1cs_shape_secondary: R1CSShape, augmented_circuit_params_primary: NovaAugmentedCircuitParams, augmented_circuit_params_secondary: NovaAugmentedCircuitParams, - digest: G1::Scalar, // digest of everything else with this field set to G1::Scalar::ZERO - _p_c1: PhantomData, - _p_c2: PhantomData, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, + _p: PhantomData<(C1, C2)>, +} + +impl SimpleDigestible for PublicParams +where + G1: Group::Scalar>, + G2: Group::Scalar>, + C1: StepCircuit, + C2: StepCircuit, +{ } impl PublicParams @@ -121,7 +134,7 @@ where let _ = circuit_secondary.synthesize(&mut cs); let (r1cs_shape_secondary, ck_secondary) = cs.r1cs_shape(); - let mut pp = Self { + PublicParams { F_arity_primary, F_arity_secondary, ro_consts_primary, @@ -134,15 +147,18 @@ where r1cs_shape_secondary, augmented_circuit_params_primary, augmented_circuit_params_secondary, - digest: G1::Scalar::ZERO, - _p_c1: Default::default(), - _p_c2: Default::default(), - }; - - // set the digest in pp - pp.digest = compute_digest::>(&pp); + digest: OnceCell::new(), + _p: Default::default(), + } + } - pp + /// Retrieve the digest of the public parameters. + pub fn digest(&self) -> G1::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .expect("Failure in retrieving digest") } /// Returns the number of constraints in the primary and secondary circuits @@ -203,7 +219,7 @@ where // base case for the primary let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest), + scalar_as_base::(pp.digest()), G1::Scalar::ZERO, z0_primary, None, @@ -230,7 +246,7 @@ where // base case for the secondary let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest, + pp.digest(), G2::Scalar::ZERO, z0_secondary, None, @@ -323,7 +339,7 @@ where let (nifs_secondary, (r_U_secondary, r_W_secondary)) = NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest), + &scalar_as_base::(pp.digest()), &pp.r1cs_shape_secondary, &self.r_U_secondary, &self.r_W_secondary, @@ -334,7 +350,7 @@ where let mut cs_primary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_primary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - scalar_as_base::(pp.digest), + scalar_as_base::(pp.digest()), G1::Scalar::from(self.i as u64), z0_primary, Some(self.zi_primary.clone()), @@ -362,7 +378,7 @@ where let (nifs_primary, (r_U_primary, r_W_primary)) = NIFS::prove( &pp.ck_primary, &pp.ro_consts_primary, - &pp.digest, + &pp.digest(), &pp.r1cs_shape_primary, &self.r_U_primary, &self.r_W_primary, @@ -373,7 +389,7 @@ where let mut cs_secondary: SatisfyingAssignment = SatisfyingAssignment::new(); let inputs_secondary: NovaAugmentedCircuitInputs = NovaAugmentedCircuitInputs::new( - pp.digest, + pp.digest(), G2::Scalar::from(self.i as u64), z0_secondary, Some(self.zi_secondary.clone()), @@ -452,7 +468,7 @@ where pp.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_primary, ); - hasher.absorb(pp.digest); + hasher.absorb(pp.digest()); hasher.absorb(G1::Scalar::from(num_steps as u64)); for e in z0_primary { hasher.absorb(*e); @@ -466,7 +482,7 @@ where pp.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * pp.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::(pp.digest)); + hasher2.absorb(scalar_as_base::(pp.digest())); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in z0_secondary { hasher2.absorb(*e); @@ -557,7 +573,7 @@ where F_arity_secondary: usize, ro_consts_primary: ROConstants, ro_consts_secondary: ROConstants, - digest: G1::Scalar, + pp_digest: G1::Scalar, vk_primary: S1::VerifierKey, vk_secondary: S2::VerifierKey, _p_c1: PhantomData, @@ -625,7 +641,7 @@ where F_arity_secondary: pp.F_arity_secondary, ro_consts_primary: pp.ro_consts_primary.clone(), ro_consts_secondary: pp.ro_consts_secondary.clone(), - digest: pp.digest, + pp_digest: pp.digest(), vk_primary, vk_secondary, _p_c1: Default::default(), @@ -645,7 +661,7 @@ where let res_secondary = NIFS::prove( &pp.ck_secondary, &pp.ro_consts_secondary, - &scalar_as_base::(pp.digest), + &scalar_as_base::(pp.digest()), &pp.r1cs_shape_secondary, &recursive_snark.r_U_secondary, &recursive_snark.r_W_secondary, @@ -719,7 +735,7 @@ where vk.ro_consts_secondary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_primary, ); - hasher.absorb(vk.digest); + hasher.absorb(vk.pp_digest); hasher.absorb(G1::Scalar::from(num_steps as u64)); for e in z0_primary { hasher.absorb(e); @@ -733,7 +749,7 @@ where vk.ro_consts_primary.clone(), NUM_FE_WITHOUT_IO_FOR_CRHF + 2 * vk.F_arity_secondary, ); - hasher2.absorb(scalar_as_base::(vk.digest)); + hasher2.absorb(scalar_as_base::(vk.pp_digest)); hasher2.absorb(G2::Scalar::from(num_steps as u64)); for e in z0_secondary { hasher2.absorb(e); @@ -758,7 +774,7 @@ where // fold the running instance and last instance to get a folded instance let f_U_secondary = self.nifs_secondary.verify( &vk.ro_consts_secondary, - &scalar_as_base::(vk.digest), + &scalar_as_base::(vk.pp_digest), &self.r_U_secondary, &self.l_u_secondary, )?; @@ -789,33 +805,6 @@ type Commitment = <::CE as CommitmentEngineTrait>::Commitment; type CompressedCommitment = <<::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment; type CE = ::CE; -fn compute_digest(o: &T) -> G::Scalar { - // obtain a vector of bytes representing public parameters - let bytes = bincode::serialize(o).unwrap(); - // convert pp_bytes into a short digest - let mut hasher = Sha3_256::new(); - hasher.update(&bytes); - let digest = hasher.finalize(); - - // truncate the digest to NUM_HASH_BITS bits - let bv = (0..NUM_HASH_BITS).map(|i| { - let (byte_pos, bit_pos) = (i / 8, i % 8); - let bit = (digest[byte_pos] >> bit_pos) & 1; - bit == 1 - }); - - // turn the bit vector into a scalar - let mut digest = G::Scalar::ZERO; - let mut coeff = G::Scalar::ONE; - for bit in bv { - if bit { - digest += coeff; - } - coeff += coeff; - } - digest -} - #[cfg(test)] mod tests { use crate::provider::bn256_grumpkin::{bn256, grumpkin}; @@ -899,7 +888,7 @@ mod tests { let pp = PublicParams::::setup(circuit1, circuit2); let digest_str = pp - .digest + .digest() .to_repr() .as_ref() .iter() @@ -919,13 +908,13 @@ mod tests { test_pp_digest_with::( &trivial_circuit1, &trivial_circuit2, - "39a4ea9dd384346fdeb6b5857c7be56fa035153b616d55311f3191dfbceea603", + "fe14a77d74cb8b8bb13105cea9c5b98b621b42c8d61da8f2adce8b9dd0d51b03", ); test_pp_digest_with::( &cubic_circuit1, &trivial_circuit2, - "3f7b25f589f2da5ab26254beba98faa54f6442ebf5fa5860caf7b08b576cab00", + "21ac840e52c75a62823cfdda4ca77aae2f07e4b6f5aa0eba80135492b2fbd003", ); let trivial_circuit1_grumpkin = @@ -937,12 +926,12 @@ mod tests { test_pp_digest_with::( &trivial_circuit1_grumpkin, &trivial_circuit2_grumpkin, - "967acca1d6b4731cd65d4072c12bbaca9648f24d7bcc2877aee720e4265d4302", + "0b25debdc99cef04b6d113a9a2814de89b3fad239aea90b29f2bdb27d95afa02", ); test_pp_digest_with::( &cubic_circuit1_grumpkin, &trivial_circuit2_grumpkin, - "44629f26a78bf6c4e3077f940232050d1793d304fdba5e221d0cf66f76a37903", + "0747f68f8d1c4bac4c3fb82689a1488b5835bbc97d6f6023fbe2760bb0053b00", ); let trivial_circuit1_secp = @@ -953,13 +942,13 @@ mod tests { test_pp_digest_with::( &trivial_circuit1_secp, - &trivial_circuit2_secp.clone(), - "b99760668a42354643e17b2f0a2d54f173d237eb213e7e758b20a88b4c653c01", + &trivial_circuit2_secp, + "0cf0880fa8debe42b7789474f6787062f8118ef251450dd5a7a4b5430f4bb902", ); test_pp_digest_with::( &cubic_circuit1_secp, &trivial_circuit2_secp, - "68db620e610a3cd75146a1e1bdd168f486b82c0b670277ad1e3d50441c501502", + "623a1dd99f3c906e79397f3de0dc1565b35fcb69abf2da51847bc9879a0a6000", ); } diff --git a/src/r1cs.rs b/src/r1cs.rs index cf5a770e..0e7726a2 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -2,6 +2,7 @@ #![allow(clippy::type_complexity)] use crate::{ constants::{BN_LIMB_WIDTH, BN_N_LIMBS}, + digest::{DigestComputer, SimpleDigestible}, errors::NovaError, gadgets::{ nonnative::{bignat::nat_to_limbs, util::f_to_nat}, @@ -14,6 +15,7 @@ use crate::{ }; use core::{cmp::max, marker::PhantomData}; use ff::Field; +use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -34,8 +36,12 @@ pub struct R1CSShape { pub(crate) A: Vec<(usize, usize, G::Scalar)>, pub(crate) B: Vec<(usize, usize, G::Scalar)>, pub(crate) C: Vec<(usize, usize, G::Scalar)>, + #[serde(skip, default = "OnceCell::new")] + pub(crate) digest: OnceCell, } +impl SimpleDigestible for R1CSShape {} + /// A type that holds a witness for a given R1CS instance #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct R1CSWitness { @@ -130,9 +136,19 @@ impl R1CSShape { A: A.to_owned(), B: B.to_owned(), C: C.to_owned(), + digest: OnceCell::new(), }) } + /// returnd the digest of the `R1CSShape` + pub fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| DigestComputer::new(self).digest()) + .cloned() + .expect("Failure retrieving digest") + } + // Checks regularity conditions on the R1CSShape, required in Spartan-class SNARKs // Panics if num_cons, num_vars, or num_io are not powers of two, or if num_io > num_vars #[inline] @@ -326,6 +342,7 @@ impl R1CSShape { A: self.A.clone(), B: self.B.clone(), C: self.C.clone(), + digest: OnceCell::new(), }; } @@ -359,6 +376,7 @@ impl R1CSShape { A: A_padded, B: B_padded, C: C_padded, + digest: OnceCell::new(), } } } diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 9936b6ad..45314d50 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -3,7 +3,7 @@ //! The verifier in this preprocessing SNARK maintains a commitment to R1CS matrices. This is beneficial when using a //! polynomial commitment scheme in which the verifier's costs is succinct. use crate::{ - compute_digest, + digest::{DigestComputer, SimpleDigestible}, errors::NovaError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{ @@ -27,6 +27,7 @@ use crate::{ }; use core::{cmp::max, marker::PhantomData}; use ff::{Field, PrimeField}; +use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -670,9 +671,12 @@ pub struct VerifierKey> { num_vars: usize, vk_ee: EE::VerifierKey, S_comm: R1CSShapeSparkCommitment, - digest: G::Scalar, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, } +impl> SimpleDigestible for VerifierKey {} + /// A succinct proof of knowledge of a witness to a relaxed R1CS instance /// The proof is produced using Spartan's combination of the sum-check and /// the commitment to a vector viewed as a polynomial commitment @@ -839,6 +843,35 @@ impl> RelaxedR1CSSNARK { } } +impl> VerifierKey { + fn new( + num_cons: usize, + num_vars: usize, + S_comm: R1CSShapeSparkCommitment, + vk_ee: EE::VerifierKey, + ) -> Self { + VerifierKey { + num_cons, + num_vars, + S_comm, + vk_ee, + digest: Default::default(), + } + } + + /// Returns the digest of the verifier's key + pub fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| { + let dc = DigestComputer::new(self); + dc.digest() + }) + .cloned() + .expect("Failure to retrieve digest!") + } +} + impl> RelaxedR1CSSNARKTrait for RelaxedR1CSSNARK { type ProverKey = ProverKey; type VerifierKey = VerifierKey; @@ -855,24 +888,14 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let S_repr = R1CSShapeSparkRepr::new(&S); let S_comm = S_repr.commit(ck); - let vk = { - let mut vk = VerifierKey { - num_cons: S.num_cons, - num_vars: S.num_vars, - S_comm: S_comm.clone(), - vk_ee, - digest: G::Scalar::ZERO, - }; - vk.digest = compute_digest::>(&vk); - vk - }; + let vk = VerifierKey::new(S.num_cons, S.num_vars, S_comm.clone(), vk_ee); let pk = ProverKey { pk_ee, S, S_repr, S_comm, - vk_digest: vk.digest, + vk_digest: vk.digest(), }; Ok((pk, vk)) @@ -1494,7 +1517,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let mut u_vec: Vec> = Vec::new(); // append the verifier key (including commitment to R1CS matrices) and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"vk", &vk.digest); + transcript.absorb(b"vk", &vk.digest()); transcript.absorb(b"U", U); let comm_Az = Commitment::::decompress(&self.comm_Az)?; diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 9fc3b006..55ec3a91 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -5,7 +5,7 @@ //! an IPA-based polynomial commitment scheme. use crate::{ - compute_digest, + digest::{DigestComputer, SimpleDigestible}, errors::NovaError, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{ @@ -20,6 +20,7 @@ use crate::{ Commitment, CommitmentKey, }; use ff::Field; +use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -39,7 +40,32 @@ pub struct ProverKey> { pub struct VerifierKey> { vk_ee: EE::VerifierKey, S: R1CSShape, - digest: G::Scalar, + #[serde(skip, default = "OnceCell::new")] + digest: OnceCell, +} + +impl> SimpleDigestible for VerifierKey {} + +impl> VerifierKey { + fn new(shape: R1CSShape, vk_ee: EE::VerifierKey) -> Self { + VerifierKey { + vk_ee, + S: shape, + digest: OnceCell::new(), + } + } + + /// Returns the digest of the verifier's key. + pub fn digest(&self) -> G::Scalar { + self + .digest + .get_or_try_init(|| { + let dc = DigestComputer::::new(self); + dc.digest() + }) + .cloned() + .expect("Failure to retrieve digest!") + } } /// A succinct proof of knowledge of a witness to a relaxed R1CS instance @@ -70,20 +96,12 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let S = S.pad(); - let vk = { - let mut vk = VerifierKey { - vk_ee, - S: S.clone(), - digest: G::Scalar::ZERO, - }; - vk.digest = compute_digest::>(&vk); - vk - }; + let vk: VerifierKey = VerifierKey::new(S.clone(), vk_ee); let pk = ProverKey { pk_ee, S, - vk_digest: vk.digest, + vk_digest: vk.digest(), }; Ok((pk, vk)) @@ -344,7 +362,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let mut transcript = G::TE::new(b"RelaxedR1CSSNARK"); // append the digest of R1CS matrices and the RelaxedR1CSInstance to the transcript - transcript.absorb(b"vk", &vk.digest); + transcript.absorb(b"vk", &vk.digest()); transcript.absorb(b"U", U); let (num_rounds_x, num_rounds_y) = (