diff --git a/Cargo.toml b/Cargo.toml index bcefb1e..2e295c4 100644 --- a/Cargo.toml +++ b/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 diff --git a/crypto-primitives/Cargo.toml b/crypto-primitives/Cargo.toml new file mode 100644 index 0000000..b4d781b --- /dev/null +++ b/crypto-primitives/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "crypto-primitives" +version = "0.1.0" +authors = ["Pratyush Mishra "] +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" } + diff --git a/crypto-primitives/src/commitment/blake2s/constraints.rs b/crypto-primitives/src/commitment/blake2s/constraints.rs new file mode 100644 index 0000000..2ad7a05 --- /dev/null +++ b/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); + +pub struct Blake2sCommitmentGadget; + +impl CommitmentGadget for Blake2sCommitmentGadget { + type OutputGadget = Blake2sOutputGadget; + type ParametersGadget = Blake2sParametersGadget; + type RandomnessGadget = Blake2sRandomnessGadget; + + fn check_commitment_gadget>( + mut cs: CS, + _: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result { + 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 AllocGadget<(), ConstraintF> for Blake2sParametersGadget { + fn alloc>(_: CS, _: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow<()>, + { + Ok(Blake2sParametersGadget) + } + + fn alloc_input>(_: CS, _: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow<()>, + { + Ok(Blake2sParametersGadget) + } +} + +impl AllocGadget<[u8; 32], ConstraintF> for Blake2sRandomnessGadget { + #[inline] + fn alloc>(cs: CS, value_gen: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = ::alloc_vec(cs, &value)?; + + Ok(Blake2sRandomnessGadget(bytes)) + } + + #[inline] + fn alloc_input>( + cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = ::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::::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(¶meters, &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 = + >::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(¶meters), + ) + .unwrap(); + let gadget_result = + >::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()); + } +} diff --git a/crypto-primitives/src/commitment/blake2s/mod.rs b/crypto-primitives/src/commitment/blake2s/mod.rs new file mode 100644 index 0000000..eeec4b7 --- /dev/null +++ b/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(_: &mut R) -> Result { + Ok(()) + } + + fn commit( + _: &Self::Parameters, + input: &[u8], + randomness: &Self::Randomness, + ) -> Result { + 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) + } +} diff --git a/crypto-primitives/src/commitment/constraints.rs b/crypto-primitives/src/commitment/constraints.rs new file mode 100644 index 0000000..639cf49 --- /dev/null +++ b/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 { + type OutputGadget: EqGadget + + ToBytesGadget + + AllocGadget + + Clone + + Sized + + Debug; + type ParametersGadget: AllocGadget + Clone; + type RandomnessGadget: AllocGadget + Clone; + + fn check_commitment_gadget>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result; +} diff --git a/crypto-primitives/src/commitment/injective_map/constraints.rs b/crypto-primitives/src/commitment/injective_map/constraints.rs new file mode 100644 index 0000000..b9dff14 --- /dev/null +++ b/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, + ConstraintF: Field, + GG: GroupGadget, + IG: InjectiveMapGadget, +> { + _compressor: PhantomData, + _compressor_gadget: PhantomData, + _crh: PedersenCommitmentGadget, +} + +impl CommitmentGadget, ConstraintF> + for PedersenCommitmentCompressorGadget +where + G: Group, + I: InjectiveMap, + ConstraintF: PrimeField, + GG: GroupGadget, + IG: InjectiveMapGadget, + W: PedersenWindow, +{ + type OutputGadget = IG::OutputGadget; + type ParametersGadget = PedersenCommitmentGadgetParameters; + type RandomnessGadget = PedersenRandomnessGadget; + + fn check_commitment_gadget>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result { + let result = PedersenCommitmentGadget::::check_commitment_gadget( + cs.ns(|| "PedersenComm"), + parameters, + input, + r, + )?; + IG::evaluate_map(cs.ns(|| "InjectiveMap"), &result) + } +} diff --git a/crypto-primitives/src/commitment/injective_map/mod.rs b/crypto-primitives/src/commitment/injective_map/mod.rs new file mode 100644 index 0000000..f8f6a4d --- /dev/null +++ b/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, W: PedersenWindow> { + _group: PhantomData, + _compressor: PhantomData, + _comm: PedersenCommitment, +} + +impl, W: PedersenWindow> CommitmentScheme + for PedersenCommCompressor +{ + type Output = I::Output; + type Parameters = PedersenParameters; + type Randomness = PedersenRandomness; + + fn setup(rng: &mut R) -> Result { + let time = start_timer!(|| format!("PedersenCompressor::Setup")); + let params = PedersenCommitment::::setup(rng); + end_timer!(time); + params + } + + fn commit( + parameters: &Self::Parameters, + input: &[u8], + randomness: &Self::Randomness, + ) -> Result { + let eval_time = start_timer!(|| "PedersenCompressor::Eval"); + let result = I::injective_map(&PedersenCommitment::::commit( + parameters, input, randomness, + )?)?; + end_timer!(eval_time); + Ok(result) + } +} diff --git a/crypto-primitives/src/commitment/mod.rs b/crypto-primitives/src/commitment/mod.rs new file mode 100644 index 0000000..2e1ff19 --- /dev/null +++ b/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: &mut R) -> Result; + + fn commit( + parameters: &Self::Parameters, + input: &[u8], + r: &Self::Randomness, + ) -> Result; +} diff --git a/crypto-primitives/src/commitment/pedersen/constraints.rs b/crypto-primitives/src/commitment/pedersen/constraints.rs new file mode 100644 index 0000000..b15e9ea --- /dev/null +++ b/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 { + params: PedersenParameters, + #[doc(hidden)] + _group: PhantomData, + #[doc(hidden)] + _engine: PhantomData, + #[doc(hidden)] + _window: PhantomData, +} + +#[derive(Clone, Debug)] +pub struct PedersenRandomnessGadget(Vec); + +pub struct PedersenCommitmentGadget>( + #[doc(hidden)] + PhantomData<*const G>, + #[doc(hidden)] + PhantomData<*const GG>, + PhantomData, +); + +impl CommitmentGadget, ConstraintF> + for PedersenCommitmentGadget +where + ConstraintF: PrimeField, + G: Group, + GG: GroupGadget, + W: PedersenWindow, +{ + type OutputGadget = GG; + type ParametersGadget = PedersenCommitmentGadgetParameters; + type RandomnessGadget = PedersenRandomnessGadget; + + fn check_commitment_gadget>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + r: &Self::RandomnessGadget, + ) -> Result { + 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"), + ¶meters.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(¶meters.params.randomness_generator), + )?; + + Ok(result) + } +} + +impl AllocGadget, ConstraintF> for PedersenCommitmentGadgetParameters +where + G: Group, + W: PedersenWindow, + ConstraintF: PrimeField, +{ + fn alloc>(_cs: CS, value_gen: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let temp = value_gen()?; + let parameters = temp.borrow().clone(); + + Ok(PedersenCommitmentGadgetParameters { + params: parameters, + _group: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } + + fn alloc_input>( + _cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let temp = value_gen()?; + let parameters = temp.borrow().clone(); + + Ok(PedersenCommitmentGadgetParameters { + params: parameters, + _group: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } +} + +impl AllocGadget, ConstraintF> for PedersenRandomnessGadget +where + G: Group, + ConstraintF: PrimeField, +{ + fn alloc>(cs: CS, value_gen: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let temp = value_gen()?; + let randomness = to_bytes![temp.borrow().0].unwrap(); + Ok(PedersenRandomnessGadget(UInt8::alloc_vec(cs, &randomness)?)) + } + + fn alloc_input>( + cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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::::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; + type TestCOMMGadget = PedersenCommitmentGadget; + + let randomness = PedersenRandomness(Fr::rand(rng)); + + let parameters = PedersenCommitment::::setup(rng).unwrap(); + let primitive_result = + PedersenCommitment::::commit(¶meters, &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 = + >::RandomnessGadget::alloc( + &mut cs.ns(|| "gadget_randomness"), + || Ok(&randomness), + ) + .unwrap(); + let gadget_parameters = + >::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(¶meters), + ) + .unwrap(); + let gadget_result = + >::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()); + } +} diff --git a/crypto-primitives/src/commitment/pedersen/mod.rs b/crypto-primitives/src/commitment/pedersen/mod.rs new file mode 100644 index 0000000..b1100fd --- /dev/null +++ b/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 { + pub randomness_generator: Vec, + pub generators: Vec>, +} + +pub struct PedersenCommitment { + group: PhantomData, + window: PhantomData, +} + +#[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(pub G::ScalarField); + +impl UniformRand for PedersenRandomness { + #[inline] + fn rand(rng: &mut R) -> Self { + PedersenRandomness(UniformRand::rand(rng)) + } +} + +impl ToBytes for PedersenRandomness { + fn write(&self, writer: W) -> IoResult<()> { + self.0.write(writer) + } +} + +impl CommitmentScheme for PedersenCommitment { + type Parameters = PedersenParameters; + type Randomness = PedersenRandomness; + type Output = G; + + fn setup(rng: &mut R) -> Result { + 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 = ::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 { + 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::>(); + scalar_bits.reverse(); + for (bit, power) in scalar_bits + .into_iter() + .zip(¶meters.randomness_generator) + { + if bit { + result += power + } + } + end_timer!(randomize_time); + end_timer!(commit_time); + + Ok(result) + } +} diff --git a/crypto-primitives/src/crh/constraints.rs b/crypto-primitives/src/crh/constraints.rs new file mode 100644 index 0000000..84f62a8 --- /dev/null +++ b/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: Sized { + type OutputGadget: ConditionalEqGadget + + EqGadget + + ToBytesGadget + + CondSelectGadget + + AllocGadget + + Debug + + Clone + + Sized; + type ParametersGadget: AllocGadget + Clone; + + fn check_evaluation_gadget>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result; +} diff --git a/crypto-primitives/src/crh/injective_map/constraints.rs b/crypto-primitives/src/crh/injective_map/constraints.rs new file mode 100644 index 0000000..d90167c --- /dev/null +++ b/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, ConstraintF: Field, GG: GroupGadget> +{ + type OutputGadget: EqGadget + + ToBytesGadget + + CondSelectGadget + + AllocGadget + + Debug + + Clone + + Sized; + + fn evaluate_map>( + cs: CS, + ge: &GG, + ) -> Result; +} + +pub struct TECompressorGadget; + +impl InjectiveMapGadget, TECompressor, ConstraintF, TwistedEdwardsGadget>> + for TECompressorGadget +where + ConstraintF: PrimeField + SquareRootField, + P: TEModelParameters + ModelParameters, +{ + type OutputGadget = FpGadget; + + fn evaluate_map>( + _cs: CS, + ge: &TwistedEdwardsGadget>, + ) -> Result { + Ok(ge.x.clone()) + } +} + +impl + InjectiveMapGadget, TECompressor, ConstraintF, TwistedEdwardsGadget>> + for TECompressorGadget +where + ConstraintF: PrimeField + SquareRootField, + P: TEModelParameters + ModelParameters, +{ + type OutputGadget = FpGadget; + + fn evaluate_map>( + _cs: CS, + ge: &TwistedEdwardsGadget>, + ) -> Result { + Ok(ge.x.clone()) + } +} + +pub struct PedersenCRHCompressorGadget< + G: Group, + I: InjectiveMap, + ConstraintF: Field, + GG: GroupGadget, + IG: InjectiveMapGadget, +> { + _compressor: PhantomData, + _compressor_gadget: PhantomData, + _crh: PedersenCRHGadget, +} + +impl FixedLengthCRHGadget, ConstraintF> + for PedersenCRHCompressorGadget +where + G: Group, + I: InjectiveMap, + ConstraintF: Field, + GG: GroupGadget, + IG: InjectiveMapGadget, + W: PedersenWindow, +{ + type OutputGadget = IG::OutputGadget; + type ParametersGadget = PedersenCRHGadgetParameters; + + fn check_evaluation_gadget>( + mut cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result { + let result = PedersenCRHGadget::::check_evaluation_gadget( + cs.ns(|| "PedCRH"), + parameters, + input, + )?; + IG::evaluate_map(cs.ns(|| "InjectiveMap"), &result) + } +} diff --git a/crypto-primitives/src/crh/injective_map/mod.rs b/crypto-primitives/src/crh/injective_map/mod.rs new file mode 100644 index 0000000..e4d4fc1 --- /dev/null +++ b/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 { + type Output: ToBytes + Clone + Eq + Hash + Default + Debug; + fn injective_map(ge: &G) -> Result; +} + +pub struct TECompressor; + +impl InjectiveMap> for TECompressor { + type Output =

::BaseField; + + fn injective_map(ge: &TEAffine

) -> Result { + debug_assert!(ge.is_in_correct_subgroup_assuming_on_curve()); + Ok(ge.x) + } +} + +impl InjectiveMap> for TECompressor { + type Output =

::BaseField; + + fn injective_map(ge: &TEProjective

) -> Result { + let ge = ge.into_affine(); + debug_assert!(ge.is_in_correct_subgroup_assuming_on_curve()); + Ok(ge.x) + } +} + +pub struct PedersenCRHCompressor, W: PedersenWindow> { + _group: PhantomData, + _compressor: PhantomData, + _crh: PedersenCRH, +} + +impl, W: PedersenWindow> FixedLengthCRH + for PedersenCRHCompressor +{ + const INPUT_SIZE_BITS: usize = PedersenCRH::::INPUT_SIZE_BITS; + type Output = I::Output; + type Parameters = PedersenParameters; + + fn setup(rng: &mut R) -> Result { + let time = start_timer!(|| format!("PedersenCRHCompressor::Setup")); + let params = PedersenCRH::::setup(rng); + end_timer!(time); + params + } + + fn evaluate(parameters: &Self::Parameters, input: &[u8]) -> Result { + let eval_time = start_timer!(|| "PedersenCRHCompressor::Eval"); + let result = I::injective_map(&PedersenCRH::::evaluate(parameters, input)?)?; + end_timer!(eval_time); + Ok(result) + } +} diff --git a/crypto-primitives/src/crh/mod.rs b/crypto-primitives/src/crh/mod.rs new file mode 100644 index 0000000..9a1d84d --- /dev/null +++ b/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: &mut R) -> Result; + fn evaluate(parameters: &Self::Parameters, input: &[u8]) -> Result; +} diff --git a/crypto-primitives/src/crh/pedersen/constraints.rs b/crypto-primitives/src/crh/pedersen/constraints.rs new file mode 100644 index 0000000..2c781a6 --- /dev/null +++ b/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" +))] +pub struct PedersenCRHGadgetParameters< + G: Group, + W: PedersenWindow, + ConstraintF: Field, + GG: GroupGadget, +> { + params: PedersenParameters, + _group_g: PhantomData, + _engine: PhantomData, + _window: PhantomData, +} + +pub struct PedersenCRHGadget> { + #[doc(hideen)] + _group: PhantomData<*const G>, + #[doc(hideen)] + _group_gadget: PhantomData<*const GG>, + #[doc(hideen)] + _engine: PhantomData, +} + +impl FixedLengthCRHGadget, ConstraintF> for PedersenCRHGadget +where + ConstraintF: Field, + G: Group, + GG: GroupGadget, + W: PedersenWindow, +{ + type OutputGadget = GG; + type ParametersGadget = PedersenCRHGadgetParameters; + + fn check_evaluation_gadget>( + cs: CS, + parameters: &Self::ParametersGadget, + input: &[UInt8], + ) -> Result { + 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, ¶meters.params.generators, input_in_bits)?; + + Ok(result) + } +} + +impl> + AllocGadget, ConstraintF> for PedersenCRHGadgetParameters +{ + fn alloc>(_cs: CS, value_gen: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let params = value_gen()?.borrow().clone(); + Ok(PedersenCRHGadgetParameters { + params, + _group_g: PhantomData, + _engine: PhantomData, + _window: PhantomData, + }) + } + + fn alloc_input>( + _cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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; + type TestCRHGadget = PedersenCRHGadget; + + #[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, R: Rng>( + mut cs: CS, + rng: &mut R, + ) -> ([u8; 128], Vec) { + 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::::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(¶meters, &input).unwrap(); + + let gadget_parameters = + >::ParametersGadget::alloc( + &mut cs.ns(|| "gadget_parameters"), + || Ok(¶meters), + ) + .unwrap(); + println!( + "number of constraints for input + params: {}", + cs.num_constraints() + ); + + let gadget_result = + >::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()); + } +} diff --git a/crypto-primitives/src/crh/pedersen/mod.rs b/crypto-primitives/src/crh/pedersen/mod.rs new file mode 100644 index 0000000..85931be --- /dev/null +++ b/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 { + pub generators: Vec>, +} + +pub struct PedersenCRH { + group: PhantomData, + window: PhantomData, +} + +impl PedersenCRH { + pub fn create_generators(rng: &mut R) -> Vec> { + 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(num_powers: usize, rng: &mut R) -> Vec { + 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 FixedLengthCRH for PedersenCRH { + const INPUT_SIZE_BITS: usize = W::WINDOW_SIZE * W::NUM_WINDOWS; + type Output = G; + type Parameters = PedersenParameters; + + fn setup(rng: &mut R) -> Result { + 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 { + 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(¶meters.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 { + 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 Debug for PedersenParameters { + 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") + } +} diff --git a/crypto-primitives/src/lib.rs b/crypto-primitives/src/lib.rs new file mode 100644 index 0000000..9fd4f95 --- /dev/null +++ b/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; + +#[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 + } +} diff --git a/crypto-primitives/src/mht/constraints-old.rs b/crypto-primitives/src/mht/constraints-old.rs new file mode 100644 index 0000000..038c3e7 --- /dev/null +++ b/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, ConstraintF: Field> { + type ParametersGadget: AllocGadget; + type CommitmentGadget: AllocGadget; + type DigestGadget: AllocGadget; + type WitnessGadget: AllocGadget; + + fn check_witness_gadget>( + 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: CS, + parameters: &Self::ParametersGadget, + ledger_state_digest: &Self::DigestGadget, + commitment: &Self::CommitmentGadget, + witness: &Self::WitnessGadget, + should_enforce: &Boolean, + ) -> Result<(), SynthesisError>; +} + +pub struct IdealLedgerGadget { + #[doc(hidden)] + _comm_scheme: PhantomData, + #[doc(hidden)] + _hash: PhantomData, + #[doc(hidden)] + _comm_gadget: PhantomData, + #[doc(hidden)] + _hash_gadget: PhantomData, +} + +pub struct CommitmentWitness< + H: FixedLengthCRH, + C: CommitmentScheme, + HGadget: FixedLengthCRHGadget, + ConstraintF: Field, +> { + path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, + _crh: PhantomData, + _comm: PhantomData, + _engine: PhantomData, +} + +pub struct DigestGadget, ConstraintF: Field> { + digest: HGadget::OutputGadget, + #[doc(hidden)] + _crh: PhantomData, + #[doc(hidden)] + _engine: PhantomData, +} + +impl LCWGadget, CommPath, ConstraintF> + for IdealLedgerGadget +where + C: CommitmentScheme, + C::Output: Eq, + ConstraintF: Field, + H: FixedLengthCRH, + CGadget: CommitmentGadget, + HGadget: FixedLengthCRHGadget, +{ + type ParametersGadget = >::ParametersGadget; + type DigestGadget = DigestGadget; + + type CommitmentGadget = >::OutputGadget; + type WitnessGadget = CommitmentWitness; + + /// 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>( + 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::::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(); + ::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(); + + ::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::( + &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( + mut cs: CS, + parameters: &HG::ParametersGadget, + left_child: &HG::OutputGadget, + right_child: &HG::OutputGadget, +) -> Result +where + ConstraintF: Field, + CS: ConstraintSystem, + H: FixedLengthCRH, + HG: FixedLengthCRHGadget, +{ + 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 AllocGadget, ConstraintF> for DigestGadget +where + H: FixedLengthCRH, + HGadget: FixedLengthCRHGadget, + ConstraintF: Field, +{ + fn alloc>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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 AllocGadget, ConstraintF> + for CommitmentWitness +where + H: FixedLengthCRH, + C: CommitmentScheme, + HGadget: FixedLengthCRHGadget, + ConstraintF: Field, +{ + fn alloc>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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; + type HG = PedersenCRHGadget; + type C = PedersenCommitment; + type CG = PedersenCommitmentGadget; + type JubJubMHT = MerkleHashTree::Output>; + type LG = IdealLedgerGadget; + type DG = DigestGadget; + type LCWG = CommitmentWitness; + + fn generate_merkle_tree(leaves: &[::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::::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 = + >::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 = >::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: &[::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::::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 = + >::ParametersGadget::alloc( + &mut cs.ns(|| format!("new_parameters_{}", i)), + || Ok(crh_parameters.clone()), + ) + .unwrap(); + let comm = >::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); + } +} diff --git a/crypto-primitives/src/mht/constraints.rs b/crypto-primitives/src/mht/constraints.rs new file mode 100644 index 0000000..a23aa62 --- /dev/null +++ b/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 +where + P: MHTParameters, + HGadget: FixedLengthCRHGadget, + ConstraintF: Field, +{ + path: Vec<(HGadget::OutputGadget, HGadget::OutputGadget)>, +} + +pub struct MerklePathVerifierGadget +where + P: MHTParameters, + ConstraintF: Field, + CRHGadget: FixedLengthCRHGadget, +{ + _p: PhantomData, + _p2: PhantomData

