arnaucube
arnaucube/ark-r1cs-stdPublic Self-hosted repository
Initial commit to extract crypto-primitives to new crate
Pratyush Mishra pratyushmishra@berkeley.edu
Tue, 24 Sep 2019 20:21:49 -0700
commit

a244e719d1c873552fd09b1457c1de18f825c560

parent

5a78e24e15e0da7b117bb90b80f933cbd1ec5903

M Cargo.toml → Cargo.toml

@@ -1,6 +1,6 @@

[workspace] -members = [ "algebra", "ff-fft", "r1cs-core", "r1cs-std", "gm17", "dpc", "bench-utils" ] +members = [ "algebra", "ff-fft", "r1cs-core", "r1cs-std", "gm17", "crypto-primitives", "dpc", "bench-utils" ] [profile.release] opt-level = 3
A crypto-primitives/Cargo.toml

@@ -0,0 +1,29 @@

+[package] +name = "crypto-primitives" +version = "0.1.0" +authors = ["Pratyush Mishra <pratyushmishra@berkeley.edu>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +algebra = { path = "../algebra" } +r1cs-core = { path = "../r1cs-core", optional = true } +r1cs-std = { path = "../r1cs-std", optional = true } +gm17 = { path = "../gm17", optional = true } +bench-utils = { path = "../bench-utils" } + +digest = "0.7" +blake2 = "0.7" + +rand = { version = "0.7" } +derivative = "1" +rayon = "1" + +[features] +r1cs = [ "r1cs-core", "r1cs-std" ] + +[dev-dependencies] +criterion = "0.2" +rand_xorshift = { version = "0.2" } +
A crypto-primitives/src/commitment/blake2s/constraints.rs

@@ -0,0 +1,169 @@

+use crate::crypto_primitives::commitment::blake2s::Blake2sCommitment; +use r1cs_core::{ConstraintSystem, SynthesisError}; + +use crate::gadgets::{ + prf::blake2s::{blake2s_gadget, Blake2sOutputGadget}, + CommitmentGadget, +}; +use algebra::{PrimeField, Field}; +use r1cs_std::prelude::*; + +use std::borrow::Borrow; + +#[derive(Clone)] +pub struct Blake2sParametersGadget; + +#[derive(Clone)] +pub struct Blake2sRandomnessGadget(pub Vec<UInt8>); + +pub struct Blake2sCommitmentGadget; + +impl<ConstraintF: PrimeField> CommitmentGadget<Blake2sCommitment, ConstraintF> for Blake2sCommitmentGadget { + type OutputGadget = Blake2sOutputGadget; + type ParametersGadget = Blake2sParametersGadget; + type RandomnessGadget = Blake2sRandomnessGadget; + + fn check_commitment_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + _: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result<Self::OutputGadget, SynthesisError> { + let mut input_bits = Vec::with_capacity(512); + for byte in input.into_iter().chain(r.0.iter()) { + input_bits.extend_from_slice(&byte.into_bits_le()); + } + let mut result = Vec::new(); + for (i, int) in blake2s_gadget(cs.ns(|| "Blake2s Eval"), &input_bits)? + .into_iter() + .enumerate() + { + let chunk = int.to_bytes(&mut cs.ns(|| format!("Result ToBytes {}", i)))?; + result.extend_from_slice(&chunk); + } + Ok(Blake2sOutputGadget(result)) + } +} + +impl<ConstraintF: Field> AllocGadget<(), ConstraintF> for Blake2sParametersGadget { + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(_: CS, _: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<()>, + { + Ok(Blake2sParametersGadget) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>(_: CS, _: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<()>, + { + Ok(Blake2sParametersGadget) + } +} + +impl<ConstraintF: PrimeField> AllocGadget<[u8; 32], ConstraintF> for Blake2sRandomnessGadget { + #[inline] + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, value_gen: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = <UInt8>::alloc_vec(cs, &value)?; + + Ok(Blake2sRandomnessGadget(bytes)) + } + + #[inline] + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = <UInt8>::alloc_input_vec(cs, &value)?; + + Ok(Blake2sRandomnessGadget(bytes)) + } +} + +#[cfg(test)] +mod test { + use algebra::fields::bls12_381::Fr; + use rand::{thread_rng, Rng}; + + use crate::{ + crypto_primitives::commitment::{blake2s::Blake2sCommitment, CommitmentScheme}, + gadgets::commitment::{ + blake2s::{Blake2sCommitmentGadget, Blake2sRandomnessGadget}, + CommitmentGadget, + }, + }; + use r1cs_core::ConstraintSystem; + use r1cs_std::prelude::*; + use r1cs_std::test_constraint_system::TestConstraintSystem; + + #[test] + fn commitment_gadget_test() { + let mut cs = TestConstraintSystem::<Fr>::new(); + + let input = [1u8; 32]; + + let rng = &mut thread_rng(); + + type TestCOMM = Blake2sCommitment; + type TestCOMMGadget = Blake2sCommitmentGadget; + + let mut randomness = [0u8; 32]; + rng.fill(&mut randomness); + + let parameters = (); + let primitive_result = Blake2sCommitment::commit(&parameters, &input, &randomness).unwrap(); + + let mut input_bytes = vec![]; + for (byte_i, input_byte) in input.into_iter().enumerate() { + let cs = cs.ns(|| format!("input_byte_gadget_{}", byte_i)); + input_bytes.push(UInt8::alloc(cs, || Ok(*input_byte)).unwrap()); + } + + let mut randomness_bytes = vec![]; + for (byte_i, random_byte) in randomness.into_iter().enumerate() { + let cs = cs.ns(|| format!("randomness_byte_gadget_{}", byte_i)); + randomness_bytes.push(UInt8::alloc(cs, || Ok(*random_byte)).unwrap()); + } + let randomness_bytes = Blake2sRandomnessGadget(randomness_bytes); + + let gadget_parameters = + <TestCOMMGadget as CommitmentGadget<TestCOMM, Fr>>::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(&parameters), + ) + .unwrap(); + let gadget_result = + <TestCOMMGadget as CommitmentGadget<TestCOMM, Fr>>::check_commitment_gadget( + &mut cs.ns(|| "gadget_evaluation"), + &gadget_parameters, + &input_bytes, + &randomness_bytes, + ) + .unwrap(); + + for i in 0..32 { + assert_eq!(primitive_result[i], gadget_result.0[i].get_value().unwrap()); + } + assert!(cs.is_satisfied()); + } +}
A crypto-primitives/src/commitment/blake2s/mod.rs

@@ -0,0 +1,30 @@

+use super::CommitmentScheme; +use blake2::Blake2s as b2s; +use digest::Digest; +use crate::Error; +use rand::Rng; + +pub struct Blake2sCommitment; + +impl CommitmentScheme for Blake2sCommitment { + type Parameters = (); + type Randomness = [u8; 32]; + type Output = [u8; 32]; + + fn setup<R: Rng>(_: &mut R) -> Result<Self::Parameters, Error> { + Ok(()) + } + + fn commit( + _: &Self::Parameters, + input: &[u8], + randomness: &Self::Randomness, + ) -> Result<Self::Output, Error> { + let mut h = b2s::new(); + h.input(input); + h.input(randomness.as_ref()); + let mut result = [0u8; 32]; + result.copy_from_slice(&h.result()); + Ok(result) + } +}
A crypto-primitives/src/commitment/constraints.rs

@@ -0,0 +1,23 @@

+use crate::CommitmentScheme; +use algebra::Field; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; +use std::fmt::Debug; + +pub trait CommitmentGadget<C: CommitmentScheme, ConstraintF: Field> { + type OutputGadget: EqGadget<ConstraintF> + + ToBytesGadget<ConstraintF> + + AllocGadget<C::Output, ConstraintF> + + Clone + + Sized + + Debug; + type ParametersGadget: AllocGadget<C::Parameters, ConstraintF> + Clone; + type RandomnessGadget: AllocGadget<C::Randomness, ConstraintF> + Clone; + + fn check_commitment_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result<Self::OutputGadget, SynthesisError>; +}
A crypto-primitives/src/commitment/injective_map/constraints.rs

@@ -0,0 +1,59 @@

+use algebra::{Field, PrimeField}; + +use crate::commitment::{ + injective_map::{InjectiveMap, PedersenCommCompressor}, + pedersen::PedersenWindow, + pedersen::constraints::{ + PedersenCommitmentGadget, PedersenCommitmentGadgetParameters, PedersenRandomnessGadget, + }, + CommitmentGadget, +}; + +pub use crate::crh::injective_map::constraints::InjectiveMapGadget; +use algebra::groups::Group; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::{groups::GroupGadget, uint8::UInt8}; + +use std::marker::PhantomData; + +pub struct PedersenCommitmentCompressorGadget< + G: Group, + I: InjectiveMap<G>, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, + IG: InjectiveMapGadget<G, I, ConstraintF, GG>, +> { + _compressor: PhantomData<I>, + _compressor_gadget: PhantomData<IG>, + _crh: PedersenCommitmentGadget<G, ConstraintF, GG>, +} + +impl<G, I, ConstraintF, GG, IG, W> CommitmentGadget<PedersenCommCompressor<G, I, W>, ConstraintF> + for PedersenCommitmentCompressorGadget<G, I, ConstraintF, GG, IG> +where + G: Group, + I: InjectiveMap<G>, + ConstraintF: PrimeField, + GG: GroupGadget<G, ConstraintF>, + IG: InjectiveMapGadget<G, I, ConstraintF, GG>, + W: PedersenWindow, +{ + type OutputGadget = IG::OutputGadget; + type ParametersGadget = PedersenCommitmentGadgetParameters<G, W, ConstraintF>; + type RandomnessGadget = PedersenRandomnessGadget; + + fn check_commitment_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result<Self::OutputGadget, SynthesisError> { + let result = PedersenCommitmentGadget::<G, ConstraintF, GG>::check_commitment_gadget( + cs.ns(|| "PedersenComm"), + parameters, + input, + r, + )?; + IG::evaluate_map(cs.ns(|| "InjectiveMap"), &result) + } +}
A crypto-primitives/src/commitment/injective_map/mod.rs

@@ -0,0 +1,47 @@

+use crate::Error; +use rand::Rng; +use std::marker::PhantomData; + +use super::{ + pedersen::{PedersenCommitment, PedersenParameters, PedersenRandomness, PedersenWindow}, + CommitmentScheme, +}; +pub use crate::crh::injective_map::InjectiveMap; +use algebra::groups::Group; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub struct PedersenCommCompressor<G: Group, I: InjectiveMap<G>, W: PedersenWindow> { + _group: PhantomData<G>, + _compressor: PhantomData<I>, + _comm: PedersenCommitment<G, W>, +} + +impl<G: Group, I: InjectiveMap<G>, W: PedersenWindow> CommitmentScheme + for PedersenCommCompressor<G, I, W> +{ + type Output = I::Output; + type Parameters = PedersenParameters<G>; + type Randomness = PedersenRandomness<G>; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error> { + let time = start_timer!(|| format!("PedersenCompressor::Setup")); + let params = PedersenCommitment::<G, W>::setup(rng); + end_timer!(time); + params + } + + fn commit( + parameters: &Self::Parameters, + input: &[u8], + randomness: &Self::Randomness, + ) -> Result<Self::Output, Error> { + let eval_time = start_timer!(|| "PedersenCompressor::Eval"); + let result = I::injective_map(&PedersenCommitment::<G, W>::commit( + parameters, input, randomness, + )?)?; + end_timer!(eval_time); + Ok(result) + } +}
A crypto-primitives/src/commitment/mod.rs

@@ -0,0 +1,30 @@

+use rand::Rng; +use algebra::UniformRand; +use std::{fmt::Debug, hash::Hash}; + +use algebra::bytes::ToBytes; + +pub mod blake2s; +pub mod injective_map; +pub mod pedersen; + +#[cfg(feature = "r1cs")] +pub mod constraints; +#[cfg(feature = "r1cs")] +pub use constraints::*; + +use crate::Error; + +pub trait CommitmentScheme { + type Output: ToBytes + Clone + Default + Eq + Hash + Debug; + type Parameters: Clone; + type Randomness: Clone + ToBytes + Default + Eq + UniformRand + Debug; + + fn setup<R: Rng>(r: &mut R) -> Result<Self::Parameters, Error>; + + fn commit( + parameters: &Self::Parameters, + input: &[u8], + r: &Self::Randomness, + ) -> Result<Self::Output, Error>; +}
A crypto-primitives/src/commitment/pedersen/constraints.rs

@@ -0,0 +1,244 @@

+use crate::{ + commitment::pedersen::{PedersenCommitment, PedersenParameters, PedersenRandomness}, + crh::pedersen::PedersenWindow, +}; +use algebra::{to_bytes, Group, ToBytes}; +use r1cs_core::{ConstraintSystem, SynthesisError}; + +use crate::commitment::CommitmentGadget; +use algebra::fields::{Field, PrimeField}; +use r1cs_std::prelude::*; +use std::{borrow::Borrow, marker::PhantomData}; + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group, W: PedersenWindow, ConstraintF: Field"))] +pub struct PedersenCommitmentGadgetParameters<G: Group, W: PedersenWindow, ConstraintF: Field> { + params: PedersenParameters<G>, + #[doc(hidden)] + _group: PhantomData<G>, + #[doc(hidden)] + _engine: PhantomData<ConstraintF>, + #[doc(hidden)] + _window: PhantomData<W>, +} + +#[derive(Clone, Debug)] +pub struct PedersenRandomnessGadget(Vec<UInt8>); + +pub struct PedersenCommitmentGadget<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>>( + #[doc(hidden)] + PhantomData<*const G>, + #[doc(hidden)] + PhantomData<*const GG>, + PhantomData<ConstraintF>, +); + +impl<ConstraintF, G, GG, W> CommitmentGadget<PedersenCommitment<G, W>, ConstraintF> + for PedersenCommitmentGadget<G, ConstraintF, GG> +where + ConstraintF: PrimeField, + G: Group, + GG: GroupGadget<G, ConstraintF>, + W: PedersenWindow, +{ + type OutputGadget = GG; + type ParametersGadget = PedersenCommitmentGadgetParameters<G, W, ConstraintF>; + type RandomnessGadget = PedersenRandomnessGadget; + + fn check_commitment_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result<Self::OutputGadget, SynthesisError> { + assert!((input.len() * 8) <= (W::WINDOW_SIZE * W::NUM_WINDOWS)); + + let mut padded_input = input.to_vec(); + // Pad if input length is less than `W::WINDOW_SIZE * W::NUM_WINDOWS`. + if (input.len() * 8) < W::WINDOW_SIZE * W::NUM_WINDOWS { + let current_length = input.len(); + for _ in current_length..((W::WINDOW_SIZE * W::NUM_WINDOWS) / 8) { + padded_input.push(UInt8::constant(0u8)); + } + } + + assert_eq!(padded_input.len() * 8, W::WINDOW_SIZE * W::NUM_WINDOWS); + assert_eq!(parameters.params.generators.len(), W::NUM_WINDOWS); + + // Allocate new variable for commitment output. + let input_in_bits: Vec<_> = padded_input + .iter() + .flat_map(|byte| byte.into_bits_le()) + .collect(); + let input_in_bits = input_in_bits.chunks(W::WINDOW_SIZE); + let mut result = GG::precomputed_base_multiscalar_mul( + cs.ns(|| "multiexp"), + &parameters.params.generators, + input_in_bits, + )?; + + // Compute h^r + let rand_bits: Vec<_> = r.0.iter().flat_map(|byte| byte.into_bits_le()).collect(); + result.precomputed_base_scalar_mul( + cs.ns(|| "Randomizer"), + rand_bits + .iter() + .zip(&parameters.params.randomness_generator), + )?; + + Ok(result) + } +} + +impl<G, W, ConstraintF> AllocGadget<PedersenParameters<G>, ConstraintF> for PedersenCommitmentGadgetParameters<G, W, ConstraintF> +where + G: Group, + W: PedersenWindow, + ConstraintF: PrimeField, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(_cs: CS, value_gen: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenParameters<G>>, + { + let temp = value_gen()?; + let parameters = temp.borrow().clone(); + + Ok(PedersenCommitmentGadgetParameters { + params: parameters, + _group: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + _cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenParameters<G>>, + { + let temp = value_gen()?; + let parameters = temp.borrow().clone(); + + Ok(PedersenCommitmentGadgetParameters { + params: parameters, + _group: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } +} + +impl<G, ConstraintF> AllocGadget<PedersenRandomness<G>, ConstraintF> for PedersenRandomnessGadget +where + G: Group, + ConstraintF: PrimeField, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, value_gen: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenRandomness<G>>, + { + let temp = value_gen()?; + let randomness = to_bytes![temp.borrow().0].unwrap(); + Ok(PedersenRandomnessGadget(UInt8::alloc_vec(cs, &randomness)?)) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenRandomness<G>>, + { + let temp = value_gen()?; + let randomness = to_bytes![temp.borrow().0].unwrap(); + Ok(PedersenRandomnessGadget(UInt8::alloc_input_vec( + cs, + &randomness, + )?)) + } +} + +#[cfg(test)] +mod test { + use algebra::{fields::jubjub::{fq::Fq, fr::Fr}}; + use rand::thread_rng; + use algebra::UniformRand; + + use crate::{ + commitment::{ + pedersen::{PedersenCommitment, PedersenRandomness, constraints::PedersenCommitmentGadget}, + CommitmentScheme, + CommitmentGadget, + }, + crh::pedersen::PedersenWindow, + }; + use algebra::curves::{jubjub::JubJubProjective as JubJub, ProjectiveCurve}; + use r1cs_core::ConstraintSystem; + use r1cs_std::{ + groups::jubjub::JubJubGadget, test_constraint_system::TestConstraintSystem, prelude::*, + }; + + #[test] + fn commitment_gadget_test() { + let mut cs = TestConstraintSystem::<Fq>::new(); + + #[derive(Clone, PartialEq, Eq, Hash)] + pub(super) struct Window; + + impl PedersenWindow for Window { + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 8; + } + + let input = [1u8; 4]; + + let rng = &mut thread_rng(); + + type TestCOMM = PedersenCommitment<JubJub, Window>; + type TestCOMMGadget = PedersenCommitmentGadget<JubJub, Fq, JubJubGadget>; + + let randomness = PedersenRandomness(Fr::rand(rng)); + + let parameters = PedersenCommitment::<JubJub, Window>::setup(rng).unwrap(); + let primitive_result = + PedersenCommitment::<JubJub, Window>::commit(&parameters, &input, &randomness).unwrap(); + + let mut input_bytes = vec![]; + for (byte_i, input_byte) in input.into_iter().enumerate() { + let cs = cs.ns(|| format!("input_byte_gadget_{}", byte_i)); + input_bytes.push(UInt8::alloc(cs, || Ok(*input_byte)).unwrap()); + } + + let randomness = + <TestCOMMGadget as CommitmentGadget<TestCOMM, Fq>>::RandomnessGadget::alloc( + &mut cs.ns(|| "gadget_randomness"), + || Ok(&randomness), + ) + .unwrap(); + let gadget_parameters = + <TestCOMMGadget as CommitmentGadget<TestCOMM, Fq>>::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(&parameters), + ) + .unwrap(); + let gadget_result = + <TestCOMMGadget as CommitmentGadget<TestCOMM, Fq>>::check_commitment_gadget( + &mut cs.ns(|| "gadget_evaluation"), + &gadget_parameters, + &input_bytes, + &randomness, + ) + .unwrap(); + + let primitive_result = primitive_result.into_affine(); + assert_eq!(primitive_result.x, gadget_result.x.value.unwrap()); + assert_eq!(primitive_result.y, gadget_result.y.value.unwrap()); + assert!(cs.is_satisfied()); + } +}
A crypto-primitives/src/commitment/pedersen/mod.rs

@@ -0,0 +1,123 @@

+use crate::Error; +use algebra::UniformRand; +use rand::Rng; +use std::marker::PhantomData; + +use super::CommitmentScheme; +use algebra::{bytes::ToBytes, groups::Group, BitIterator, FpParameters, PrimeField}; +use std::io::{Result as IoResult, Write}; + +pub use crate::crh::pedersen::PedersenWindow; +use crate::crh::{ + pedersen::{PedersenCRH, PedersenParameters as PedersenCRHParameters}, + FixedLengthCRH, +}; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +#[derive(Clone)] +pub struct PedersenParameters<G: Group> { + pub randomness_generator: Vec<G>, + pub generators: Vec<Vec<G>>, +} + +pub struct PedersenCommitment<G: Group, W: PedersenWindow> { + group: PhantomData<G>, + window: PhantomData<W>, +} + +#[derive(Derivative)] +#[derivative( + Clone(bound = "G: Group"), + PartialEq(bound = "G: Group"), + Debug(bound = "G: Group"), + Eq(bound = "G: Group"), + Default(bound = "G: Group") +)] +pub struct PedersenRandomness<G: Group>(pub G::ScalarField); + +impl<G: Group> UniformRand for PedersenRandomness<G> { + #[inline] + fn rand<R: Rng + ?Sized>(rng: &mut R) -> Self { + PedersenRandomness(UniformRand::rand(rng)) + } +} + +impl<G: Group> ToBytes for PedersenRandomness<G> { + fn write<W: Write>(&self, writer: W) -> IoResult<()> { + self.0.write(writer) + } +} + +impl<G: Group, W: PedersenWindow> CommitmentScheme for PedersenCommitment<G, W> { + type Parameters = PedersenParameters<G>; + type Randomness = PedersenRandomness<G>; + type Output = G; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error> { + let time = start_timer!(|| format!( + "PedersenCOMM::Setup: {} {}-bit windows; {{0,1}}^{{{}}} -> G", + W::NUM_WINDOWS, + W::WINDOW_SIZE, + W::NUM_WINDOWS * W::WINDOW_SIZE + )); + let num_powers = <G::ScalarField as PrimeField>::Params::MODULUS_BITS as usize; + let randomness_generator = PedersenCRH::<_, W>::generator_powers(num_powers, rng); + let generators = PedersenCRH::<_, W>::create_generators(rng); + end_timer!(time); + + Ok(Self::Parameters { + randomness_generator, + generators, + }) + } + + fn commit( + parameters: &Self::Parameters, + input: &[u8], + randomness: &Self::Randomness, + ) -> Result<Self::Output, Error> { + let commit_time = start_timer!(|| "PedersenCOMM::Commit"); + // If the input is too long, return an error. + if input.len() > W::WINDOW_SIZE * W::NUM_WINDOWS { + panic!("incorrect input length: {:?}", input.len()); + } + // Pad the input to the necessary length. + let mut padded_input = Vec::with_capacity(input.len()); + let mut input = input; + if (input.len() * 8) < W::WINDOW_SIZE * W::NUM_WINDOWS { + let current_length = input.len(); + padded_input.extend_from_slice(input); + for _ in current_length..((W::WINDOW_SIZE * W::NUM_WINDOWS) / 8) { + padded_input.push(0u8); + } + input = padded_input.as_slice(); + } + assert_eq!(parameters.generators.len(), W::NUM_WINDOWS); + + // Invoke Pedersen CRH here, to prevent code duplication. + + let crh_parameters = PedersenCRHParameters { + generators: parameters.generators.clone(), + }; + let mut result = PedersenCRH::<_, W>::evaluate(&crh_parameters, &input)?; + let randomize_time = start_timer!(|| "Randomize"); + + // Compute h^r. + let mut scalar_bits = BitIterator::new(randomness.0.into_repr()).collect::<Vec<_>>(); + scalar_bits.reverse(); + for (bit, power) in scalar_bits + .into_iter() + .zip(&parameters.randomness_generator) + { + if bit { + result += power + } + } + end_timer!(randomize_time); + end_timer!(commit_time); + + Ok(result) + } +}
A crypto-primitives/src/crh/constraints.rs

@@ -0,0 +1,25 @@

+use algebra::Field; +use std::fmt::Debug; + +use crate::crh::FixedLengthCRH; +use r1cs_core::{ConstraintSystem, SynthesisError}; + +use r1cs_std::prelude::*; + +pub trait FixedLengthCRHGadget<H: FixedLengthCRH, ConstraintF: Field>: Sized { + type OutputGadget: ConditionalEqGadget<ConstraintF> + + EqGadget<ConstraintF> + + ToBytesGadget<ConstraintF> + + CondSelectGadget<ConstraintF> + + AllocGadget<H::Output, ConstraintF> + + Debug + + Clone + + Sized; + type ParametersGadget: AllocGadget<H::Parameters, ConstraintF> + Clone; + + fn check_evaluation_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result<Self::OutputGadget, SynthesisError>; +}
A crypto-primitives/src/crh/injective_map/constraints.rs

@@ -0,0 +1,115 @@

+use std::{fmt::Debug, marker::PhantomData}; + +use crate::crh::{ + FixedLengthCRHGadget, + injective_map::{InjectiveMap, PedersenCRHCompressor, TECompressor}, + pedersen::{ + PedersenWindow, + constraints::{PedersenCRHGadget, PedersenCRHGadgetParameters}, + } +}; + +use algebra::{ + curves::{ + models::{ModelParameters, TEModelParameters}, + twisted_edwards_extended::{GroupAffine as TEAffine, GroupProjective as TEProjective}, + }, + fields::{Field, PrimeField, SquareRootField}, + groups::Group, +}; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::{ + fields::fp::FpGadget, + groups::{curves::twisted_edwards::AffineGadget as TwistedEdwardsGadget, GroupGadget}, + prelude::*, +}; + +pub trait InjectiveMapGadget<G: Group, I: InjectiveMap<G>, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> +{ + type OutputGadget: EqGadget<ConstraintF> + + ToBytesGadget<ConstraintF> + + CondSelectGadget<ConstraintF> + + AllocGadget<I::Output, ConstraintF> + + Debug + + Clone + + Sized; + + fn evaluate_map<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + ge: &GG, + ) -> Result<Self::OutputGadget, SynthesisError>; +} + +pub struct TECompressorGadget; + +impl<ConstraintF, P> InjectiveMapGadget<TEAffine<P>, TECompressor, ConstraintF, TwistedEdwardsGadget<P, ConstraintF, FpGadget<ConstraintF>>> + for TECompressorGadget +where + ConstraintF: PrimeField + SquareRootField, + P: TEModelParameters + ModelParameters<BaseField = ConstraintF>, +{ + type OutputGadget = FpGadget<ConstraintF>; + + fn evaluate_map<CS: ConstraintSystem<ConstraintF>>( + _cs: CS, + ge: &TwistedEdwardsGadget<P, ConstraintF, FpGadget<ConstraintF>>, + ) -> Result<Self::OutputGadget, SynthesisError> { + Ok(ge.x.clone()) + } +} + +impl<ConstraintF, P> + InjectiveMapGadget<TEProjective<P>, TECompressor, ConstraintF, TwistedEdwardsGadget<P, ConstraintF, FpGadget<ConstraintF>>> + for TECompressorGadget +where + ConstraintF: PrimeField + SquareRootField, + P: TEModelParameters + ModelParameters<BaseField = ConstraintF>, +{ + type OutputGadget = FpGadget<ConstraintF>; + + fn evaluate_map<CS: ConstraintSystem<ConstraintF>>( + _cs: CS, + ge: &TwistedEdwardsGadget<P, ConstraintF, FpGadget<ConstraintF>>, + ) -> Result<Self::OutputGadget, SynthesisError> { + Ok(ge.x.clone()) + } +} + +pub struct PedersenCRHCompressorGadget< + G: Group, + I: InjectiveMap<G>, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, + IG: InjectiveMapGadget<G, I, ConstraintF, GG>, +> { + _compressor: PhantomData<I>, + _compressor_gadget: PhantomData<IG>, + _crh: PedersenCRHGadget<G, ConstraintF, GG>, +} + +impl<G, I, ConstraintF, GG, IG, W> FixedLengthCRHGadget<PedersenCRHCompressor<G, I, W>, ConstraintF> + for PedersenCRHCompressorGadget<G, I, ConstraintF, GG, IG> +where + G: Group, + I: InjectiveMap<G>, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, + IG: InjectiveMapGadget<G, I, ConstraintF, GG>, + W: PedersenWindow, +{ + type OutputGadget = IG::OutputGadget; + type ParametersGadget = PedersenCRHGadgetParameters<G, W, ConstraintF, GG>; + + fn check_evaluation_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result<Self::OutputGadget, SynthesisError> { + let result = PedersenCRHGadget::<G, ConstraintF, GG>::check_evaluation_gadget( + cs.ns(|| "PedCRH"), + parameters, + input, + )?; + IG::evaluate_map(cs.ns(|| "InjectiveMap"), &result) + } +}
A crypto-primitives/src/crh/injective_map/mod.rs

