From 512decfb6c1527404d7e47889ddbf13844a2a265 Mon Sep 17 00:00:00 2001 From: Victor Lopez Date: Fri, 3 Jun 2022 14:47:04 +0200 Subject: [PATCH] Add FiatShamirRng --- src/constraints/ahp.rs | 8 +- src/constraints/verifier.rs | 6 +- src/fiat_shamir/constraints.rs | 301 +++++++++++++++++++++++++++++++++ src/fiat_shamir/mod.rs | 269 +++++++++++++++++++++++++++++ src/lib.rs | 5 + src/sponge/mod.rs | 3 +- 6 files changed, 584 insertions(+), 8 deletions(-) create mode 100644 src/fiat_shamir/constraints.rs create mode 100644 src/fiat_shamir/mod.rs diff --git a/src/constraints/ahp.rs b/src/constraints/ahp.rs index ec2563d..406b878 100644 --- a/src/constraints/ahp.rs +++ b/src/constraints/ahp.rs @@ -8,7 +8,7 @@ use crate::{ sponge::CryptographicSpongeVarNonNative, CryptographicSpongeParameters, PhantomData, PrimeField, String, ToString, Vec, }; -use ark_nonnative_field::NonNativeFieldVar; +use ark_nonnative_field::{params::OptimizationType, NonNativeFieldVar}; use ark_poly::univariate::DensePolynomial; use ark_poly_commit::{ EvaluationsVar, LCTerm, LabeledPointVar, LinearCombinationCoeffVar, LinearCombinationVar, @@ -103,7 +103,7 @@ where elems.append(&mut comm.to_constraint_field().unwrap()); }); sponge_var.absorb(&elems)?; - sponge_var.absorb_nonnative(&message)?; + sponge_var.absorb_nonnative(&message, OptimizationType::Weight)?; } // obtain four elements from the sponge_var @@ -153,7 +153,7 @@ where elems.append(&mut comm.to_constraint_field().unwrap()); }); sponge_var.absorb(&elems)?; - sponge_var.absorb_nonnative(&message)?; + sponge_var.absorb_nonnative(&message, OptimizationType::Weight)?; } // obtain one element from the sponge_var @@ -195,7 +195,7 @@ where elems.append(&mut comm.to_constraint_field().unwrap()); }); sponge_var.absorb(&elems)?; - sponge_var.absorb_nonnative(&message)?; + sponge_var.absorb_nonnative(&message, OptimizationType::Weight)?; } // obtain one element from the sponge_var diff --git a/src/constraints/verifier.rs b/src/constraints/verifier.rs index 3d53f82..3bdf405 100644 --- a/src/constraints/verifier.rs +++ b/src/constraints/verifier.rs @@ -5,7 +5,7 @@ use crate::{ CryptographicSpongeParameters, CryptographicSpongeWithRate, Error, PhantomData, PrimeField, String, Vec, }; -use ark_nonnative_field::NonNativeFieldVar; +use ark_nonnative_field::{params::OptimizationType, NonNativeFieldVar}; use ark_poly::univariate::DensePolynomial; use ark_poly_commit::{PCCheckRandomDataVar, PCCheckVar, PolynomialCommitment}; use ark_r1cs_std::{bits::boolean::Boolean, fields::FieldVar, R1CSVar, ToConstraintFieldGadget}; @@ -59,7 +59,7 @@ where eprintln!("before AHP: constraints: {}", cs.num_constraints()); - sponge_var.absorb_nonnative(&public_input)?; + sponge_var.absorb_nonnative(&public_input, OptimizationType::Weight)?; let (_, verifier_state) = AHPForR1CS::::verifier_first_round( index_pvk.domain_h_size, @@ -116,7 +116,7 @@ where } } - sponge_var.absorb_nonnative(&evals_vec)?; + sponge_var.absorb_nonnative(&evals_vec, OptimizationType::Weight)?; let (opening_challenges, opening_challenges_bits) = sponge_var.squeeze_nonnative_field_elements(num_opening_challenges)?; diff --git a/src/fiat_shamir/constraints.rs b/src/fiat_shamir/constraints.rs new file mode 100644 index 0000000..405ce20 --- /dev/null +++ b/src/fiat_shamir/constraints.rs @@ -0,0 +1,301 @@ +use crate::{overhead, Vec}; +use ark_ff::PrimeField; +use ark_nonnative_field::params::{get_params, OptimizationType}; +use ark_nonnative_field::{AllocatedNonNativeFieldVar, NonNativeFieldVar}; +use ark_r1cs_std::{ + alloc::AllocVar, + bits::{uint8::UInt8, ToBitsGadget}, + boolean::Boolean, + fields::fp::AllocatedFp, + fields::fp::FpVar, + R1CSVar, +}; +use ark_relations::lc; +use ark_relations::r1cs::{ + ConstraintSystemRef, LinearCombination, OptimizationGoal, SynthesisError, +}; +use ark_sponge::constraints::{AbsorbGadget, CryptographicSpongeVar}; +use ark_sponge::CryptographicSponge; +use core::marker::PhantomData; + +/// Building the Fiat-Shamir sponge's gadget from any algebraic sponge's gadget. +#[derive(Clone)] +pub struct FiatShamirAlgebraicSpongeRngVar< + F: PrimeField, + CF: PrimeField, + PS: CryptographicSponge, + S: CryptographicSpongeVar, +> { + pub cs: ConstraintSystemRef, + pub s: S, + #[doc(hidden)] + f_phantom: PhantomData, + cf_phantom: PhantomData, + ps_phantom: PhantomData, +} + +impl> + FiatShamirAlgebraicSpongeRngVar +{ + /// Compress every two elements if possible. Provides a vector of (limb, num_of_additions), + /// both of which are CF. + #[tracing::instrument(target = "r1cs")] + pub fn compress_gadgets( + src_limbs: &[(FpVar, CF)], + ty: OptimizationType, + ) -> Result>, SynthesisError> { + let capacity = CF::size_in_bits() - 1; + let mut dest_limbs = Vec::>::new(); + + if src_limbs.is_empty() { + return Ok(vec![]); + } + + let params = get_params(F::size_in_bits(), CF::size_in_bits(), ty); + + let adjustment_factor_lookup_table = { + let mut table = Vec::::new(); + + let mut cur = CF::one(); + for _ in 1..=capacity { + table.push(cur); + cur.double_in_place(); + } + + table + }; + + let mut i: usize = 0; + let src_len = src_limbs.len(); + while i < src_len { + let first = &src_limbs[i]; + let second = if i + 1 < src_len { + Some(&src_limbs[i + 1]) + } else { + None + }; + + let first_max_bits_per_limb = params.bits_per_limb + overhead!(first.1 + &CF::one()); + let second_max_bits_per_limb = if second.is_some() { + params.bits_per_limb + overhead!(second.unwrap().1 + &CF::one()) + } else { + 0 + }; + + if second.is_some() && first_max_bits_per_limb + second_max_bits_per_limb <= capacity { + let adjustment_factor = &adjustment_factor_lookup_table[second_max_bits_per_limb]; + + dest_limbs.push(&first.0 * *adjustment_factor + &second.unwrap().0); + i += 2; + } else { + dest_limbs.push(first.0.clone()); + i += 1; + } + } + + Ok(dest_limbs) + } + + /// Push gadgets to sponge. + #[tracing::instrument(target = "r1cs", skip(sponge))] + pub fn push_gadgets_to_sponge( + sponge: &mut S, + src: &[NonNativeFieldVar], + ty: OptimizationType, + ) -> Result<(), SynthesisError> { + let mut src_limbs: Vec<(FpVar, CF)> = Vec::new(); + + for elem in src.iter() { + match elem { + NonNativeFieldVar::Constant(c) => { + let v = AllocatedNonNativeFieldVar::::new_constant(sponge.cs(), c)?; + + for limb in v.limbs.iter() { + let num_of_additions_over_normal_form = + if v.num_of_additions_over_normal_form == CF::zero() { + CF::one() + } else { + v.num_of_additions_over_normal_form + }; + src_limbs.push((limb.clone(), num_of_additions_over_normal_form)); + } + } + NonNativeFieldVar::Var(v) => { + for limb in v.limbs.iter() { + let num_of_additions_over_normal_form = + if v.num_of_additions_over_normal_form == CF::zero() { + CF::one() + } else { + v.num_of_additions_over_normal_form + }; + src_limbs.push((limb.clone(), num_of_additions_over_normal_form)); + } + } + } + } + + let dest_limbs = Self::compress_gadgets(&src_limbs, ty)?; + sponge.absorb(&dest_limbs)?; + Ok(()) + } + + /// Obtain random bits from hashchain gadget. (Not guaranteed to be uniformly distributed, + /// should only be used in certain situations.) + #[tracing::instrument(target = "r1cs", skip(sponge))] + pub fn get_booleans_from_sponge( + sponge: &mut S, + num_bits: usize, + ) -> Result>, SynthesisError> { + let bits_per_element = CF::size_in_bits() - 1; + let num_elements = (num_bits + bits_per_element - 1) / bits_per_element; + + let src_elements = sponge.squeeze_field_elements(num_elements)?; + let mut dest_bits = Vec::>::new(); + + for elem in src_elements.iter() { + let elem_bits = elem.to_bits_be()?; + dest_bits.extend_from_slice(&elem_bits[1..]); // discard the highest bit + } + + Ok(dest_bits) + } + + /// Obtain random elements from hashchain gadget. (Not guaranteed to be uniformly distributed, + /// should only be used in certain situations.) + #[tracing::instrument(target = "r1cs", skip(sponge))] + pub fn get_gadgets_from_sponge( + sponge: &mut S, + num_elements: usize, + outputs_short_elements: bool, + ) -> Result>, SynthesisError> { + let (dest_gadgets, _) = + Self::get_gadgets_and_bits_from_sponge(sponge, num_elements, outputs_short_elements)?; + + Ok(dest_gadgets) + } + + /// Obtain random elements, and the corresponding bits, from hashchain gadget. (Not guaranteed + /// to be uniformly distributed, should only be used in certain situations.) + #[tracing::instrument(target = "r1cs", skip(sponge))] + #[allow(clippy::type_complexity)] + pub fn get_gadgets_and_bits_from_sponge( + sponge: &mut S, + num_elements: usize, + outputs_short_elements: bool, + ) -> Result<(Vec>, Vec>>), SynthesisError> { + let cs = sponge.cs(); + + let optimization_type = match cs.optimization_goal() { + OptimizationGoal::None => OptimizationType::Constraints, + OptimizationGoal::Constraints => OptimizationType::Constraints, + OptimizationGoal::Weight => OptimizationType::Weight, + }; + + let params = get_params(F::size_in_bits(), CF::size_in_bits(), optimization_type); + + let num_bits_per_nonnative = if outputs_short_elements { + 128 + } else { + F::size_in_bits() - 1 // also omit the highest bit + }; + let bits = Self::get_booleans_from_sponge(sponge, num_bits_per_nonnative * num_elements)?; + + let mut lookup_table = Vec::>::new(); + let mut cur = F::one(); + for _ in 0..num_bits_per_nonnative { + let repr = AllocatedNonNativeFieldVar::::get_limbs_representations( + &cur, + optimization_type, + )?; + lookup_table.push(repr); + cur.double_in_place(); + } + + let mut dest_gadgets = Vec::>::new(); + let mut dest_bits = Vec::>>::new(); + bits.chunks_exact(num_bits_per_nonnative) + .for_each(|per_nonnative_bits| { + let mut val = vec![CF::zero(); params.num_limbs]; + let mut lc = vec![LinearCombination::::zero(); params.num_limbs]; + + let mut per_nonnative_bits_le = per_nonnative_bits.to_vec(); + per_nonnative_bits_le.reverse(); + + dest_bits.push(per_nonnative_bits_le.clone()); + + for (j, bit) in per_nonnative_bits_le.iter().enumerate() { + if bit.value().unwrap_or_default() { + for (k, val) in val.iter_mut().enumerate().take(params.num_limbs) { + *val += &lookup_table[j][k]; + } + } + + #[allow(clippy::needless_range_loop)] + for k in 0..params.num_limbs { + lc[k] = &lc[k] + bit.lc() * lookup_table[j][k]; + } + } + + let mut limbs = Vec::new(); + for k in 0..params.num_limbs { + let gadget = + AllocatedFp::new_witness(ark_relations::ns!(cs, "alloc"), || Ok(val[k])) + .unwrap(); + lc[k] = lc[k].clone() - (CF::one(), gadget.variable); + cs.enforce_constraint(lc!(), lc!(), lc[k].clone()).unwrap(); + limbs.push(FpVar::::from(gadget)); + } + + dest_gadgets.push(NonNativeFieldVar::::Var( + AllocatedNonNativeFieldVar:: { + cs: cs.clone(), + limbs, + num_of_additions_over_normal_form: CF::zero(), + is_in_the_normal_form: true, + target_phantom: Default::default(), + }, + )); + }); + + Ok((dest_gadgets, dest_bits)) + } +} + +impl> + CryptographicSpongeVar for FiatShamirAlgebraicSpongeRngVar +{ + type Parameters = S::Parameters; + + fn new(cs: ConstraintSystemRef, params: &Self::Parameters) -> Self { + Self { + cs: cs.clone(), + s: S::new(cs, params), + f_phantom: PhantomData, + cf_phantom: PhantomData, + ps_phantom: PhantomData, + } + } + + fn cs(&self) -> ConstraintSystemRef { + self.cs.clone() + } + + fn absorb(&mut self, input: &impl AbsorbGadget) -> Result<(), SynthesisError> { + self.s.absorb(input) + } + + fn squeeze_bytes(&mut self, num_bytes: usize) -> Result>, SynthesisError> { + self.s.squeeze_bytes(num_bytes) + } + + fn squeeze_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { + self.s.squeeze_bits(num_bits) + } + + fn squeeze_field_elements( + &mut self, + num_elements: usize, + ) -> Result>, SynthesisError> { + self.s.squeeze_field_elements(num_elements) + } +} diff --git a/src/fiat_shamir/mod.rs b/src/fiat_shamir/mod.rs new file mode 100644 index 0000000..e68800b --- /dev/null +++ b/src/fiat_shamir/mod.rs @@ -0,0 +1,269 @@ +use crate::Vec; +use ark_ff::{BigInteger, PrimeField}; +use ark_nonnative_field::params::{get_params, OptimizationType}; +use ark_nonnative_field::AllocatedNonNativeFieldVar; +use ark_sponge::{Absorb, CryptographicSponge}; +use ark_std::marker::PhantomData; +use ark_std::rand::RngCore; +use digest::Digest; +use rand_chacha::ChaChaRng; + +/// The constraints for Fiat-Shamir +pub mod constraints; + +/// a macro for computing ceil(log2(x))+1 for a field element x +#[doc(hidden)] +#[macro_export] +macro_rules! overhead { + ($x:expr) => {{ + use ark_ff::BigInteger; + let num = $x; + let num_bits = num.into_repr().to_bits_be(); + let mut skipped_bits = 0; + for b in num_bits.iter() { + if *b == false { + skipped_bits += 1; + } else { + break; + } + } + + let mut is_power_of_2 = true; + for b in num_bits.iter().skip(skipped_bits + 1) { + if *b == true { + is_power_of_2 = false; + } + } + + if is_power_of_2 { + num_bits.len() - skipped_bits + } else { + num_bits.len() - skipped_bits + 1 + } + }}; +} + +/// use a ChaCha stream cipher to generate the actual pseudorandom bits +/// use a digest funcion to do absorbing +pub struct FiatShamirChaChaRng { + pub r: ChaChaRng, + pub seed: Vec, + #[doc(hidden)] + field: PhantomData, + representation_field: PhantomData, + digest: PhantomData, +} + +impl RngCore for FiatShamirChaChaRng { + fn next_u32(&mut self) -> u32 { + self.r.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.r.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.r.fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ark_std::rand::Error> { + self.r.try_fill_bytes(dest) + } +} + +/// rng from any algebraic sponge +pub struct FiatShamirAlgebraicSpongeRng { + pub s: S, + #[doc(hidden)] + f_phantom: PhantomData, + cf_phantom: PhantomData, +} + +impl FiatShamirAlgebraicSpongeRng +where + CF: Absorb, +{ + /// compress every two elements if possible. Provides a vector of (limb, num_of_additions), both of which are P::BaseField. + pub fn compress_elements(src_limbs: &[(CF, CF)], ty: OptimizationType) -> Vec { + let capacity = CF::size_in_bits() - 1; + let mut dest_limbs = Vec::::new(); + + let params = get_params(F::size_in_bits(), CF::size_in_bits(), ty); + + let adjustment_factor_lookup_table = { + let mut table = Vec::::new(); + + let mut cur = CF::one(); + for _ in 1..=capacity { + table.push(cur); + cur.double_in_place(); + } + + table + }; + + let mut i = 0; + let src_len = src_limbs.len(); + while i < src_len { + let first = &src_limbs[i]; + let second = if i + 1 < src_len { + Some(&src_limbs[i + 1]) + } else { + None + }; + + let first_max_bits_per_limb = params.bits_per_limb + overhead!(first.1 + &CF::one()); + let second_max_bits_per_limb = if let Some(second) = second { + params.bits_per_limb + overhead!(second.1 + &CF::one()) + } else { + 0 + }; + + if let Some(second) = second { + if first_max_bits_per_limb + second_max_bits_per_limb <= capacity { + let adjustment_factor = + &adjustment_factor_lookup_table[second_max_bits_per_limb]; + + dest_limbs.push(first.0 * adjustment_factor + &second.0); + i += 2; + } else { + dest_limbs.push(first.0); + i += 1; + } + } else { + dest_limbs.push(first.0); + i += 1; + } + } + + dest_limbs + } + + /// push elements to sponge, treated in the non-native field representations. + pub fn push_elements_to_sponge(sponge: &mut S, src: &[F], ty: OptimizationType) { + let mut src_limbs = Vec::<(CF, CF)>::new(); + + for elem in src.iter() { + let limbs = + AllocatedNonNativeFieldVar::::get_limbs_representations(elem, ty).unwrap(); + for limb in limbs.iter() { + src_limbs.push((*limb, CF::one())); + // specifically set to one, since most gadgets in the constraint world would not have zero noise (due to the relatively weak normal form testing in `alloc`) + } + } + + let dest_limbs = Self::compress_elements(&src_limbs, ty); + sponge.absorb(&dest_limbs); + } + + /// obtain random elements from hashchain. + /// not guaranteed to be uniformly distributed, should only be used in certain situations. + pub fn get_elements_from_sponge( + sponge: &mut S, + num_elements: usize, + outputs_short_elements: bool, + ) -> Vec { + let num_bits_per_nonnative = if outputs_short_elements { + 128 + } else { + F::size_in_bits() - 1 // also omit the highest bit + }; + let bits = sponge.squeeze_bits(num_bits_per_nonnative * num_elements); + + let mut lookup_table = Vec::::new(); + let mut cur = F::one(); + for _ in 0..num_bits_per_nonnative { + lookup_table.push(cur); + cur.double_in_place(); + } + + let mut dest_elements = Vec::::new(); + bits.chunks_exact(num_bits_per_nonnative) + .for_each(|per_nonnative_bits| { + // technically, this can be done via BigInterger::from_bits; here, we use this method for consistency with the gadget counterpart + let mut res = F::zero(); + + for (i, bit) in per_nonnative_bits.iter().rev().enumerate() { + if *bit { + res += &lookup_table[i]; + } + } + + dest_elements.push(res); + }); + + dest_elements + } +} + +impl RngCore + for FiatShamirAlgebraicSpongeRng +{ + fn next_u32(&mut self) -> u32 { + assert!( + CF::size_in_bits() > 128, + "The native field of the algebraic sponge is too small." + ); + + let mut dest = [0u8; 4]; + self.fill_bytes(&mut dest); + + u32::from_be_bytes(dest) + } + + fn next_u64(&mut self) -> u64 { + assert!( + CF::size_in_bits() > 128, + "The native field of the algebraic sponge is too small." + ); + + let mut dest = [0u8; 8]; + self.fill_bytes(&mut dest); + + u64::from_be_bytes(dest) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + assert!( + CF::size_in_bits() > 128, + "The native field of the algebraic sponge is too small." + ); + + let capacity = CF::size_in_bits() - 128; + let len = dest.len() * 8; + + let num_of_elements = (capacity + len - 1) / len; + let elements: Vec = self.s.squeeze_field_elements(num_of_elements); + + let mut bits = Vec::::new(); + for elem in elements.iter() { + let mut elem_bits = elem.into_repr().to_bits_be(); + elem_bits.reverse(); + bits.extend_from_slice(&elem_bits[0..capacity]); + } + + bits.truncate(len); + bits.chunks_exact(8) + .enumerate() + .for_each(|(i, bits_per_byte)| { + let mut byte = 0; + for (j, bit) in bits_per_byte.iter().enumerate() { + if *bit { + byte += 1 << j; + } + } + dest[i] = byte; + }); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), ark_std::rand::Error> { + assert!( + CF::size_in_bits() > 128, + "The native field of the algebraic sponge is too small." + ); + + self.fill_bytes(dest); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index bbbe81f..ea047ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,11 @@ macro_rules! eprintln { ($($arg: tt)*) => {}; } +/// Implements a Fiat-Shamir based Rng that allows one to incrementally update +/// the seed based on new messages in the proof transcript. +pub mod fiat_shamir; +//use crate::fiat_shamir::FiatShamirRng; + mod error; pub use error::*; diff --git a/src/sponge/mod.rs b/src/sponge/mod.rs index 73bf827..dade203 100644 --- a/src/sponge/mod.rs +++ b/src/sponge/mod.rs @@ -1,5 +1,5 @@ use ark_ff::PrimeField; -use ark_nonnative_field::NonNativeFieldVar; +use ark_nonnative_field::{params::OptimizationType, NonNativeFieldVar}; use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError}; use ark_sponge::{ constraints::CryptographicSpongeVar, poseidon::PoseidonSponge, CryptographicSponge, @@ -45,5 +45,6 @@ where fn absorb_nonnative( &mut self, input: &[NonNativeFieldVar], + ty: OptimizationType, ) -> Result<(), SynthesisError>; }