, + _f: PhantomData, +} + +impl MerklePathVerifierGadget +where + P: MHTParameters, + ConstraintF: Field, + CRHGadget: FixedLengthCRHGadget, +{ + + pub fn check_membership>( + cs: CS, + parameters: &CRHGadget::ParametersGadget, + root: &CRHGadget::OutputGadget, + leaf: impl ToBytesGadget, + witness: &MerklePath, + ) -> Result<(), SynthesisError> { + Self::conditionally_check_membership( + cs, + parameters, + root, + leaf, + witness, + &Boolean::Constant(true), + ) + } + + pub fn conditionally_check_membership>( + mut cs: CS, + parameters: &CRHGadget::ParametersGadget, + root: &CRHGadget::OutputGadget, + leaf: impl ToBytesGadget, + witness: &MerklePath, + 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::( + &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( + mut cs: CS, + parameters: &HG::ParametersGadget, + left_child: &HG::OutputGadget, + right_child: &HG::OutputGadget, +) -> Result +where + ConstraintF: Field, + CS: ConstraintSystem, + H: FixedLengthCRH, + HG: FixedLengthCRHGadget, +{ + 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 AllocGadget, ConstraintF> + for MerklePath +where + P: MHTParameters, + HGadget: FixedLengthCRHGadget, + ConstraintF: Field, +{ + fn alloc>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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>( + mut cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + 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; + type HG = PedersenCRHGadget; + + struct JubJubMHTParams; + + impl MHTParameters for JubJubMHTParams { + const HEIGHT: usize = 32; + type H = H; + } + + type JubJubMHT = MerkleHashTree; + + 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::::new(); + let proof = tree.generate_proof(i, &leaf).unwrap(); + assert!(proof.verify(&crh_parameters, &root, &leaf).unwrap()); + + // Allocate Merkle Tree Root + let root = >::OutputGadget::alloc(&mut cs.ns(|| format!("new_digest_{}", i)), || { + if use_bad_root { + Ok(::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 = + >::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); + } +} diff --git a/crypto-primitives/src/mht/mod.rs b/crypto-primitives/src/mht/mod.rs new file mode 100644 index 0000000..f462cf3 --- /dev/null +++ b/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, ::Output: fmt::Debug") +)] +pub struct HashMembershipProof { + pub(crate) path: Vec<(::Output, ::Output)>, +} + +impl Default for HashMembershipProof

{ + fn default() -> Self { + let mut path = Vec::with_capacity(P::HEIGHT as usize); + for _i in 1..P::HEIGHT as usize { + path.push((::Output::default(), ::Output::default())); + } + Self { + path, + } + } +} + +impl HashMembershipProof

{ + pub fn verify( + &self, + parameters: &::Parameters, + root_hash: &::Output, + leaf: &L, + ) -> Result { + 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::(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::(parameters, hash, sibling_hash, &mut buffer)?; + } + + if root_hash != &prev { + return Ok(false); + } + Ok(true) + } else { + Ok(false) + } + } +} + +pub struct MerkleHashTree { + tree: Vec<::Output>, + padding_tree: Vec<(::Output, ::Output)>, + parameters: Rc<::Parameters>, + root: Option<::Output>, +} + +impl MerkleHashTree

{ + pub const HEIGHT: u8 = P::HEIGHT as u8; + + pub fn blank(parameters: Rc<::Parameters>) -> Self { + MerkleHashTree { + tree: Vec::new(), + padding_tree: Vec::new(), + root: None, + parameters, + } + } + + pub fn new(parameters: Rc<::Parameters>, leaves: &[L]) -> Result { + 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::(¶meters)?; + 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::(¶meters, 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::( + ¶meters, + &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::(¶meters, &cur_hash, &empty_hash, &mut buffer)?; + padding_tree.push((cur_hash.clone(), empty_hash.clone())); + cur_height += 1; + } + + let root_hash = hash_inner_node::(¶meters, &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) -> ::Output { + self.root.clone().unwrap() + } + + pub fn generate_proof( + &self, + index: usize, + leaf: &L, + ) -> Result, Error> { + let prove_time = start_timer!(|| "MHT::GenProof"); + let mut path = Vec::new(); + + let mut buffer = [0u8; 128]; + let leaf_hash = hash_leaf::(&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::(&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 { + 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 { + 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( + parameters: &H::Parameters, + left: &H::Output, + right: &H::Output, + buffer: &mut [u8], +) -> Result { + 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( + parameters: &H::Parameters, + leaf: &L, + buffer: &mut [u8], +) -> Result { + 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( + parameters: &H::Parameters, +) -> Result { + 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; + + struct JubJubMHTParams; + + impl MHTParameters for JubJubMHTParams { + const HEIGHT: usize = 32; + type H = H; + } + type JubJubMHT = MerkleHashTree; + + + fn generate_merkle_tree(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(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); + } +} diff --git a/crypto-primitives/src/nizk/constraints.rs b/crypto-primitives/src/nizk/constraints.rs new file mode 100644 index 0000000..92d6925 --- /dev/null +++ b/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 { + type VerificationKeyGadget: AllocGadget + ToBytesGadget; + + type ProofGadget: AllocGadget; + + fn check_verify<'a, CS, I, T>( + cs: CS, + verification_key: &Self::VerificationKeyGadget, + input: I, + proof: &Self::ProofGadget, + ) -> Result<(), SynthesisError> + where + CS: ConstraintSystem, + I: Iterator, + T: 'a + ToBitsGadget + ?Sized; +} diff --git a/crypto-primitives/src/nizk/gm17/constraints.rs b/crypto-primitives/src/nizk/gm17/constraints.rs new file mode 100644 index 0000000..3ad2002 --- /dev/null +++ b/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, +> { + 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, +> { + 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, +} + +impl< + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, + > VerifyingKeyGadget +{ + pub fn prepare>( + &self, + mut cs: CS, + ) -> Result, 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, +> { + 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, +} + +pub struct Gm17VerifierGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + _pairing_engine: PhantomData, + _engine: PhantomData, + _pairing_gadget: PhantomData

, +} + +impl NIZKVerifierGadget, ConstraintF> + for Gm17VerifierGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + C: ConstraintSynthesizer, + V: ToConstraintField, + P: PairingGadget, +{ + type VerificationKeyGadget = VerifyingKeyGadget; + type ProofGadget = ProofGadget; + + 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, + I: Iterator, + T: 'a + ToBitsGadget + ?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 AllocGadget, ConstraintF> + for VerifyingKeyGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn alloc>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + 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::>() + .into_iter() + .collect::>()?; + Ok(Self { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + }) + }) + } + + #[inline] + fn alloc_input>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + 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::>() + .into_iter() + .collect::>()?; + Ok(Self { + h_g2, + g_alpha_g1, + h_beta_g2, + g_gamma_g1, + h_gamma_g2, + query, + }) + }) + } +} + +impl AllocGadget, ConstraintF> + for ProofGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn alloc>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + 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>( + mut cs: CS, + value_gen: FN, + ) -> Result + where + FN: FnOnce() -> Result, + T: Borrow>, + { + 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 ToBytesGadget + for VerifyingKeyGadget +where + PairingE: PairingEngine, + ConstraintF: Field, + P: PairingGadget, +{ + #[inline] + fn to_bytes>( + &self, + mut cs: CS, + ) -> Result, 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>( + &self, + cs: CS, + ) -> Result, 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, Fr>; + type TestVerifierGadget = Gm17VerifierGadget; + type TestProofGadget = ProofGadget; + type TestVkGadget = VerifyingKeyGadget; + + struct Bench { + inputs: Vec>, + num_constraints: usize, + } + + impl ConstraintSynthesizer for Bench { + fn generate_constraints>(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> = Vec::with_capacity(num_inputs); + for _ in 0..num_inputs { + inputs.push(Some(rng.gen())); + } + let params = { + let c = Bench:: { + 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, ¶ms, rng).unwrap() + }; + + // assert!(!verify_proof(&pvk, &proof, &[a]).unwrap()); + let mut cs = TestConstraintSystem::::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::>(); + // Input must be in little-endian, but BitIterator outputs in big-endian. + input_bits.reverse(); + + let input_bits = + Vec::::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(¶ms.vk)).unwrap(); + let proof_gadget = + TestProofGadget::alloc(cs.ns(|| "Proof"), || Ok(proof.clone())).unwrap(); + println!("Time to verify!\n\n\n\n"); + >::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()); + } + } +} diff --git a/crypto-primitives/src/nizk/gm17/mod.rs b/crypto-primitives/src/nizk/gm17/mod.rs new file mode 100644 index 0000000..32126b1 --- /dev/null +++ b/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` in the same order as +/// during the constraint generation. +pub struct Gm17, V: ToConstraintField + ?Sized> { + #[doc(hidden)] + _engine: PhantomData, + #[doc(hidden)] + _circuit: PhantomData, + #[doc(hidden)] + _verifier_input: PhantomData, +} + +impl, V: ToConstraintField + ?Sized> NIZK for Gm17 { + type Circuit = C; + type AssignedCircuit = C; + type ProvingParameters = Parameters; + type VerificationParameters = VerifyingKey; + type PreparedVerificationParameters = PreparedVerifyingKey; + type VerifierInput = V; + type Proof = Proof; + + fn setup( + 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::(circuit, rng)?; + let vk = prepare_verifying_key(&pp.vk); + end_timer!(nizk_time); + Ok((pp, vk)) + } + + fn prove( + pp: &Self::ProvingParameters, + input_and_witness: Self::AssignedCircuit, + rng: &mut R, + ) -> Result { + let proof_time = start_timer!(|| "{Groth-Maller 2017}::Prove"); + let result = create_random_proof::(input_and_witness, pp, rng)?; + end_timer!(proof_time); + Ok(result) + } + + fn verify( + vk: &Self::PreparedVerificationParameters, + input: &Self::VerifierInput, + proof: &Self::Proof, + ) -> Result { + 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) + } +} diff --git a/crypto-primitives/src/nizk/mod.rs b/crypto-primitives/src/nizk/mod.rs new file mode 100644 index 0000000..c5645e1 --- /dev/null +++ b/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; + type PreparedVerificationParameters: Clone + Default + From; + type Proof: ToBytes + Clone + Default; + + fn setup( + circuit: Self::Circuit, + rng: &mut R, + ) -> Result< + ( + Self::ProvingParameters, + Self::PreparedVerificationParameters, + ), + Error, + >; + + fn prove( + parameter: &Self::ProvingParameters, + input_and_witness: Self::AssignedCircuit, + rng: &mut R, + ) -> Result; + + fn verify( + verifier_key: &Self::PreparedVerificationParameters, + input: &Self::VerifierInput, + proof: &Self::Proof, + ) -> Result; +} + +#[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, + sum: Option, + w: Option, + } + + 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 for R1CSCircuit { + fn generate_constraints>( + 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::::setup(circuit, rng).unwrap(); + + let proof = + Gm17::::prove(¶meters.0, circuit, rng).unwrap(); + + let result = + Gm17::::verify(¶meters.1, &[Fr::one(), sum], &proof) + .unwrap(); + assert!(result); + } +} diff --git a/crypto-primitives/src/prf/blake2s/constraints.rs b/crypto-primitives/src/prf/blake2s/constraints.rs new file mode 100644 index 0000000..e33c686 --- /dev/null +++ b/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>( + 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>( + 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>( + mut cs: CS, + input: &[Boolean], +) -> Result, 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![]; + + 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); + +impl PartialEq for Blake2sOutputGadget { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for Blake2sOutputGadget {} + +impl EqGadget for Blake2sOutputGadget {} + +impl ConditionalEqGadget for Blake2sOutputGadget { + #[inline] + fn conditional_enforce_equal>( + &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 * >::cost() + } +} + +impl ToBytesGadget for Blake2sOutputGadget { + #[inline] + fn to_bytes>(&self, _cs: CS) -> Result, SynthesisError> { + Ok(self.0.clone()) + } + + #[inline] + fn to_bytes_strict>( + &self, + cs: CS, + ) -> Result, SynthesisError> { + self.to_bytes(cs) + } +} + +impl AllocGadget<[u8; 32], ConstraintF> for Blake2sOutputGadget { + #[inline] + fn alloc>(cs: CS, value_gen: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = ::alloc_vec(cs, &value)?; + + Ok(Blake2sOutputGadget(bytes)) + } + + #[inline] + fn alloc_input>( + cs: CS, + value_gen: F, + ) -> Result + where + F: FnOnce() -> Result, + T: Borrow<[u8; 32]>, + { + let zeros = [0u8; 32]; + let value = match value_gen() { + Ok(val) => *(val.borrow()), + Err(_) => zeros, + }; + let bytes = ::alloc_input_vec(cs, &value)?; + + Ok(Blake2sOutputGadget(bytes)) + } +} + +impl PRFGadget for Blake2sGadget { + type OutputGadget = Blake2sOutputGadget; + + fn new_seed>(mut cs: CS, seed: &[u8; 32]) -> Vec { + UInt8::alloc_vec(&mut cs.ns(|| "alloc_seed"), seed).unwrap() + } + + fn check_evaluation_gadget>( + mut cs: CS, + seed: &[UInt8], + input: &[UInt8], + ) -> Result { + 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::::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::::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 = >::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::::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::::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 = (0..input_len).map(|_| rng.gen()).collect(); + + h.process(&data); + + let hash_result = h.fixed_result(); + + let mut cs = TestConstraintSystem::::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); + }, + } + } + } + } + } +} diff --git a/crypto-primitives/src/prf/blake2s/mod.rs b/crypto-primitives/src/prf/blake2s/mod.rs new file mode 100644 index 0000000..23d1d67 --- /dev/null +++ b/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 { + 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) + } +} diff --git a/crypto-primitives/src/prf/constraints.rs b/crypto-primitives/src/prf/constraints.rs new file mode 100644 index 0000000..6f8bdef --- /dev/null +++ b/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 { + type OutputGadget: EqGadget + ToBytesGadget + AllocGadget + Clone + Debug; + + fn new_seed>(cs: CS, output: &P::Seed) -> Vec; + + fn check_evaluation_gadget>( + cs: CS, + seed: &[UInt8], + input: &[UInt8], + ) -> Result; +} diff --git a/crypto-primitives/src/prf/mod.rs b/crypto-primitives/src/prf/mod.rs new file mode 100644 index 0000000..0729bea --- /dev/null +++ b/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; +} diff --git a/crypto-primitives/src/signature/constraints.rs b/crypto-primitives/src/signature/constraints.rs new file mode 100644 index 0000000..e98a713 --- /dev/null +++ b/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 { + type ParametersGadget: AllocGadget + Clone; + + type PublicKeyGadget: ToBytesGadget + EqGadget + AllocGadget + Clone; + + fn check_randomization_gadget>( + cs: CS, + parameters: &Self::ParametersGadget, + public_key: &Self::PublicKeyGadget, + randomness: &[UInt8], + ) -> Result; +} diff --git a/crypto-primitives/src/signature/mod.rs b/crypto-primitives/src/signature/mod.rs new file mode 100644 index 0000000..dae68f2 --- /dev/null +++ b/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(rng: &mut R) -> Result; + + fn keygen( + pp: &Self::Parameters, + rng: &mut R, + ) -> Result<(Self::PublicKey, Self::SecretKey), Error>; + + fn sign( + pp: &Self::Parameters, + sk: &Self::SecretKey, + message: &[u8], + rng: &mut R, + ) -> Result; + + fn verify( + pp: &Self::Parameters, + pk: &Self::PublicKey, + message: &[u8], + signature: &Self::Signature, + ) -> Result; + + fn randomize_public_key( + pp: &Self::Parameters, + public_key: &Self::PublicKey, + randomness: &[u8], + ) -> Result; + + fn randomize_signature( + pp: &Self::Parameters, + signature: &Self::Signature, + randomness: &[u8], + ) -> Result; +} + +#[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(message: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(¶meters, rng).unwrap(); + let sig = S::sign(¶meters, &sk, &message, rng).unwrap(); + assert!(S::verify(¶meters, &pk, &message, &sig).unwrap()); + } + + fn failed_verification(message: &[u8], bad_message: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(¶meters, rng).unwrap(); + let sig = S::sign(¶meters, &sk, message, rng).unwrap(); + assert!(!S::verify(¶meters, &pk, bad_message, &sig).unwrap()); + } + + fn randomize_and_verify(message: &[u8], randomness: &[u8]) { + let rng = &mut thread_rng(); + let parameters = S::setup::<_>(rng).unwrap(); + let (pk, sk) = S::keygen(¶meters, rng).unwrap(); + let sig = S::sign(¶meters, &sk, message, rng).unwrap(); + assert!(S::verify(¶meters, &pk, message, &sig).unwrap()); + let randomized_pk = S::randomize_public_key(¶meters, &pk, randomness).unwrap(); + let randomized_sig = S::randomize_signature(¶meters, &sig, randomness).unwrap(); + assert!(S::verify(¶meters, &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::>(message.as_bytes()); + failed_verification::>( + message.as_bytes(), + "Bad message".as_bytes(), + ); + let random_scalar = to_bytes!(::ScalarField::rand(rng)).unwrap(); + randomize_and_verify::>( + message.as_bytes(), + &random_scalar.as_slice(), + ); + } +} diff --git a/crypto-primitives/src/signature/schnorr/constraints.rs b/crypto-primitives/src/signature/schnorr/constraints.rs new file mode 100644 index 0000000..f9fcbd0 --- /dev/null +++ b/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> { + generator: GG, + _group: PhantomData<*const G>, + _engine: PhantomData<*const ConstraintF>, +} + +impl> Clone + for SchnorrSigGadgetParameters +{ + fn clone(&self) -> Self { + Self { + generator: self.generator.clone(), + _group: PhantomData, + _engine: PhantomData, + } + } +} + +#[derive(Derivative)] +#[derivative( + Debug(bound = "G: Group, ConstraintF: Field, GG: GroupGadget"), + Clone(bound = "G: Group, ConstraintF: Field, GG: GroupGadget"), + PartialEq(bound = "G: Group, ConstraintF: Field, GG: GroupGadget"), + Eq(bound = "G: Group, ConstraintF: Field, GG: GroupGadget") +)] +pub struct SchnorrSigGadgetPk> { + pub_key: GG, + #[doc(hidden)] + _group: PhantomData<*const G>, + #[doc(hidden)] + _engine: PhantomData<*const ConstraintF>, +} + +pub struct SchnorrRandomizePkGadget> { + #[doc(hidden)] + _group: PhantomData<*const G>, + #[doc(hidden)] + _group_gadget: PhantomData<*const GG>, + #[doc(hidden)] + _engine: PhantomData<*const ConstraintF>, +} + +impl SigRandomizePkGadget, ConstraintF> + for SchnorrRandomizePkGadget +where + G: Group, + GG: GroupGadget, + D: Digest + Send + Sync, + ConstraintF: Field, +{ + type ParametersGadget = SchnorrSigGadgetParameters; + type PublicKeyGadget = SchnorrSigGadgetPk; + + fn check_randomization_gadget>( + mut cs: CS, + parameters: &Self::ParametersGadget, + public_key: &Self::PublicKeyGadget, + randomness: &[UInt8], + ) -> Result { + let base = parameters.generator.clone(); + let randomness = randomness + .iter() + .flat_map(|b| b.into_bits_le()) + .collect::>(); + 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 AllocGadget, ConstraintF> + for SchnorrSigGadgetParameters +where + G: Group, + ConstraintF: Field, + GG: GroupGadget, + D: Digest, +{ + fn alloc>(cs: CS, f: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let generator = GG::alloc_checked(cs, || f().map(|pp| pp.borrow().generator))?; + Ok(Self { + generator, + _engine: PhantomData, + _group: PhantomData, + }) + } + + fn alloc_input>(cs: CS, f: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let generator = GG::alloc_input(cs, || f().map(|pp| pp.borrow().generator))?; + Ok(Self { + generator, + _engine: PhantomData, + _group: PhantomData, + }) + } +} + +impl AllocGadget, ConstraintF> for SchnorrSigGadgetPk +where + G: Group, + ConstraintF: Field, + GG: GroupGadget, +{ + fn alloc>(cs: CS, f: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let pub_key = GG::alloc_input(cs, || f().map(|pk| *pk.borrow()))?; + Ok(Self { + pub_key, + _engine: PhantomData, + _group: PhantomData, + }) + } + + fn alloc_input>(cs: CS, f: F) -> Result + where + F: FnOnce() -> Result, + T: Borrow>, + { + let pub_key = GG::alloc_input(cs, || f().map(|pk| *pk.borrow()))?; + Ok(Self { + pub_key, + _engine: PhantomData, + _group: PhantomData, + }) + } +} + +impl ConditionalEqGadget for SchnorrSigGadgetPk +where + G: Group, + ConstraintF: Field, + GG: GroupGadget, +{ + #[inline] + fn conditional_enforce_equal>( + &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 { + >::cost() + } +} + +impl EqGadget for SchnorrSigGadgetPk +where + G: Group, + ConstraintF: Field, + GG: GroupGadget, +{ +} + +impl ToBytesGadget for SchnorrSigGadgetPk +where + G: Group, + ConstraintF: Field, + GG: GroupGadget, +{ + fn to_bytes>(&self, mut cs: CS) -> Result, SynthesisError> { + self.pub_key.to_bytes(&mut cs.ns(|| "PubKey To Bytes")) + } + + fn to_bytes_strict>( + &self, + mut cs: CS, + ) -> Result, SynthesisError> { + self.pub_key + .to_bytes_strict(&mut cs.ns(|| "PubKey To Bytes")) + } +} diff --git a/crypto-primitives/src/signature/schnorr/mod.rs b/crypto-primitives/src/signature/schnorr/mod.rs new file mode 100644 index 0000000..8e50200 --- /dev/null +++ b/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 { + _group: PhantomData, + _hash: PhantomData, +} + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group, H: Digest"))] +pub struct SchnorrSigParameters { + _hash: PhantomData, + pub generator: G, + pub salt: [u8; 32], +} + +pub type SchnorrPublicKey = G; + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group"), Default(bound = "G: Group"))] +pub struct SchnorrSecretKey(pub G::ScalarField); + +impl ToBytes for SchnorrSecretKey { + #[inline] + fn write(&self, writer: W) -> IoResult<()> { + self.0.write(writer) + } +} + +#[derive(Derivative)] +#[derivative(Clone(bound = "G: Group"), Default(bound = "G: Group"))] +pub struct SchnorrSig { + pub prover_response: G::ScalarField, + pub verifier_challenge: G::ScalarField, +} + +impl SignatureScheme for SchnorrSignature +where + G::ScalarField: PrimeField, +{ + type Parameters = SchnorrSigParameters; + type PublicKey = G; + type SecretKey = SchnorrSecretKey; + type Signature = SchnorrSig; + + fn setup(rng: &mut R) -> Result { + 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( + 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( + parameters: &Self::Parameters, + sk: &Self::SecretKey, + message: &[u8], + rng: &mut R, + ) -> Result { + 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(¶meters.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 { + 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(¶meters.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 { + 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 { + 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 { + 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 +} diff --git a/r1cs-std/src/bits/mod.rs b/r1cs-std/src/bits/mod.rs index 3540bed..97b4940 100644 --- a/r1cs-std/src/bits/mod.rs +++ b/r1cs-std/src/bits/mod.rs @@ -81,4 +81,41 @@ pub trait ToBytesGadget { ) -> Result, SynthesisError>; } +impl ToBytesGadget for [UInt8] { + fn to_bytes>(&self, _cs: CS) -> Result, SynthesisError> { + Ok(self.to_vec()) + } + + fn to_bytes_strict>( + &self, + cs: CS, + ) -> Result, SynthesisError> { + self.to_bytes(cs) + } +} + +impl<'a, ConstraintF: Field, T: 'a + ToBytesGadget> ToBytesGadget for &'a T { + fn to_bytes>(&self, cs: CS) -> Result, SynthesisError> { + (*self).to_bytes(cs) + } + + fn to_bytes_strict>( + &self, + cs: CS, + ) -> Result, SynthesisError> { + self.to_bytes(cs) + } +} + +impl<'a, ConstraintF: Field> ToBytesGadget for &'a [UInt8] { + fn to_bytes>(&self, _cs: CS) -> Result, SynthesisError> { + Ok(self.to_vec()) + } + fn to_bytes_strict>( + &self, + cs: CS, + ) -> Result, SynthesisError> { + self.to_bytes(cs) + } +}