@@ -0,0 +1,76 @@

+use crate::CryptoError; +use algebra::bytes::ToBytes; +use crate::Error; +use rand::Rng; +use std::{fmt::Debug, hash::Hash, marker::PhantomData}; + +use super::{ + pedersen::{PedersenCRH, PedersenParameters, PedersenWindow}, + FixedLengthCRH, +}; +use algebra::{ + curves::{ + models::{ModelParameters, TEModelParameters}, + twisted_edwards_extended::{GroupAffine as TEAffine, GroupProjective as TEProjective}, + ProjectiveCurve, + }, + groups::Group, +}; + + +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub trait InjectiveMap<G: Group> { + type Output: ToBytes + Clone + Eq + Hash + Default + Debug; + fn injective_map(ge: &G) -> Result<Self::Output, CryptoError>; +} + +pub struct TECompressor; + +impl<P: TEModelParameters> InjectiveMap<TEAffine<P>> for TECompressor { + type Output = <P as ModelParameters>::BaseField; + + fn injective_map(ge: &TEAffine<P>) -> Result<Self::Output, CryptoError> { + debug_assert!(ge.is_in_correct_subgroup_assuming_on_curve()); + Ok(ge.x) + } +} + +impl<P: TEModelParameters> InjectiveMap<TEProjective<P>> for TECompressor { + type Output = <P as ModelParameters>::BaseField; + + fn injective_map(ge: &TEProjective<P>) -> Result<Self::Output, CryptoError> { + let ge = ge.into_affine(); + debug_assert!(ge.is_in_correct_subgroup_assuming_on_curve()); + Ok(ge.x) + } +} + +pub struct PedersenCRHCompressor<G: Group, I: InjectiveMap<G>, W: PedersenWindow> { + _group: PhantomData<G>, + _compressor: PhantomData<I>, + _crh: PedersenCRH<G, W>, +} + +impl<G: Group, I: InjectiveMap<G>, W: PedersenWindow> FixedLengthCRH + for PedersenCRHCompressor<G, I, W> +{ + const INPUT_SIZE_BITS: usize = PedersenCRH::<G, W>::INPUT_SIZE_BITS; + type Output = I::Output; + type Parameters = PedersenParameters<G>; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error> { + let time = start_timer!(|| format!("PedersenCRHCompressor::Setup")); + let params = PedersenCRH::<G, W>::setup(rng); + end_timer!(time); + params + } + + fn evaluate(parameters: &Self::Parameters, input: &[u8]) -> Result<Self::Output, Error> { + let eval_time = start_timer!(|| "PedersenCRHCompressor::Eval"); + let result = I::injective_map(&PedersenCRH::<G, W>::evaluate(parameters, input)?)?; + end_timer!(eval_time); + Ok(result) + } +}
A crypto-primitives/src/crh/mod.rs

@@ -0,0 +1,24 @@

+use algebra::bytes::ToBytes; +use rand::Rng; +use std::hash::Hash; + +pub mod injective_map; +pub mod pedersen; + +use crate::Error; + + +#[cfg(feature = "r1cs")] +pub mod constraints; +#[cfg(feature = "r1cs")] +pub use constraints::*; + + +pub trait FixedLengthCRH { + const INPUT_SIZE_BITS: usize; + type Output: ToBytes + Clone + Eq + Hash + Default; + type Parameters: Clone + Default; + + fn setup<R: Rng>(r: &mut R) -> Result<Self::Parameters, Error>; + fn evaluate(parameters: &Self::Parameters, input: &[u8]) -> Result<Self::Output, Error>; +}
A crypto-primitives/src/crh/pedersen/constraints.rs

@@ -0,0 +1,193 @@

+use algebra::Field; + +use crate::crh::{ + FixedLengthCRHGadget, + pedersen::{PedersenCRH, PedersenParameters, PedersenWindow}, +}; +use algebra::groups::Group; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use std::{borrow::Borrow, marker::PhantomData}; + +#[derive(Derivative)] +#[derivative(Clone( + bound = "G: Group, W: PedersenWindow, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>" +))] +pub struct PedersenCRHGadgetParameters< + G: Group, + W: PedersenWindow, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, +> { + params: PedersenParameters<G>, + _group_g: PhantomData<GG>, + _engine: PhantomData<ConstraintF>, + _window: PhantomData<W>, +} + +pub struct PedersenCRHGadget<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> { + #[doc(hideen)] + _group: PhantomData<*const G>, + #[doc(hideen)] + _group_gadget: PhantomData<*const GG>, + #[doc(hideen)] + _engine: PhantomData<ConstraintF>, +} + +impl<ConstraintF, G, GG, W> FixedLengthCRHGadget<PedersenCRH<G, W>, ConstraintF> for PedersenCRHGadget<G, ConstraintF, GG> +where + ConstraintF: Field, + G: Group, + GG: GroupGadget<G, ConstraintF>, + W: PedersenWindow, +{ + type OutputGadget = GG; + type ParametersGadget = PedersenCRHGadgetParameters<G, W, ConstraintF, GG>; + + fn check_evaluation_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result<Self::OutputGadget, SynthesisError> { + let mut padded_input = input.to_vec(); + // Pad the input if it is not the current length. + if input.len() * 8 < W::WINDOW_SIZE * W::NUM_WINDOWS { + let current_length = input.len(); + for _ in current_length..(W::WINDOW_SIZE * W::NUM_WINDOWS / 8) { + padded_input.push(UInt8::constant(0u8)); + } + } + assert_eq!(padded_input.len() * 8, W::WINDOW_SIZE * W::NUM_WINDOWS); + assert_eq!(parameters.params.generators.len(), W::NUM_WINDOWS); + + // Allocate new variable for the result. + let input_in_bits: Vec<_> = padded_input + .iter() + .flat_map(|byte| byte.into_bits_le()) + .collect(); + let input_in_bits = input_in_bits.chunks(W::WINDOW_SIZE); + let result = + GG::precomputed_base_multiscalar_mul(cs, &parameters.params.generators, input_in_bits)?; + + Ok(result) + } +} + +impl<G: Group, W: PedersenWindow, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> + AllocGadget<PedersenParameters<G>, ConstraintF> for PedersenCRHGadgetParameters<G, W, ConstraintF, GG> +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(_cs: CS, value_gen: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenParameters<G>>, + { + let params = value_gen()?.borrow().clone(); + Ok(PedersenCRHGadgetParameters { + params, + _group_g: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + _cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<PedersenParameters<G>>, + { + let params = value_gen()?.borrow().clone(); + Ok(PedersenCRHGadgetParameters { + params, + _group_g: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } +} + +#[cfg(test)] +mod test { + use algebra::fields::bls12_381::fr::Fr; + use rand::{thread_rng, Rng}; + + use crate::crh::{ + pedersen::{PedersenCRH, PedersenWindow}, + pedersen::constraints::PedersenCRHGadget, + FixedLengthCRH, + FixedLengthCRHGadget + }; + use algebra::curves::{jubjub::JubJubProjective as JubJub, ProjectiveCurve}; + use r1cs_core::ConstraintSystem; + use r1cs_std::{ + groups::curves::twisted_edwards::jubjub::JubJubGadget, + test_constraint_system::TestConstraintSystem, prelude::*, + }; + + type TestCRH = PedersenCRH<JubJub, Window>; + type TestCRHGadget = PedersenCRHGadget<JubJub, Fr, JubJubGadget>; + + #[derive(Clone, PartialEq, Eq, Hash)] + pub(super) struct Window; + + impl PedersenWindow for Window { + const WINDOW_SIZE: usize = 128; + const NUM_WINDOWS: usize = 8; + } + + fn generate_input<CS: ConstraintSystem<Fr>, R: Rng>( + mut cs: CS, + rng: &mut R, + ) -> ([u8; 128], Vec<UInt8>) { + let mut input = [1u8; 128]; + rng.fill_bytes(&mut input); + + let mut input_bytes = vec![]; + for (byte_i, input_byte) in input.into_iter().enumerate() { + let cs = cs.ns(|| format!("input_byte_gadget_{}", byte_i)); + input_bytes.push(UInt8::alloc(cs, || Ok(*input_byte)).unwrap()); + } + (input, input_bytes) + } + + #[test] + fn crh_primitive_gadget_test() { + let rng = &mut thread_rng(); + let mut cs = TestConstraintSystem::<Fr>::new(); + + let (input, input_bytes) = generate_input(&mut cs, rng); + println!("number of constraints for input: {}", cs.num_constraints()); + + let parameters = TestCRH::setup(rng).unwrap(); + let primitive_result = TestCRH::evaluate(&parameters, &input).unwrap(); + + let gadget_parameters = + <TestCRHGadget as FixedLengthCRHGadget<TestCRH, Fr>>::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(&parameters), + ) + .unwrap(); + println!( + "number of constraints for input + params: {}", + cs.num_constraints() + ); + + let gadget_result = + <TestCRHGadget as FixedLengthCRHGadget<TestCRH, Fr>>::check_evaluation_gadget( + &mut cs.ns(|| "gadget_evaluation"), + &gadget_parameters, + &input_bytes, + ) + .unwrap(); + + println!("number of constraints total: {}", cs.num_constraints()); + + let primitive_result = primitive_result.into_affine(); + assert_eq!(primitive_result.x, gadget_result.x.value.unwrap()); + assert_eq!(primitive_result.y, gadget_result.y.value.unwrap()); + assert!(cs.is_satisfied()); + } +}
A crypto-primitives/src/crh/pedersen/mod.rs

