diff --git a/crypto/ciphersuite/src/dalek.rs b/crypto/ciphersuite/src/dalek.rs index bd9c70c1..a04195b2 100644 --- a/crypto/ciphersuite/src/dalek.rs +++ b/crypto/ciphersuite/src/dalek.rs @@ -28,6 +28,12 @@ macro_rules! dalek_curve { $Point::generator() } + fn reduce_512(mut scalar: [u8; 64]) -> Self::F { + let res = Scalar::from_bytes_mod_order_wide(&scalar); + scalar.zeroize(); + res + } + fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { Scalar::from_hash(Sha512::new_with_prefix(&[dst, data].concat())) } diff --git a/crypto/ciphersuite/src/ed448.rs b/crypto/ciphersuite/src/ed448.rs index 8a927251..0b19ffa5 100644 --- a/crypto/ciphersuite/src/ed448.rs +++ b/crypto/ciphersuite/src/ed448.rs @@ -66,6 +66,12 @@ impl Ciphersuite for Ed448 { Point::generator() } + fn reduce_512(mut scalar: [u8; 64]) -> Self::F { + let res = Self::hash_to_F(b"Ciphersuite-reduce_512", &scalar); + scalar.zeroize(); + res + } + fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { Scalar::wide_reduce(Self::H::digest([dst, data].concat()).as_ref().try_into().unwrap()) } diff --git a/crypto/ciphersuite/src/helioselene.rs b/crypto/ciphersuite/src/helioselene.rs index d5375305..9b74e9e5 100644 --- a/crypto/ciphersuite/src/helioselene.rs +++ b/crypto/ciphersuite/src/helioselene.rs @@ -21,6 +21,12 @@ impl Ciphersuite for Helios { ::generator() } + fn reduce_512(mut scalar: [u8; 64]) -> Self::F { + let res = HelioseleneField::wide_reduce(scalar); + scalar.zeroize(); + res + } + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { let mut uniform = [0; 64]; let mut hash = Blake2b512::digest([dst, msg].concat()); @@ -46,6 +52,12 @@ impl Ciphersuite for Selene { ::generator() } + fn reduce_512(mut scalar: [u8; 64]) -> Self::F { + let res = Field25519::wide_reduce(scalar); + scalar.zeroize(); + res + } + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { let mut uniform = [0; 64]; let mut hash = Blake2b512::digest([dst, msg].concat()); diff --git a/crypto/ciphersuite/src/kp256.rs b/crypto/ciphersuite/src/kp256.rs index 37fdb2e4..a1f64ae4 100644 --- a/crypto/ciphersuite/src/kp256.rs +++ b/crypto/ciphersuite/src/kp256.rs @@ -6,7 +6,7 @@ use group::ff::PrimeField; use elliptic_curve::{ generic_array::GenericArray, - bigint::{NonZero, CheckedAdd, Encoding, U384}, + bigint::{NonZero, CheckedAdd, Encoding, U384, U512}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}, }; @@ -31,6 +31,22 @@ macro_rules! kp_curve { $lib::ProjectivePoint::GENERATOR } + fn reduce_512(scalar: [u8; 64]) -> Self::F { + let mut modulus = [0; 64]; + modulus[32 ..].copy_from_slice(&(Self::F::ZERO - Self::F::ONE).to_bytes()); + let modulus = U512::from_be_slice(&modulus).checked_add(&U512::ONE).unwrap(); + + let mut wide = + U512::from_be_bytes(scalar).rem(&NonZero::new(modulus).unwrap()).to_be_bytes(); + + let mut array = *GenericArray::from_slice(&wide[32 ..]); + let res = $lib::Scalar::from_repr(array).unwrap(); + + wide.zeroize(); + array.zeroize(); + res + } + fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F { // While one of these two libraries does support directly hashing to the Scalar field, the // other doesn't. While that's probably an oversight, this is a universally working method diff --git a/crypto/ciphersuite/src/lib.rs b/crypto/ciphersuite/src/lib.rs index 37007939..d338c8ba 100644 --- a/crypto/ciphersuite/src/lib.rs +++ b/crypto/ciphersuite/src/lib.rs @@ -67,6 +67,13 @@ pub trait Ciphersuite: // While group does provide this in its API, privacy coins may want to use a custom basepoint fn generator() -> Self::G; + /// Reduce 512 bits into a uniform scalar. + /// + /// If 512 bits is insufficient to perform a reduction into a uniform scalar, the ciphersuite + /// will perform a hash to sample the necessary bits. + #[allow(non_snake_case)] + fn reduce_512(scalar: [u8; 64]) -> Self::F; + /// Hash the provided domain-separation tag and message to a scalar. Ciphersuites MAY naively /// prefix the tag to the message, enabling transpotion between the two. Accordingly, this /// function should NOT be used in any scheme where one tag is a valid substring of another diff --git a/crypto/dkg/src/tests/promote.rs b/crypto/dkg/src/tests/promote.rs index 99c00433..242f085b 100644 --- a/crypto/dkg/src/tests/promote.rs +++ b/crypto/dkg/src/tests/promote.rs @@ -28,6 +28,10 @@ impl Ciphersuite for AltGenerator { C::G::generator() * ::hash_to_F(b"DKG Promotion Test", b"generator") } + fn reduce_512(scalar: [u8; 64]) -> Self::F { + ::reduce_512(scalar) + } + fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { ::hash_to_F(dst, data) } diff --git a/crypto/fcmps/circuit-abstraction/src/lib.rs b/crypto/fcmps/circuit-abstraction/src/lib.rs index be816363..8a4b826b 100644 --- a/crypto/fcmps/circuit-abstraction/src/lib.rs +++ b/crypto/fcmps/circuit-abstraction/src/lib.rs @@ -8,10 +8,7 @@ use std_shims::{vec, vec::Vec}; use zeroize::{Zeroize, ZeroizeOnDrop}; -use ciphersuite::{ - group::ff::{Field, PrimeField}, - Ciphersuite, -}; +use ciphersuite::{group::ff::Field, Ciphersuite}; use generalized_bulletproofs::{ ScalarVector, PedersenCommitment, PedersenVectorCommitment, ProofGenerators, @@ -29,7 +26,7 @@ pub trait Transcript { /// /// It is the caller's responsibility to have properly transcripted all variables prior to /// sampling this challenge. - fn challenge(&mut self) -> F; + fn challenge(&mut self) -> C::F; /// Sample a challenge as a byte array. /// @@ -38,16 +35,16 @@ pub trait Transcript { fn challenge_bytes(&mut self) -> [u8; 64]; } impl Transcript for ProverTranscript { - fn challenge(&mut self) -> F { - self.challenge() + fn challenge(&mut self) -> C::F { + self.challenge::() } fn challenge_bytes(&mut self) -> [u8; 64] { self.challenge_bytes() } } impl Transcript for VerifierTranscript<'_> { - fn challenge(&mut self) -> F { - self.challenge() + fn challenge(&mut self) -> C::F { + self.challenge::() } fn challenge_bytes(&mut self) -> [u8; 64] { self.challenge_bytes() diff --git a/crypto/fcmps/ec-gadgets/src/dlog.rs b/crypto/fcmps/ec-gadgets/src/dlog.rs index 03fdff5a..d124e07f 100644 --- a/crypto/fcmps/ec-gadgets/src/dlog.rs +++ b/crypto/fcmps/ec-gadgets/src/dlog.rs @@ -356,7 +356,7 @@ impl EcDlogGadgets for Circuit { let sign_of_point_0 = (sign_of_points[0] & 1) == 1; let sign_of_point_1 = ((sign_of_points[0] >> 1) & 1) == 1; let (c0_x, c0_y) = loop { - let c0_x: C::F = transcript.challenge(); + let c0_x = transcript.challenge::(); let Some(c0_y) = Option::::from(((c0_x.square() * c0_x) + (curve.a * c0_x) + curve.b).sqrt()) else { @@ -367,7 +367,7 @@ impl EcDlogGadgets for Circuit { break (c0_x, if bool::from(c0_y.is_odd()) != sign_of_point_0 { -c0_y } else { c0_y }); }; let (c1_x, c1_y) = loop { - let c1_x: C::F = transcript.challenge(); + let c1_x = transcript.challenge::(); let Some(c1_y) = Option::::from(((c1_x.square() * c1_x) + (curve.a * c1_x) + curve.b).sqrt()) else { diff --git a/crypto/fcmps/src/gadgets/interactive.rs b/crypto/fcmps/src/gadgets/interactive.rs index 14824247..9a5828e3 100644 --- a/crypto/fcmps/src/gadgets/interactive.rs +++ b/crypto/fcmps/src/gadgets/interactive.rs @@ -22,9 +22,9 @@ impl Circuit { } // Create challenges which we use to aggregate tuples into LinCombs - let mut challenges: Vec = vec![]; + let mut challenges = vec![]; for _ in 0 .. member.len() { - challenges.push(transcript.challenge()); + challenges.push(transcript.challenge::()); } // Aggregate the claimed member diff --git a/crypto/fcmps/src/lib.rs b/crypto/fcmps/src/lib.rs index 2c90811a..b8db9b3a 100644 --- a/crypto/fcmps/src/lib.rs +++ b/crypto/fcmps/src/lib.rs @@ -644,13 +644,13 @@ where let mut root_blind_pok = [0; 64]; if matches!(tree, TreeRoot::C1(_)) { - let s = *root_blind_r_C1.unwrap() + - (transcript.challenge::<::F>() * root_blind_C1.unwrap()); + let s = + *root_blind_r_C1.unwrap() + (transcript.challenge::() * root_blind_C1.unwrap()); root_blind_pok[.. 32].copy_from_slice(&root_blind_R); root_blind_pok[32 ..].copy_from_slice(s.to_repr().as_ref()); } else { - let s = *root_blind_r_C2.unwrap() + - (transcript.challenge::<::F>() * root_blind_C2.unwrap()); + let s = + *root_blind_r_C2.unwrap() + (transcript.challenge::() * root_blind_C2.unwrap()); root_blind_pok[.. 32].copy_from_slice(&root_blind_R); root_blind_pok[32 ..].copy_from_slice(s.to_repr().as_ref()); } @@ -912,7 +912,7 @@ where let R = ::read_G(&mut self.root_blind_pok[.. 32].as_ref())?; let s = ::read_F(&mut self.root_blind_pok[32 ..].as_ref())?; - let c: ::F = transcript.challenge(); + let c = transcript.challenge::(); // R + cX == sH, where X is the difference in the roots // (which should only be the randomness, and H is the generator for the randomness) @@ -925,7 +925,7 @@ where let R = ::read_G(&mut self.root_blind_pok[.. 32].as_ref())?; let s = ::read_F(&mut self.root_blind_pok[32 ..].as_ref())?; - let c: ::F = transcript.challenge(); + let c = transcript.challenge::(); // R + cX == sH, where X is the difference in the roots // (which should only be the randomness, and H is the generator for the randomness) diff --git a/crypto/generalized-bulletproofs/src/arithmetic_circuit_proof.rs b/crypto/generalized-bulletproofs/src/arithmetic_circuit_proof.rs index 3e443447..9820d4c0 100644 --- a/crypto/generalized-bulletproofs/src/arithmetic_circuit_proof.rs +++ b/crypto/generalized-bulletproofs/src/arithmetic_circuit_proof.rs @@ -292,8 +292,8 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { transcript.push_point(AI); transcript.push_point(AO); transcript.push_point(S); - let y = transcript.challenge(); - let z = transcript.challenge(); + let y = transcript.challenge::(); + let z = transcript.challenge::(); let YzChallenges { y_inv, z } = self.yz_challenges(y, z); let y = ScalarVector::powers(y, n); @@ -416,7 +416,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { transcript.push_point(multiexp(&[(*t, self.generators.g()), (*tau, self.generators.h())])); } - let x: ScalarVector = ScalarVector::powers(transcript.challenge(), t.len()); + let x: ScalarVector = ScalarVector::powers(transcript.challenge::(), t.len()); let poly_eval = |poly: &[ScalarVector], x: &ScalarVector<_>| -> ScalarVector<_> { let mut res = ScalarVector::::new(poly[0].0.len()); @@ -480,7 +480,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { transcript.push_scalar(tau_x); transcript.push_scalar(u); transcript.push_scalar(t_caret); - let ip_x = transcript.challenge(); + let ip_x = transcript.challenge::(); P_terms.push((ip_x * t_caret, self.generators.g())); IpStatement::new( self.generators, @@ -523,8 +523,8 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { let AI = transcript.read_point::().map_err(|_| AcError::IncompleteProof)?; let AO = transcript.read_point::().map_err(|_| AcError::IncompleteProof)?; let S = transcript.read_point::().map_err(|_| AcError::IncompleteProof)?; - let y = transcript.challenge(); - let z = transcript.challenge(); + let y = transcript.challenge::(); + let z = transcript.challenge::(); let YzChallenges { y_inv, z } = self.yz_challenges(y, z); let mut l_weights = ScalarVector::new(n); @@ -547,7 +547,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { for _ in 0 .. (t_poly_len - ni - 1) { T_after_ni.push(transcript.read_point::().map_err(|_| AcError::IncompleteProof)?); } - let x: ScalarVector = ScalarVector::powers(transcript.challenge(), t_poly_len); + let x: ScalarVector = ScalarVector::powers(transcript.challenge::(), t_poly_len); let tau_x = transcript.read_scalar::().map_err(|_| AcError::IncompleteProof)?; let u = transcript.read_scalar::().map_err(|_| AcError::IncompleteProof)?; @@ -645,7 +645,7 @@ impl<'a, C: Ciphersuite> ArithmeticCircuitStatement<'a, C> { // Prove for lines 88, 92 with an Inner-Product statement // This inlines Protocol 1, as our IpStatement implements Protocol 2 - let ip_x = transcript.challenge(); + let ip_x = transcript.challenge::(); // P is amended with this additional term verifier.g += verifier_weight * ip_x * t_caret; IpStatement::new(self.generators, y_inv, ip_x, P::Verifier { verifier_weight }) diff --git a/crypto/generalized-bulletproofs/src/inner_product.rs b/crypto/generalized-bulletproofs/src/inner_product.rs index 4c33ab47..a064bdd7 100644 --- a/crypto/generalized-bulletproofs/src/inner_product.rs +++ b/crypto/generalized-bulletproofs/src/inner_product.rs @@ -188,7 +188,7 @@ impl<'a, C: Ciphersuite> IpStatement<'a, C> { // Now that we've calculate L, R, transcript them to receive x (26-27) transcript.push_point(L); transcript.push_point(R); - let x: C::F = transcript.challenge(); + let x: C::F = transcript.challenge::(); let x_inv = x.invert().unwrap(); // The prover and verifier now calculate the following (28-31) @@ -304,7 +304,7 @@ impl<'a, C: Ciphersuite> IpStatement<'a, C> { for _ in 0 .. lr_len { L.push(transcript.read_point::().map_err(|_| IpError::IncompleteProof)?); R.push(transcript.read_point::().map_err(|_| IpError::IncompleteProof)?); - xs.push(transcript.challenge()); + xs.push(transcript.challenge::()); } // We calculate their inverse in batch diff --git a/crypto/generalized-bulletproofs/src/transcript.rs b/crypto/generalized-bulletproofs/src/transcript.rs index d50861b6..80757fae 100644 --- a/crypto/generalized-bulletproofs/src/transcript.rs +++ b/crypto/generalized-bulletproofs/src/transcript.rs @@ -3,7 +3,10 @@ use std_shims::{vec::Vec, io}; use blake2::{Digest, Blake2b512}; use ciphersuite::{ - group::{ff::PrimeField, GroupEncoding}, + group::{ + ff::{Field, PrimeField}, + GroupEncoding, + }, Ciphersuite, }; @@ -13,26 +16,11 @@ const SCALAR: u8 = 0; const POINT: u8 = 1; const CHALLENGE: u8 = 2; -fn challenge(digest: &mut Blake2b512) -> F { - // Ensure this field is small enough this is a successful wide reduction - assert!(F::NUM_BITS <= (512 - 128)); - +fn challenge(digest: &mut Blake2b512) -> C::F { digest.update([CHALLENGE]); - let chl = digest.clone().finalize(); - - let mut res = F::ZERO; - for (i, mut byte) in chl.iter().cloned().enumerate() { - for j in 0 .. 8 { - let lsb = byte & 1; - let mut bit = F::from(u64::from(lsb)); - for _ in 0 .. ((i * 8) + j) { - bit = bit.double(); - } - res += bit; - - byte >>= 1; - } - } + let chl = digest.clone().finalize().into(); + + let res = C::reduce_512(chl); // Negligible probability if bool::from(res.is_zero()) { @@ -113,8 +101,8 @@ impl Transcript { } /// Sample a challenge. - pub fn challenge(&mut self) -> F { - challenge(&mut self.digest) + pub fn challenge(&mut self) -> C::F { + challenge::(&mut self.digest) } /// Sample a challenge as a byte array. @@ -182,8 +170,8 @@ impl<'a> VerifierTranscript<'a> { } /// Sample a challenge. - pub fn challenge(&mut self) -> F { - challenge(&mut self.digest) + pub fn challenge(&mut self) -> C::F { + challenge::(&mut self.digest) } /// Sample a challenge as a byte array. diff --git a/networks/monero/ringct/fcmp++/src/sal/multisig.rs b/networks/monero/ringct/fcmp++/src/sal/multisig.rs index 75ceb118..1e3f262c 100644 --- a/networks/monero/ringct/fcmp++/src/sal/multisig.rs +++ b/networks/monero/ringct/fcmp++/src/sal/multisig.rs @@ -39,6 +39,10 @@ impl Ciphersuite for Ed25519T { EdwardsPoint(T()) } + fn reduce_512(scalar: [u8; 64]) -> Self::F { + ::reduce_512(scalar) + } + fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F { ::hash_to_F(dst, data) }