From eccf1d9b493cbf5c815a0cad07e8390b89e597ad Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 13 Jan 2023 11:40:54 +0100 Subject: [PATCH 1/9] Sha384 passes most tests --- src/crh/sha384/constraints.rs | 560 ++++++++++++++++++++++++++++++++++ src/crh/sha384/mod.rs | 81 +++++ src/crh/sha384/r1cs_utils.rs | 122 ++++++++ 3 files changed, 763 insertions(+) create mode 100644 src/crh/sha384/constraints.rs create mode 100644 src/crh/sha384/mod.rs create mode 100644 src/crh/sha384/r1cs_utils.rs diff --git a/src/crh/sha384/constraints.rs b/src/crh/sha384/constraints.rs new file mode 100644 index 00000000..35ff4191 --- /dev/null +++ b/src/crh/sha384/constraints.rs @@ -0,0 +1,560 @@ +// This file was adapted from +// the sha384 code which was adapted from +// https://github.com/nanpuyue/sha384/blob/bf6656b7dc72e76bb617445a8865f906670e585b/src/lib.rs +// See LICENSE-MIT in the root directory for a copy of the license +// Thank you! + +use crate::crh::{ + sha384::{r1cs_utils::UInt64Ext, Sha384}, + CRHSchemeGadget, TwoToOneCRHSchemeGadget, +}; + +use core::{borrow::Borrow, iter, marker::PhantomData}; + +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBytesGadget}, + eq::EqGadget, + select::CondSelectGadget, + R1CSVar, +}; +use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{vec, vec::Vec}; + +const STATE_LEN: usize = 8; + +type State = [u64; STATE_LEN]; + +const K: [u64; 80] = [ + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +]; + +const H: State = [ + 0xcbbb9d5dc1059ed8, 0x629a292a367cd507, 0x9159015a3070dd17, 0x152fecd8f70e5939, + 0x67332667ffc00b31, 0x8eb44a8768581511, 0xdb0c2e0d64f98fa7, 0x47b5481dbefa4fa4 +]; + +#[derive(Clone)] +pub struct Sha384Gadget { + state: Vec>, + completed_data_blocks: u128, + pending: Vec>, + num_pending: usize, +} + +impl Default for Sha384Gadget { + fn default() -> Self { + Self { + state: H.iter().cloned().map(UInt64::constant).collect(), + completed_data_blocks: 0, + pending: iter::repeat(0u8).take(128).map(UInt8::constant).collect(), + num_pending: 0, + } + } +} + +// Wikipedia's pseudocode is a good companion for understanding the below +// https://en.wikipedia.org/wiki/SHA-2#Pseudocode +impl Sha384Gadget { + fn update_state( + state: &mut [UInt64], + data: &[UInt8], + ) -> Result<(), SynthesisError> { + assert_eq!(data.len(), 128); + + let mut w = vec![UInt64::constant(0); 80]; + for (word, chunk) in w.iter_mut().zip(data.chunks(8)) { + *word = UInt64::from_bytes_be(chunk)?; + } + + for i in 16..80 { + let s0 = { + let x1 = w[i - 15].rotr(1); + let x2 = w[i - 15].rotr(8); + let x3 = w[i - 15].shr(7); + x1.xor(&x2)?.xor(&x3)? + }; + let s1 = { + let x1 = w[i - 2].rotr(19); + let x2 = w[i - 2].rotr(61); + let x3 = w[i - 2].shr(6); + x1.xor(&x2)?.xor(&x3)? + }; + w[i] = UInt64::addmany(&[w[i - 16].clone(), s0, w[i - 7].clone(), s1])?; + } + + let mut h = state.to_vec(); + for i in 0..80 { + let ch = { + let x1 = h[4].bitand(&h[5])?; + let x2 = h[4].not().bitand(&h[6])?; + x1.xor(&x2)? + }; + let ma = { + let x1 = h[0].bitand(&h[1])?; + let x2 = h[0].bitand(&h[2])?; + let x3 = h[1].bitand(&h[2])?; + x1.xor(&x2)?.xor(&x3)? + }; + let s0 = { + let x1 = h[0].rotr(28); + let x2 = h[0].rotr(34); + let x3 = h[0].rotr(39); + x1.xor(&x2)?.xor(&x3)? + }; + let s1 = { + let x1 = h[4].rotr(14); + let x2 = h[4].rotr(18); + let x3 = h[4].rotr(41); + x1.xor(&x2)?.xor(&x3)? + }; + let t0 = + UInt64::addmany(&[h[7].clone(), s1, ch, UInt64::constant(K[i]), w[i].clone()])?; + let t1 = UInt64::addmany(&[s0, ma])?; + + h[7] = h[6].clone(); + h[6] = h[5].clone(); + h[5] = h[4].clone(); + h[4] = UInt64::addmany(&[h[3].clone(), t0.clone()])?; + h[3] = h[2].clone(); + h[2] = h[1].clone(); + h[1] = h[0].clone(); + h[0] = UInt64::addmany(&[t0, t1])?; + } + + for (s, hi) in state.iter_mut().zip(h.iter()) { + *s = UInt64::addmany(&[s.clone(), hi.clone()])?; + } + + Ok(()) + } + + /// Consumes the given data and updates the internal state + pub fn update(&mut self, data: &[UInt8]) -> Result<(), SynthesisError> { + let mut offset = 0; + if self.num_pending > 0 && self.num_pending + data.len() >= 128 { + offset = 128 - self.num_pending; + // If the inputted data pushes the pending buffer over the chunk size, process it all + self.pending[self.num_pending..].clone_from_slice(&data[..offset]); + Self::update_state(&mut self.state, &self.pending)?; + + self.completed_data_blocks += 1; + self.num_pending = 0; + } + + for chunk in data[offset..].chunks(128) { + let chunk_size = chunk.len(); + + if chunk_size == 128 { + // If it's a full chunk, process it + Self::update_state(&mut self.state, chunk)?; + self.completed_data_blocks += 1; + } else { + // Otherwise, add the bytes to the `pending` buffer + self.pending[self.num_pending..self.num_pending + chunk_size] + .clone_from_slice(chunk); + self.num_pending += chunk_size; + } + } + + Ok(()) + } + + /// Outputs the final digest of all the inputted data + pub fn finalize(mut self) -> Result, SynthesisError> { + // Encode the number of processed bits as a u128, then serialize it to 16 big-endian bytes + let data_bitlen = self.completed_data_blocks * 1024 + self.num_pending as u128 * 8; + let encoded_bitlen: Vec> = { + let bytes = data_bitlen.to_be_bytes(); + bytes.iter().map(|&b| UInt8::constant(b)).collect() + }; + + // Padding starts with a 1 followed by some number of zeros (0x80 = 0b10000000) + let mut pending = vec![UInt8::constant(0); 136]; + pending[0] = UInt8::constant(0x80); + + // We'll either append to the 112+16 = 128 byte boundary or the 240+16=256 byte boundary, + // depending on whether we have at least 112 unprocessed bytes + let offset = if self.num_pending < 112 { + 112 - self.num_pending + } else { + 240 - self.num_pending + }; + + // Write the bitlen to the end of the padding. Then process all the padding + pending[offset..offset + 16].clone_from_slice(&encoded_bitlen); + self.update(&pending[..offset + 16])?; + + // Collect the state into big-endian bytes + let bytes: Vec<_> = self.state.iter().flat_map(UInt64::to_bytes_be).collect(); + Ok(DigestVar(bytes)) + } + + /// Computes the digest of the given data. This is a shortcut for `default()` followed by + /// `update()` followed by `finalize()`. + pub fn digest(data: &[UInt8]) -> Result, SynthesisError> { + let mut sha384_var = Self::default(); + sha384_var.update(data)?; + sha384_var.finalize() + } +} + +// Now implement the CRH traits for SHA384 + +/// Contains a 64-byte SHA384 digest +#[derive(Clone, Debug)] +pub struct DigestVar(pub Vec>); + +impl EqGadget for DigestVar +where + ConstraintF: PrimeField, +{ + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + self.0.is_eq(&other.0) + } +} + +impl ToBytesGadget for DigestVar { + fn to_bytes(&self) -> Result>, SynthesisError> { + Ok(self.0.clone()) + } +} + +impl CondSelectGadget for DigestVar +where + Self: Sized, + Self: Clone, +{ + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + let bytes: Result, _> = true_value + .0 + .iter() + .zip(false_value.0.iter()) + .map(|(t, f)| UInt8::conditionally_select(cond, t, f)) + .collect(); + bytes.map(DigestVar) + } +} + +/// The unit type for circuit variables. This contains no data. +#[derive(Clone, Debug, Default)] +pub struct UnitVar(PhantomData); + +impl AllocVar<(), ConstraintF> for UnitVar { + // Allocates 64 UInt8s + fn new_variable>( + _cs: impl Into>, + _f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + Ok(UnitVar(PhantomData)) + } +} + +impl AllocVar, ConstraintF> for DigestVar { + // Allocates 64 UInt8s + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let native_bytes = f(); + + if native_bytes + .as_ref() + .map(|b| b.borrow().len()) + .unwrap_or(64) + != 64 + { + panic!("DigestVar must be allocated with precisely 64 bytes"); + } + + // For each i, allocate the i-th byte + let var_bytes: Result, _> = (0..64) + .map(|i| { + UInt8::new_variable( + cs.clone(), + || native_bytes.as_ref().map(|v| v.borrow()[i]).map_err(|e| *e), + mode, + ) + }) + .collect(); + + var_bytes.map(DigestVar) + } +} + +impl R1CSVar for DigestVar { + type Value = [u8; 48]; + + fn cs(&self) -> ConstraintSystemRef { + let mut result = ConstraintSystemRef::None; + for var in &self.0 { + result = var.cs().or(result); + } + result + } + + fn value(&self) -> Result { + let mut buf = [0u8; 48]; + for (b, var) in buf.iter_mut().zip(self.0.iter()) { + *b = var.value()?; + } + + Ok(buf) + } +} + +impl CRHSchemeGadget for Sha384Gadget +where + ConstraintF: PrimeField, +{ + type InputVar = [UInt8]; + type OutputVar = DigestVar; + type ParametersVar = UnitVar; + + #[tracing::instrument(target = "r1cs", skip(_parameters))] + fn evaluate( + _parameters: &Self::ParametersVar, + input: &Self::InputVar, + ) -> Result { + Self::digest(input) + } +} + +impl TwoToOneCRHSchemeGadget for Sha384Gadget +where + ConstraintF: PrimeField, +{ + type InputVar = [UInt8]; + type OutputVar = DigestVar; + type ParametersVar = UnitVar; + + #[tracing::instrument(target = "r1cs", skip(_parameters))] + fn evaluate( + _parameters: &Self::ParametersVar, + left_input: &Self::InputVar, + right_input: &Self::InputVar, + ) -> Result { + let mut h = Self::default(); + h.update(left_input)?; + h.update(right_input)?; + h.finalize() + } + + #[tracing::instrument(target = "r1cs", skip(parameters))] + fn compress( + parameters: &Self::ParametersVar, + left_input: &Self::OutputVar, + right_input: &Self::OutputVar, + ) -> Result { + // Convert output to bytes + let left_input = left_input.to_bytes()?; + let right_input = right_input.to_bytes()?; + >::evaluate( + parameters, + &left_input, + &right_input, + ) + } +} + +// All the tests below test against the RustCrypto sha2 implementation +#[cfg(test)] +mod test { + use super::*; + use crate::crh::{ + sha384::{digest::Digest, Sha384}, + CRHScheme, CRHSchemeGadget, TwoToOneCRHScheme, TwoToOneCRHSchemeGadget, + }; + + use ark_bls12_377::Fr; + use ark_r1cs_std::R1CSVar; + use ark_relations::{ + ns, + r1cs::{ConstraintSystem, Namespace}, + }; + use ark_std::rand::RngCore; + + const TEST_LENGTHS: &[usize] = &[ + 0, 1, 2, 8, 20, 40, 55, 56, 57, 63, 64, 65, 90, 100, 127, 128, 129, + ]; + + /// Witnesses bytes + fn to_byte_vars(cs: impl Into>, data: &[u8]) -> Vec> { + let cs = cs.into().cs(); + UInt8::new_witness_vec(cs, data).unwrap() + } + + /// Finalizes a SHA384 gadget and gets the bytes + fn finalize_var(sha384_var: Sha384Gadget) -> Vec { + sha384_var.finalize().unwrap().value().unwrap().to_vec() + } + + /// Finalizes a native SHA384 struct and gets the bytes + fn finalize(sha384: Sha384) -> Vec { + sha384.finalize().to_vec() + } + + /// Tests the SHA384 of random strings of varied lengths + #[test] + fn varied_lengths() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + for &len in TEST_LENGTHS { + let mut sha384 = Sha384::default(); + let mut sha384_var = Sha384Gadget::default(); + + // Make a random string of the given length + let mut input_str = vec![0u8; len]; + rng.fill_bytes(&mut input_str); + + // Compute the hashes and assert consistency + sha384_var + .update(&to_byte_vars(ns!(cs, "input"), &input_str)) + .unwrap(); + sha384.update(input_str); + assert_eq!( + finalize_var(sha384_var), + finalize(sha384), + "error at length {}", + len + ); + } + } + + /// Calls `update()` many times + #[test] + fn many_updates() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + let mut sha384 = Sha384::default(); + let mut sha384_var = Sha384Gadget::default(); + + // Append the same 7-byte string 20 times + for _ in 0..20 { + let mut input_str = vec![0u8; 7]; + rng.fill_bytes(&mut input_str); + + sha384_var + .update(&to_byte_vars(ns!(cs, "input"), &input_str)) + .unwrap(); + sha384.update(input_str); + } + + // Make sure the result is consistent + assert_eq!(finalize_var(sha384_var), finalize(sha384)); + } + + /// Tests the CRHCheme trait + #[test] + fn crh() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // CRH parameters are nothing + let unit = (); + let unit_var = UnitVar::default(); + + for &len in TEST_LENGTHS { + // Make a random string of the given length + let mut input_str = vec![0u8; len]; + rng.fill_bytes(&mut input_str); + + // Compute the hashes and assert consistency + let computed_output = as CRHSchemeGadget>::evaluate( + &unit_var, + &to_byte_vars(ns!(cs, "input"), &input_str), + ) + .unwrap(); + let expected_output = ::evaluate(&unit, input_str).unwrap(); + assert_eq!( + computed_output.value().unwrap().to_vec(), + expected_output, + "CRH error at length {}", + len + ) + } + } + + /// Tests the TwoToOneCRHScheme trait + #[test] + fn to_to_one_crh() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // CRH parameters are nothing + let unit = (); + let unit_var = UnitVar::default(); + + for &len in TEST_LENGTHS { + // Make random strings of the given length + let mut left_input = vec![0u8; len]; + let mut right_input = vec![0u8; len]; + rng.fill_bytes(&mut left_input); + rng.fill_bytes(&mut right_input); + + // Compute the hashes and assert consistency + let computed_output = + as TwoToOneCRHSchemeGadget>::evaluate( + &unit_var, + &to_byte_vars(ns!(cs, "left input"), &left_input), + &to_byte_vars(ns!(cs, "right input"), &right_input), + ) + .unwrap(); + let expected_output = + ::evaluate(&unit, left_input, right_input).unwrap(); + assert_eq!( + computed_output.value().unwrap().to_vec(), + expected_output, + "TwoToOneCRH error at length {}", + len + ) + } + } + + /// Tests the EqGadget impl of DigestVar + #[test] + fn digest_eq() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // Make two distinct digests + let mut digest1 = [0u8; 64]; + let mut digest2 = [0u8; 64]; + rng.fill_bytes(&mut digest1); + rng.fill_bytes(&mut digest2); + + // Witness them + let digest1_var = DigestVar::new_witness(cs.clone(), || Ok(digest1.to_vec())).unwrap(); + let digest2_var = DigestVar::new_witness(cs.clone(), || Ok(digest2.to_vec())).unwrap(); + + // Assert that the distinct digests are distinct + assert!(!digest1_var.is_eq(&digest2_var).unwrap().value().unwrap()); + + // Now assert that a digest equals itself + assert!(digest1_var.is_eq(&digest1_var).unwrap().value().unwrap()); + } +} diff --git a/src/crh/sha384/mod.rs b/src/crh/sha384/mod.rs new file mode 100644 index 00000000..a613ff39 --- /dev/null +++ b/src/crh/sha384/mod.rs @@ -0,0 +1,81 @@ +use crate::crh::{CRHScheme, TwoToOneCRHScheme}; +use crate::{Error, Vec}; + +use ark_std::rand::Rng; + +// Re-export the RustCrypto Sha384 type and its associated traits +pub use sha2::{digest, Sha384}; + +#[cfg(feature = "r1cs")] +mod r1cs_utils; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +// Implement the CRH traits for SHA-384 + +use core::borrow::Borrow; +use sha2::digest::Digest; + +impl CRHScheme for Sha384 { + type Input = [u8]; + // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + type Output = Vec; + // There are no parameters for SHA384 + type Parameters = (); + + // There are no parameters for SHA384 + fn setup(_rng: &mut R) -> Result { + Ok(()) + } + + // Evaluates SHA384(input) + fn evaluate>( + _parameters: &Self::Parameters, + input: T, + ) -> Result { + Ok(Sha384::digest(input.borrow()).to_vec()) + } +} + +impl TwoToOneCRHScheme for Sha384 { + type Input = [u8]; + // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + type Output = Vec; + // There are no parameters for SHA384 + type Parameters = (); + + // There are no parameters for SHA384 + fn setup(_rng: &mut R) -> Result { + Ok(()) + } + + // Evaluates SHA384(left_input || right_input) + fn evaluate>( + _parameters: &Self::Parameters, + left_input: T, + right_input: T, + ) -> Result { + let left_input = left_input.borrow(); + let right_input = right_input.borrow(); + + // Process the left input then the right input + let mut h = Sha384::default(); + h.update(left_input); + h.update(right_input); + Ok(h.finalize().to_vec()) + } + + // Evaluates SHA384(left_input || right_input) + fn compress>( + parameters: &Self::Parameters, + left_input: T, + right_input: T, + ) -> Result { + ::evaluate( + parameters, + left_input.borrow().as_slice(), + right_input.borrow().as_slice(), + ) + } +} diff --git a/src/crh/sha384/r1cs_utils.rs b/src/crh/sha384/r1cs_utils.rs new file mode 100644 index 00000000..41c24d4f --- /dev/null +++ b/src/crh/sha384/r1cs_utils.rs @@ -0,0 +1,122 @@ +use crate::Vec; + +use ark_ff::PrimeField; +use ark_r1cs_std::bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBitsGadget}; +use ark_relations::r1cs::SynthesisError; +use core::iter; + +/// Extra traits not automatically implemented by UInt64 +pub(crate) trait UInt64Ext: Sized { + /// Right shift + fn shr(&self, by: usize) -> Self; + + /// Bitwise NOT + fn not(&self) -> Self; + + /// Bitwise AND + fn bitand(&self, rhs: &Self) -> Result; + + /// Converts from big-endian bytes + fn from_bytes_be(bytes: &[UInt8]) -> Result; + + /// Converts to big-endian bytes + fn to_bytes_be(&self) -> Vec>; +} + +impl UInt64Ext for UInt64 { + fn shr(&self, by: usize) -> Self { + assert!(by < 64); + + let zeros = iter::repeat(Boolean::constant(false)).take(by); + let new_bits: Vec<_> = self + .to_bits_le() + .into_iter() + .skip(by) + .chain(zeros) + .collect(); + UInt64::from_bits_le(&new_bits) + } + + fn not(&self) -> Self { + let new_bits: Vec<_> = self.to_bits_le().iter().map(Boolean::not).collect(); + UInt64::from_bits_le(&new_bits) + } + + fn bitand(&self, rhs: &Self) -> Result { + let new_bits: Result, SynthesisError> = self + .to_bits_le() + .into_iter() + .zip(rhs.to_bits_le().into_iter()) + .map(|(a, b)| a.and(&b)) + .collect(); + Ok(UInt64::from_bits_le(&new_bits?)) + } + + fn from_bytes_be(bytes: &[UInt8]) -> Result { + assert_eq!(bytes.len(), 8); + + let mut bits: Vec> = Vec::new(); + for byte in bytes.iter().rev() { + let b: Vec> = byte.to_bits_le()?; + bits.extend(b); + } + Ok(UInt64::from_bits_le(&bits)) + } + + fn to_bytes_be(&self) -> Vec> { + self.to_bits_le() + .chunks(8) + .rev() + .map(UInt8::from_bits_le) + .collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + + use ark_bls12_377::Fr; + use ark_r1cs_std::{bits::uint64::UInt64, R1CSVar}; + use ark_std::rand::Rng; + + const NUM_TESTS: usize = 10_000; + + #[test] + fn test_shr() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = rng.gen::(); + let by = rng.gen::() % 64; + assert_eq!(UInt64::::constant(x).shr(by).value().unwrap(), x >> by); + } + } + + #[test] + fn test_bitand() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = rng.gen::(); + let y = rng.gen::(); + assert_eq!( + UInt64::::constant(x) + .bitand(&UInt64::constant(y)) + .unwrap() + .value() + .unwrap(), + x & y + ); + } + } + + #[test] + fn test_to_from_bytes_be() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = UInt64::::constant(rng.gen::()); + let bytes = x.to_bytes_be(); + let y = UInt64::from_bytes_be(&bytes).unwrap(); + assert_eq!(x.value(), y.value()); + } + } +} From 4f42b0ea27ce556ddb2fa6c7b863cfc22f4d1654 Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 13 Jan 2023 14:06:38 +0100 Subject: [PATCH 2/9] Fixed pending size --- src/crh/sha384/constraints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crh/sha384/constraints.rs b/src/crh/sha384/constraints.rs index 35ff4191..41c2f0c1 100644 --- a/src/crh/sha384/constraints.rs +++ b/src/crh/sha384/constraints.rs @@ -186,7 +186,7 @@ impl Sha384Gadget { }; // Padding starts with a 1 followed by some number of zeros (0x80 = 0b10000000) - let mut pending = vec![UInt8::constant(0); 136]; + let mut pending = vec![UInt8::constant(0); 144]; pending[0] = UInt8::constant(0x80); // We'll either append to the 112+16 = 128 byte boundary or the 240+16=256 byte boundary, From 05500f3390ee4cd70ba04c46b3d4821ba6875d16 Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 13 Jan 2023 14:15:26 +0100 Subject: [PATCH 3/9] Added extra test lengths since 384 has longer message sizes --- src/crh/sha384/constraints.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crh/sha384/constraints.rs b/src/crh/sha384/constraints.rs index 41c2f0c1..82bda3ed 100644 --- a/src/crh/sha384/constraints.rs +++ b/src/crh/sha384/constraints.rs @@ -398,7 +398,8 @@ mod test { use ark_std::rand::RngCore; const TEST_LENGTHS: &[usize] = &[ - 0, 1, 2, 8, 20, 40, 55, 56, 57, 63, 64, 65, 90, 100, 127, 128, 129, + 0, 1, 2, 4, 8, 16, 20, 40, 55, 56, 57, 63, 64, 65, 80, 90, + 100, 111, 112, 113, 127, 128, 129, 180, 200, 255, 256, 257, ]; /// Witnesses bytes From 74ff5221104ccf3c5098df65b32dd3c3e8a63303 Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 13 Jan 2023 14:36:32 +0100 Subject: [PATCH 4/9] Added sha512 --- src/crh/sha512/constraints.rs | 560 ++++++++++++++++++++++++++++++++++ src/crh/sha512/mod.rs | 81 +++++ src/crh/sha512/r1cs_utils.rs | 122 ++++++++ 3 files changed, 763 insertions(+) create mode 100644 src/crh/sha512/constraints.rs create mode 100644 src/crh/sha512/mod.rs create mode 100644 src/crh/sha512/r1cs_utils.rs diff --git a/src/crh/sha512/constraints.rs b/src/crh/sha512/constraints.rs new file mode 100644 index 00000000..5620db9a --- /dev/null +++ b/src/crh/sha512/constraints.rs @@ -0,0 +1,560 @@ +// This file was adapted from the sha256 code which was adapted from +// https://github.com/nanpuyue/sha256/blob/bf6656b7dc72e76bb617445a8865f906670e585b/src/lib.rs +// See LICENSE-MIT in the root directory for a copy of the license +// Thank you! + +use crate::crh::{ + sha512::{r1cs_utils::UInt64Ext, Sha512}, + CRHSchemeGadget, TwoToOneCRHSchemeGadget, +}; + +use core::{borrow::Borrow, iter, marker::PhantomData}; + +use ark_ff::PrimeField; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBytesGadget}, + eq::EqGadget, + select::CondSelectGadget, + R1CSVar, +}; +use ark_relations::r1cs::{ConstraintSystemRef, Namespace, SynthesisError}; +use ark_std::{vec, vec::Vec}; + +const STATE_LEN: usize = 8; + +type State = [u64; STATE_LEN]; + +const K: [u64; 80] = [ + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817 +]; + +const H: State = [ + 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179 +]; + +#[derive(Clone)] +pub struct Sha512Gadget { + state: Vec>, + completed_data_blocks: u128, + pending: Vec>, + num_pending: usize, +} + +impl Default for Sha512Gadget { + fn default() -> Self { + Self { + state: H.iter().cloned().map(UInt64::constant).collect(), + completed_data_blocks: 0, + pending: iter::repeat(0u8).take(128).map(UInt8::constant).collect(), + num_pending: 0, + } + } +} + +// Wikipedia's pseudocode is a good companion for understanding the below +// https://en.wikipedia.org/wiki/SHA-2#Pseudocode +impl Sha512Gadget { + fn update_state( + state: &mut [UInt64], + data: &[UInt8], + ) -> Result<(), SynthesisError> { + assert_eq!(data.len(), 128); + + let mut w = vec![UInt64::constant(0); 80]; + for (word, chunk) in w.iter_mut().zip(data.chunks(8)) { + *word = UInt64::from_bytes_be(chunk)?; + } + + for i in 16..80 { + let s0 = { + let x1 = w[i - 15].rotr(1); + let x2 = w[i - 15].rotr(8); + let x3 = w[i - 15].shr(7); + x1.xor(&x2)?.xor(&x3)? + }; + let s1 = { + let x1 = w[i - 2].rotr(19); + let x2 = w[i - 2].rotr(61); + let x3 = w[i - 2].shr(6); + x1.xor(&x2)?.xor(&x3)? + }; + w[i] = UInt64::addmany(&[w[i - 16].clone(), s0, w[i - 7].clone(), s1])?; + } + + let mut h = state.to_vec(); + for i in 0..80 { + let ch = { + let x1 = h[4].bitand(&h[5])?; + let x2 = h[4].not().bitand(&h[6])?; + x1.xor(&x2)? + }; + let ma = { + let x1 = h[0].bitand(&h[1])?; + let x2 = h[0].bitand(&h[2])?; + let x3 = h[1].bitand(&h[2])?; + x1.xor(&x2)?.xor(&x3)? + }; + let s0 = { + let x1 = h[0].rotr(28); + let x2 = h[0].rotr(34); + let x3 = h[0].rotr(39); + x1.xor(&x2)?.xor(&x3)? + }; + let s1 = { + let x1 = h[4].rotr(14); + let x2 = h[4].rotr(18); + let x3 = h[4].rotr(41); + x1.xor(&x2)?.xor(&x3)? + }; + let t0 = + UInt64::addmany(&[h[7].clone(), s1, ch, UInt64::constant(K[i]), w[i].clone()])?; + let t1 = UInt64::addmany(&[s0, ma])?; + + h[7] = h[6].clone(); + h[6] = h[5].clone(); + h[5] = h[4].clone(); + h[4] = UInt64::addmany(&[h[3].clone(), t0.clone()])?; + h[3] = h[2].clone(); + h[2] = h[1].clone(); + h[1] = h[0].clone(); + h[0] = UInt64::addmany(&[t0, t1])?; + } + + for (s, hi) in state.iter_mut().zip(h.iter()) { + *s = UInt64::addmany(&[s.clone(), hi.clone()])?; + } + + Ok(()) + } + + /// Consumes the given data and updates the internal state + pub fn update(&mut self, data: &[UInt8]) -> Result<(), SynthesisError> { + let mut offset = 0; + if self.num_pending > 0 && self.num_pending + data.len() >= 128 { + offset = 128 - self.num_pending; + // If the inputted data pushes the pending buffer over the chunk size, process it all + self.pending[self.num_pending..].clone_from_slice(&data[..offset]); + Self::update_state(&mut self.state, &self.pending)?; + + self.completed_data_blocks += 1; + self.num_pending = 0; + } + + for chunk in data[offset..].chunks(128) { + let chunk_size = chunk.len(); + + if chunk_size == 128 { + // If it's a full chunk, process it + Self::update_state(&mut self.state, chunk)?; + self.completed_data_blocks += 1; + } else { + // Otherwise, add the bytes to the `pending` buffer + self.pending[self.num_pending..self.num_pending + chunk_size] + .clone_from_slice(chunk); + self.num_pending += chunk_size; + } + } + + Ok(()) + } + + /// Outputs the final digest of all the inputted data + pub fn finalize(mut self) -> Result, SynthesisError> { + // Encode the number of processed bits as a u128, then serialize it to 16 big-endian bytes + let data_bitlen = self.completed_data_blocks * 1024 + self.num_pending as u128 * 8; + let encoded_bitlen: Vec> = { + let bytes = data_bitlen.to_be_bytes(); + bytes.iter().map(|&b| UInt8::constant(b)).collect() + }; + + // Padding starts with a 1 followed by some number of zeros (0x80 = 0b10000000) + let mut pending = vec![UInt8::constant(0); 144]; + pending[0] = UInt8::constant(0x80); + + // We'll either append to the 112+16 = 128 byte boundary or the 240+16=256 byte boundary, + // depending on whether we have at least 112 unprocessed bytes + let offset = if self.num_pending < 112 { + 112 - self.num_pending + } else { + 240 - self.num_pending + }; + + // Write the bitlen to the end of the padding. Then process all the padding + pending[offset..offset + 16].clone_from_slice(&encoded_bitlen); + self.update(&pending[..offset + 16])?; + + // Collect the state into big-endian bytes + let bytes: Vec<_> = self.state.iter().flat_map(UInt64::to_bytes_be).collect(); + Ok(DigestVar(bytes)) + } + + /// Computes the digest of the given data. This is a shortcut for `default()` followed by + /// `update()` followed by `finalize()`. + pub fn digest(data: &[UInt8]) -> Result, SynthesisError> { + let mut sha512_var = Self::default(); + sha512_var.update(data)?; + sha512_var.finalize() + } +} + +// Now implement the CRH traits for SHA512 + +/// Contains a 64-byte SHA512 digest +#[derive(Clone, Debug)] +pub struct DigestVar(pub Vec>); + +impl EqGadget for DigestVar +where + ConstraintF: PrimeField, +{ + fn is_eq(&self, other: &Self) -> Result, SynthesisError> { + self.0.is_eq(&other.0) + } +} + +impl ToBytesGadget for DigestVar { + fn to_bytes(&self) -> Result>, SynthesisError> { + Ok(self.0.clone()) + } +} + +impl CondSelectGadget for DigestVar +where + Self: Sized, + Self: Clone, +{ + fn conditionally_select( + cond: &Boolean, + true_value: &Self, + false_value: &Self, + ) -> Result { + let bytes: Result, _> = true_value + .0 + .iter() + .zip(false_value.0.iter()) + .map(|(t, f)| UInt8::conditionally_select(cond, t, f)) + .collect(); + bytes.map(DigestVar) + } +} + +/// The unit type for circuit variables. This contains no data. +#[derive(Clone, Debug, Default)] +pub struct UnitVar(PhantomData); + +impl AllocVar<(), ConstraintF> for UnitVar { + // Allocates 64 UInt8s + fn new_variable>( + _cs: impl Into>, + _f: impl FnOnce() -> Result, + _mode: AllocationMode, + ) -> Result { + Ok(UnitVar(PhantomData)) + } +} + +impl AllocVar, ConstraintF> for DigestVar { + // Allocates 64 UInt8s + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + let cs = cs.into().cs(); + let native_bytes = f(); + + if native_bytes + .as_ref() + .map(|b| b.borrow().len()) + .unwrap_or(64) + != 64 + { + panic!("DigestVar must be allocated with precisely 64 bytes"); + } + + // For each i, allocate the i-th byte + let var_bytes: Result, _> = (0..64) + .map(|i| { + UInt8::new_variable( + cs.clone(), + || native_bytes.as_ref().map(|v| v.borrow()[i]).map_err(|e| *e), + mode, + ) + }) + .collect(); + + var_bytes.map(DigestVar) + } +} + +impl R1CSVar for DigestVar { + type Value = [u8; 64]; + + fn cs(&self) -> ConstraintSystemRef { + let mut result = ConstraintSystemRef::None; + for var in &self.0 { + result = var.cs().or(result); + } + result + } + + fn value(&self) -> Result { + let mut buf = [0u8; 64]; + for (b, var) in buf.iter_mut().zip(self.0.iter()) { + *b = var.value()?; + } + + Ok(buf) + } +} + +impl CRHSchemeGadget for Sha512Gadget +where + ConstraintF: PrimeField, +{ + type InputVar = [UInt8]; + type OutputVar = DigestVar; + type ParametersVar = UnitVar; + + #[tracing::instrument(target = "r1cs", skip(_parameters))] + fn evaluate( + _parameters: &Self::ParametersVar, + input: &Self::InputVar, + ) -> Result { + Self::digest(input) + } +} + +impl TwoToOneCRHSchemeGadget for Sha512Gadget +where + ConstraintF: PrimeField, +{ + type InputVar = [UInt8]; + type OutputVar = DigestVar; + type ParametersVar = UnitVar; + + #[tracing::instrument(target = "r1cs", skip(_parameters))] + fn evaluate( + _parameters: &Self::ParametersVar, + left_input: &Self::InputVar, + right_input: &Self::InputVar, + ) -> Result { + let mut h = Self::default(); + h.update(left_input)?; + h.update(right_input)?; + h.finalize() + } + + #[tracing::instrument(target = "r1cs", skip(parameters))] + fn compress( + parameters: &Self::ParametersVar, + left_input: &Self::OutputVar, + right_input: &Self::OutputVar, + ) -> Result { + // Convert output to bytes + let left_input = left_input.to_bytes()?; + let right_input = right_input.to_bytes()?; + >::evaluate( + parameters, + &left_input, + &right_input, + ) + } +} + +// All the tests below test against the RustCrypto sha2 implementation +#[cfg(test)] +mod test { + use super::*; + use crate::crh::{ + sha512::{digest::Digest, Sha512}, + CRHScheme, CRHSchemeGadget, TwoToOneCRHScheme, TwoToOneCRHSchemeGadget, + }; + + use ark_bls12_377::Fr; + use ark_r1cs_std::R1CSVar; + use ark_relations::{ + ns, + r1cs::{ConstraintSystem, Namespace}, + }; + use ark_std::rand::RngCore; + + const TEST_LENGTHS: &[usize] = &[ + 0, 1, 2, 4, 8, 16, 20, 40, 55, 56, 57, 63, 64, 65, 80, 90, + 100, 111, 112, 113, 127, 128, 129, 180, 200, 255, 256, 257, + ]; + + /// Witnesses bytes + fn to_byte_vars(cs: impl Into>, data: &[u8]) -> Vec> { + let cs = cs.into().cs(); + UInt8::new_witness_vec(cs, data).unwrap() + } + + /// Finalizes a SHA512 gadget and gets the bytes + fn finalize_var(sha512_var: Sha512Gadget) -> Vec { + sha512_var.finalize().unwrap().value().unwrap().to_vec() + } + + /// Finalizes a native SHA512 struct and gets the bytes + fn finalize(sha512: Sha512) -> Vec { + sha512.finalize().to_vec() + } + + /// Tests the SHA512 of random strings of varied lengths + #[test] + fn varied_lengths() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + for &len in TEST_LENGTHS { + let mut sha512 = Sha512::default(); + let mut sha512_var = Sha512Gadget::default(); + + // Make a random string of the given length + let mut input_str = vec![0u8; len]; + rng.fill_bytes(&mut input_str); + + // Compute the hashes and assert consistency + sha512_var + .update(&to_byte_vars(ns!(cs, "input"), &input_str)) + .unwrap(); + sha512.update(input_str); + assert_eq!( + finalize_var(sha512_var), + finalize(sha512), + "error at length {}", + len + ); + } + } + + /// Calls `update()` many times + #[test] + fn many_updates() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + let mut sha512 = Sha512::default(); + let mut sha512_var = Sha512Gadget::default(); + + // Append the same 7-byte string 20 times + for _ in 0..20 { + let mut input_str = vec![0u8; 7]; + rng.fill_bytes(&mut input_str); + + sha512_var + .update(&to_byte_vars(ns!(cs, "input"), &input_str)) + .unwrap(); + sha512.update(input_str); + } + + // Make sure the result is consistent + assert_eq!(finalize_var(sha512_var), finalize(sha512)); + } + + /// Tests the CRHCheme trait + #[test] + fn crh() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // CRH parameters are nothing + let unit = (); + let unit_var = UnitVar::default(); + + for &len in TEST_LENGTHS { + // Make a random string of the given length + let mut input_str = vec![0u8; len]; + rng.fill_bytes(&mut input_str); + + // Compute the hashes and assert consistency + let computed_output = as CRHSchemeGadget>::evaluate( + &unit_var, + &to_byte_vars(ns!(cs, "input"), &input_str), + ) + .unwrap(); + let expected_output = ::evaluate(&unit, input_str).unwrap(); + assert_eq!( + computed_output.value().unwrap().to_vec(), + expected_output, + "CRH error at length {}", + len + ) + } + } + + /// Tests the TwoToOneCRHScheme trait + #[test] + fn to_to_one_crh() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // CRH parameters are nothing + let unit = (); + let unit_var = UnitVar::default(); + + for &len in TEST_LENGTHS { + // Make random strings of the given length + let mut left_input = vec![0u8; len]; + let mut right_input = vec![0u8; len]; + rng.fill_bytes(&mut left_input); + rng.fill_bytes(&mut right_input); + + // Compute the hashes and assert consistency + let computed_output = + as TwoToOneCRHSchemeGadget>::evaluate( + &unit_var, + &to_byte_vars(ns!(cs, "left input"), &left_input), + &to_byte_vars(ns!(cs, "right input"), &right_input), + ) + .unwrap(); + let expected_output = + ::evaluate(&unit, left_input, right_input).unwrap(); + assert_eq!( + computed_output.value().unwrap().to_vec(), + expected_output, + "TwoToOneCRH error at length {}", + len + ) + } + } + + /// Tests the EqGadget impl of DigestVar + #[test] + fn digest_eq() { + let mut rng = ark_std::test_rng(); + let cs = ConstraintSystem::::new_ref(); + + // Make two distinct digests + let mut digest1 = [0u8; 64]; + let mut digest2 = [0u8; 64]; + rng.fill_bytes(&mut digest1); + rng.fill_bytes(&mut digest2); + + // Witness them + let digest1_var = DigestVar::new_witness(cs.clone(), || Ok(digest1.to_vec())).unwrap(); + let digest2_var = DigestVar::new_witness(cs.clone(), || Ok(digest2.to_vec())).unwrap(); + + // Assert that the distinct digests are distinct + assert!(!digest1_var.is_eq(&digest2_var).unwrap().value().unwrap()); + + // Now assert that a digest equals itself + assert!(digest1_var.is_eq(&digest1_var).unwrap().value().unwrap()); + } +} diff --git a/src/crh/sha512/mod.rs b/src/crh/sha512/mod.rs new file mode 100644 index 00000000..9823aa48 --- /dev/null +++ b/src/crh/sha512/mod.rs @@ -0,0 +1,81 @@ +use crate::crh::{CRHScheme, TwoToOneCRHScheme}; +use crate::{Error, Vec}; + +use ark_std::rand::Rng; + +// Re-export the RustCrypto Sha512 type and its associated traits +pub use sha2::{digest, Sha512}; + +#[cfg(feature = "r1cs")] +mod r1cs_utils; + +#[cfg(feature = "r1cs")] +pub mod constraints; + +// Implement the CRH traits for SHA-512 + +use core::borrow::Borrow; +use sha2::digest::Digest; + +impl CRHScheme for Sha512 { + type Input = [u8]; + // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + type Output = Vec; + // There are no parameters for SHA512 + type Parameters = (); + + // There are no parameters for SHA512 + fn setup(_rng: &mut R) -> Result { + Ok(()) + } + + // Evaluates SHA512(input) + fn evaluate>( + _parameters: &Self::Parameters, + input: T, + ) -> Result { + Ok(Sha512::digest(input.borrow()).to_vec()) + } +} + +impl TwoToOneCRHScheme for Sha512 { + type Input = [u8]; + // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + type Output = Vec; + // There are no parameters for SHA512 + type Parameters = (); + + // There are no parameters for SHA512 + fn setup(_rng: &mut R) -> Result { + Ok(()) + } + + // Evaluates SHA512(left_input || right_input) + fn evaluate>( + _parameters: &Self::Parameters, + left_input: T, + right_input: T, + ) -> Result { + let left_input = left_input.borrow(); + let right_input = right_input.borrow(); + + // Process the left input then the right input + let mut h = Sha512::default(); + h.update(left_input); + h.update(right_input); + Ok(h.finalize().to_vec()) + } + + // Evaluates SHA512(left_input || right_input) + fn compress>( + parameters: &Self::Parameters, + left_input: T, + right_input: T, + ) -> Result { + ::evaluate( + parameters, + left_input.borrow().as_slice(), + right_input.borrow().as_slice(), + ) + } +} diff --git a/src/crh/sha512/r1cs_utils.rs b/src/crh/sha512/r1cs_utils.rs new file mode 100644 index 00000000..41c24d4f --- /dev/null +++ b/src/crh/sha512/r1cs_utils.rs @@ -0,0 +1,122 @@ +use crate::Vec; + +use ark_ff::PrimeField; +use ark_r1cs_std::bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBitsGadget}; +use ark_relations::r1cs::SynthesisError; +use core::iter; + +/// Extra traits not automatically implemented by UInt64 +pub(crate) trait UInt64Ext: Sized { + /// Right shift + fn shr(&self, by: usize) -> Self; + + /// Bitwise NOT + fn not(&self) -> Self; + + /// Bitwise AND + fn bitand(&self, rhs: &Self) -> Result; + + /// Converts from big-endian bytes + fn from_bytes_be(bytes: &[UInt8]) -> Result; + + /// Converts to big-endian bytes + fn to_bytes_be(&self) -> Vec>; +} + +impl UInt64Ext for UInt64 { + fn shr(&self, by: usize) -> Self { + assert!(by < 64); + + let zeros = iter::repeat(Boolean::constant(false)).take(by); + let new_bits: Vec<_> = self + .to_bits_le() + .into_iter() + .skip(by) + .chain(zeros) + .collect(); + UInt64::from_bits_le(&new_bits) + } + + fn not(&self) -> Self { + let new_bits: Vec<_> = self.to_bits_le().iter().map(Boolean::not).collect(); + UInt64::from_bits_le(&new_bits) + } + + fn bitand(&self, rhs: &Self) -> Result { + let new_bits: Result, SynthesisError> = self + .to_bits_le() + .into_iter() + .zip(rhs.to_bits_le().into_iter()) + .map(|(a, b)| a.and(&b)) + .collect(); + Ok(UInt64::from_bits_le(&new_bits?)) + } + + fn from_bytes_be(bytes: &[UInt8]) -> Result { + assert_eq!(bytes.len(), 8); + + let mut bits: Vec> = Vec::new(); + for byte in bytes.iter().rev() { + let b: Vec> = byte.to_bits_le()?; + bits.extend(b); + } + Ok(UInt64::from_bits_le(&bits)) + } + + fn to_bytes_be(&self) -> Vec> { + self.to_bits_le() + .chunks(8) + .rev() + .map(UInt8::from_bits_le) + .collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + + use ark_bls12_377::Fr; + use ark_r1cs_std::{bits::uint64::UInt64, R1CSVar}; + use ark_std::rand::Rng; + + const NUM_TESTS: usize = 10_000; + + #[test] + fn test_shr() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = rng.gen::(); + let by = rng.gen::() % 64; + assert_eq!(UInt64::::constant(x).shr(by).value().unwrap(), x >> by); + } + } + + #[test] + fn test_bitand() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = rng.gen::(); + let y = rng.gen::(); + assert_eq!( + UInt64::::constant(x) + .bitand(&UInt64::constant(y)) + .unwrap() + .value() + .unwrap(), + x & y + ); + } + } + + #[test] + fn test_to_from_bytes_be() { + let mut rng = ark_std::test_rng(); + for _ in 0..NUM_TESTS { + let x = UInt64::::constant(rng.gen::()); + let bytes = x.to_bytes_be(); + let y = UInt64::from_bytes_be(&bytes).unwrap(); + assert_eq!(x.value(), y.value()); + } + } +} From e26876e3a0bac542519b815179047d7f298a1baa Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 13 Jan 2023 14:40:22 +0100 Subject: [PATCH 5/9] Added sha384 and sha512 to the crh mod --- src/crh/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/crh/mod.rs b/src/crh/mod.rs index 5b464791..22ba3760 100644 --- a/src/crh/mod.rs +++ b/src/crh/mod.rs @@ -7,6 +7,8 @@ pub mod injective_map; pub mod pedersen; pub mod poseidon; pub mod sha256; +pub mod sha384; +pub mod sha512; use crate::Error; From a59307b968d624bdb35cdebf1a5611308b5920bb Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Mon, 16 Jan 2023 10:55:21 +0100 Subject: [PATCH 6/9] Fixed comment at top of sha384 --- src/crh/sha384/constraints.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crh/sha384/constraints.rs b/src/crh/sha384/constraints.rs index 82bda3ed..53ee9183 100644 --- a/src/crh/sha384/constraints.rs +++ b/src/crh/sha384/constraints.rs @@ -1,6 +1,5 @@ -// This file was adapted from -// the sha384 code which was adapted from -// https://github.com/nanpuyue/sha384/blob/bf6656b7dc72e76bb617445a8865f906670e585b/src/lib.rs +// This file was adapted from the sha256 code which was adapted from +// https://github.com/nanpuyue/sha256/blob/bf6656b7dc72e76bb617445a8865f906670e585b/src/lib.rs // See LICENSE-MIT in the root directory for a copy of the license // Thank you! From 755d4b8dc2592eb9c74ae2f467a839e42196c5d0 Mon Sep 17 00:00:00 2001 From: Tom Godden Date: Fri, 20 Jan 2023 10:11:33 +0100 Subject: [PATCH 7/9] Fixed comments about output length of sha512/sha384 --- src/crh/sha384/mod.rs | 2 +- src/crh/sha512/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crh/sha384/mod.rs b/src/crh/sha384/mod.rs index a613ff39..2547d0a7 100644 --- a/src/crh/sha384/mod.rs +++ b/src/crh/sha384/mod.rs @@ -19,7 +19,7 @@ use sha2::digest::Digest; impl CRHScheme for Sha384 { type Input = [u8]; - // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + // This is always 48 bytes. It has to be a Vec to impl CanonicalSerialize type Output = Vec; // There are no parameters for SHA384 type Parameters = (); diff --git a/src/crh/sha512/mod.rs b/src/crh/sha512/mod.rs index 9823aa48..884108ae 100644 --- a/src/crh/sha512/mod.rs +++ b/src/crh/sha512/mod.rs @@ -19,7 +19,7 @@ use sha2::digest::Digest; impl CRHScheme for Sha512 { type Input = [u8]; - // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize + // This is always 64 bytes. It has to be a Vec to impl CanonicalSerialize type Output = Vec; // There are no parameters for SHA512 type Parameters = (); From 90068c83f7572803c1af0bd75e2d6a04ae4122e8 Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Fri, 30 Jun 2023 09:18:55 -0700 Subject: [PATCH 8/9] Apply suggestions from code review --- src/crh/sha384/mod.rs | 5 ++--- src/crh/sha512/mod.rs | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/crh/sha384/mod.rs b/src/crh/sha384/mod.rs index 2547d0a7..eebdb7ae 100644 --- a/src/crh/sha384/mod.rs +++ b/src/crh/sha384/mod.rs @@ -20,7 +20,7 @@ use sha2::digest::Digest; impl CRHScheme for Sha384 { type Input = [u8]; // This is always 48 bytes. It has to be a Vec to impl CanonicalSerialize - type Output = Vec; + type Output = [u8; 48]; // There are no parameters for SHA384 type Parameters = (); @@ -40,8 +40,7 @@ impl CRHScheme for Sha384 { impl TwoToOneCRHScheme for Sha384 { type Input = [u8]; - // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize - type Output = Vec; + type Output = [u8; 48]; // There are no parameters for SHA384 type Parameters = (); diff --git a/src/crh/sha512/mod.rs b/src/crh/sha512/mod.rs index 884108ae..f35080fa 100644 --- a/src/crh/sha512/mod.rs +++ b/src/crh/sha512/mod.rs @@ -19,8 +19,7 @@ use sha2::digest::Digest; impl CRHScheme for Sha512 { type Input = [u8]; - // This is always 64 bytes. It has to be a Vec to impl CanonicalSerialize - type Output = Vec; + type Output = [u8; 64]; // There are no parameters for SHA512 type Parameters = (); @@ -40,8 +39,7 @@ impl CRHScheme for Sha512 { impl TwoToOneCRHScheme for Sha512 { type Input = [u8]; - // This is always 32 bytes. It has to be a Vec to impl CanonicalSerialize - type Output = Vec; + type Output = [u8; 64]; // There are no parameters for SHA512 type Parameters = (); From e1f3b0fb5b96a8c07f2e37e586cb818edffd451b Mon Sep 17 00:00:00 2001 From: Pratyush Mishra Date: Thu, 25 Jan 2024 11:10:28 -0500 Subject: [PATCH 9/9] Tweak --- src/crh/sha384/constraints.rs | 21 +++--- src/crh/sha384/mod.rs | 39 ++++++++--- src/crh/sha384/r1cs_utils.rs | 122 ---------------------------------- src/crh/sha512/constraints.rs | 23 ++++--- src/crh/sha512/mod.rs | 40 ++++++++--- src/crh/sha512/r1cs_utils.rs | 122 ---------------------------------- 6 files changed, 81 insertions(+), 286 deletions(-) delete mode 100644 src/crh/sha384/r1cs_utils.rs delete mode 100644 src/crh/sha512/r1cs_utils.rs diff --git a/src/crh/sha384/constraints.rs b/src/crh/sha384/constraints.rs index 53ee9183..d6af0009 100644 --- a/src/crh/sha384/constraints.rs +++ b/src/crh/sha384/constraints.rs @@ -4,7 +4,7 @@ // Thank you! use crate::crh::{ - sha384::{r1cs_utils::UInt64Ext, Sha384}, + sha384::Sha384, CRHSchemeGadget, TwoToOneCRHSchemeGadget, }; @@ -13,7 +13,7 @@ use core::{borrow::Borrow, iter, marker::PhantomData}; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, - bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBytesGadget}, + boolean::Boolean, uint64::UInt64, uint8::UInt8, convert::ToBytesGadget, eq::EqGadget, select::CondSelectGadget, R1CSVar, @@ -95,7 +95,7 @@ impl Sha384Gadget { let x3 = w[i - 2].shr(6); x1.xor(&x2)?.xor(&x3)? }; - w[i] = UInt64::addmany(&[w[i - 16].clone(), s0, w[i - 7].clone(), s1])?; + w[i] = w[i - 16].wrapping_add_many(&[s0, w[i - 7].clone(), s1])?; } let mut h = state.to_vec(); @@ -123,22 +123,21 @@ impl Sha384Gadget { let x3 = h[4].rotr(41); x1.xor(&x2)?.xor(&x3)? }; - let t0 = - UInt64::addmany(&[h[7].clone(), s1, ch, UInt64::constant(K[i]), w[i].clone()])?; - let t1 = UInt64::addmany(&[s0, ma])?; + let t0 = h[7].wrapping_add_many(&[s1, ch, UInt64::constant(K[i]), w[i].clone()])?; + let t1 = s0.wrapping_add(ma)?; h[7] = h[6].clone(); h[6] = h[5].clone(); h[5] = h[4].clone(); - h[4] = UInt64::addmany(&[h[3].clone(), t0.clone()])?; + h[4] = h[3].wrapping_add(&t0)?; h[3] = h[2].clone(); h[2] = h[1].clone(); h[1] = h[0].clone(); - h[0] = UInt64::addmany(&[t0, t1])?; + h[0] = t0.wrapping_add(&t1)?; } for (s, hi) in state.iter_mut().zip(h.iter()) { - *s = UInt64::addmany(&[s.clone(), hi.clone()])?; + s.wrapping_add_in_place(hi)?; } Ok(()) @@ -218,7 +217,7 @@ impl Sha384Gadget { /// Contains a 64-byte SHA384 digest #[derive(Clone, Debug)] -pub struct DigestVar(pub Vec>); +pub struct DigestVar([UInt8; 48]); impl EqGadget for DigestVar where @@ -230,7 +229,7 @@ where } impl ToBytesGadget for DigestVar { - fn to_bytes(&self) -> Result>, SynthesisError> { + fn to_bytes_le(&self) -> Result>, SynthesisError> { Ok(self.0.clone()) } } diff --git a/src/crh/sha384/mod.rs b/src/crh/sha384/mod.rs index eebdb7ae..75b6816d 100644 --- a/src/crh/sha384/mod.rs +++ b/src/crh/sha384/mod.rs @@ -1,14 +1,12 @@ use crate::crh::{CRHScheme, TwoToOneCRHScheme}; -use crate::{Error, Vec}; +use crate::Error; +use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; use ark_std::rand::Rng; // Re-export the RustCrypto Sha384 type and its associated traits pub use sha2::{digest, Sha384}; -#[cfg(feature = "r1cs")] -mod r1cs_utils; - #[cfg(feature = "r1cs")] pub mod constraints; @@ -17,10 +15,31 @@ pub mod constraints; use core::borrow::Borrow; use sha2::digest::Digest; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, CanonicalSerialize, CanonicalDeserialize)] +pub struct Output(pub [u8; 48]); + +impl Default for Output { + fn default() -> Self { + Self([0u8; 48]) + } +} + +impl AsRef<[u8]> for Output { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From> for Output { + fn from(output: sha2::digest::Output) -> Self { + Self(output.into()) + } +} + impl CRHScheme for Sha384 { type Input = [u8]; // This is always 48 bytes. It has to be a Vec to impl CanonicalSerialize - type Output = [u8; 48]; + type Output = Output; // There are no parameters for SHA384 type Parameters = (); @@ -34,13 +53,13 @@ impl CRHScheme for Sha384 { _parameters: &Self::Parameters, input: T, ) -> Result { - Ok(Sha384::digest(input.borrow()).to_vec()) + Ok(Sha384::digest(input.borrow()).into()) } } impl TwoToOneCRHScheme for Sha384 { type Input = [u8]; - type Output = [u8; 48]; + type Output = Output; // There are no parameters for SHA384 type Parameters = (); @@ -62,7 +81,7 @@ impl TwoToOneCRHScheme for Sha384 { let mut h = Sha384::default(); h.update(left_input); h.update(right_input); - Ok(h.finalize().to_vec()) + Ok(h.finalize().into()) } // Evaluates SHA384(left_input || right_input) @@ -73,8 +92,8 @@ impl TwoToOneCRHScheme for Sha384 { ) -> Result { ::evaluate( parameters, - left_input.borrow().as_slice(), - right_input.borrow().as_slice(), + left_input.borrow().0.as_slice(), + right_input.borrow().0.as_slice(), ) } } diff --git a/src/crh/sha384/r1cs_utils.rs b/src/crh/sha384/r1cs_utils.rs deleted file mode 100644 index 41c24d4f..00000000 --- a/src/crh/sha384/r1cs_utils.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::Vec; - -use ark_ff::PrimeField; -use ark_r1cs_std::bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBitsGadget}; -use ark_relations::r1cs::SynthesisError; -use core::iter; - -/// Extra traits not automatically implemented by UInt64 -pub(crate) trait UInt64Ext: Sized { - /// Right shift - fn shr(&self, by: usize) -> Self; - - /// Bitwise NOT - fn not(&self) -> Self; - - /// Bitwise AND - fn bitand(&self, rhs: &Self) -> Result; - - /// Converts from big-endian bytes - fn from_bytes_be(bytes: &[UInt8]) -> Result; - - /// Converts to big-endian bytes - fn to_bytes_be(&self) -> Vec>; -} - -impl UInt64Ext for UInt64 { - fn shr(&self, by: usize) -> Self { - assert!(by < 64); - - let zeros = iter::repeat(Boolean::constant(false)).take(by); - let new_bits: Vec<_> = self - .to_bits_le() - .into_iter() - .skip(by) - .chain(zeros) - .collect(); - UInt64::from_bits_le(&new_bits) - } - - fn not(&self) -> Self { - let new_bits: Vec<_> = self.to_bits_le().iter().map(Boolean::not).collect(); - UInt64::from_bits_le(&new_bits) - } - - fn bitand(&self, rhs: &Self) -> Result { - let new_bits: Result, SynthesisError> = self - .to_bits_le() - .into_iter() - .zip(rhs.to_bits_le().into_iter()) - .map(|(a, b)| a.and(&b)) - .collect(); - Ok(UInt64::from_bits_le(&new_bits?)) - } - - fn from_bytes_be(bytes: &[UInt8]) -> Result { - assert_eq!(bytes.len(), 8); - - let mut bits: Vec> = Vec::new(); - for byte in bytes.iter().rev() { - let b: Vec> = byte.to_bits_le()?; - bits.extend(b); - } - Ok(UInt64::from_bits_le(&bits)) - } - - fn to_bytes_be(&self) -> Vec> { - self.to_bits_le() - .chunks(8) - .rev() - .map(UInt8::from_bits_le) - .collect() - } -} - -#[cfg(test)] -mod test { - use super::*; - - use ark_bls12_377::Fr; - use ark_r1cs_std::{bits::uint64::UInt64, R1CSVar}; - use ark_std::rand::Rng; - - const NUM_TESTS: usize = 10_000; - - #[test] - fn test_shr() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = rng.gen::(); - let by = rng.gen::() % 64; - assert_eq!(UInt64::::constant(x).shr(by).value().unwrap(), x >> by); - } - } - - #[test] - fn test_bitand() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = rng.gen::(); - let y = rng.gen::(); - assert_eq!( - UInt64::::constant(x) - .bitand(&UInt64::constant(y)) - .unwrap() - .value() - .unwrap(), - x & y - ); - } - } - - #[test] - fn test_to_from_bytes_be() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = UInt64::::constant(rng.gen::()); - let bytes = x.to_bytes_be(); - let y = UInt64::from_bytes_be(&bytes).unwrap(); - assert_eq!(x.value(), y.value()); - } - } -} diff --git a/src/crh/sha512/constraints.rs b/src/crh/sha512/constraints.rs index 5620db9a..b5f1ac4c 100644 --- a/src/crh/sha512/constraints.rs +++ b/src/crh/sha512/constraints.rs @@ -4,7 +4,7 @@ // Thank you! use crate::crh::{ - sha512::{r1cs_utils::UInt64Ext, Sha512}, + sha512::Sha512, CRHSchemeGadget, TwoToOneCRHSchemeGadget, }; @@ -13,7 +13,7 @@ use core::{borrow::Borrow, iter, marker::PhantomData}; use ark_ff::PrimeField; use ark_r1cs_std::{ alloc::{AllocVar, AllocationMode}, - bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBytesGadget}, + boolean::Boolean, uint64::UInt64, uint8::UInt8, convert::ToBytesGadget, eq::EqGadget, select::CondSelectGadget, R1CSVar, @@ -95,7 +95,8 @@ impl Sha512Gadget { let x3 = w[i - 2].shr(6); x1.xor(&x2)?.xor(&x3)? }; - w[i] = UInt64::addmany(&[w[i - 16].clone(), s0, w[i - 7].clone(), s1])?; + + w[i] = w[i - 16].wrapping_add_many(&[s0, w[i - 7].clone(), s1])?; } let mut h = state.to_vec(); @@ -123,22 +124,22 @@ impl Sha512Gadget { let x3 = h[4].rotr(41); x1.xor(&x2)?.xor(&x3)? }; - let t0 = - UInt64::addmany(&[h[7].clone(), s1, ch, UInt64::constant(K[i]), w[i].clone()])?; - let t1 = UInt64::addmany(&[s0, ma])?; + + let t0 = h[7].wrapping_add_many(&[s1, ch, UInt64::constant(K[i]), w[i].clone()])?; + let t1 = s0.wrapping_add(ma)?; h[7] = h[6].clone(); h[6] = h[5].clone(); h[5] = h[4].clone(); - h[4] = UInt64::addmany(&[h[3].clone(), t0.clone()])?; + h[4] = h[3].wrapping_add(&t0)?; h[3] = h[2].clone(); h[2] = h[1].clone(); h[1] = h[0].clone(); - h[0] = UInt64::addmany(&[t0, t1])?; + h[0] = t0.wrapping_add(&t1)?; } for (s, hi) in state.iter_mut().zip(h.iter()) { - *s = UInt64::addmany(&[s.clone(), hi.clone()])?; + s.wrapping_add_in_place(hi)?; } Ok(()) @@ -218,7 +219,7 @@ impl Sha512Gadget { /// Contains a 64-byte SHA512 digest #[derive(Clone, Debug)] -pub struct DigestVar(pub Vec>); +pub struct DigestVar([UInt8; 64]); impl EqGadget for DigestVar where @@ -230,7 +231,7 @@ where } impl ToBytesGadget for DigestVar { - fn to_bytes(&self) -> Result>, SynthesisError> { + fn to_bytes_le(&self) -> Result>, SynthesisError> { Ok(self.0.clone()) } } diff --git a/src/crh/sha512/mod.rs b/src/crh/sha512/mod.rs index f35080fa..81c9a086 100644 --- a/src/crh/sha512/mod.rs +++ b/src/crh/sha512/mod.rs @@ -1,14 +1,12 @@ use crate::crh::{CRHScheme, TwoToOneCRHScheme}; -use crate::{Error, Vec}; +use crate::Error; +use ark_serialize::{CanonicalSerialize, CanonicalDeserialize}; use ark_std::rand::Rng; // Re-export the RustCrypto Sha512 type and its associated traits pub use sha2::{digest, Sha512}; -#[cfg(feature = "r1cs")] -mod r1cs_utils; - #[cfg(feature = "r1cs")] pub mod constraints; @@ -17,9 +15,30 @@ pub mod constraints; use core::borrow::Borrow; use sha2::digest::Digest; +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, CanonicalSerialize, CanonicalDeserialize)] +pub struct Output(pub [u8; 64]); + +impl Default for Output { + fn default() -> Self { + Self([0u8; 64]) + } +} + +impl AsRef<[u8]> for Output { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl From> for Output { + fn from(output: sha2::digest::Output) -> Self { + Self(output.into()) + } +} + impl CRHScheme for Sha512 { type Input = [u8]; - type Output = [u8; 64]; + type Output = Output; // There are no parameters for SHA512 type Parameters = (); @@ -33,13 +52,14 @@ impl CRHScheme for Sha512 { _parameters: &Self::Parameters, input: T, ) -> Result { - Ok(Sha512::digest(input.borrow()).to_vec()) + Ok(Sha512::digest(input.borrow()).into()) } } + impl TwoToOneCRHScheme for Sha512 { type Input = [u8]; - type Output = [u8; 64]; + type Output = Output; // There are no parameters for SHA512 type Parameters = (); @@ -61,7 +81,7 @@ impl TwoToOneCRHScheme for Sha512 { let mut h = Sha512::default(); h.update(left_input); h.update(right_input); - Ok(h.finalize().to_vec()) + Ok(h.finalize().into()) } // Evaluates SHA512(left_input || right_input) @@ -72,8 +92,8 @@ impl TwoToOneCRHScheme for Sha512 { ) -> Result { ::evaluate( parameters, - left_input.borrow().as_slice(), - right_input.borrow().as_slice(), + left_input.borrow().0.as_slice(), + right_input.borrow().0.as_slice(), ) } } diff --git a/src/crh/sha512/r1cs_utils.rs b/src/crh/sha512/r1cs_utils.rs deleted file mode 100644 index 41c24d4f..00000000 --- a/src/crh/sha512/r1cs_utils.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::Vec; - -use ark_ff::PrimeField; -use ark_r1cs_std::bits::{boolean::Boolean, uint64::UInt64, uint8::UInt8, ToBitsGadget}; -use ark_relations::r1cs::SynthesisError; -use core::iter; - -/// Extra traits not automatically implemented by UInt64 -pub(crate) trait UInt64Ext: Sized { - /// Right shift - fn shr(&self, by: usize) -> Self; - - /// Bitwise NOT - fn not(&self) -> Self; - - /// Bitwise AND - fn bitand(&self, rhs: &Self) -> Result; - - /// Converts from big-endian bytes - fn from_bytes_be(bytes: &[UInt8]) -> Result; - - /// Converts to big-endian bytes - fn to_bytes_be(&self) -> Vec>; -} - -impl UInt64Ext for UInt64 { - fn shr(&self, by: usize) -> Self { - assert!(by < 64); - - let zeros = iter::repeat(Boolean::constant(false)).take(by); - let new_bits: Vec<_> = self - .to_bits_le() - .into_iter() - .skip(by) - .chain(zeros) - .collect(); - UInt64::from_bits_le(&new_bits) - } - - fn not(&self) -> Self { - let new_bits: Vec<_> = self.to_bits_le().iter().map(Boolean::not).collect(); - UInt64::from_bits_le(&new_bits) - } - - fn bitand(&self, rhs: &Self) -> Result { - let new_bits: Result, SynthesisError> = self - .to_bits_le() - .into_iter() - .zip(rhs.to_bits_le().into_iter()) - .map(|(a, b)| a.and(&b)) - .collect(); - Ok(UInt64::from_bits_le(&new_bits?)) - } - - fn from_bytes_be(bytes: &[UInt8]) -> Result { - assert_eq!(bytes.len(), 8); - - let mut bits: Vec> = Vec::new(); - for byte in bytes.iter().rev() { - let b: Vec> = byte.to_bits_le()?; - bits.extend(b); - } - Ok(UInt64::from_bits_le(&bits)) - } - - fn to_bytes_be(&self) -> Vec> { - self.to_bits_le() - .chunks(8) - .rev() - .map(UInt8::from_bits_le) - .collect() - } -} - -#[cfg(test)] -mod test { - use super::*; - - use ark_bls12_377::Fr; - use ark_r1cs_std::{bits::uint64::UInt64, R1CSVar}; - use ark_std::rand::Rng; - - const NUM_TESTS: usize = 10_000; - - #[test] - fn test_shr() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = rng.gen::(); - let by = rng.gen::() % 64; - assert_eq!(UInt64::::constant(x).shr(by).value().unwrap(), x >> by); - } - } - - #[test] - fn test_bitand() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = rng.gen::(); - let y = rng.gen::(); - assert_eq!( - UInt64::::constant(x) - .bitand(&UInt64::constant(y)) - .unwrap() - .value() - .unwrap(), - x & y - ); - } - } - - #[test] - fn test_to_from_bytes_be() { - let mut rng = ark_std::test_rng(); - for _ in 0..NUM_TESTS { - let x = UInt64::::constant(rng.gen::()); - let bytes = x.to_bytes_be(); - let y = UInt64::from_bytes_be(&bytes).unwrap(); - assert_eq!(x.value(), y.value()); - } - } -}