diff --git a/Cargo.toml b/Cargo.toml index 4d9780cd2..fbe898b1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ tracing-texray = "0.2.0" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } cfg-if = "1.0.0" once_cell = "1.18.0" +home = "0.5.5" +memmap2 = "0.9.0" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { git="https://github.com/lurk-lab/pasta-msm", branch="dev", version = "0.1.4" } diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 000000000..7903fbf67 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,305 @@ +//! This module provides caching utils + +use std::{ + fs::{create_dir_all, remove_dir_all, File, OpenOptions}, + io::{self, BufWriter, Error, ErrorKind, Seek}, + ops::Deref, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use abomonation::{encode, Abomonation}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; + +use ff::PrimeField; +use memmap2::MmapMut; +use once_cell::sync::Lazy; + +use crate::{ + r1cs::commitment_key_size, + supernova::{AuxParams, CircuitDigests, NonUniformCircuit, PublicParams}, + traits::{ + circuit_supernova::StepCircuit, + commitment::{CommitmentEngineTrait, CommitmentKeyTrait}, + Group, + }, + CircuitShape, CommitmentKey, +}; + +/// Global registry for public parameters and commitment keys that are +/// cached onto the users local file system +pub static ARECIBO_CACHE: Lazy = Lazy::new(init_cache); + +/// If the env var `ARECIBO_CACHE` exists, then use that value. +/// Otherwise, set `ARECIBO_CACHE` to be `$HOME/.arecibo/`. +fn init_cache() -> Cache { + let path = if let Ok(x) = std::env::var("ARECIBO_CACHE") { + tracing::debug!("{:?}", &x); + PathBuf::from(x) + } else { + let path = home::home_dir() + .expect("targets without $HOME not supported") + .join(".arecibo/"); + std::env::set_var("ARECIBO_CACHE", &path); + path + }; + + create_dir_all(&path).unwrap(); + create_dir_all(path.join("commitment_keys")).unwrap(); + create_dir_all(path.join("public_params")).unwrap(); + + Cache { + inner: Arc::new(Mutex::new(path)), + } +} + +/// Cache structure that holds the root directory of all the cached files +pub struct Cache { + inner: Arc>, +} + +impl Cache { + /// Sets the inner root directory of the cache + pub fn set_inner(&self, path: &PathBuf) { + self.inner.lock().unwrap().clone_from(path); + + create_dir_all(path).unwrap(); + create_dir_all(path.join("commitment_keys")).unwrap(); + create_dir_all(path.join("public_params")).unwrap(); + } + + /// Gets the inner root directory of the cache + pub fn get_inner(&self) -> PathBuf { + self.inner.lock().unwrap().to_path_buf() + } + + /// Returns the commitment key file, and creates one if it doesn't exist + pub fn ck_file(&self) -> io::Result { + let path = self.get_inner().join("commitment_keys").join(G::name()); + let exists = path.exists(); + + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?; + + if !exists { + let ck = G::CE::setup(b"ck", 0); + file.write_u64::(ck.length() as u64)?; + ck.encode(&mut file)?; + } + + file.rewind()?; + Ok(file) + } + + /// Returns the file of the given digest, and creates one if it doesn't exist + pub fn digest_file(&self, digest: F) -> io::Result<(File, bool)> { + let path = self + .get_inner() + .join("public_params") + .join(format!("{:?}", digest)); + let exists = path.exists(); + + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path)?; + + file.rewind()?; + Ok((file, exists)) + } + + /// Returns a commitment key from the cache. If the cached commitment key is shorter + /// than what we need, we extend the key and update the cache as a side effect. + /// + /// This is the same as calling `G::CE::setup(b"ck", n)` + pub fn commitment_key(&self, n: usize) -> io::Result> { + let mut file = self.ck_file::()?; + + let ck_size = file.read_u64::()? as usize; + println!("ck_size: {}, n: {}", ck_size, n); + + let mut mmap = unsafe { MmapMut::map_mut(&file)? }; + let skip = std::mem::size_of::(); + let bytes = &mut mmap[skip..]; // skip the first `usize` bytes + + if ck_size < n { + let mut ck = CommitmentKey::::decode(bytes, ck_size)?; + G::CE::extend(&mut ck, b"ck", n - ck_size) + .map_err(|e| Error::new(ErrorKind::Other, format!("{:?}", e)))?; + + file.rewind()?; + file.write_u64::(ck.length() as u64)?; + ck.encode(&mut file)?; // update the cache + Ok(ck) + } else { + CommitmentKey::::decode(bytes, n) + } + } + + /// Updates the cached commitment key. If the cached commitment key is shorter than the given one, we extend it + pub fn set_commitment_key(&self, ck: &CommitmentKey) -> io::Result<()> { + let mut file = self.ck_file::()?; + + let ck_size = file.read_u64::()? as usize; + + if ck_size < ck.length() { + ck.encode(&mut file)?; // update the cache + } + + Ok(()) + } + + /// Returns the length of the commitment key in cache + pub fn commitment_key_len(&self) -> io::Result { + let mut file = self.ck_file::()?; + + let len = file.read_u64::()? as usize; + Ok(len) + } + + /// Returns a set of public params from the cache. + /// + /// This is the same as calling `PublicParams::new(non_uniform_circuit)` + pub fn public_params( + &self, + non_uniform_circuit: &NC, + ) -> io::Result> + where + G1: Group::Scalar>, + G2: Group::Scalar>, + C1: StepCircuit, + C2: StepCircuit, + NC: NonUniformCircuit, + ::Repr: Abomonation, + ::Repr: Abomonation, + { + let num_circuits = non_uniform_circuit.num_circuits(); + + let mut digests = vec![]; + let maybe_circuit_shapes = (0..num_circuits) + .map(|circuit_index| { + let circuit_primary = non_uniform_circuit.primary_circuit(circuit_index); + let digest = circuit_primary.circuit_digest::(num_circuits); + digests.push(digest); + + self.get::>(digest) + }) + .collect::>, _>>(); + + let circuit_digests = CircuitDigests::::new(digests); + let maybe_aux_params = self.get::>(circuit_digests.digest()); + + let pp = match (maybe_circuit_shapes, maybe_aux_params) { + (Ok(circuit_shapes), Ok(aux_params)) => { + tracing::info!("cache hit"); + + let size_primary = circuit_shapes + .iter() + .map(|circuit| commitment_key_size(&circuit.r1cs_shape, None)) + .max() + .unwrap(); + let ck_primary = self.commitment_key::(size_primary)?; + + PublicParams::::from_parts_unchecked(circuit_shapes, aux_params, ck_primary) + } + _ => { + tracing::info!("generating public params"); + let pp = PublicParams::new(non_uniform_circuit); + + let (circuit_shapes, aux_params, ck_primary) = pp.into_parts(); + + self.set::>(circuit_digests.digest(), &aux_params)?; + for (digest, circuit_shape) in circuit_digests.deref().iter().zip(circuit_shapes.iter()) { + self.set::>(*digest, circuit_shape)?; + } + self.set_commitment_key::(&ck_primary)?; + + PublicParams::::from_parts_unchecked(circuit_shapes, aux_params, ck_primary) + } + }; + + Ok(pp) + } + + fn get(&self, digest: F) -> io::Result { + let (file, exists) = self.digest_file::(digest)?; + + if exists { + unsafe { + let mut mmap = MmapMut::map_mut(&file)?; + let (t, remaining) = abomonation::decode::(&mut mmap).unwrap(); + assert!(remaining.is_empty()); + Ok(t.clone()) + } + } else { + Err(Error::new( + ErrorKind::Other, + "failed to retrieve from cache", + )) + } + } + + fn set(&self, digest: F, t: &T) -> io::Result<()> { + let (file, exists) = self.digest_file::(digest)?; + + if exists { + tracing::info!("object already in cache; nothing will be written"); + } else { + let mut writer = BufWriter::new(file); + unsafe { encode(t, &mut writer)? }; + } + + Ok(()) + } + + /// Deletes all the commitment keys in the cache + /// + /// **Warning**: This will indiscriminately wipe out everything in `/commitment_keys`, + /// so don't call this unless you're **sure** that there's nothing important in there. + pub fn clear_commitment_keys(&self) -> io::Result<()> { + let path = self.get_inner().join("commitment_keys"); + remove_dir_all(&path)?; + create_dir_all(path) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use pasta_curves::pallas; + + use crate::{ + provider::{bn256_grumpkin::bn256, secp_secq::secq256k1}, + traits::{commitment::CommitmentEngineTrait, Group}, + }; + + use super::ARECIBO_CACHE; + + fn test_ck_cache_with() { + for n in [10, 100, 1000] { + let ck = ARECIBO_CACHE.commitment_key::(n).unwrap(); + assert_eq!(ck, G::CE::setup(b"ck", n)); + assert_eq!( + ARECIBO_CACHE.commitment_key_len::().unwrap(), + n.next_power_of_two() + ); + } + } + + #[test] + fn test_ck_cache() { + ARECIBO_CACHE.set_inner(&PathBuf::from("./arecibo_cache_test")); + ARECIBO_CACHE.clear_commitment_keys().unwrap(); + + test_ck_cache_with::(); + test_ck_cache_with::(); + test_ck_cache_with::(); + + ARECIBO_CACHE.clear_commitment_keys().unwrap(); + } +} diff --git a/src/constants.rs b/src/constants.rs index 084a615fa..bc47b5585 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -8,3 +8,7 @@ pub(crate) const NUM_FE_FOR_RO: usize = 24; /// Bit size of Nova field element hashes pub const NUM_HASH_BITS: usize = 250; + +/// The threshold of elements we check when extending a `CommitmentKey` to avoid regressions due to code changes. +/// There are no security guarantees here +pub(crate) const CK_CHECKING_THRESHOLD: usize = 1; diff --git a/src/errors.rs b/src/errors.rs index 6474cfb51..60eff742d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -62,4 +62,7 @@ pub enum NovaError { /// returned when there is an error creating a digest #[error("DigestError")] DigestError, + /// returned when attempting to extend a commitment key with a label it was not originally generated from + #[error("InvalidCommitmentKeyLabel")] + InvalidCommitmentKeyLabel, } diff --git a/src/lib.rs b/src/lib.rs index a0325ab6b..e9f43c26f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod digest; mod nifs; // public modules +pub mod cache; pub mod constants; pub mod errors; pub mod gadgets; diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index 4a2cbbe94..2791e2271 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -58,7 +58,8 @@ impl_traits!( Bn256Compressed, Bn256Point, Bn256Affine, - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001" + "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001", + "bn256" ); impl_traits!( @@ -66,7 +67,8 @@ impl_traits!( GrumpkinCompressed, GrumpkinPoint, GrumpkinAffine, - "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", + "grumpkin" ); #[cfg(test)] @@ -101,4 +103,22 @@ mod tests { assert_eq!(ck_par, ck_ser); } } + + #[test] + fn test_from_label_with_offset() { + let label = b"test_from_label"; + let mut ck_par = vec![]; + for n in [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, + ] { + let offset = ck_par.len(); + ck_par.extend(::from_label_with_offset(label, offset, n)); + + let ck_ser = from_label_serial(label, n); + + assert_eq!(ck_par.len(), n); + assert_eq!(ck_ser.len(), n); + assert_eq!(ck_par, ck_ser); + } + } } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 62c702277..8257aab83 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -152,7 +152,8 @@ macro_rules! impl_traits { $name_compressed:ident, $name_curve:ident, $name_curve_affine:ident, - $order_str:literal + $order_str:literal, + $name_str:literal ) => { impl Group for $name::Point { type Base = $name::Base; @@ -179,17 +180,33 @@ macro_rules! impl_traits { self.to_bytes() } - fn from_label(label: &'static [u8], n: usize) -> Vec { + fn from_label_with_offset( + label: &'static [u8], + offset: usize, + n: usize, + ) -> Vec { + // deal with weird edge cases + if offset >= n { + return Vec::default(); + } + let mut shake = Shake256::default(); shake.update(label); let mut reader = shake.finalize_xof(); let mut uniform_bytes_vec = Vec::new(); - for _ in 0..n { + + // discard the first `offset` number of `uniform_bytes`; this is very cheap + for _ in 0..offset { + let mut uniform_bytes = [0u8; 32]; + reader.read_exact(&mut uniform_bytes).unwrap(); + } + for _ in 0..(n - offset) { let mut uniform_bytes = [0u8; 32]; reader.read_exact(&mut uniform_bytes).unwrap(); uniform_bytes_vec.push(uniform_bytes); } - let gens_proj: Vec<$name_curve> = (0..n) + + let gens_proj: Vec<$name_curve> = (0..(n - offset)) .into_par_iter() .map(|i| { let hash = $name_curve::hash_to_curve("from_uniform_bytes"); @@ -198,6 +215,7 @@ macro_rules! impl_traits { .collect(); let num_threads = rayon::current_num_threads(); + if gens_proj.len() > num_threads { let chunk = (gens_proj.len() as f64 / num_threads as f64).ceil() as usize; (0..num_threads) @@ -219,7 +237,7 @@ macro_rules! impl_traits { }) .collect() } else { - let mut gens = vec![$name_curve_affine::identity(); n]; + let mut gens = vec![$name_curve_affine::identity(); n - offset]; ::batch_normalize(&gens_proj, &mut gens); gens } @@ -252,6 +270,10 @@ macro_rules! impl_traits { fn get_generator() -> Self { $name::Point::generator() } + + fn name() -> &'static str { + $name_str + } } impl PrimeFieldExt for $name::Scalar { diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 5e357adf7..0739240c3 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -55,7 +55,8 @@ macro_rules! impl_traits { $name_compressed:ident, $name_curve:ident, $name_curve_affine:ident, - $order_str:literal + $order_str:literal, + $name_str:literal ) => { impl Group for $name::Point { type Base = $name::Base; @@ -100,17 +101,31 @@ macro_rules! impl_traits { $name_compressed::new(self.to_bytes()) } - fn from_label(label: &'static [u8], n: usize) -> Vec { + fn from_label_with_offset( + label: &'static [u8], + offset: usize, + n: usize, + ) -> Vec { + // deal with weird edge cases + if offset >= n { + return Vec::default(); + } + let mut shake = Shake256::default(); shake.update(label); let mut reader = shake.finalize_xof(); let mut uniform_bytes_vec = Vec::new(); - for _ in 0..n { + // discard the first `offset` number of `uniform_bytes`; this is very cheap + for _ in 0..offset { + let mut uniform_bytes = [0u8; 32]; + reader.read_exact(&mut uniform_bytes).unwrap(); + } + for _ in 0..(n - offset) { let mut uniform_bytes = [0u8; 32]; reader.read_exact(&mut uniform_bytes).unwrap(); uniform_bytes_vec.push(uniform_bytes); } - let ck_proj: Vec<$name_curve> = (0..n) + let ck_proj: Vec<$name_curve> = (0..(n - offset)) .into_par_iter() .map(|i| { let hash = $name_curve::hash_to_curve("from_uniform_bytes"); @@ -140,7 +155,7 @@ macro_rules! impl_traits { }) .collect() } else { - let mut ck = vec![$name_curve_affine::identity(); n]; + let mut ck = vec![$name_curve_affine::identity(); n - offset]; ::batch_normalize(&ck_proj, &mut ck); ck } @@ -170,6 +185,10 @@ macro_rules! impl_traits { fn get_generator() -> Self { $name::Point::generator() } + + fn name() -> &'static str { + $name_str + } } impl PrimeFieldExt for $name::Scalar { @@ -212,7 +231,8 @@ impl_traits!( PallasCompressedElementWrapper, Ep, EpAffine, - "40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001" + "40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", + "pallas" ); impl_traits!( @@ -220,7 +240,8 @@ impl_traits!( VestaCompressedElementWrapper, Eq, EqAffine, - "40000000000000000000000000000000224698fc094cf91b992d30ed00000001" + "40000000000000000000000000000000224698fc094cf91b992d30ed00000001", + "vesta" ); #[cfg(test)] @@ -255,4 +276,22 @@ mod tests { assert_eq!(ck_par, ck_ser); } } + + #[test] + fn test_from_label_with_offset() { + let label = b"test_from_label"; + let mut ck_par = vec![]; + for n in [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, + ] { + let offset = ck_par.len(); + ck_par.extend(::from_label_with_offset(label, offset, n)); + + let ck_ser = from_label_serial(label, n); + + assert_eq!(ck_par.len(), n); + assert_eq!(ck_ser.len(), n); + assert_eq!(ck_par, ck_ser); + } + } } diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index b9e5dfa74..b4d717a1e 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -2,7 +2,7 @@ use crate::{ errors::NovaError, traits::{ - commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, + commitment::{CommitmentEngineTrait, CommitmentKeyTrait, CommitmentTrait}, AbsorbInROTrait, CompressedGroup, Group, ROTrait, TranscriptReprTrait, }, }; @@ -15,6 +15,7 @@ use core::{ use ff::Field; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use std::{io, mem}; /// A type that holds commitment generators #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Abomonation)] @@ -33,10 +34,40 @@ impl Clone for CommitmentKey { } } -impl Len for CommitmentKey { +impl CommitmentKeyTrait for CommitmentKey { fn length(&self) -> usize { self.ck.len() } + + fn is_prefix_of(&self, other: &Self) -> bool { + other.ck.starts_with(&self.ck) + } + + fn encode(&self, write: &mut W) -> io::Result<()> { + // TODO: unwrap() + unsafe { abomonation::encode(self, write) } + } + + fn decode(bytes: &mut [u8], n: usize) -> io::Result { + // Note: this is unsafe but stable; it only assumes that `bytes` is any valid commitment key + unsafe { + // Ideally, we would like to allocate only one commitment key, because if we simply loaded + // all the bytes from the reader, we might be tapping into a cached commitment key with + // >=10^7 elements and kill the process. Instead, we need to manipulate the bytes to convince + // `abomonation::decode` to only decode what we need by manipulating the `Vec` header. + let mut test_ck = Self { ck: Vec::new() }; + test_ck.ck.set_len(n); + let header_size = mem::size_of::(); + let header: &[u8] = + std::slice::from_raw_parts(&test_ck as *const _ as *const u8, header_size); + bytes[..header_size].copy_from_slice(header); + + let (ck, _remaining) = abomonation::decode::(bytes) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to decode bytes"))?; + + Ok(ck.clone()) + } + } } /// A type that holds a commitment @@ -213,6 +244,11 @@ impl CommitmentEngineTrait for CommitmentEngine { } } + fn extend_aux(ck: &mut Self::CommitmentKey, label: &'static [u8], n: usize) { + let ck_extend = G::from_label_with_offset(label, ck.length(), n.next_power_of_two()); + ck.ck.extend(ck_extend); + } + fn commit(ck: &Self::CommitmentKey, v: &[G::Scalar]) -> Self::Commitment { assert!(ck.ck.len() >= v.len()); Commitment { diff --git a/src/provider/secp_secq.rs b/src/provider/secp_secq.rs index bd604d25b..0c26babf5 100644 --- a/src/provider/secp_secq.rs +++ b/src/provider/secp_secq.rs @@ -55,7 +55,8 @@ impl_traits!( Secp256k1Compressed, Secp256k1, Secp256k1Affine, - "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "secp256k1" ); impl_traits!( @@ -63,7 +64,8 @@ impl_traits!( Secq256k1Compressed, Secq256k1, Secq256k1Affine, - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + "secq256k1" ); #[cfg(test)] @@ -98,4 +100,22 @@ mod tests { assert_eq!(ck_par, ck_ser); } } + + #[test] + fn test_from_label_with_offset() { + let label = b"test_from_label"; + let mut ck_par = vec![]; + for n in [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 1021, + ] { + let offset = ck_par.len(); + ck_par.extend(::from_label_with_offset(label, offset, n)); + + let ck_ser = from_label_serial(label, n); + + assert_eq!(ck_par.len(), n); + assert_eq!(ck_ser.len(), n); + assert_eq!(ck_par, ck_ser); + } + } } diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 3ed9a6f7c..110b99f4c 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -18,7 +18,7 @@ use crate::{ PolyEvalInstance, PolyEvalWitness, SparsePolynomial, }, traits::{ - commitment::{CommitmentEngineTrait, CommitmentTrait, Len}, + commitment::{CommitmentEngineTrait, CommitmentKeyTrait, CommitmentTrait}, evaluation::EvaluationEngineTrait, snark::RelaxedR1CSSNARKTrait, Group, TranscriptEngineTrait, TranscriptReprTrait, diff --git a/src/supernova/mod.rs b/src/supernova/mod.rs index 78eb5086c..dc6750fbb 100644 --- a/src/supernova/mod.rs +++ b/src/supernova/mod.rs @@ -124,7 +124,6 @@ where { ro_consts_primary: ROConstants, ro_consts_circuit_primary: ROConstantsCircuit, - ck_primary: CommitmentKey, // This is shared between all circuit params augmented_circuit_params_primary: SuperNovaAugmentedCircuitParams, ro_consts_secondary: ROConstants, @@ -168,8 +167,8 @@ where C2: StepCircuit, { /// Construct a new [PublicParams] - pub fn new>(non_unifrom_circuit: &NC) -> Self { - let num_circuits = non_unifrom_circuit.num_circuits(); + pub fn new>(non_uniform_circuit: &NC) -> Self { + let num_circuits = non_uniform_circuit.num_circuits(); let augmented_circuit_params_primary = SuperNovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, true); @@ -179,7 +178,7 @@ where let circuit_shapes = (0..num_circuits) .map(|i| { - let c_primary = non_unifrom_circuit.primary_circuit(i); + let c_primary = non_uniform_circuit.primary_circuit(i); let F_arity = c_primary.arity(); // Initialize ck for the primary let circuit_primary: SuperNovaAugmentedCircuit<'_, G2, C1> = SuperNovaAugmentedCircuit::new( @@ -202,7 +201,7 @@ where let augmented_circuit_params_secondary = SuperNovaAugmentedCircuitParams::new(BN_LIMB_WIDTH, BN_N_LIMBS, false); let ro_consts_secondary: ROConstants = ROConstants::::default(); - let c_secondary = non_unifrom_circuit.secondary_circuit(); + let c_secondary = non_uniform_circuit.secondary_circuit(); let F_arity_secondary = c_secondary.arity(); let ro_consts_circuit_secondary: ROConstantsCircuit = ROConstantsCircuit::::default(); @@ -239,8 +238,8 @@ where pp } - /// Breaks down an instance of [PublicParams] into the circuit params and auxilliary params. - pub fn into_parts(self) -> (Vec>, AuxParams) { + /// Breaks down an instance of [PublicParams] into the circuit params, auxilliary params, and primary commitment key. + pub fn into_parts(self) -> (Vec>, AuxParams, CommitmentKey) { let digest = self.digest(); let PublicParams { @@ -261,7 +260,6 @@ where let aux_params = AuxParams { ro_consts_primary, ro_consts_circuit_primary, - ck_primary, augmented_circuit_params_primary, ro_consts_secondary, ro_consts_circuit_secondary, @@ -271,16 +269,20 @@ where digest, }; - (circuit_shapes, aux_params) + (circuit_shapes, aux_params, ck_primary) } - /// Create a [PublicParams] from a vector of raw [CircuitShape] and auxilliary params. - pub fn from_parts(circuit_shapes: Vec>, aux_params: AuxParams) -> Self { + /// Create a [PublicParams] from a vector of raw [CircuitShape], auxilliary params, and the primary commitment key. + pub fn from_parts( + circuit_shapes: Vec>, + aux_params: AuxParams, + ck_primary: CommitmentKey, + ) -> Self { let pp = PublicParams { circuit_shapes, ro_consts_primary: aux_params.ro_consts_primary, ro_consts_circuit_primary: aux_params.ro_consts_circuit_primary, - ck_primary: aux_params.ck_primary, + ck_primary, augmented_circuit_params_primary: aux_params.augmented_circuit_params_primary, ro_consts_secondary: aux_params.ro_consts_secondary, ro_consts_circuit_secondary: aux_params.ro_consts_circuit_secondary, @@ -298,17 +300,18 @@ where pp } - /// Create a [PublicParams] from a vector of raw [CircuitShape] and auxilliary params. + /// Create a [PublicParams] from a vector of raw [CircuitShape], auxilliary params, and the primary commitment key. /// We don't check that the `aux_params.digest` is a valid digest for the created params. pub fn from_parts_unchecked( circuit_shapes: Vec>, aux_params: AuxParams, + ck_primary: CommitmentKey, ) -> Self { PublicParams { circuit_shapes, ro_consts_primary: aux_params.ro_consts_primary, ro_consts_circuit_primary: aux_params.ro_consts_circuit_primary, - ck_primary: aux_params.ck_primary, + ck_primary, augmented_circuit_params_primary: aux_params.augmented_circuit_params_primary, ro_consts_secondary: aux_params.ro_consts_secondary, ro_consts_circuit_secondary: aux_params.ro_consts_circuit_secondary, diff --git a/src/traits/circuit_supernova.rs b/src/traits/circuit_supernova.rs index 1da4cb492..9178fcec7 100644 --- a/src/traits/circuit_supernova.rs +++ b/src/traits/circuit_supernova.rs @@ -3,6 +3,8 @@ use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use core::marker::PhantomData; use ff::PrimeField; +use super::Group; + /// A helper trait for a step of the incremental computation for `SuperNova` (i.e., circuit for F) -- to be implemented by /// applications. pub trait StepCircuit: Send + Sync + Clone { @@ -25,6 +27,22 @@ pub trait StepCircuit: Send + Sync + Clone { pc: Option<&AllocatedNum>, z: &[AllocatedNum], ) -> Result<(Option>, Vec>), SynthesisError>; + + /// Return a digest that represents a hash of the circuit shape. This is useful for keeping + /// track of differences between many iterations of a circuit. Specifically, this is used by + /// the cache as a key to fetch public parameters. + /// + /// Note: Because this is used as a cache key, implementers of this method should design good + /// performance characteristics by leveraging the particular shape of their circuits. + /// Otherwise, the default implementation will synthesize and digest the full circuit given. + /// See: [crate::supernova::circuit_digest]. + fn circuit_digest(&self, num_augmented_circuits: usize) -> F + where + G1: Group::Scalar>, + G2: Group::Scalar>, + { + crate::supernova::circuit_digest::(self, num_augmented_circuits) + } } /// A helper trait for a step of the incremental computation for `SuperNova` (i.e., circuit for F) -- automatically diff --git a/src/traits/commitment.rs b/src/traits/commitment.rs index e13726735..15c645731 100644 --- a/src/traits/commitment.rs +++ b/src/traits/commitment.rs @@ -10,6 +10,10 @@ use core::{ ops::{Add, AddAssign}, }; use serde::{Deserialize, Serialize}; +use std::{ + cmp::min, + io::{self, Write}, +}; use super::ScalarMul; @@ -74,18 +78,28 @@ pub trait CommitmentTrait: fn decompress(c: &Self::CompressedCommitment) -> Result; } -/// A trait that helps determine the lenght of a structure. -/// Note this does not impose any memory representation contraints on the structure. -pub trait Len { - /// Returns the length of the structure. +/// A trait that defines the behavior of commitment keys +pub trait CommitmentKeyTrait { + /// Returns the length of the commitment key. fn length(&self) -> usize; + + /// Checks if self is a prefix of the given key + fn is_prefix_of(&self, other: &Self) -> bool; + + /// Encode into a writer + fn encode(&self, write: &mut W) -> io::Result<()>; + + /// Decode from a reader + fn decode(bytes: &mut [u8], n: usize) -> io::Result + where + Self: Sized; } /// A trait that ties different pieces of the commitment generation together pub trait CommitmentEngineTrait: Clone + Send + Sync { /// Holds the type of the commitment key /// The key should quantify its length in terms of group generators. - type CommitmentKey: Len + type CommitmentKey: CommitmentKeyTrait + Clone + PartialEq + Debug @@ -101,6 +115,33 @@ pub trait CommitmentEngineTrait: Clone + Send + Sync { /// Samples a new commitment key of a specified size fn setup(label: &'static [u8], n: usize) -> Self::CommitmentKey; + /// Internal implementation of [CommitmentEngineTrait::extend] + /// + /// **Do not call this directly**. This does not check the consistency of the given label. + fn extend_aux(ck: &mut Self::CommitmentKey, label: &'static [u8], n: usize); + + /// Extends a commitment key by a given size. + /// + /// This is an optimization to avoid regenerating an entire key again when + /// we just need to extend it by a few elements. To verify that the same label + /// was used between the given one and the original one used to generate the key, + /// we check [crate::constants::CK_CHECKING_THRESHOLD] elements and assert equality. + fn extend(ck: &mut Self::CommitmentKey, label: &'static [u8], n: usize) -> Result<(), NovaError> + where + Self: Sized, + { + let threshold = min(ck.length(), crate::constants::CK_CHECKING_THRESHOLD); + let test_ck = Self::setup(label, threshold); + + if !test_ck.is_prefix_of(ck) { + return Err(NovaError::InvalidCommitmentKeyLabel); + } + + Self::extend_aux(ck, label, n); + + Ok(()) + } + /// Commits to the provided vector using the provided generators fn commit(ck: &Self::CommitmentKey, v: &[G::Scalar]) -> Self::Commitment; } diff --git a/src/traits/mod.rs b/src/traits/mod.rs index 9970a0d5e..788b83285 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -80,7 +80,20 @@ pub trait Group: fn preprocessed(&self) -> Self::PreprocessedGroupElement; /// Produce a vector of group elements using a static label - fn from_label(label: &'static [u8], n: usize) -> Vec; + fn from_label(label: &'static [u8], n: usize) -> Vec { + Self::from_label_with_offset(label, 0, n) + } + + /// Produce a vector of `n - offset` group elements using a static label, + /// by first generating `n` elements and then discarding the first `offset` of them + /// + /// Note: the elements are *deterministically* generated from the label. The intention here + /// is to leverage that and save some work by skipping the first `offset` when we already have them. + fn from_label_with_offset( + label: &'static [u8], + offset: usize, + n: usize, + ) -> Vec; /// Returns the affine coordinates (x, y, infinty) for the point fn to_coordinates(&self) -> (Self::Base, Self::Base, bool); @@ -93,6 +106,9 @@ pub trait Group: /// Returns A, B, and the order of the group as a big integer fn get_curve_params() -> (Self::Base, Self::Base, BigInt); + + /// Returns the user name of this group + fn name() -> &'static str; } /// Represents a compressed version of a group element