@@ -0,0 +1,141 @@

+use crate::Error; +use rand::Rng; +use rayon::prelude::*; +use std::{ + fmt::{Debug, Formatter, Result as FmtResult}, + marker::PhantomData, +}; + +use crate::crh::FixedLengthCRH; +use algebra::groups::Group; + + +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub trait PedersenWindow: Clone { + const WINDOW_SIZE: usize; + const NUM_WINDOWS: usize; +} + +#[derive(Clone, Default)] +pub struct PedersenParameters<G: Group> { + pub generators: Vec<Vec<G>>, +} + +pub struct PedersenCRH<G: Group, W: PedersenWindow> { + group: PhantomData<G>, + window: PhantomData<W>, +} + +impl<G: Group, W: PedersenWindow> PedersenCRH<G, W> { + pub fn create_generators<R: Rng>(rng: &mut R) -> Vec<Vec<G>> { + let mut generators_powers = Vec::new(); + for _ in 0..W::NUM_WINDOWS { + generators_powers.push(Self::generator_powers(W::WINDOW_SIZE, rng)); + } + generators_powers + } + + pub fn generator_powers<R: Rng>(num_powers: usize, rng: &mut R) -> Vec<G> { + let mut cur_gen_powers = Vec::with_capacity(num_powers); + let mut base = G::rand(rng); + for _ in 0..num_powers { + cur_gen_powers.push(base); + base.double_in_place(); + } + cur_gen_powers + } +} + +impl<G: Group, W: PedersenWindow> FixedLengthCRH for PedersenCRH<G, W> { + const INPUT_SIZE_BITS: usize = W::WINDOW_SIZE * W::NUM_WINDOWS; + type Output = G; + type Parameters = PedersenParameters<G>; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error> { + let time = start_timer!(|| format!( + "PedersenCRH::Setup: {} {}-bit windows; {{0,1}}^{{{}}} -> G", + W::NUM_WINDOWS, + W::WINDOW_SIZE, + W::NUM_WINDOWS * W::WINDOW_SIZE + )); + let generators = Self::create_generators(rng); + end_timer!(time); + Ok(Self::Parameters { generators }) + } + + fn evaluate(parameters: &Self::Parameters, input: &[u8]) -> Result<Self::Output, Error> { + let eval_time = start_timer!(|| "PedersenCRH::Eval"); + + if (input.len() * 8) > W::WINDOW_SIZE * W::NUM_WINDOWS { + panic!( + "incorrect input length {:?} for window params {:?}x{:?}", + input.len(), + W::WINDOW_SIZE, + W::NUM_WINDOWS + ); + } + + let mut padded_input = Vec::with_capacity(input.len()); + let mut input = input; + // Pad the input if it is not the current length. + if (input.len() * 8) < W::WINDOW_SIZE * W::NUM_WINDOWS { + let current_length = input.len(); + padded_input.extend_from_slice(input); + for _ in current_length..((W::WINDOW_SIZE * W::NUM_WINDOWS) / 8) { + padded_input.push(0u8); + } + input = padded_input.as_slice(); + } + + assert_eq!( + parameters.generators.len(), + W::NUM_WINDOWS, + "Incorrect pp of size {:?}x{:?} for window params {:?}x{:?}", + parameters.generators[0].len(), + parameters.generators.len(), + W::WINDOW_SIZE, + W::NUM_WINDOWS + ); + + // Compute sum of h_i^{m_i} for all i. + let result = bytes_to_bits(input) + .par_chunks(W::WINDOW_SIZE) + .zip(&parameters.generators) + .map(|(bits, generator_powers)| { + let mut encoded = G::zero(); + for (bit, base) in bits.iter().zip(generator_powers.iter()) { + if *bit { + encoded = encoded + base; + } + } + encoded + }) + .reduce(|| G::zero(), |a, b| a + &b); + end_timer!(eval_time); + + Ok(result) + } +} + +pub fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> { + let mut bits = Vec::with_capacity(bytes.len() * 8); + for byte in bytes { + for i in 0..8 { + let bit = (*byte >> i) & 1; + bits.push(bit == 1) + } + } + bits +} + +impl<G: Group> Debug for PedersenParameters<G> { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Pedersen Hash Parameters {{\n")?; + for (i, g) in self.generators.iter().enumerate() { + write!(f, "\t Generator {}: {:?}\n", i, g)?; + } + write!(f, "}}\n") + } +}
A crypto-primitives/src/lib.rs

@@ -0,0 +1,46 @@

+#[macro_use] +extern crate bench_utils; + +#[macro_use] +extern crate derivative; + +pub mod commitment; +pub mod crh; +pub mod mht; +pub mod nizk; +pub mod prf; +pub mod signature; + +pub use self::{ + commitment::CommitmentScheme, + crh::FixedLengthCRH, + mht::{HashMembershipProof, MerkleHashTree}, + nizk::NIZK, + prf::PRF, + signature::SignatureScheme, +}; + +pub type Error = Box<dyn std::error::Error>; + +#[derive(Debug)] +pub enum CryptoError { + IncorrectInputLength(usize), + NotPrimeOrder, +} + +impl std::fmt::Display for CryptoError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = match self { + CryptoError::IncorrectInputLength(len) => format!("input length is wrong: {}", len), + CryptoError::NotPrimeOrder => "element is not prime order".to_owned(), + }; + write!(f, "{}", msg) + } +} + +impl std::error::Error for CryptoError { + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +}
A crypto-primitives/src/mht/constraints-old.rs

@@ -0,0 +1,512 @@

+use algebra::Field; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; +use r1cs_std::boolean::AllocatedBit; + +use crate::{mht::HashMembershipProof, CommitmentScheme, FixedLengthCRH}; + +use crate::{commitment::CommitmentGadget, crh::FixedLengthCRHGadget}; + +use crate::ledger::{CommPath, Digest, LedgerDigest, LedgerWitness}; + +use std::{borrow::Borrow, marker::PhantomData}; + +pub trait LCWGadget<C: CommitmentScheme, D: LedgerDigest, CW: LedgerWitness<D>, ConstraintF: Field> { + type ParametersGadget: AllocGadget<D::Parameters, ConstraintF>; + type CommitmentGadget: AllocGadget<C::Output, ConstraintF>; + type DigestGadget: AllocGadget<D, ConstraintF>; + type WitnessGadget: AllocGadget<CW, ConstraintF>; + + fn check_witness_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + ledger_state_digest: &Self::DigestGadget, + commitment: &Self::CommitmentGadget, + witness: &Self::WitnessGadget, + ) -> Result<(), SynthesisError> { + Self::conditionally_check_witness_gadget( + cs, + parameters, + ledger_state_digest, + commitment, + witness, + &Boolean::Constant(true), + ) + } + + fn conditionally_check_witness_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + ledger_state_digest: &Self::DigestGadget, + commitment: &Self::CommitmentGadget, + witness: &Self::WitnessGadget, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError>; +} + +pub struct IdealLedgerGadget<C, H, HGadget, CGadget> { + #[doc(hidden)] + _comm_scheme: PhantomData<C>, + #[doc(hidden)] + _hash: PhantomData<H>, + #[doc(hidden)] + _comm_gadget: PhantomData<CGadget>, + #[doc(hidden)] + _hash_gadget: PhantomData<HGadget>, +} + +pub struct CommitmentWitness< + H: FixedLengthCRH, + C: CommitmentScheme, + HGadget: FixedLengthCRHGadget<H, ConstraintF>, + ConstraintF: Field, +> { + path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, + _crh: PhantomData<H>, + _comm: PhantomData<C>, + _engine: PhantomData<ConstraintF>, +} + +pub struct DigestGadget<H: FixedLengthCRH, HGadget: FixedLengthCRHGadget<H, ConstraintF>, ConstraintF: Field> { + digest: HGadget::OutputGadget, + #[doc(hidden)] + _crh: PhantomData<H>, + #[doc(hidden)] + _engine: PhantomData<ConstraintF>, +} + +impl<C, ConstraintF, H, CGadget, HGadget> LCWGadget<C, Digest<H>, CommPath<H, C::Output>, ConstraintF> + for IdealLedgerGadget<C, H, HGadget, CGadget> +where + C: CommitmentScheme, + C::Output: Eq, + ConstraintF: Field, + H: FixedLengthCRH, + CGadget: CommitmentGadget<C, ConstraintF>, + HGadget: FixedLengthCRHGadget<H, ConstraintF>, +{ + type ParametersGadget = <HGadget as FixedLengthCRHGadget<H, ConstraintF>>::ParametersGadget; + type DigestGadget = DigestGadget<H, HGadget, ConstraintF>; + + type CommitmentGadget = <CGadget as CommitmentGadget<C, ConstraintF>>::OutputGadget; + type WitnessGadget = CommitmentWitness<H, C, HGadget, ConstraintF>; + + /// Given a `leaf` and `path`, check that the `path` is a valid + /// authentication path for the `leaf` in a Merkle tree. + /// Note: It is assumed that the root is contained in the `path`. + fn conditionally_check_witness_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &Self::ParametersGadget, + root_hash: &Self::DigestGadget, + commitment: &Self::CommitmentGadget, + witness: &Self::WitnessGadget, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError> { + assert_eq!( + witness.path.len(), + (HashMembershipProof::<H, C::Output>::MAX_HEIGHT - 1) as usize + ); + // Check that the hash of the given leaf matches the leaf hash in the membership + // proof. + let commitment_bits = commitment.to_bytes(&mut cs.ns(|| "commitment_to_bytes"))?; + let commitment_hash = HGadget::check_evaluation_gadget( + cs.ns(|| "check_evaluation_gadget"), + parameters, + &commitment_bits, + )?; + + // Check if leaf is one of the bottom-most siblings. + let leaf_is_left = AllocatedBit::alloc(&mut cs.ns(|| "leaf_is_left"), || { + Ok(commitment_hash == witness.path[0].0) + })? + .into(); + <HGadget::OutputGadget>::conditional_enforce_equal_or( + &mut cs.ns(|| "check_leaf_is_left"), + &leaf_is_left, + &commitment_hash, + &witness.path[0].0, + &witness.path[0].1, + should_enforce, + )?; + + // Check levels between leaf level and root. + let mut previous_hash = commitment_hash; + for (i, &(ref left_hash, ref right_hash)) in witness.path.iter().enumerate() { + // Check if the previous_hash matches the correct current hash. + let previous_is_left = + AllocatedBit::alloc(&mut cs.ns(|| format!("previous_is_left_{}", i)), || { + Ok(&previous_hash == left_hash) + })? + .into(); + + <HGadget::OutputGadget>::conditional_enforce_equal_or( + &mut cs.ns(|| format!("check_equals_which_{}", i)), + &previous_is_left, + &previous_hash, + left_hash, + right_hash, + should_enforce, + )?; + + previous_hash = hash_inner_node_gadget::<H, HGadget, ConstraintF, _>( + &mut cs.ns(|| format!("hash_inner_node_{}", i)), + parameters, + left_hash, + right_hash, + )?; + } + + root_hash.digest.conditional_enforce_equal( + &mut cs.ns(|| "root_is_last"), + &previous_hash, + should_enforce, + ) + } +} + +pub(crate) fn hash_inner_node_gadget<H, HG, ConstraintF, CS>( + mut cs: CS, + parameters: &HG::ParametersGadget, + left_child: &HG::OutputGadget, + right_child: &HG::OutputGadget, +) -> Result<HG::OutputGadget, SynthesisError> +where + ConstraintF: Field, + CS: ConstraintSystem<ConstraintF>, + H: FixedLengthCRH, + HG: FixedLengthCRHGadget<H, ConstraintF>, +{ + let left_bytes = left_child.to_bytes(&mut cs.ns(|| "left_to_bytes"))?; + let right_bytes = right_child.to_bytes(&mut cs.ns(|| "right_to_bytes"))?; + let mut bytes = left_bytes; + bytes.extend_from_slice(&right_bytes); + + HG::check_evaluation_gadget(cs, parameters, &bytes) +} + +impl<H, HGadget, ConstraintF> AllocGadget<Digest<H>, ConstraintF> for DigestGadget<H, HGadget, ConstraintF> +where + H: FixedLengthCRH, + HGadget: FixedLengthCRHGadget<H, ConstraintF>, + ConstraintF: Field, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<Digest<H>>, + { + let digest = HGadget::OutputGadget::alloc(&mut cs.ns(|| "digest"), || { + Ok(value_gen()?.borrow().0.clone()) + })?; + + Ok(DigestGadget { + digest, + _crh: PhantomData, + _engine: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<Digest<H>>, + { + let digest = HGadget::OutputGadget::alloc_input(&mut cs.ns(|| "input_digest"), || { + Ok(value_gen()?.borrow().0.clone()) + })?; + Ok(DigestGadget { + digest, + _crh: PhantomData, + _engine: PhantomData, + }) + } +} + +impl<H, C, HGadget, ConstraintF> AllocGadget<CommPath<H, C::Output>, ConstraintF> + for CommitmentWitness<H, C, HGadget, ConstraintF> +where + H: FixedLengthCRH, + C: CommitmentScheme, + HGadget: FixedLengthCRHGadget<H, ConstraintF>, + ConstraintF: Field, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<CommPath<H, C::Output>>, + { + let mut path = Vec::new(); + for (i, &(ref l, ref r)) in value_gen()?.borrow().0.path.iter().enumerate() { + let l_hash = + HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("l_child_{}", i)), || { + Ok(l.clone()) + })?; + let r_hash = + HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("r_child_{}", i)), || { + Ok(r.clone()) + })?; + path.push((l_hash, r_hash)); + } + Ok(CommitmentWitness { + path, + _crh: PhantomData, + _comm: PhantomData, + _engine: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<CommPath<H, C::Output>>, + { + let mut path = Vec::new(); + for (i, &(ref l, ref r)) in value_gen()?.borrow().0.path.iter().enumerate() { + let l_hash = HGadget::OutputGadget::alloc_input( + &mut cs.ns(|| format!("l_child_{}", i)), + || Ok(l.clone()), + )?; + let r_hash = HGadget::OutputGadget::alloc_input( + &mut cs.ns(|| format!("r_child_{}", i)), + || Ok(r.clone()), + )?; + path.push((l_hash, r_hash)); + } + + Ok(CommitmentWitness { + path, + _crh: PhantomData, + _comm: PhantomData, + _engine: PhantomData, + }) + } +} + +#[cfg(test)] +mod test { + use std::rc::Rc; + + use crate::crypto_primitives::{ + commitment::{ + pedersen::{PedersenCommitment, PedersenRandomness}, + CommitmentScheme, + }, + crh::{ + pedersen::{PedersenCRH, PedersenWindow}, + FixedLengthCRH, + }, + mht::*, + }; + use algebra::{ + curves::jubjub::JubJubAffine as JubJub, + fields::jubjub::fr::Fr, + fields::jubjub::fq::Fq, + Group + }; + use rand::SeedableRng; + use algebra::UniformRand; + use rand_xorshift::XorShiftRng; + use r1cs_core::ConstraintSystem; + + use super::*; + use crate::gadgets::{ + commitment::pedersen::PedersenCommitmentGadget, + crh::{pedersen::PedersenCRHGadget, FixedLengthCRHGadget}, + }; + use r1cs_std::{ + groups::curves::twisted_edwards::jubjub::JubJubGadget, + test_constraint_system::TestConstraintSystem, + }; + + use crate::ledger::{CommPath, Digest}; + + #[derive(Clone)] + pub(super) struct Window4x256; + impl PedersenWindow for Window4x256 { + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 256; + } + + type H = PedersenCRH<JubJub, Window4x256>; + type HG = PedersenCRHGadget<JubJub, Fq, JubJubGadget>; + type C = PedersenCommitment<JubJub, Window4x256>; + type CG = PedersenCommitmentGadget<JubJub, Fq, JubJubGadget>; + type JubJubMHT = MerkleHashTree<H, <C as CommitmentScheme>::Output>; + type LG = IdealLedgerGadget<C, H, HG, CG>; + type DG = DigestGadget<H, HG, Fq>; + type LCWG = CommitmentWitness<H, C, HG, Fq>; + + fn generate_merkle_tree(leaves: &[<C as CommitmentScheme>::Output]) -> () { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); + let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); + let root = tree.root(); + let mut satisfied = true; + for (i, leaf) in leaves.iter().enumerate() { + let mut cs = TestConstraintSystem::<Fq>::new(); + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + + let digest = DG::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { + Ok(Digest(root)) + }) + .unwrap(); + let constraints_from_digest = cs.num_constraints(); + println!("constraints from digest: {}", constraints_from_digest); + + let crh_parameters = + <HG as FixedLengthCRHGadget<H, Fq>>::ParametersGadget::alloc( + &mut cs.ns(|| format!("new_parameters_{}", i)), + || Ok(crh_parameters.clone()), + ) + .unwrap(); + let constraints_from_parameters = cs.num_constraints() - constraints_from_digest; + println!( + "constraints from parameters: {}", + constraints_from_parameters + ); + + let comm = <CG as CommitmentGadget<C, Fq>>::OutputGadget::alloc( + &mut cs.ns(|| format!("new_comm_{}", i)), + || { + let leaf: JubJub = *leaf; + Ok(leaf) + }, + ) + .unwrap(); + let constraints_from_comm = + cs.num_constraints() - constraints_from_parameters - constraints_from_digest; + println!("constraints from comm: {}", constraints_from_comm); + + let cw = LCWG::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || { + Ok(CommPath(proof)) + }) + .unwrap(); + let constraints_from_path = cs.num_constraints() + - constraints_from_parameters + - constraints_from_digest + - constraints_from_comm; + println!("constraints from path: {}", constraints_from_path); + LG::check_witness_gadget( + &mut cs.ns(|| format!("new_witness_check_{}", i)), + &crh_parameters, + &digest, + &comm, + &cw, + ) + .unwrap(); + if !cs.is_satisfied() { + satisfied = false; + println!( + "Unsatisfied constraint: {}", + cs.which_is_unsatisfied().unwrap() + ); + } + let setup_constraints = constraints_from_comm + + constraints_from_digest + + constraints_from_parameters + + constraints_from_path; + println!( + "number of constraints: {}", + cs.num_constraints() - setup_constraints + ); + } + assert!(satisfied); + } + + #[test] + fn mht_gadget_test() { + let mut leaves = Vec::new(); + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let comm_parameters = C::setup(&mut rng).unwrap(); + for i in 0..4u8 { + let r = PedersenRandomness(Fr::rand(&mut rng)); + let input = [i, i, i, i, i, i, i, i]; + leaves.push(C::commit(&comm_parameters, &input, &r).unwrap()); + } + generate_merkle_tree(&leaves); + // let mut leaves = Vec::new(); + // for i in 0..100u8 { + // leaves.push([i, i, i, i, i, i, i, i]); + // } + // generate_merkle_tree(&leaves); + } + + fn bad_merkle_tree_verify(leaves: &[<C as CommitmentScheme>::Output]) -> () { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); + let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); + let root = tree.root(); + for (i, leaf) in leaves.iter().enumerate() { + let mut cs = TestConstraintSystem::<Fq>::new(); + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + + let digest = DG::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { + Ok(Digest(JubJub::zero())) + }) + .unwrap(); + let crh_parameters = + <HG as FixedLengthCRHGadget<H, Fq>>::ParametersGadget::alloc( + &mut cs.ns(|| format!("new_parameters_{}", i)), + || Ok(crh_parameters.clone()), + ) + .unwrap(); + let comm = <CG as CommitmentGadget<C, Fq>>::OutputGadget::alloc( + &mut cs.ns(|| format!("new_comm_{}", i)), + || { + let leaf = *leaf; + Ok(leaf) + }, + ) + .unwrap(); + let cw = LCWG::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || { + Ok(CommPath(proof)) + }) + .unwrap(); + LG::check_witness_gadget( + &mut cs.ns(|| format!("new_witness_check_{}", i)), + &crh_parameters, + &digest, + &comm, + &cw, + ) + .unwrap(); + if !cs.is_satisfied() { + println!( + "Unsatisfied constraints: {}", + cs.which_is_unsatisfied().unwrap() + ); + } + assert!(cs.is_satisfied()); + } + } + + #[should_panic] + #[test] + fn bad_root_test() { + let mut leaves = Vec::new(); + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let comm_parameters = C::setup(&mut rng).unwrap(); + for i in 0..4u8 { + let r = PedersenRandomness(Fr::rand(&mut rng)); + let input = [i, i, i, i, i, i, i, i]; + leaves.push(C::commit(&comm_parameters, &input, &r).unwrap()); + } + bad_merkle_tree_verify(&leaves); + } +}
A crypto-primitives/src/mht/constraints.rs

@@ -0,0 +1,353 @@

+use algebra::Field; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; +use r1cs_std::boolean::AllocatedBit; + +use crate::mht::*; +use crate::crh::{FixedLengthCRH, FixedLengthCRHGadget}; + +use std::{borrow::Borrow, marker::PhantomData}; + +pub struct MerklePath<P, HGadget, ConstraintF> +where + P: MHTParameters, + HGadget: FixedLengthCRHGadget<P::H, ConstraintF>, + ConstraintF: Field, +{ + path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, +} + +pub struct MerklePathVerifierGadget<P, CRHGadget, ConstraintF> +where + P: MHTParameters, + ConstraintF: Field, + CRHGadget: FixedLengthCRHGadget<P::H, ConstraintF>, +{ + _p: PhantomData<CRHGadget>, + _p2: PhantomData<P>, + _f: PhantomData<ConstraintF>, +} + +impl<P, CRHGadget, ConstraintF> MerklePathVerifierGadget<P, CRHGadget, ConstraintF> +where + P: MHTParameters, + ConstraintF: Field, + CRHGadget: FixedLengthCRHGadget<P::H, ConstraintF>, +{ + + pub fn check_membership<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &CRHGadget::ParametersGadget, + root: &CRHGadget::OutputGadget, + leaf: impl ToBytesGadget<ConstraintF>, + witness: &MerklePath<P, CRHGadget, ConstraintF>, + ) -> Result<(), SynthesisError> { + Self::conditionally_check_membership( + cs, + parameters, + root, + leaf, + witness, + &Boolean::Constant(true), + ) + } + + pub fn conditionally_check_membership<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &CRHGadget::ParametersGadget, + root: &CRHGadget::OutputGadget, + leaf: impl ToBytesGadget<ConstraintF>, + witness: &MerklePath<P, CRHGadget, ConstraintF>, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError> { + assert_eq!(witness.path.len(), P::HEIGHT - 1); + // Check that the hash of the given leaf matches the leaf hash in the membership + // proof. + let leaf_bits = leaf.to_bytes(&mut cs.ns(|| "leaf_to_bytes"))?; + let leaf_hash = CRHGadget::check_evaluation_gadget( + cs.ns(|| "check_evaluation_gadget"), + parameters, + &leaf_bits, + )?; + + // Check if leaf is one of the bottom-most siblings. + let leaf_is_left = AllocatedBit::alloc(&mut cs.ns(|| "leaf_is_left"), || { + Ok(leaf_hash == witness.path[0].0) + })? + .into(); + CRHGadget::OutputGadget::conditional_enforce_equal_or( + &mut cs.ns(|| "check_leaf_is_left"), + &leaf_is_left, + &leaf_hash, + &witness.path[0].0, + &witness.path[0].1, + should_enforce, + )?; + + // Check levels between leaf level and root. + let mut previous_hash = leaf_hash; + for (i, &(ref left_hash, ref right_hash)) in witness.path.iter().enumerate() { + // Check if the previous_hash matches the correct current hash. + let previous_is_left = + AllocatedBit::alloc(&mut cs.ns(|| format!("previous_is_left_{}", i)), || { + Ok(&previous_hash == left_hash) + })? + .into(); + + CRHGadget::OutputGadget::conditional_enforce_equal_or( + &mut cs.ns(|| format!("check_equals_which_{}", i)), + &previous_is_left, + &previous_hash, + left_hash, + right_hash, + should_enforce, + )?; + + previous_hash = hash_inner_node_gadget::<P::H, CRHGadget, ConstraintF, _>( + &mut cs.ns(|| format!("hash_inner_node_{}", i)), + parameters, + left_hash, + right_hash, + )?; + } + + root.conditional_enforce_equal( + &mut cs.ns(|| "root_is_last"), + &previous_hash, + should_enforce, + ) + } +} + +pub(crate) fn hash_inner_node_gadget<H, HG, ConstraintF, CS>( + mut cs: CS, + parameters: &HG::ParametersGadget, + left_child: &HG::OutputGadget, + right_child: &HG::OutputGadget, +) -> Result<HG::OutputGadget, SynthesisError> +where + ConstraintF: Field, + CS: ConstraintSystem<ConstraintF>, + H: FixedLengthCRH, + HG: FixedLengthCRHGadget<H, ConstraintF>, +{ + let left_bytes = left_child.to_bytes(&mut cs.ns(|| "left_to_bytes"))?; + let right_bytes = right_child.to_bytes(&mut cs.ns(|| "right_to_bytes"))?; + let mut bytes = left_bytes; + bytes.extend_from_slice(&right_bytes); + + HG::check_evaluation_gadget(cs, parameters, &bytes) +} + +impl<P, HGadget, ConstraintF> AllocGadget<HashMembershipProof<P>, ConstraintF> + for MerklePath<P, HGadget, ConstraintF> +where + P: MHTParameters, + HGadget: FixedLengthCRHGadget<P::H, ConstraintF>, + ConstraintF: Field, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<HashMembershipProof<P>>, + { + let mut path = Vec::new(); + for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() { + let l_hash = + HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("l_child_{}", i)), || { + Ok(l.clone()) + })?; + let r_hash = + HGadget::OutputGadget::alloc(&mut cs.ns(|| format!("r_child_{}", i)), || { + Ok(r.clone()) + })?; + path.push((l_hash, r_hash)); + } + Ok(MerklePath { + path, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<HashMembershipProof<P>>, + { + let mut path = Vec::new(); + for (i, &(ref l, ref r)) in value_gen()?.borrow().path.iter().enumerate() { + let l_hash = HGadget::OutputGadget::alloc_input( + &mut cs.ns(|| format!("l_child_{}", i)), + || Ok(l.clone()), + )?; + let r_hash = HGadget::OutputGadget::alloc_input( + &mut cs.ns(|| format!("r_child_{}", i)), + || Ok(r.clone()), + )?; + path.push((l_hash, r_hash)); + } + + Ok(MerklePath { + path, + }) + } +} + +#[cfg(test)] +mod test { + use std::rc::Rc; + + use crate::{ + crh::{ + pedersen::{PedersenCRH, PedersenWindow}, + pedersen::constraints::PedersenCRHGadget, + FixedLengthCRH, + FixedLengthCRHGadget, + }, + mht::*, + }; + use algebra::{ + curves::jubjub::JubJubAffine as JubJub, + fields::jubjub::fq::Fq, + }; + use rand::SeedableRng; + use rand_xorshift::XorShiftRng; + use r1cs_core::ConstraintSystem; + + use super::*; + use r1cs_std::{ + groups::curves::twisted_edwards::jubjub::JubJubGadget, + test_constraint_system::TestConstraintSystem, + }; + + #[derive(Clone)] + pub(super) struct Window4x256; + impl PedersenWindow for Window4x256 { + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 256; + } + + type H = PedersenCRH<JubJub, Window4x256>; + type HG = PedersenCRHGadget<JubJub, Fq, JubJubGadget>; + + struct JubJubMHTParams; + + impl MHTParameters for JubJubMHTParams { + const HEIGHT: usize = 32; + type H = H; + } + + type JubJubMHT = MerkleHashTree<JubJubMHTParams>; + + fn generate_merkle_tree(leaves: &[[u8; 30]], use_bad_root: bool) -> () { + let mut rng = XorShiftRng::seed_from_u64(9174123u64); + + let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); + let tree = JubJubMHT::new(crh_parameters.clone(), leaves).unwrap(); + let root = tree.root(); + let mut satisfied = true; + for (i, leaf) in leaves.iter().enumerate() { + let mut cs = TestConstraintSystem::<Fq>::new(); + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + + // Allocate Merkle Tree Root + let root = <HG as FixedLengthCRHGadget<H, _>>::OutputGadget::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { + if use_bad_root { + Ok(<H as FixedLengthCRH>::Output::default()) + } else { + Ok(root) + } + }) + .unwrap(); + + let constraints_from_digest = cs.num_constraints(); + println!("constraints from digest: {}", constraints_from_digest); + + // Allocate Parameters for CRH + let crh_parameters = + <HG as FixedLengthCRHGadget<H, Fq>>::ParametersGadget::alloc( + &mut cs.ns(|| format!("new_parameters_{}", i)), + || Ok(crh_parameters.clone()), + ) + .unwrap(); + + let constraints_from_parameters = cs.num_constraints() - constraints_from_digest; + println!( + "constraints from parameters: {}", + constraints_from_parameters + ); + + // Allocate Leaf + let leaf_g = UInt8::constant_vec(leaf); + + let constraints_from_leaf = + cs.num_constraints() - constraints_from_parameters - constraints_from_digest; + println!("constraints from leaf: {}", constraints_from_leaf); + + // Allocate Merkle Tree Path + let cw = MerklePath::<_, HG, _>::alloc(&mut cs.ns(|| format!("new_witness_{}", i)), || { + Ok(proof) + }) + .unwrap(); + + let constraints_from_path = cs.num_constraints() + - constraints_from_parameters + - constraints_from_digest + - constraints_from_leaf; + println!("constraints from path: {}", constraints_from_path); + let leaf_g: &[UInt8] = leaf_g.as_slice(); + MerklePathVerifierGadget::<_, HG, _>::check_membership( + &mut cs.ns(|| format!("new_witness_check_{}", i)), + &crh_parameters, + &root, + &leaf_g, + &cw, + ) + .unwrap(); + if !cs.is_satisfied() { + satisfied = false; + println!( + "Unsatisfied constraint: {}", + cs.which_is_unsatisfied().unwrap() + ); + } + let setup_constraints = constraints_from_leaf + + constraints_from_digest + + constraints_from_parameters + + constraints_from_path; + println!( + "number of constraints: {}", + cs.num_constraints() - setup_constraints + ); + } + + assert!(satisfied); + } + + #[test] + fn mht_gadget_test() { + let mut leaves = Vec::new(); + for i in 0..4u8 { + let input = [i ; 30]; + leaves.push(input); + } + generate_merkle_tree(&leaves, false); + } + + #[should_panic] + #[test] + fn bad_root_test() { + let mut leaves = Vec::new(); + for i in 0..4u8 { + let input = [i ; 30]; + leaves.push(input); + } + generate_merkle_tree(&leaves, true); + } +}
A crypto-primitives/src/mht/mod.rs

@@ -0,0 +1,437 @@

+use crate::crh::FixedLengthCRH; +use algebra::bytes::ToBytes; +use crate::Error; +use std::{fmt, rc::Rc}; + + +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub trait MHTParameters { + const HEIGHT: usize; + type H: FixedLengthCRH; +} + +/// Stores the hashes of a particular path (in order) from leaf to root. +/// Our path `is_left_child()` if the boolean in `path` is true. +#[derive(Derivative)] +#[derivative( + Clone(bound = "P: MHTParameters"), + Debug(bound = "P: MHTParameters, <P::H as FixedLengthCRH>::Output: fmt::Debug") +)] +pub struct HashMembershipProof<P: MHTParameters> { + pub(crate) path: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>, +} + +impl<P: MHTParameters> Default for HashMembershipProof<P> { + fn default() -> Self { + let mut path = Vec::with_capacity(P::HEIGHT as usize); + for _i in 1..P::HEIGHT as usize { + path.push((<P::H as FixedLengthCRH>::Output::default(), <P::H as FixedLengthCRH>::Output::default())); + } + Self { + path, + } + } +} + +impl<P: MHTParameters> HashMembershipProof<P> { + pub fn verify<L: ToBytes>( + &self, + parameters: &<P::H as FixedLengthCRH>::Parameters, + root_hash: &<P::H as FixedLengthCRH>::Output, + leaf: &L, + ) -> Result<bool, Error> { + if self.path.len() != (P::HEIGHT - 1) as usize { + return Ok(false); + } + // Check that the given leaf matches the leaf in the membership proof. + let mut buffer = [0u8; 128]; + + if !self.path.is_empty() { + let claimed_leaf_hash = hash_leaf::<P::H, L>(parameters, leaf, &mut buffer)?; + + // Check if leaf is one of the bottom-most siblings. + if claimed_leaf_hash != self.path[0].0 && claimed_leaf_hash != self.path[0].1 { + return Ok(false); + }; + + let mut prev = claimed_leaf_hash; + // Check levels between leaf level and root. + for &(ref hash, ref sibling_hash) in &self.path { + // Check if the previous hash matches the correct current hash. + if &prev != hash && &prev != sibling_hash { + return Ok(false); + }; + prev = hash_inner_node::<P::H>(parameters, hash, sibling_hash, &mut buffer)?; + } + + if root_hash != &prev { + return Ok(false); + } + Ok(true) + } else { + Ok(false) + } + } +} + +pub struct MerkleHashTree<P: MHTParameters> { + tree: Vec<<P::H as FixedLengthCRH>::Output>, + padding_tree: Vec<(<P::H as FixedLengthCRH>::Output, <P::H as FixedLengthCRH>::Output)>, + parameters: Rc<<P::H as FixedLengthCRH>::Parameters>, + root: Option<<P::H as FixedLengthCRH>::Output>, +} + +impl<P: MHTParameters> MerkleHashTree<P> { + pub const HEIGHT: u8 = P::HEIGHT as u8; + + pub fn blank(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>) -> Self { + MerkleHashTree { + tree: Vec::new(), + padding_tree: Vec::new(), + root: None, + parameters, + } + } + + pub fn new<L: ToBytes>(parameters: Rc<<P::H as FixedLengthCRH>::Parameters>, leaves: &[L]) -> Result<Self, Error> { + let new_time = start_timer!(|| "MHT::New"); + + let last_level_size = leaves.len().next_power_of_two(); + let tree_size = 2 * last_level_size - 1; + let tree_height = tree_height(tree_size); + assert!(tree_height as u8 <= Self::HEIGHT); + + // Initialize the merkle tree. + let mut tree = Vec::with_capacity(tree_size); + let empty_hash = hash_empty::<P::H>(&parameters)?; + for _ in 0..tree_size { + tree.push(empty_hash.clone()); + } + + // Compute the starting indices for each level of the tree. + let mut index = 0; + let mut level_indices = Vec::with_capacity(tree_height); + for _ in 0..tree_height { + level_indices.push(index); + index = left_child(index); + } + + // Compute and store the hash values for each leaf. + let last_level_index = level_indices.pop().unwrap(); + let mut buffer = [0u8; 128]; + for (i, leaf) in leaves.iter().enumerate() { + tree[last_level_index + i] = hash_leaf::<P::H, _>(&parameters, leaf, &mut buffer)?; + } + + // Compute the hash values for every node in the tree. + let mut upper_bound = last_level_index; + let mut buffer = [0u8; 128]; + level_indices.reverse(); + for &start_index in &level_indices { + // Iterate over the current level. + for current_index in start_index..upper_bound { + let left_index = left_child(current_index); + let right_index = right_child(current_index); + + // Compute Hash(left || right). + tree[current_index] = hash_inner_node::<P::H>( + &parameters, + &tree[left_index], + &tree[right_index], + &mut buffer, + )?; + } + upper_bound = start_index; + } + // Finished computing actual tree. + // Now, we compute the dummy nodes until we hit our HEIGHT goal. + let mut cur_height = tree_height; + let mut padding_tree = Vec::new(); + let mut cur_hash = tree[0].clone(); + while cur_height < (Self::HEIGHT - 1) as usize { + cur_hash = hash_inner_node::<P::H>(&parameters, &cur_hash, &empty_hash, &mut buffer)?; + padding_tree.push((cur_hash.clone(), empty_hash.clone())); + cur_height += 1; + } + + let root_hash = hash_inner_node::<P::H>(&parameters, &cur_hash, &empty_hash, &mut buffer)?; + + end_timer!(new_time); + + Ok(MerkleHashTree { + tree, + padding_tree, + parameters, + root: Some(root_hash), + }) + } + + #[inline] + pub fn root(&self) -> <P::H as FixedLengthCRH>::Output { + self.root.clone().unwrap() + } + + pub fn generate_proof<L: ToBytes>( + &self, + index: usize, + leaf: &L, + ) -> Result<HashMembershipProof<P>, Error> { + let prove_time = start_timer!(|| "MHT::GenProof"); + let mut path = Vec::new(); + + let mut buffer = [0u8; 128]; + let leaf_hash = hash_leaf::<P::H, _>(&self.parameters, leaf, &mut buffer)?; + let tree_height = tree_height(self.tree.len()); + let tree_index = convert_index_to_last_level(index, tree_height); + let empty_hash = hash_empty::<P::H>(&self.parameters)?; + + // Check that the given index corresponds to the correct leaf. + if leaf_hash != self.tree[tree_index] { + Err(MHTError::IncorrectLeafIndex(tree_index))? + } + + // Iterate from the leaf up to the root, storing all intermediate hash values. + let mut current_node = tree_index; + while !is_root(current_node) { + let sibling_node = sibling(current_node).unwrap(); + let (curr_hash, sibling_hash) = ( + self.tree[current_node].clone(), + self.tree[sibling_node].clone(), + ); + if is_left_child(current_node) { + path.push((curr_hash, sibling_hash)); + } else { + path.push((sibling_hash, curr_hash)); + } + current_node = parent(current_node).unwrap(); + } + + // Store the root node. Set boolean as true for consistency with digest + // location. + assert!(path.len() < Self::HEIGHT as usize); + if path.len() != (Self::HEIGHT - 1) as usize { + path.push((self.tree[0].clone(), empty_hash)); + for &(ref hash, ref sibling_hash) in &self.padding_tree { + path.push((hash.clone(), sibling_hash.clone())); + } + } + end_timer!(prove_time); + if path.len() != (Self::HEIGHT - 1) as usize { + Err(MHTError::IncorrectPathLength(path.len()))? + } else { + Ok(HashMembershipProof { + path, + }) + } + } +} + +#[derive(Debug)] +pub enum MHTError { + IncorrectLeafIndex(usize), + IncorrectPathLength(usize), +} + +impl std::fmt::Display for MHTError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = match self { + MHTError::IncorrectLeafIndex(index) => format!("incorrect leaf index: {}", index), + MHTError::IncorrectPathLength(len) => format!("incorrect path length: {}", len), + }; + write!(f, "{}", msg) + } +} + +impl std::error::Error for MHTError { + #[inline] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +/// Returns the log2 value of the given number. +#[inline] +fn log2(number: usize) -> usize { + (number as f64).log2() as usize +} + +/// Returns the height of the tree, given the size of the tree. +#[inline] +fn tree_height(tree_size: usize) -> usize { + log2(tree_size + 1) +} + +/// Returns true iff the index represents the root. +#[inline] +fn is_root(index: usize) -> bool { + index == 0 +} + +/// Returns the index of the left child, given an index. +#[inline] +fn left_child(index: usize) -> usize { + 2 * index + 1 +} + +/// Returns the index of the right child, given an index. +#[inline] +fn right_child(index: usize) -> usize { + 2 * index + 2 +} + +/// Returns the index of the sibling, given an index. +#[inline] +fn sibling(index: usize) -> Option<usize> { + if index == 0 { + None + } else if is_left_child(index) { + Some(index + 1) + } else { + Some(index - 1) + } +} + +/// Returns true iff the given index represents a left child. +#[inline] +fn is_left_child(index: usize) -> bool { + index % 2 == 1 +} + +/// Returns the index of the parent, given an index. +#[inline] +fn parent(index: usize) -> Option<usize> { + if index > 0 { + Some((index - 1) >> 1) + } else { + None + } +} + +#[inline] +fn convert_index_to_last_level(index: usize, tree_height: usize) -> usize { + index + (1 << (tree_height - 1)) - 1 +} + +/// Returns the output hash, given a left and right hash value. +pub(crate) fn hash_inner_node<H: FixedLengthCRH>( + parameters: &H::Parameters, + left: &H::Output, + right: &H::Output, + buffer: &mut [u8], +) -> Result<H::Output, Error> { + use std::io::Cursor; + let mut writer = Cursor::new(buffer); + // Construct left input. + left.write(&mut writer)?; + + // Construct right input. + right.write(&mut writer)?; + + let buffer = writer.into_inner(); + H::evaluate(parameters, &buffer[..(H::INPUT_SIZE_BITS / 8)]) +} + +/// Returns the hash of a leaf. +pub(crate) fn hash_leaf<H: FixedLengthCRH, L: ToBytes>( + parameters: &H::Parameters, + leaf: &L, + buffer: &mut [u8], +) -> Result<H::Output, Error> { + use std::io::Cursor; + let mut writer = Cursor::new(buffer); + leaf.write(&mut writer)?; + + let buffer = writer.into_inner(); + H::evaluate(parameters, &buffer[..(H::INPUT_SIZE_BITS / 8)]) +} + +pub(crate) fn hash_empty<H: FixedLengthCRH>( + parameters: &H::Parameters, +) -> Result<H::Output, Error> { + let empty_buffer = vec![0u8; H::INPUT_SIZE_BITS / 8]; + H::evaluate(parameters, &empty_buffer) +} + + +#[cfg(test)] +mod test { + use crate::{crh::{pedersen::*, *}, mht::*}; + use algebra::curves::jubjub::JubJubAffine as JubJub; + use rand::SeedableRng; + use rand_xorshift::XorShiftRng; + + #[derive(Clone)] + pub(super) struct Window4x256; + impl PedersenWindow for Window4x256 { + const WINDOW_SIZE: usize = 4; + const NUM_WINDOWS: usize = 256; + } + + + type H = PedersenCRH<JubJub, Window4x256>; + + struct JubJubMHTParams; + + impl MHTParameters for JubJubMHTParams { + const HEIGHT: usize = 32; + type H = H; + } + type JubJubMHT = MerkleHashTree<JubJubMHTParams>; + + + fn generate_merkle_tree<L: ToBytes + Clone + Eq>(leaves: &[L]) -> () { + let mut rng = XorShiftRng::seed_from_u64(9174123u64); + + let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); + let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); + let root = tree.root(); + for (i, leaf) in leaves.iter().enumerate() { + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + } + } + + #[test] + fn mht_test() { + let mut leaves = Vec::new(); + for i in 0..4u8 { + leaves.push([i, i, i, i, i, i, i, i]); + } + generate_merkle_tree(&leaves); + let mut leaves = Vec::new(); + for i in 0..100u8 { + leaves.push([i, i, i, i, i, i, i, i]); + } + generate_merkle_tree(&leaves); + } + + fn bad_merkle_tree_verify<L: ToBytes + Clone + Eq>(leaves: &[L]) -> () { + use algebra::groups::Group; + let mut rng = XorShiftRng::seed_from_u64(13423423u64); + + let crh_parameters = Rc::new(H::setup(&mut rng).unwrap()); + let tree = JubJubMHT::new(crh_parameters.clone(), &leaves).unwrap(); + let root = JubJub::zero(); + for (i, leaf) in leaves.iter().enumerate() { + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + } + } + + #[should_panic] + #[test] + fn bad_root_mht_test() { + let mut leaves = Vec::new(); + for i in 0..4u8 { + leaves.push([i, i, i, i, i, i, i, i]); + } + generate_merkle_tree(&leaves); + let mut leaves = Vec::new(); + for i in 0..100u8 { + leaves.push([i, i, i, i, i, i, i, i]); + } + bad_merkle_tree_verify(&leaves); + } +}
A crypto-primitives/src/nizk/constraints.rs

@@ -0,0 +1,22 @@

+use algebra::Field; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use crate::nizk::NIZK; + +pub trait NIZKVerifierGadget<N: NIZK, ConstraintF: Field> { + type VerificationKeyGadget: AllocGadget<N::VerificationParameters, ConstraintF> + ToBytesGadget<ConstraintF>; + + type ProofGadget: AllocGadget<N::Proof, ConstraintF>; + + fn check_verify<'a, CS, I, T>( + cs: CS, + verification_key: &Self::VerificationKeyGadget, + input: I, + proof: &Self::ProofGadget, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem<ConstraintF>, + I: Iterator<Item = &'a T>, + T: 'a + ToBitsGadget<ConstraintF> + ?Sized; +}
A crypto-primitives/src/nizk/gm17/constraints.rs

@@ -0,0 +1,543 @@

+use crate::nizk::{gm17::Gm17, NIZKVerifierGadget}; +use algebra::{Field, ToConstraintField, AffineCurve, PairingEngine}; +use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use gm17::{Proof, VerifyingKey}; +use std::{borrow::Borrow, marker::PhantomData}; + +#[derive(Derivative)] +#[derivative(Clone(bound = "P::G1Gadget: Clone, P::G2Gadget: Clone"))] +pub struct ProofGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +> { + pub a: P::G1Gadget, + pub b: P::G2Gadget, + pub c: P::G1Gadget, +} + +#[derive(Derivative)] +#[derivative(Clone( + bound = "P::G1Gadget: Clone, P::GTGadget: Clone, P::G1PreparedGadget: Clone, \ + P::G2PreparedGadget: Clone, " +))] +pub struct VerifyingKeyGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +> { + pub h_g2: P::G2Gadget, + pub g_alpha_g1: P::G1Gadget, + pub h_beta_g2: P::G2Gadget, + pub g_gamma_g1: P::G1Gadget, + pub h_gamma_g2: P::G2Gadget, + pub query: Vec<P::G1Gadget>, +} + +impl< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, + > VerifyingKeyGadget<PairingE, ConstraintF, P> +{ + pub fn prepare<CS: ConstraintSystem<ConstraintF>>( + &self, + mut cs: CS, + ) -> Result<PreparedVerifyingKeyGadget<PairingE, ConstraintF, P>, SynthesisError> { + let mut cs = cs.ns(|| "Preparing verifying key"); + let g_alpha_pc = P::prepare_g1(&mut cs.ns(|| "Prepare g_alpha_g1"), &self.g_alpha_g1)?; + let h_beta_pc = P::prepare_g2(&mut cs.ns(|| "Prepare h_beta_g2"), &self.h_beta_g2)?; + let g_gamma_pc = P::prepare_g1(&mut cs.ns(|| "Prepare g_gamma_pc"), &self.g_gamma_g1)?; + let h_gamma_pc = P::prepare_g2(&mut cs.ns(|| "Prepare h_gamma_pc"), &self.h_gamma_g2)?; + let h_pc = P::prepare_g2(&mut cs.ns(|| "Prepare h_pc"), &self.h_g2)?; + Ok(PreparedVerifyingKeyGadget { + g_alpha: self.g_alpha_g1.clone(), + h_beta: self.h_beta_g2.clone(), + g_alpha_pc, + h_beta_pc, + g_gamma_pc, + h_gamma_pc, + h_pc, + query: self.query.clone(), + }) + } +} + +#[derive(Derivative)] +#[derivative(Clone( + bound = "P::G1Gadget: Clone, P::GTGadget: Clone, P::G1PreparedGadget: Clone, \ + P::G2PreparedGadget: Clone, " +))] +pub struct PreparedVerifyingKeyGadget< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +> { + pub g_alpha: P::G1Gadget, + pub h_beta: P::G2Gadget, + pub g_alpha_pc: P::G1PreparedGadget, + pub h_beta_pc: P::G2PreparedGadget, + pub g_gamma_pc: P::G1PreparedGadget, + pub h_gamma_pc: P::G2PreparedGadget, + pub h_pc: P::G2PreparedGadget, + pub query: Vec<P::G1Gadget>, +} + +pub struct Gm17VerifierGadget<PairingE, ConstraintF, P> +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +{ + _pairing_engine: PhantomData<PairingE>, + _engine: PhantomData<ConstraintF>, + _pairing_gadget: PhantomData<P>, +} + +impl<PairingE, ConstraintF, P, C, V> NIZKVerifierGadget<Gm17<PairingE, C, V>, ConstraintF> + for Gm17VerifierGadget<PairingE, ConstraintF, P> +where + PairingE: PairingEngine, + ConstraintF: Field, + C: ConstraintSynthesizer<PairingE::Fr>, + V: ToConstraintField<PairingE::Fr>, + P: PairingGadget<PairingE, ConstraintF>, +{ + type VerificationKeyGadget = VerifyingKeyGadget<PairingE, ConstraintF, P>; + type ProofGadget = ProofGadget<PairingE, ConstraintF, P>; + + fn check_verify<'a, CS, I, T>( + mut cs: CS, + vk: &Self::VerificationKeyGadget, + mut public_inputs: I, + proof: &Self::ProofGadget, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem<ConstraintF>, + I: Iterator<Item = &'a T>, + T: 'a + ToBitsGadget<ConstraintF> + ?Sized, + { + let pvk = vk.prepare(&mut cs.ns(|| "Prepare vk"))?; + // e(A*G^{alpha}, B*H^{beta}) = e(G^{alpha}, H^{beta}) * e(G^{psi}, H^{gamma}) * + // e(C, H) where psi = \sum_{i=0}^l input_i pvk.query[i] + + let g_psi = { + let mut cs = cs.ns(|| "Process input"); + let mut g_psi = pvk.query[0].clone(); + let mut input_len = 1; + for (i, (input, b)) in public_inputs + .by_ref() + .zip(pvk.query.iter().skip(1)) + .enumerate() + { + let input_bits = input.to_bits(cs.ns(|| format!("Input {}", i)))?; + g_psi = b.mul_bits(cs.ns(|| format!("Mul {}", i)), &g_psi, input_bits.iter())?; + input_len += 1; + } + // Check that the input and the query in the verification are of the + // same length. + assert!(input_len == pvk.query.len() && public_inputs.next().is_none()); + g_psi + }; + + let mut test1_a_g_alpha = proof.a.add(cs.ns(|| "A * G^{alpha}"), &pvk.g_alpha)?; + let test1_b_h_beta = proof.b.add(cs.ns(|| "B * H^{beta}"), &pvk.h_beta)?; + + let test1_exp = { + test1_a_g_alpha = test1_a_g_alpha.negate(cs.ns(|| "neg 1"))?; + let test1_a_g_alpha_prep = P::prepare_g1(cs.ns(|| "First prep"), &test1_a_g_alpha)?; + let test1_b_h_beta_prep = P::prepare_g2(cs.ns(|| "Second prep"), &test1_b_h_beta)?; + + let g_psi_prep = P::prepare_g1(cs.ns(|| "Third prep"), &g_psi)?; + + let c_prep = P::prepare_g1(cs.ns(|| "Fourth prep"), &proof.c)?; + + P::miller_loop( + cs.ns(|| "Miller loop 1"), + &[ + test1_a_g_alpha_prep, + g_psi_prep, + c_prep, + pvk.g_alpha_pc.clone(), + ], + &[ + test1_b_h_beta_prep, + pvk.h_gamma_pc.clone(), + pvk.h_pc.clone(), + pvk.h_beta_pc.clone(), + ], + )? + }; + + let test1 = P::final_exponentiation(cs.ns(|| "Final Exp 1"), &test1_exp).unwrap(); + + // e(A, H^{gamma}) = e(G^{gamma}, B) + let test2_exp = { + let a_prep = P::prepare_g1(cs.ns(|| "Fifth prep"), &proof.a)?; + // pvk.h_gamma_pc + //&pvk.g_gamma_pc + let proof_b = proof.b.negate(cs.ns(|| "Negate b"))?; + let b_prep = P::prepare_g2(cs.ns(|| "Sixth prep"), &proof_b)?; + P::miller_loop( + cs.ns(|| "Miller loop 4"), + &[a_prep, pvk.g_gamma_pc.clone()], + &[pvk.h_gamma_pc.clone(), b_prep], + )? + }; + let test2 = P::final_exponentiation(cs.ns(|| "Final Exp 2"), &test2_exp)?; + + let one = P::GTGadget::one(cs.ns(|| "GT One"))?; + test1.enforce_equal(cs.ns(|| "Test 1"), &one)?; + test2.enforce_equal(cs.ns(|| "Test 2"), &one)?; + Ok(()) + } +} + +impl<PairingE, ConstraintF, P> AllocGadget<VerifyingKey<PairingE>, ConstraintF> + for VerifyingKeyGadget<PairingE, ConstraintF, P> +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +{ + #[inline] + fn alloc<FN, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: FN, + ) -> Result<Self, SynthesisError> + where + FN: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<VerifyingKey<PairingE>>, + { + value_gen().and_then(|vk| { + let VerifyingKey { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + } = vk.borrow().clone(); + let h_g2 = P::G2Gadget::alloc(cs.ns(|| "h_g2"), || Ok(h_g2.into_projective()))?; + let g_alpha_g1 = + P::G1Gadget::alloc(cs.ns(|| "g_alpha"), || Ok(g_alpha_g1.into_projective()))?; + let h_beta_g2 = + P::G2Gadget::alloc(cs.ns(|| "h_beta"), || Ok(h_beta_g2.into_projective()))?; + let g_gamma_g1 = + P::G1Gadget::alloc(cs.ns(|| "g_gamma_g1"), || Ok(g_gamma_g1.into_projective()))?; + let h_gamma_g2 = + P::G2Gadget::alloc(cs.ns(|| "h_gamma_g2"), || Ok(h_gamma_g2.into_projective()))?; + + let query = query + .into_iter() + .enumerate() + .map(|(i, query_i)| { + P::G1Gadget::alloc(cs.ns(|| format!("query_{}", i)), || { + Ok(query_i.into_projective()) + }) + }) + .collect::<Vec<_>>() + .into_iter() + .collect::<Result<_, _>>()?; + Ok(Self { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + }) + }) + } + + #[inline] + fn alloc_input<FN, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: FN, + ) -> Result<Self, SynthesisError> + where + FN: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<VerifyingKey<PairingE>>, + { + value_gen().and_then(|vk| { + let VerifyingKey { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + } = vk.borrow().clone(); + let h_g2 = P::G2Gadget::alloc_input(cs.ns(|| "h_g2"), || Ok(h_g2.into_projective()))?; + let g_alpha_g1 = + P::G1Gadget::alloc_input(cs.ns(|| "g_alpha"), || Ok(g_alpha_g1.into_projective()))?; + let h_beta_g2 = + P::G2Gadget::alloc_input(cs.ns(|| "h_beta"), || Ok(h_beta_g2.into_projective()))?; + let g_gamma_g1 = P::G1Gadget::alloc_input(cs.ns(|| "g_gamma_g1"), || { + Ok(g_gamma_g1.into_projective()) + })?; + let h_gamma_g2 = P::G2Gadget::alloc_input(cs.ns(|| "h_gamma_g2"), || { + Ok(h_gamma_g2.into_projective()) + })?; + + let query = query + .into_iter() + .enumerate() + .map(|(i, query_i)| { + P::G1Gadget::alloc_input(cs.ns(|| format!("query_{}", i)), || { + Ok(query_i.into_projective()) + }) + }) + .collect::<Vec<_>>() + .into_iter() + .collect::<Result<_, _>>()?; + Ok(Self { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + }) + }) + } +} + +impl<PairingE, ConstraintF, P> AllocGadget<Proof<PairingE>, ConstraintF> + for ProofGadget<PairingE, ConstraintF, P> +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +{ + #[inline] + fn alloc<FN, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: FN, + ) -> Result<Self, SynthesisError> + where + FN: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<Proof<PairingE>>, + { + value_gen().and_then(|proof| { + let Proof { a, b, c } = proof.borrow().clone(); + let a = P::G1Gadget::alloc_checked(cs.ns(|| "a"), || Ok(a.into_projective()))?; + let b = P::G2Gadget::alloc_checked(cs.ns(|| "b"), || Ok(b.into_projective()))?; + let c = P::G1Gadget::alloc_checked(cs.ns(|| "c"), || Ok(c.into_projective()))?; + Ok(Self { a, b, c }) + }) + } + + #[inline] + fn alloc_input<FN, T, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + value_gen: FN, + ) -> Result<Self, SynthesisError> + where + FN: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<Proof<PairingE>>, + { + value_gen().and_then(|proof| { + let Proof { a, b, c } = proof.borrow().clone(); + // We don't need to check here because the prime order check can be performed + // in plain. + let a = P::G1Gadget::alloc_input(cs.ns(|| "a"), || Ok(a.into_projective()))?; + let b = P::G2Gadget::alloc_input(cs.ns(|| "b"), || Ok(b.into_projective()))?; + let c = P::G1Gadget::alloc_input(cs.ns(|| "c"), || Ok(c.into_projective()))?; + Ok(Self { a, b, c }) + }) + } +} + +impl<PairingE, ConstraintF, P> ToBytesGadget<ConstraintF> + for VerifyingKeyGadget<PairingE, ConstraintF, P> +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget<PairingE, ConstraintF>, +{ + #[inline] + fn to_bytes<CS: ConstraintSystem<ConstraintF>>( + &self, + mut cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&self.h_g2.to_bytes(&mut cs.ns(|| "h_g2 to bytes"))?); + bytes.extend_from_slice( + &self + .g_alpha_g1 + .to_bytes(&mut cs.ns(|| "g_alpha_g1 to bytes"))?, + ); + bytes.extend_from_slice( + &self + .h_beta_g2 + .to_bytes(&mut cs.ns(|| "h_beta_g2 to bytes"))?, + ); + bytes.extend_from_slice( + &self + .g_gamma_g1 + .to_bytes(&mut cs.ns(|| "g_gamma_g1 to bytes"))?, + ); + bytes.extend_from_slice( + &self + .h_gamma_g2 + .to_bytes(&mut cs.ns(|| "h_gamma_g2 to bytes"))?, + ); + for (i, q) in self.query.iter().enumerate() { + let mut cs = cs.ns(|| format!("Iteration {}", i)); + bytes.extend_from_slice(&q.to_bytes(&mut cs.ns(|| "q"))?); + } + Ok(bytes) + } + + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.to_bytes(cs) + } +} + +#[cfg(test)] +mod test { + use gm17::*; + use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; + + use super::*; + use algebra::{ + curves::bls12_377::Bls12_377, + fields::bls12_377::Fr, + fields::bls12_377::Fq, + BitIterator, PrimeField, + }; + use rand::{thread_rng, Rng}; + use r1cs_std::{ + boolean::Boolean, pairing::bls12_377::PairingGadget as Bls12_377PairingGadget, + test_constraint_system::TestConstraintSystem, + }; + + type TestProofSystem = Gm17<Bls12_377, Bench<Fr>, Fr>; + type TestVerifierGadget = Gm17VerifierGadget<Bls12_377, Fq, Bls12_377PairingGadget>; + type TestProofGadget = ProofGadget<Bls12_377, Fq, Bls12_377PairingGadget>; + type TestVkGadget = VerifyingKeyGadget<Bls12_377, Fq, Bls12_377PairingGadget>; + + struct Bench<F: Field> { + inputs: Vec<Option<F>>, + num_constraints: usize, + } + + impl<F: Field> ConstraintSynthesizer<F> for Bench<F> { + fn generate_constraints<CS: ConstraintSystem<F>>(self, cs: &mut CS) -> Result<(), SynthesisError> { + assert!(self.inputs.len() >= 2); + assert!(self.num_constraints >= self.inputs.len()); + + let mut variables: Vec<_> = Vec::with_capacity(self.inputs.len()); + for (i, input) in self.inputs.into_iter().enumerate() { + let input_var = cs.alloc_input( + || format!("Input {}", i), + || input.ok_or(SynthesisError::AssignmentMissing), + )?; + variables.push((input, input_var)); + } + + for i in 0..self.num_constraints { + let new_entry = { + let (input_1_val, input_1_var) = variables[i]; + let (input_2_val, input_2_var) = variables[i + 1]; + let result_val = input_1_val + .and_then(|input_1| input_2_val.map(|input_2| input_1 * &input_2)); + let result_var = cs.alloc( + || format!("Result {}", i), + || result_val.ok_or(SynthesisError::AssignmentMissing), + )?; + cs.enforce( + || format!("Enforce constraint {}", i), + |lc| lc + input_1_var, + |lc| lc + input_2_var, + |lc| lc + result_var, + ); + (result_val, result_var) + }; + variables.push(new_entry); + } + Ok(()) + } + } + + #[test] + fn gm17_verifier_test() { + let num_inputs = 100; + let num_constraints = num_inputs; + let rng = &mut thread_rng(); + let mut inputs: Vec<Option<Fr>> = Vec::with_capacity(num_inputs); + for _ in 0..num_inputs { + inputs.push(Some(rng.gen())); + } + let params = { + let c = Bench::<Fr> { + inputs: vec![None; num_inputs], + num_constraints, + }; + + generate_random_parameters(c, rng).unwrap() + }; + + { + let proof = { + // Create an instance of our circuit (with the + // witness) + let c = Bench { + inputs: inputs.clone(), + num_constraints, + }; + // Create a gm17 proof with our parameters. + create_random_proof(c, &params, rng).unwrap() + }; + + // assert!(!verify_proof(&pvk, &proof, &[a]).unwrap()); + let mut cs = TestConstraintSystem::<Fq>::new(); + + let inputs: Vec<_> = inputs.into_iter().map(|input| input.unwrap()).collect(); + let mut input_gadgets = Vec::new(); + + { + let mut cs = cs.ns(|| "Allocate Input"); + for (i, input) in inputs.into_iter().enumerate() { + let mut input_bits = BitIterator::new(input.into_repr()).collect::<Vec<_>>(); + // Input must be in little-endian, but BitIterator outputs in big-endian. + input_bits.reverse(); + + let input_bits = + Vec::<Boolean>::alloc_input(cs.ns(|| format!("Input {}", i)), || { + Ok(input_bits) + }) + .unwrap(); + input_gadgets.push(input_bits); + } + } + + let vk_gadget = TestVkGadget::alloc_input(cs.ns(|| "Vk"), || Ok(&params.vk)).unwrap(); + let proof_gadget = + TestProofGadget::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap(); + println!("Time to verify!\n\n\n\n"); + <TestVerifierGadget as NIZKVerifierGadget<TestProofSystem, Fq>>::check_verify( + cs.ns(|| "Verify"), + &vk_gadget, + input_gadgets.iter(), + &proof_gadget, + ) + .unwrap(); + if !cs.is_satisfied() { + println!("========================================================="); + println!("Unsatisfied constraints:"); + println!("{:?}", cs.which_is_unsatisfied().unwrap()); + println!("========================================================="); + } + + // cs.print_named_objects(); + assert!(cs.is_satisfied()); + } + } +}
A crypto-primitives/src/nizk/gm17/mod.rs

@@ -0,0 +1,81 @@

+use algebra::PairingEngine; +use crate::Error; +use rand::Rng; +use gm17::{ + create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, + Parameters, PreparedVerifyingKey, Proof, VerifyingKey, +}; +use r1cs_core::ConstraintSynthesizer; + +use algebra::ToConstraintField; +use std::marker::PhantomData; + +use super::NIZK; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +/// Note: V should serialize its contents to `Vec<E::Fr>` in the same order as +/// during the constraint generation. +pub struct Gm17<E: PairingEngine, C: ConstraintSynthesizer<E::Fr>, V: ToConstraintField<E::Fr> + ?Sized> { + #[doc(hidden)] + _engine: PhantomData<E>, + #[doc(hidden)] + _circuit: PhantomData<C>, + #[doc(hidden)] + _verifier_input: PhantomData<V>, +} + +impl<E: PairingEngine, C: ConstraintSynthesizer<E::Fr>, V: ToConstraintField<E::Fr> + ?Sized> NIZK for Gm17<E, C, V> { + type Circuit = C; + type AssignedCircuit = C; + type ProvingParameters = Parameters<E>; + type VerificationParameters = VerifyingKey<E>; + type PreparedVerificationParameters = PreparedVerifyingKey<E>; + type VerifierInput = V; + type Proof = Proof<E>; + + fn setup<R: Rng>( + circuit: Self::Circuit, + rng: &mut R, + ) -> Result< + ( + Self::ProvingParameters, + Self::PreparedVerificationParameters, + ), + Error, + > { + let nizk_time = start_timer!(|| "{Groth-Maller 2017}::Setup"); + let pp = generate_random_parameters::<E, Self::Circuit, R>(circuit, rng)?; + let vk = prepare_verifying_key(&pp.vk); + end_timer!(nizk_time); + Ok((pp, vk)) + } + + fn prove<R: Rng>( + pp: &Self::ProvingParameters, + input_and_witness: Self::AssignedCircuit, + rng: &mut R, + ) -> Result<Self::Proof, Error> { + let proof_time = start_timer!(|| "{Groth-Maller 2017}::Prove"); + let result = create_random_proof::<E, _, _>(input_and_witness, pp, rng)?; + end_timer!(proof_time); + Ok(result) + } + + fn verify( + vk: &Self::PreparedVerificationParameters, + input: &Self::VerifierInput, + proof: &Self::Proof, + ) -> Result<bool, Error> { + let verify_time = start_timer!(|| "{Groth-Maller 2017}::Verify"); + let conversion_time = start_timer!(|| "Convert input to E::Fr"); + let input = input.to_field_elements()?; + end_timer!(conversion_time); + let verification = start_timer!(|| format!("Verify proof w/ input len: {}", input.len())); + let result = verify_proof(&vk, proof, &input)?; + end_timer!(verification); + end_timer!(verify_time); + Ok(result) + } +}
A crypto-primitives/src/nizk/mod.rs

@@ -0,0 +1,112 @@

+use algebra::bytes::ToBytes; +use rand::Rng; + +#[cfg(feature = "gm17")] +pub mod gm17; +#[cfg(feature = "gm17")] +pub use self::gm17::Gm17; + +#[cfg(feature = "r1cs")] +pub mod constraints; +#[cfg(feature = "r1cs")] +pub use constraints::*; + +use crate::Error; + +pub trait NIZK { + type Circuit; + type AssignedCircuit; + type VerifierInput: ?Sized; + type ProvingParameters: Clone; + type VerificationParameters: Clone + Default + From<Self::PreparedVerificationParameters>; + type PreparedVerificationParameters: Clone + Default + From<Self::VerificationParameters>; + type Proof: ToBytes + Clone + Default; + + fn setup<R: Rng>( + circuit: Self::Circuit, + rng: &mut R, + ) -> Result< + ( + Self::ProvingParameters, + Self::PreparedVerificationParameters, + ), + Error, + >; + + fn prove<R: Rng>( + parameter: &Self::ProvingParameters, + input_and_witness: Self::AssignedCircuit, + rng: &mut R, + ) -> Result<Self::Proof, Error>; + + fn verify( + verifier_key: &Self::PreparedVerificationParameters, + input: &Self::VerifierInput, + proof: &Self::Proof, + ) -> Result<bool, Error>; +} + +#[cfg(all(feature = "gm17", test))] +mod test { + use rand::thread_rng; + use std::ops::AddAssign; + + #[test] + fn test_gm17() { + use crate::nizk::{gm17::Gm17, NIZK}; + use algebra::{curves::bls12_381::Bls12_381, fields::bls12_381::Fr, Field}; + use r1cs_core::{ConstraintSynthesizer, ConstraintSystem, SynthesisError}; + + #[derive(Copy, Clone)] + struct R1CSCircuit { + x: Option<Fr>, + sum: Option<Fr>, + w: Option<Fr>, + } + + impl R1CSCircuit { + pub(super) fn new(x: Fr, sum: Fr, w: Fr) -> Self { + Self { + x: Some(x), + sum: Some(sum), + w: Some(w), + } + } + } + + impl ConstraintSynthesizer<Fr> for R1CSCircuit { + fn generate_constraints<CS: ConstraintSystem<Fr>>( + self, + cs: &mut CS, + ) -> Result<(), SynthesisError> { + let input = cs.alloc_input(|| "x", || Ok(self.x.unwrap()))?; + let sum = cs.alloc_input(|| "sum", || Ok(self.sum.unwrap()))?; + let witness = cs.alloc(|| "w", || Ok(self.w.unwrap()))?; + + cs.enforce( + || "check_one", + |lc| lc + sum, + |lc| lc + CS::one(), + |lc| lc + input + witness, + ); + Ok(()) + } + } + + let mut sum = Fr::one(); + sum.add_assign(&Fr::one()); + let circuit = R1CSCircuit::new(Fr::one(), sum, Fr::one()); + + let rng = &mut thread_rng(); + + let parameters = Gm17::<Bls12_381, R1CSCircuit, [Fr]>::setup(circuit, rng).unwrap(); + + let proof = + Gm17::<Bls12_381, R1CSCircuit, [Fr]>::prove(&parameters.0, circuit, rng).unwrap(); + + let result = + Gm17::<Bls12_381, R1CSCircuit, [Fr]>::verify(&parameters.1, &[Fr::one(), sum], &proof) + .unwrap(); + assert!(result); + } +}
A crypto-primitives/src/prf/blake2s/constraints.rs

@@ -0,0 +1,650 @@

+use algebra::PrimeField; +use r1cs_core::{ConstraintSystem, SynthesisError}; + +use crate::prf::PRFGadget; +use r1cs_std::prelude::*; + +use std::borrow::Borrow; + +// 2.1. Parameters +// The following table summarizes various parameters and their ranges: +// | BLAKE2b | BLAKE2s | +// --------------+------------------+------------------+ +// Bits in word | w = 64 | w = 32 | +// Rounds in F | r = 12 | r = 10 | +// Block bytes | bb = 128 | bb = 64 | +// Hash bytes | 1 <= nn <= 64 | 1 <= nn <= 32 | +// Key bytes | 0 <= kk <= 64 | 0 <= kk <= 32 | +// Input bytes | 0 <= ll < 2**128 | 0 <= ll < 2**64 | +// --------------+------------------+------------------+ +// G Rotation | (R1, R2, R3, R4) | (R1, R2, R3, R4) | +// constants = | (32, 24, 16, 63) | (16, 12, 8, 7) | +// --------------+------------------+------------------+ +// + +const R1: usize = 16; +const R2: usize = 12; +const R3: usize = 8; +const R4: usize = 7; + +// Round | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | +// ----------+-------------------------------------------------+ +// SIGMA[0] | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | +// SIGMA[1] | 14 10 4 8 9 15 13 6 1 12 0 2 11 7 5 3 | +// SIGMA[2] | 11 8 12 0 5 2 15 13 10 14 3 6 7 1 9 4 | +// SIGMA[3] | 7 9 3 1 13 12 11 14 2 6 5 10 4 0 15 8 | +// SIGMA[4] | 9 0 5 7 2 4 10 15 14 1 11 12 6 8 3 13 | +// SIGMA[5] | 2 12 6 10 0 11 8 3 4 13 7 5 15 14 1 9 | +// SIGMA[6] | 12 5 1 15 14 13 4 10 0 7 6 3 9 2 8 11 | +// SIGMA[7] | 13 11 7 14 12 1 3 9 5 0 15 4 8 6 2 10 | +// SIGMA[8] | 6 15 14 9 11 3 0 8 12 2 13 7 1 4 10 5 | +// SIGMA[9] | 10 2 8 4 7 6 1 5 15 11 9 14 3 12 13 0 | +// ----------+-------------------------------------------------+ +// + +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +// 3.1. Mixing Function G +// The G primitive function mixes two input words, "x" and "y", into +// four words indexed by "a", "b", "c", and "d" in the working vector +// v[0..15]. The full modified vector is returned. The rotation +// constants (R1, R2, R3, R4) are given in Section 2.1. +// FUNCTION G( v[0..15], a, b, c, d, x, y ) +// | +// | v[a] := (v[a] + v[b] + x) mod 2**w +// | v[d] := (v[d] ^ v[a]) >>> R1 +// | v[c] := (v[c] + v[d]) mod 2**w +// | v[b] := (v[b] ^ v[c]) >>> R2 +// | v[a] := (v[a] + v[b] + y) mod 2**w +// | v[d] := (v[d] ^ v[a]) >>> R3 +// | v[c] := (v[c] + v[d]) mod 2**w +// | v[b] := (v[b] ^ v[c]) >>> R4 +// | +// | RETURN v[0..15] +// | +// END FUNCTION. +// + +fn mixing_g<ConstraintF: PrimeField, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + v: &mut [UInt32], + a: usize, + b: usize, + c: usize, + d: usize, + x: &UInt32, + y: &UInt32, +) -> Result<(), SynthesisError> { + v[a] = UInt32::addmany( + cs.ns(|| "mixing step 1"), + &[v[a].clone(), v[b].clone(), x.clone()], + )?; + v[d] = v[d].xor(cs.ns(|| "mixing step 2"), &v[a])?.rotr(R1); + v[c] = UInt32::addmany(cs.ns(|| "mixing step 3"), &[v[c].clone(), v[d].clone()])?; + v[b] = v[b].xor(cs.ns(|| "mixing step 4"), &v[c])?.rotr(R2); + v[a] = UInt32::addmany( + cs.ns(|| "mixing step 5"), + &[v[a].clone(), v[b].clone(), y.clone()], + )?; + v[d] = v[d].xor(cs.ns(|| "mixing step 6"), &v[a])?.rotr(R3); + v[c] = UInt32::addmany(cs.ns(|| "mixing step 7"), &[v[c].clone(), v[d].clone()])?; + v[b] = v[b].xor(cs.ns(|| "mixing step 8"), &v[c])?.rotr(R4); + + Ok(()) +} + +// 3.2. Compression Function F +// Compression function F takes as an argument the state vector "h", +// message block vector "m" (last block is padded with zeros to full +// block size, if required), 2w-bit_gadget offset counter "t", and final block +// indicator flag "f". Local vector v[0..15] is used in processing. F +// returns a new state vector. The number of rounds, "r", is 12 for +// BLAKE2b and 10 for BLAKE2s. Rounds are numbered from 0 to r - 1. +// FUNCTION F( h[0..7], m[0..15], t, f ) +// | +// | // Initialize local work vector v[0..15] +// | v[0..7] := h[0..7] // First half from state. +// | v[8..15] := IV[0..7] // Second half from IV. +// | +// | v[12] := v[12] ^ (t mod 2**w) // Low word of the offset. +// | v[13] := v[13] ^ (t >> w) // High word. +// | +// | IF f = TRUE THEN // last block flag? +// | | v[14] := v[14] ^ 0xFF..FF // Invert all bits. +// | END IF. +// | +// | // Cryptographic mixing +// | FOR i = 0 TO r - 1 DO // Ten or twelve rounds. +// | | +// | | // Message word selection permutation for this round. +// | | s[0..15] := SIGMA[i mod 10][0..15] +// | | +// | | v := G( v, 0, 4, 8, 12, m[s[ 0]], m[s[ 1]] ) +// | | v := G( v, 1, 5, 9, 13, m[s[ 2]], m[s[ 3]] ) +// | | v := G( v, 2, 6, 10, 14, m[s[ 4]], m[s[ 5]] ) +// | | v := G( v, 3, 7, 11, 15, m[s[ 6]], m[s[ 7]] ) +// | | +// | | v := G( v, 0, 5, 10, 15, m[s[ 8]], m[s[ 9]] ) +// | | v := G( v, 1, 6, 11, 12, m[s[10]], m[s[11]] ) +// | | v := G( v, 2, 7, 8, 13, m[s[12]], m[s[13]] ) +// | | v := G( v, 3, 4, 9, 14, m[s[14]], m[s[15]] ) +// | | +// | END FOR +// | +// | FOR i = 0 TO 7 DO // XOR the two halves. +// | | h[i] := h[i] ^ v[i] ^ v[i + 8] +// | END FOR. +// | +// | RETURN h[0..7] // New state. +// | +// END FUNCTION. +// + +fn blake2s_compression<ConstraintF: PrimeField, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + h: &mut [UInt32], + m: &[UInt32], + t: u64, + f: bool, +) -> Result<(), SynthesisError> { + assert_eq!(h.len(), 8); + assert_eq!(m.len(), 16); + + // static const uint32_t blake2s_iv[8] = + // { + // 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, + // 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 + // }; + // + + let mut v = Vec::with_capacity(16); + v.extend_from_slice(h); + v.push(UInt32::constant(0x6A09E667)); + v.push(UInt32::constant(0xBB67AE85)); + v.push(UInt32::constant(0x3C6EF372)); + v.push(UInt32::constant(0xA54FF53A)); + v.push(UInt32::constant(0x510E527F)); + v.push(UInt32::constant(0x9B05688C)); + v.push(UInt32::constant(0x1F83D9AB)); + v.push(UInt32::constant(0x5BE0CD19)); + + assert_eq!(v.len(), 16); + + v[12] = v[12].xor(cs.ns(|| "first xor"), &UInt32::constant(t as u32))?; + v[13] = v[13].xor(cs.ns(|| "second xor"), &UInt32::constant((t >> 32) as u32))?; + + if f { + v[14] = v[14].xor(cs.ns(|| "third xor"), &UInt32::constant(u32::max_value()))?; + } + + for i in 0..10 { + let mut cs = cs.ns(|| format!("round {}", i)); + + let s = SIGMA[i % 10]; + + mixing_g( + cs.ns(|| "mixing invocation 1"), + &mut v, + 0, + 4, + 8, + 12, + &m[s[0]], + &m[s[1]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 2"), + &mut v, + 1, + 5, + 9, + 13, + &m[s[2]], + &m[s[3]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 3"), + &mut v, + 2, + 6, + 10, + 14, + &m[s[4]], + &m[s[5]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 4"), + &mut v, + 3, + 7, + 11, + 15, + &m[s[6]], + &m[s[7]], + )?; + + mixing_g( + cs.ns(|| "mixing invocation 5"), + &mut v, + 0, + 5, + 10, + 15, + &m[s[8]], + &m[s[9]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 6"), + &mut v, + 1, + 6, + 11, + 12, + &m[s[10]], + &m[s[11]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 7"), + &mut v, + 2, + 7, + 8, + 13, + &m[s[12]], + &m[s[13]], + )?; + mixing_g( + cs.ns(|| "mixing invocation 8"), + &mut v, + 3, + 4, + 9, + 14, + &m[s[14]], + &m[s[15]], + )?; + } + + for i in 0..8 { + let mut cs = cs.ns(|| format!("h[{i}] ^ v[{i}] ^ v[{i} + 8]", i = i)); + + h[i] = h[i].xor(cs.ns(|| "first xor"), &v[i])?; + h[i] = h[i].xor(cs.ns(|| "second xor"), &v[i + 8])?; + } + + Ok(()) +} + +// FUNCTION BLAKE2( d[0..dd-1], ll, kk, nn ) +// | +// | h[0..7] := IV[0..7] // Initialization Vector. +// | +// | // Parameter block p[0] +// | h[0] := h[0] ^ 0x01010000 ^ (kk << 8) ^ nn +// | +// | // Process padded key and data blocks +// | IF dd > 1 THEN +// | | FOR i = 0 TO dd - 2 DO +// | | | h := F( h, d[i], (i + 1) * bb, FALSE ) +// | | END FOR. +// | END IF. +// | +// | // Final block. +// | IF kk = 0 THEN +// | | h := F( h, d[dd - 1], ll, TRUE ) +// | ELSE +// | | h := F( h, d[dd - 1], ll + bb, TRUE ) +// | END IF. +// | +// | RETURN first "nn" bytes from little-endian word array h[]. +// | +// END FUNCTION. +// + +pub fn blake2s_gadget<ConstraintF: PrimeField, CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + input: &[Boolean], +) -> Result<Vec<UInt32>, SynthesisError> { + assert!(input.len() % 8 == 0); + + let mut h = Vec::with_capacity(8); + h.push(UInt32::constant(0x6A09E667 ^ 0x01010000 ^ 32)); + h.push(UInt32::constant(0xBB67AE85)); + h.push(UInt32::constant(0x3C6EF372)); + h.push(UInt32::constant(0xA54FF53A)); + h.push(UInt32::constant(0x510E527F)); + h.push(UInt32::constant(0x9B05688C)); + h.push(UInt32::constant(0x1F83D9AB)); + h.push(UInt32::constant(0x5BE0CD19)); + + let mut blocks: Vec<Vec<UInt32>> = vec![]; + + for block in input.chunks(512) { + let mut this_block = Vec::with_capacity(16); + for word in block.chunks(32) { + let mut tmp = word.to_vec(); + while tmp.len() < 32 { + tmp.push(Boolean::constant(false)); + } + this_block.push(UInt32::from_bits_le(&tmp)); + } + while this_block.len() < 16 { + this_block.push(UInt32::constant(0)); + } + blocks.push(this_block); + } + + if blocks.is_empty() { + blocks.push((0..16).map(|_| UInt32::constant(0)).collect()); + } + + for (i, block) in blocks[0..blocks.len() - 1].iter().enumerate() { + let cs = cs.ns(|| format!("block {}", i)); + + blake2s_compression(cs, &mut h, block, ((i as u64) + 1) * 64, false)?; + } + + { + let cs = cs.ns(|| "final block"); + + blake2s_compression( + cs, + &mut h, + &blocks[blocks.len() - 1], + (input.len() / 8) as u64, + true, + )?; + } + + Ok(h) +} + +use crate::prf::Blake2s; + +pub struct Blake2sGadget; +#[derive(Clone, Debug)] +pub struct Blake2sOutputGadget(pub Vec<UInt8>); + +impl PartialEq for Blake2sOutputGadget { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for Blake2sOutputGadget {} + +impl<ConstraintF: PrimeField> EqGadget<ConstraintF> for Blake2sOutputGadget {} + +impl<ConstraintF: PrimeField> ConditionalEqGadget<ConstraintF> for Blake2sOutputGadget { + #[inline] + fn conditional_enforce_equal<CS: ConstraintSystem<ConstraintF>>( + &self, + mut cs: CS, + other: &Self, + condition: &Boolean, + ) -> Result<(), SynthesisError> { + for (i, (a, b)) in self.0.iter().zip(other.0.iter()).enumerate() { + a.conditional_enforce_equal( + &mut cs.ns(|| format!("blake2s_equal_{}", i)), + b, + condition, + )?; + } + Ok(()) + } + + fn cost() -> usize { + 32 * <UInt8 as ConditionalEqGadget<ConstraintF>>::cost() + } +} + +impl<ConstraintF: PrimeField> ToBytesGadget<ConstraintF> for Blake2sOutputGadget { + #[inline] + fn to_bytes<CS: ConstraintSystem<ConstraintF>>(&self, _cs: CS) -> Result<Vec<UInt8>, SynthesisError> { + Ok(self.0.clone()) + } + + #[inline] + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.to_bytes(cs) + } +} + +impl<ConstraintF: PrimeField> AllocGadget<[u8; 32], ConstraintF> for Blake2sOutputGadget { + #[inline] + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, value_gen: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = <UInt8>::alloc_vec(cs, &value)?; + + Ok(Blake2sOutputGadget(bytes)) + } + + #[inline] + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>( + cs: CS, + value_gen: F, + ) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = <UInt8>::alloc_input_vec(cs, &value)?; + + Ok(Blake2sOutputGadget(bytes)) + } +} + +impl<ConstraintF: PrimeField> PRFGadget<Blake2s, ConstraintF> for Blake2sGadget { + type OutputGadget = Blake2sOutputGadget; + + fn new_seed<CS: ConstraintSystem<ConstraintF>>(mut cs: CS, seed: &[u8; 32]) -> Vec<UInt8> { + UInt8::alloc_vec(&mut cs.ns(|| "alloc_seed"), seed).unwrap() + } + + fn check_evaluation_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + seed: &[UInt8], + input: &[UInt8], + ) -> Result<Self::OutputGadget, SynthesisError> { + assert_eq!(seed.len(), 32); + // assert_eq!(input.len(), 32); + let mut gadget_input = Vec::with_capacity(512); + for byte in seed.into_iter().chain(input) { + gadget_input.extend_from_slice(&byte.into_bits_le()); + } + let mut result = Vec::new(); + for (i, int) in blake2s_gadget(cs.ns(|| "Blake2s Eval"), &gadget_input)? + .into_iter() + .enumerate() + { + let chunk = int.to_bytes(&mut cs.ns(|| format!("Result ToBytes {}", i)))?; + result.extend_from_slice(&chunk); + } + Ok(Blake2sOutputGadget(result)) + } +} + +#[cfg(test)] +mod test { + use algebra::fields::bls12_377::fr::Fr; + use digest::{FixedOutput, Input}; + use rand::{Rng, SeedableRng}; + use rand_xorshift::XorShiftRng; + + use crate::prf::blake2s::{Blake2s as B2SPRF, constraints::blake2s_gadget}; + use blake2::Blake2s; + use r1cs_core::ConstraintSystem; + + use super::Blake2sGadget; + use r1cs_std::{prelude::*, boolean::AllocatedBit, test_constraint_system::TestConstraintSystem}; + + #[test] + fn test_blake2s_constraints() { + let mut cs = TestConstraintSystem::<Fr>::new(); + let input_bits: Vec<_> = (0..512) + .map(|i| { + AllocatedBit::alloc(cs.ns(|| format!("input bit_gadget {}", i)), || Ok(true)) + .unwrap() + .into() + }) + .collect(); + blake2s_gadget(&mut cs, &input_bits).unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21792); + } + + #[test] + fn test_blake2s_prf() { + use crate::prf::{PRF, PRFGadget}; + use rand::Rng; + + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let mut cs = TestConstraintSystem::<Fr>::new(); + + let mut seed = [0u8; 32]; + rng.fill(&mut seed); + + let mut input = [0u8; 32]; + rng.fill(&mut input); + + let seed_gadget = Blake2sGadget::new_seed(&mut cs.ns(|| "declare_seed"), &seed); + let input_gadget = UInt8::alloc_vec(&mut cs.ns(|| "declare_input"), &input).unwrap(); + let out = B2SPRF::evaluate(&seed, &input).unwrap(); + let actual_out_gadget = <Blake2sGadget as PRFGadget<_, Fr>>::OutputGadget::alloc( + &mut cs.ns(|| "declare_output"), + || Ok(out), + ) + .unwrap(); + + let output_gadget = Blake2sGadget::check_evaluation_gadget( + &mut cs.ns(|| "eval_blake2s"), + &seed_gadget, + &input_gadget, + ) + .unwrap(); + output_gadget + .enforce_equal(&mut cs, &actual_out_gadget) + .unwrap(); + + if !cs.is_satisfied() { + println!( + "which is unsatisfied: {:?}", + cs.which_is_unsatisfied().unwrap() + ); + } + assert!(cs.is_satisfied()); + } + + #[test] + fn test_blake2s_precomp_constraints() { + // Test that 512 fixed leading bits (constants) + // doesn't result in more constraints. + + let mut cs = TestConstraintSystem::<Fr>::new(); + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let input_bits: Vec<_> = (0..512) + .map(|_| Boolean::constant(rng.gen())) + .chain((0..512).map(|i| { + AllocatedBit::alloc(cs.ns(|| format!("input bit_gadget {}", i)), || Ok(true)) + .unwrap() + .into() + })) + .collect(); + blake2s_gadget(&mut cs, &input_bits).unwrap(); + assert!(cs.is_satisfied()); + assert_eq!(cs.num_constraints(), 21792); + } + + #[test] + fn test_blake2s_constant_constraints() { + let mut cs = TestConstraintSystem::<Fr>::new(); + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + let input_bits: Vec<_> = (0..512).map(|_| Boolean::constant(rng.gen())).collect(); + blake2s_gadget(&mut cs, &input_bits).unwrap(); + assert_eq!(cs.num_constraints(), 0); + } + + #[test] + fn test_blake2s() { + let mut rng = XorShiftRng::seed_from_u64(1231275789u64); + + for input_len in (0..32).chain((32..256).filter(|a| a % 8 == 0)) { + let mut h = Blake2s::new_keyed(&[], 32); + + let data: Vec<u8> = (0..input_len).map(|_| rng.gen()).collect(); + + h.process(&data); + + let hash_result = h.fixed_result(); + + let mut cs = TestConstraintSystem::<Fr>::new(); + + let mut input_bits = vec![]; + + for (byte_i, input_byte) in data.into_iter().enumerate() { + for bit_i in 0..8 { + let cs = cs.ns(|| format!("input bit_gadget {} {}", byte_i, bit_i)); + + input_bits.push( + AllocatedBit::alloc(cs, || Ok((input_byte >> bit_i) & 1u8 == 1u8)) + .unwrap() + .into(), + ); + } + } + + let r = blake2s_gadget(&mut cs, &input_bits).unwrap(); + + assert!(cs.is_satisfied()); + + let mut s = hash_result + .as_ref() + .iter() + .flat_map(|&byte| (0..8).map(move |i| (byte >> i) & 1u8 == 1u8)); + + for chunk in r { + for b in chunk.to_bits_le() { + match b { + Boolean::Is(b) => { + assert!(s.next().unwrap() == b.get_value().unwrap()); + }, + Boolean::Not(b) => { + assert!(s.next().unwrap() != b.get_value().unwrap()); + }, + Boolean::Constant(b) => { + assert!(input_len == 0); + assert!(s.next().unwrap() == b); + }, + } + } + } + } + } +}
A crypto-primitives/src/prf/blake2s/mod.rs

@@ -0,0 +1,28 @@

+use blake2::Blake2s as b2s; +use digest::Digest; + +use super::PRF; +use crate::CryptoError; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +#[derive(Clone)] +pub struct Blake2s; + +impl PRF for Blake2s { + type Input = [u8; 32]; + type Output = [u8; 32]; + type Seed = [u8; 32]; + + fn evaluate(seed: &Self::Seed, input: &Self::Input) -> Result<Self::Output, CryptoError> { + let eval_time = start_timer!(|| "Blake2s::Eval"); + let mut h = b2s::new(); + h.input(seed.as_ref()); + h.input(input.as_ref()); + let mut result = [0u8; 32]; + result.copy_from_slice(&h.result()); + end_timer!(eval_time); + Ok(result) + } +}
A crypto-primitives/src/prf/constraints.rs

@@ -0,0 +1,19 @@

+use algebra::Field; +use std::fmt::Debug; + +use crate::prf::PRF; +use r1cs_core::{ConstraintSystem, SynthesisError}; + +use r1cs_std::prelude::*; + +pub trait PRFGadget<P: PRF, ConstraintF: Field> { + type OutputGadget: EqGadget<ConstraintF> + ToBytesGadget<ConstraintF> + AllocGadget<P::Output, ConstraintF> + Clone + Debug; + + fn new_seed<CS: ConstraintSystem<ConstraintF>>(cs: CS, output: &P::Seed) -> Vec<UInt8>; + + fn check_evaluation_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + seed: &[UInt8], + input: &[UInt8], + ) -> Result<Self::OutputGadget, SynthesisError>; +}
A crypto-primitives/src/prf/mod.rs

@@ -0,0 +1,20 @@

+use algebra::bytes::{FromBytes, ToBytes}; +use std::{fmt::Debug, hash::Hash}; + +use crate::CryptoError; + +#[cfg(feature = "r1cs")] +pub mod constraints; +#[cfg(feature = "r1cs")] +pub use constraints::*; + +pub mod blake2s; +pub use self::blake2s::*; + +pub trait PRF { + type Input: FromBytes + Default; + type Output: ToBytes + Eq + Clone + Default + Hash; + type Seed: FromBytes + ToBytes + Clone + Default + Debug; + + fn evaluate(seed: &Self::Seed, input: &Self::Input) -> Result<Self::Output, CryptoError>; +}
A crypto-primitives/src/signature/constraints.rs

@@ -0,0 +1,18 @@

+use algebra::Field; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use crate::signature::SignatureScheme; + +pub trait SigRandomizePkGadget<S: SignatureScheme, ConstraintF: Field> { + type ParametersGadget: AllocGadget<S::Parameters, ConstraintF> + Clone; + + type PublicKeyGadget: ToBytesGadget<ConstraintF> + EqGadget<ConstraintF> + AllocGadget<S::PublicKey, ConstraintF> + Clone; + + fn check_randomization_gadget<CS: ConstraintSystem<ConstraintF>>( + cs: CS, + parameters: &Self::ParametersGadget, + public_key: &Self::PublicKeyGadget, + randomness: &[UInt8], + ) -> Result<Self::PublicKeyGadget, SynthesisError>; +}
A crypto-primitives/src/signature/mod.rs

@@ -0,0 +1,106 @@

+use algebra::bytes::ToBytes; +use crate::Error; +use rand::Rng; +use std::hash::Hash; + +#[cfg(feature = "r1cs")] +pub mod constraints; +#[cfg(feature = "r1cs")] +pub use constraints::*; + + +pub mod schnorr; + +pub trait SignatureScheme { + type Parameters: Clone + Send + Sync; + type PublicKey: ToBytes + Hash + Eq + Clone + Default + Send + Sync; + type SecretKey: ToBytes + Clone + Default; + type Signature: Clone + Default + Send + Sync; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error>; + + fn keygen<R: Rng>( + pp: &Self::Parameters, + rng: &mut R, + ) -> Result<(Self::PublicKey, Self::SecretKey), Error>; + + fn sign<R: Rng>( + pp: &Self::Parameters, + sk: &Self::SecretKey, + message: &[u8], + rng: &mut R, + ) -> Result<Self::Signature, Error>; + + fn verify( + pp: &Self::Parameters, + pk: &Self::PublicKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result<bool, Error>; + + fn randomize_public_key( + pp: &Self::Parameters, + public_key: &Self::PublicKey, + randomness: &[u8], + ) -> Result<Self::PublicKey, Error>; + + fn randomize_signature( + pp: &Self::Parameters, + signature: &Self::Signature, + randomness: &[u8], + ) -> Result<Self::Signature, Error>; +} + +#[cfg(test)] +mod test { + use crate::{signature::schnorr::SchnorrSignature, SignatureScheme}; + use algebra::{ + curves::edwards_sw6::EdwardsAffine as Edwards, groups::Group, to_bytes, ToBytes, + }; + use blake2::Blake2s; + use rand::thread_rng; + use algebra::UniformRand; + + fn sign_and_verify<S: SignatureScheme>(message: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(&parameters, rng).unwrap(); + let sig = S::sign(&parameters, &sk, &message, rng).unwrap(); + assert!(S::verify(&parameters, &pk, &message, &sig).unwrap()); + } + + fn failed_verification<S: SignatureScheme>(message: &[u8], bad_message: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(&parameters, rng).unwrap(); + let sig = S::sign(&parameters, &sk, message, rng).unwrap(); + assert!(!S::verify(&parameters, &pk, bad_message, &sig).unwrap()); + } + + fn randomize_and_verify<S: SignatureScheme>(message: &[u8], randomness: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(&parameters, rng).unwrap(); + let sig = S::sign(&parameters, &sk, message, rng).unwrap(); + assert!(S::verify(&parameters, &pk, message, &sig).unwrap()); + let randomized_pk = S::randomize_public_key(&parameters, &pk, randomness).unwrap(); + let randomized_sig = S::randomize_signature(&parameters, &sig, randomness).unwrap(); + assert!(S::verify(&parameters, &randomized_pk, &message, &randomized_sig).unwrap()); + } + + #[test] + fn schnorr_signature_test() { + let message = "Hi, I am a Schnorr signature!"; + let rng = &mut thread_rng(); + sign_and_verify::<SchnorrSignature<Edwards, Blake2s>>(message.as_bytes()); + failed_verification::<SchnorrSignature<Edwards, Blake2s>>( + message.as_bytes(), + "Bad message".as_bytes(), + ); + let random_scalar = to_bytes!(<Edwards as Group>::ScalarField::rand(rng)).unwrap(); + randomize_and_verify::<SchnorrSignature<Edwards, Blake2s>>( + message.as_bytes(), + &random_scalar.as_slice(), + ); + } +}
A crypto-primitives/src/signature/schnorr/constraints.rs

@@ -0,0 +1,210 @@

+use algebra::{groups::Group, Field}; +use r1cs_core::{ConstraintSystem, SynthesisError}; +use r1cs_std::prelude::*; + +use crate::signature::SigRandomizePkGadget; + +use std::{borrow::Borrow, marker::PhantomData}; + +use crate::signature::schnorr::{ + SchnorrPublicKey, SchnorrSigParameters, SchnorrSignature, +}; +use digest::Digest; + +pub struct SchnorrSigGadgetParameters<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> { + generator: GG, + _group: PhantomData<*const G>, + _engine: PhantomData<*const ConstraintF>, +} + +impl<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> Clone + for SchnorrSigGadgetParameters<G, ConstraintF, GG> +{ + fn clone(&self) -> Self { + Self { + generator: self.generator.clone(), + _group: PhantomData, + _engine: PhantomData, + } + } +} + +#[derive(Derivative)] +#[derivative( + Debug(bound = "G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>"), + Clone(bound = "G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>"), + PartialEq(bound = "G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>"), + Eq(bound = "G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>") +)] +pub struct SchnorrSigGadgetPk<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> { + pub_key: GG, + #[doc(hidden)] + _group: PhantomData<*const G>, + #[doc(hidden)] + _engine: PhantomData<*const ConstraintF>, +} + +pub struct SchnorrRandomizePkGadget<G: Group, ConstraintF: Field, GG: GroupGadget<G, ConstraintF>> { + #[doc(hidden)] + _group: PhantomData<*const G>, + #[doc(hidden)] + _group_gadget: PhantomData<*const GG>, + #[doc(hidden)] + _engine: PhantomData<*const ConstraintF>, +} + +impl<G, GG, D, ConstraintF> SigRandomizePkGadget<SchnorrSignature<G, D>, ConstraintF> + for SchnorrRandomizePkGadget<G, ConstraintF, GG> +where + G: Group, + GG: GroupGadget<G, ConstraintF>, + D: Digest + Send + Sync, + ConstraintF: Field, +{ + type ParametersGadget = SchnorrSigGadgetParameters<G, ConstraintF, GG>; + type PublicKeyGadget = SchnorrSigGadgetPk<G, ConstraintF, GG>; + + fn check_randomization_gadget<CS: ConstraintSystem<ConstraintF>>( + mut cs: CS, + parameters: &Self::ParametersGadget, + public_key: &Self::PublicKeyGadget, + randomness: &[UInt8], + ) -> Result<Self::PublicKeyGadget, SynthesisError> { + let base = parameters.generator.clone(); + let randomness = randomness + .iter() + .flat_map(|b| b.into_bits_le()) + .collect::<Vec<_>>(); + let rand_pk = base.mul_bits( + &mut cs.ns(|| "Compute Randomizer"), + &public_key.pub_key, + randomness.iter(), + )?; + Ok(SchnorrSigGadgetPk { + pub_key: rand_pk, + _group: PhantomData, + _engine: PhantomData, + }) + } +} + +impl<G, ConstraintF, GG, D> AllocGadget<SchnorrSigParameters<G, D>, ConstraintF> + for SchnorrSigGadgetParameters<G, ConstraintF, GG> +where + G: Group, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, + D: Digest, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, f: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<SchnorrSigParameters<G, D>>, + { + let generator = GG::alloc_checked(cs, || f().map(|pp| pp.borrow().generator))?; + Ok(Self { + generator, + _engine: PhantomData, + _group: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, f: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<SchnorrSigParameters<G, D>>, + { + let generator = GG::alloc_input(cs, || f().map(|pp| pp.borrow().generator))?; + Ok(Self { + generator, + _engine: PhantomData, + _group: PhantomData, + }) + } +} + +impl<G, ConstraintF, GG> AllocGadget<SchnorrPublicKey<G>, ConstraintF> for SchnorrSigGadgetPk<G, ConstraintF, GG> +where + G: Group, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, +{ + fn alloc<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, f: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<SchnorrPublicKey<G>>, + { + let pub_key = GG::alloc_input(cs, || f().map(|pk| *pk.borrow()))?; + Ok(Self { + pub_key, + _engine: PhantomData, + _group: PhantomData, + }) + } + + fn alloc_input<F, T, CS: ConstraintSystem<ConstraintF>>(cs: CS, f: F) -> Result<Self, SynthesisError> + where + F: FnOnce() -> Result<T, SynthesisError>, + T: Borrow<SchnorrPublicKey<G>>, + { + let pub_key = GG::alloc_input(cs, || f().map(|pk| *pk.borrow()))?; + Ok(Self { + pub_key, + _engine: PhantomData, + _group: PhantomData, + }) + } +} + +impl<G, ConstraintF, GG> ConditionalEqGadget<ConstraintF> for SchnorrSigGadgetPk<G, ConstraintF, GG> +where + G: Group, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, +{ + #[inline] + fn conditional_enforce_equal<CS: ConstraintSystem<ConstraintF>>( + &self, + mut cs: CS, + other: &Self, + condition: &Boolean, + ) -> Result<(), SynthesisError> { + self.pub_key.conditional_enforce_equal( + &mut cs.ns(|| "PubKey equality"), + &other.pub_key, + condition, + )?; + Ok(()) + } + + fn cost() -> usize { + <GG as ConditionalEqGadget<ConstraintF>>::cost() + } +} + +impl<G, ConstraintF, GG> EqGadget<ConstraintF> for SchnorrSigGadgetPk<G, ConstraintF, GG> +where + G: Group, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, +{ +} + +impl<G, ConstraintF, GG> ToBytesGadget<ConstraintF> for SchnorrSigGadgetPk<G, ConstraintF, GG> +where + G: Group, + ConstraintF: Field, + GG: GroupGadget<G, ConstraintF>, +{ + fn to_bytes<CS: ConstraintSystem<ConstraintF>>(&self, mut cs: CS) -> Result<Vec<UInt8>, SynthesisError> { + self.pub_key.to_bytes(&mut cs.ns(|| "PubKey To Bytes")) + } + + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + mut cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.pub_key + .to_bytes_strict(&mut cs.ns(|| "PubKey To Bytes")) + } +}
A crypto-primitives/src/signature/schnorr/mod.rs

@@ -0,0 +1,223 @@

+use crate::SignatureScheme; +use algebra::{ + bytes::ToBytes, + fields::{Field, PrimeField}, + groups::Group, + to_bytes, +}; +use digest::Digest; +use crate::Error; +use algebra::UniformRand; +use rand::Rng; +use std::{ + hash::Hash, + io::{Result as IoResult, Write}, + marker::PhantomData, +}; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +pub struct SchnorrSignature<G: Group, D: Digest> { + _group: PhantomData<G>, + _hash: PhantomData<D>, +} + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group, H: Digest"))] +pub struct SchnorrSigParameters<G: Group, H: Digest> { + _hash: PhantomData<H>, + pub generator: G, + pub salt: [u8; 32], +} + +pub type SchnorrPublicKey<G> = G; + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group"), Default(bound = "G: Group"))] +pub struct SchnorrSecretKey<G: Group>(pub G::ScalarField); + +impl<G: Group> ToBytes for SchnorrSecretKey<G> { + #[inline] + fn write<W: Write>(&self, writer: W) -> IoResult<()> { + self.0.write(writer) + } +} + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group"), Default(bound = "G: Group"))] +pub struct SchnorrSig<G: Group> { + pub prover_response: G::ScalarField, + pub verifier_challenge: G::ScalarField, +} + +impl<G: Group + Hash, D: Digest + Send + Sync> SignatureScheme for SchnorrSignature<G, D> +where + G::ScalarField: PrimeField, +{ + type Parameters = SchnorrSigParameters<G, D>; + type PublicKey = G; + type SecretKey = SchnorrSecretKey<G>; + type Signature = SchnorrSig<G>; + + fn setup<R: Rng>(rng: &mut R) -> Result<Self::Parameters, Error> { + let setup_time = start_timer!(|| "SchnorrSig::Setup"); + + let mut salt = [0u8; 32]; + rng.fill_bytes(&mut salt); + let generator = G::rand(rng); + + end_timer!(setup_time); + Ok(SchnorrSigParameters { + _hash: PhantomData, + generator, + salt, + }) + } + + fn keygen<R: Rng>( + parameters: &Self::Parameters, + rng: &mut R, + ) -> Result<(Self::PublicKey, Self::SecretKey), Error> { + let keygen_time = start_timer!(|| "SchnorrSig::KeyGen"); + + let secret_key = G::ScalarField::rand(rng); + let public_key = parameters.generator.mul(&secret_key); + + end_timer!(keygen_time); + Ok((public_key, SchnorrSecretKey(secret_key))) + } + + fn sign<R: Rng>( + parameters: &Self::Parameters, + sk: &Self::SecretKey, + message: &[u8], + rng: &mut R, + ) -> Result<Self::Signature, Error> { + let sign_time = start_timer!(|| "SchnorrSig::Sign"); + // (k, e); + let (random_scalar, verifier_challenge) = loop { + // Sample a random scalar `k` from the prime scalar field. + let random_scalar: G::ScalarField = G::ScalarField::rand(rng); + // Commit to the random scalar via r := k · g. + // This is the prover's first msg in the Sigma protocol. + let prover_commitment: G = parameters.generator.mul(&random_scalar); + + // Hash everything to get verifier challenge. + let mut hash_input = Vec::new(); + hash_input.extend_from_slice(&parameters.salt); + hash_input.extend_from_slice(&to_bytes![prover_commitment]?); + hash_input.extend_from_slice(message); + + // Compute the supposed verifier response: e := H(salt || r || msg); + if let Some(verifier_challenge) = + G::ScalarField::from_random_bytes(&D::digest(&hash_input)) + { + break (random_scalar, verifier_challenge); + }; + }; + + // k - xe; + let prover_response = random_scalar - &(verifier_challenge * &sk.0); + let signature = SchnorrSig { + prover_response, + verifier_challenge, + }; + + end_timer!(sign_time); + Ok(signature) + } + + fn verify( + parameters: &Self::Parameters, + pk: &Self::PublicKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result<bool, Error> { + let verify_time = start_timer!(|| "SchnorrSig::Verify"); + + let SchnorrSig { + prover_response, + verifier_challenge, + } = signature; + let mut claimed_prover_commitment = parameters.generator.mul(prover_response); + let public_key_times_verifier_challenge = pk.mul(verifier_challenge); + claimed_prover_commitment += &public_key_times_verifier_challenge; + + let mut hash_input = Vec::new(); + hash_input.extend_from_slice(&parameters.salt); + hash_input.extend_from_slice(&to_bytes![claimed_prover_commitment]?); + hash_input.extend_from_slice(&message); + + let obtained_verifier_challenge = if let Some(obtained_verifier_challenge) = + G::ScalarField::from_random_bytes(&D::digest(&hash_input)) + { + obtained_verifier_challenge + } else { + return Ok(false); + }; + end_timer!(verify_time); + Ok(verifier_challenge == &obtained_verifier_challenge) + } + + fn randomize_public_key( + parameters: &Self::Parameters, + public_key: &Self::PublicKey, + randomness: &[u8], + ) -> Result<Self::PublicKey, Error> { + let rand_pk_time = start_timer!(|| "SchnorrSig::RandomizePubKey"); + + let mut randomized_pk = *public_key; + let mut base = parameters.generator; + let mut encoded = G::zero(); + for bit in bytes_to_bits(randomness) { + if bit { + encoded += &base; + } + base.double_in_place(); + } + randomized_pk += &encoded; + + end_timer!(rand_pk_time); + + Ok(randomized_pk) + } + + fn randomize_signature( + _parameter: &Self::Parameters, + signature: &Self::Signature, + randomness: &[u8], + ) -> Result<Self::Signature, Error> { + let rand_signature_time = start_timer!(|| "SchnorrSig::RandomizeSig"); + let SchnorrSig { + prover_response, + verifier_challenge, + } = signature; + let mut base = G::ScalarField::one(); + let mut multiplier = G::ScalarField::zero(); + for bit in bytes_to_bits(randomness) { + if bit { + multiplier += &base; + } + base.double_in_place(); + } + + let new_sig = SchnorrSig { + prover_response: *prover_response - &(*verifier_challenge * &multiplier), + verifier_challenge: *verifier_challenge, + }; + end_timer!(rand_signature_time); + Ok(new_sig) + } +} + +pub fn bytes_to_bits(bytes: &[u8]) -> Vec<bool> { + let mut bits = Vec::with_capacity(bytes.len() * 8); + for byte in bytes { + for i in 0..8 { + let bit = (*byte >> (8 - i - 1)) & 1; + bits.push(bit == 1); + } + } + bits +}
M r1cs-std/src/bits/mod.rs → r1cs-std/src/bits/mod.rs

@@ -81,4 +81,41 @@ cs: CS,

) -> Result<Vec<UInt8>, SynthesisError>; } +impl<ConstraintF: Field> ToBytesGadget<ConstraintF> for [UInt8] { + fn to_bytes<CS: ConstraintSystem<ConstraintF>>(&self, _cs: CS) -> Result<Vec<UInt8>, SynthesisError> { + Ok(self.to_vec()) + } + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.to_bytes(cs) + } +} + +impl<'a, ConstraintF: Field, T: 'a + ToBytesGadget<ConstraintF>> ToBytesGadget<ConstraintF> for &'a T { + fn to_bytes<CS: ConstraintSystem<ConstraintF>>(&self, cs: CS) -> Result<Vec<UInt8>, SynthesisError> { + (*self).to_bytes(cs) + } + + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.to_bytes(cs) + } +} + +impl<'a, ConstraintF: Field> ToBytesGadget<ConstraintF> for &'a [UInt8] { + fn to_bytes<CS: ConstraintSystem<ConstraintF>>(&self, _cs: CS) -> Result<Vec<UInt8>, SynthesisError> { + Ok(self.to_vec()) + } + + fn to_bytes_strict<CS: ConstraintSystem<ConstraintF>>( + &self, + cs: CS, + ) -> Result<Vec<UInt8>, SynthesisError> { + self.to_bytes(cs) + } +}