From 3ee7208c11084db34571b007a420f27df37af8b0 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Wed, 8 Nov 2023 13:58:47 -0800 Subject: [PATCH] Use an XOF instead of a PRNG to derive ZK proof challenges. This signals the intent better, and also fixes the issue with consistency between 32- and 64-bit targets. --- synedrion/Cargo.toml | 1 - synedrion/src/cggmp21/sigma/aff_g.rs | 20 ++++++--- synedrion/src/cggmp21/sigma/dec.rs | 16 ++++--- synedrion/src/cggmp21/sigma/enc.rs | 16 ++++--- synedrion/src/cggmp21/sigma/fac.rs | 16 ++++--- synedrion/src/cggmp21/sigma/log_star.rs | 16 ++++--- synedrion/src/cggmp21/sigma/mod_.rs | 11 +++-- synedrion/src/cggmp21/sigma/mul.rs | 16 ++++--- synedrion/src/cggmp21/sigma/mul_star.rs | 16 ++++--- synedrion/src/cggmp21/sigma/prm.rs | 12 ++--- synedrion/src/tools/hashing.rs | 29 +++++++++++-- synedrion/src/uint/signed.rs | 23 +++++++++- synedrion/src/uint/traits.rs | 58 +++++++++++-------------- 13 files changed, 164 insertions(+), 86 deletions(-) diff --git a/synedrion/Cargo.toml b/synedrion/Cargo.toml index 9243e588..9607207b 100644 --- a/synedrion/Cargo.toml +++ b/synedrion/Cargo.toml @@ -18,7 +18,6 @@ sha3 = { version = "0.10", default-features = false } digest = { version = "0.10", default-features = false, features = ["alloc"]} hex = { version = "0.4", default-features = false, features = ["alloc"] } base64 = { version = "0.21", default-features = false, features = ["alloc"] } -rand_chacha = { version = "0.3", default-features = false } crypto-bigint = { version = "0.5.3", features = ["serde"] } crypto-primes = "0.5" diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index a9a78894..6c96e47e 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -9,7 +9,7 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, Randomizer, RandomizerMod, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{FromScalar, NonZero, Signed}; const HASH_TAG: &[u8] = b"P_aff_g"; @@ -45,10 +45,13 @@ impl AffGProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let e_wide = e.into_wide(); let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); @@ -128,12 +131,15 @@ impl AffGProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); - - let aux_pk = aux_rp.public_key(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + + let aux_pk = aux_rp.public_key(); // C^{z_1} (1 + N_0)^{z_2} \omega^{N_0} = A D^e \mod N_0^2 // => C (*) z_1 (+) encrypt_0(z_2, \omega) = A (+) D (*) e diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index b251dc6c..7e133ca4 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -9,7 +9,7 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, Randomizer, RandomizerMod, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{FromScalar, NonZero, Signed}; const HASH_TAG: &[u8] = b"P_dec"; @@ -34,10 +34,13 @@ impl DecProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$ @@ -75,10 +78,13 @@ impl DecProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); // enc(z_1, \omega) == A (+) C (*) e if Ciphertext::new_with_randomizer_signed(pk, &self.z1, &self.omega) diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index b2c12002..a470111d 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -8,7 +8,7 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, Randomizer, RandomizerMod, SecretKeyPaillierPrecomputed, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{NonZero, Signed}; const HASH_TAG: &[u8] = b"P_enc"; @@ -32,10 +32,13 @@ impl EncProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let pk = sk.public_key(); let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$ @@ -72,10 +75,13 @@ impl EncProof

{ aux_rp: &RPParamsMod, // $s$, $t$ aux: &impl Hashable, // CHECK: used to derive `\hat{N}, s, t` ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); // z_1 \in \pm 2^{\ell + \eps} if !self.z1.in_range_bits(P::L_BOUND + P::EPS_BOUND) { diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index e7fb2b26..c0a8aaa5 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -8,7 +8,7 @@ use crate::paillier::{ PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, SecretKeyPaillierPrecomputed, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{HasWide, Integer, NonZero, Signed}; const HASH_TAG: &[u8] = b"P_fac"; @@ -35,10 +35,13 @@ impl FacProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let e_wide = e.into_wide(); let pk = sk.public_key(); @@ -109,10 +112,13 @@ impl FacProof

{ aux_rp: &RPParamsMod, // $s$, $t$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let aux_pk = aux_rp.public_key(); diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index eafdac34..208fe3d7 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -9,7 +9,7 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, Randomizer, RandomizerMod, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{FromScalar, NonZero, Signed}; const HASH_TAG: &[u8] = b"P_log*"; @@ -36,10 +36,13 @@ impl LogStarProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$ @@ -78,10 +81,13 @@ impl LogStarProof

{ aux_rp: &RPParamsMod, // $s$, $t$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); // enc_0(z1, z2) == A (+) C (*) e let c = Ciphertext::new_with_randomizer_signed(pk, &self.z1, &self.z2); diff --git a/synedrion/src/cggmp21/sigma/mod_.rs b/synedrion/src/cggmp21/sigma/mod_.rs index 6563883f..7898f23b 100644 --- a/synedrion/src/cggmp21/sigma/mod_.rs +++ b/synedrion/src/cggmp21/sigma/mod_.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use super::super::SchemeParams; use crate::paillier::{PaillierParams, PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; -use crate::tools::hashing::{Chain, Hash, Hashable}; -use crate::uint::{JacobiSymbol, JacobiSymbolTrait, RandomMod, Retrieve, UintModLike}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; +use crate::uint::{JacobiSymbol, JacobiSymbolTrait, RandomMod, Retrieve, UintLike, UintModLike}; const HASH_TAG: &[u8] = b"P_mod"; @@ -35,10 +35,13 @@ struct ModChallenge(Vec<::Uint>) impl ModChallenge

{ fn new(aux: &impl Hashable, pk: &PublicKeyPaillierPrecomputed) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); + let modulus = pk.modulus_nonzero(); let ys = (0..P::SECURITY_PARAMETER) - .map(|_| ::Uint::random_mod(&mut aux_rng, &modulus)) + .map(|_| ::Uint::from_xof(&mut reader, &modulus)) .collect(); Self(ys) } diff --git a/synedrion/src/cggmp21/sigma/mul.rs b/synedrion/src/cggmp21/sigma/mul.rs index af4b5038..f7db67d1 100644 --- a/synedrion/src/cggmp21/sigma/mul.rs +++ b/synedrion/src/cggmp21/sigma/mul.rs @@ -7,7 +7,7 @@ use super::super::SchemeParams; use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, Randomizer, RandomizerMod, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{Bounded, NonZero, Retrieve, Signed}; const HASH_TAG: &[u8] = b"P_mul"; @@ -32,10 +32,13 @@ impl MulProof

{ cap_y: &Ciphertext, // $Y$ aux: &impl Hashable, ) -> Self { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let alpha_mod = pk.random_invertible_group_elem(rng); let r_mod = RandomizerMod::random(rng, pk); @@ -75,10 +78,13 @@ impl MulProof

{ cap_c: &Ciphertext, // $C = (Y (*) x) * \rho^N$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); // Y^z u^N = A * C^e \mod N^2 if cap_y diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 80b31a33..6a19fb1a 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -11,7 +11,7 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, Randomizer, RandomizerMod, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{FromScalar, NonZero, Signed}; const HASH_TAG: &[u8] = b"P_mul*"; @@ -46,10 +46,13 @@ impl MulStarProof

{ (and judging by the condition the verifier checks, it should be == 0) */ - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let hat_cap_n = &aux_rp.public_key().modulus_nonzero(); // $\hat{N}$ @@ -90,10 +93,13 @@ impl MulStarProof

{ aux_rp: &RPParamsMod, // $\hat{N}$, $s$, $t$ aux: &impl Hashable, ) -> bool { - let mut aux_rng = Hash::new_with_dst(HASH_TAG).chain(aux).finalize_to_rng(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(aux) + .finalize_to_reader(); // Non-interactive challenge - let e = Signed::random_bounded(&mut aux_rng, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = + Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); let aux_pk = aux_rp.public_key(); diff --git a/synedrion/src/cggmp21/sigma/prm.rs b/synedrion/src/cggmp21/sigma/prm.rs index 6ca839db..e82324d6 100644 --- a/synedrion/src/cggmp21/sigma/prm.rs +++ b/synedrion/src/cggmp21/sigma/prm.rs @@ -5,8 +5,8 @@ use alloc::{vec, vec::Vec}; -use rand_core::{CryptoRngCore, RngCore}; - +use digest::XofReader; +use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; @@ -14,7 +14,7 @@ use crate::paillier::{ PaillierParams, PublicKeyPaillierPrecomputed, RPParamsMod, RPSecret, SecretKeyPaillierPrecomputed, }; -use crate::tools::hashing::{Chain, Hash, Hashable}; +use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{ subtle::{Choice, ConditionallySelectable}, Bounded, Retrieve, UintModLike, @@ -73,12 +73,12 @@ struct PrmChallenge(Vec); impl PrmChallenge { fn new(aux: &impl Hashable, commitment: &PrmCommitment

) -> Self { // TODO: generate m/8 random bytes instead and fill the vector bit by bit. - let mut aux_rng = Hash::new_with_dst(HASH_TAG) + let mut reader = XofHash::new_with_dst(HASH_TAG) .chain(aux) .chain(commitment) - .finalize_to_rng(); + .finalize_to_reader(); let mut bytes = vec![0u8; P::SECURITY_PARAMETER]; - aux_rng.fill_bytes(&mut bytes); + reader.read(&mut bytes); Self(bytes.iter().map(|b| b & 1 == 1).collect()) } } diff --git a/synedrion/src/tools/hashing.rs b/synedrion/src/tools/hashing.rs index 4ca18783..e31a9900 100644 --- a/synedrion/src/tools/hashing.rs +++ b/synedrion/src/tools/hashing.rs @@ -2,10 +2,10 @@ use alloc::boxed::Box; use alloc::collections::BTreeMap; use alloc::vec::Vec; -use digest::{Digest, XofReader}; -use rand_core::SeedableRng; +use digest::{Digest, ExtendableOutput, Update, XofReader}; use serde::{Deserialize, Serialize}; use sha2::Sha256; +use sha3::{Shake256, Shake256Reader}; use crate::curve::Scalar; use crate::tools::serde_bytes; @@ -79,9 +79,30 @@ impl Hash { pub fn finalize_to_scalar(self) -> Scalar { Scalar::from_digest(self.0) } +} + +/// Wraps an extendable output hash for easier replacement, and standardizes the use of DST. +pub struct XofHash(Shake256); + +impl Chain for XofHash { + fn chain_raw_bytes(self, bytes: &[u8]) -> Self { + let mut digest = self.0; + digest.update(bytes); + Self(digest) + } +} + +impl XofHash { + fn new() -> Self { + Self(Shake256::default()) + } + + pub fn new_with_dst(dst: &[u8]) -> Self { + Self::new().chain_bytes(dst) + } - pub fn finalize_to_rng(self) -> rand_chacha::ChaCha8Rng { - rand_chacha::ChaCha8Rng::from_seed(self.0.finalize().into()) + pub fn finalize_to_reader(self) -> Shake256Reader { + self.0.finalize_xof() } } diff --git a/synedrion/src/uint/signed.rs b/synedrion/src/uint/signed.rs index 379aa018..68a0d531 100644 --- a/synedrion/src/uint/signed.rs +++ b/synedrion/src/uint/signed.rs @@ -2,6 +2,7 @@ use core::fmt; use core::marker::PhantomData; use core::ops::{Add, Mul, Neg, Not, Sub}; +use digest::XofReader; use rand_core::CryptoRngCore; use serde::{ de, de::Error, ser::SerializeTupleStruct, Deserialize, Deserializer, Serialize, Serializer, @@ -158,7 +159,7 @@ impl Signed { let bound_bits = bound.as_ref().bits_vartime(); assert!(bound_bits < ::BITS); // Will not overflow because of the assertion above - let positive_bound = (*bound.as_ref() << 1).checked_add(&T::ONE).unwrap(); + let positive_bound = bound.as_ref().shl_vartime(1).checked_add(&T::ONE).unwrap(); let positive_result = T::random_mod(rng, &NonZero::new(positive_bound).unwrap()); // Will not panic because of the assertion above Self::new_from_unsigned( @@ -168,6 +169,26 @@ impl Signed { .unwrap() } + /// Returns a value in range `[-bound, bound]` derived from an extendable-output hash. + /// + /// This method should be used for deriving non-interactive challenges, + /// since it is guaranteed to produce the same results on 32- and 64-bit platforms. + /// + /// Note: variable time in bit size of `bound`. + pub fn from_xof_reader_bounded(rng: &mut impl XofReader, bound: &NonZero) -> Self { + let bound_bits = bound.as_ref().bits_vartime(); + assert!(bound_bits < ::BITS); + // Will not overflow because of the assertion above + let positive_bound = bound.as_ref().shl_vartime(1).checked_add(&T::ONE).unwrap(); + let positive_result = T::from_xof(rng, &NonZero::new(positive_bound).unwrap()); + // Will not panic because of the assertion above + Self::new_from_unsigned( + positive_result.wrapping_sub(bound.as_ref()), + bound_bits as u32, + ) + .unwrap() + } + /// Returns a random value in range `[-2^bound_bits, 2^bound_bits]`. /// /// Note: variable time in `bound_bits`. diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index 8cc37663..761b85df 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -7,8 +7,8 @@ use crypto_bigint::{ }, nlimbs, subtle::{self, Choice, ConstantTimeLess, CtOption}, - Encoding, Integer, Invert, Limb, NonZero, PowBoundedExp, Random, RandomMod, Uint, Word, Zero, - U1024, U2048, U4096, U512, U8192, + Encoding, Integer, Invert, NonZero, PowBoundedExp, Random, RandomMod, Uint, Zero, U1024, U2048, + U4096, U512, U8192, }; use crypto_primes::RandomPrimeWithRng; use digest::XofReader; @@ -45,8 +45,7 @@ pub trait UintLike: + Random + subtle::ConditionallySelectable { - // TODO: do we really need this? Or can we just use a simple RNG and `random_mod()`? - fn hash_into_mod(reader: &mut impl XofReader, modulus: &NonZero) -> Self; + fn from_xof(reader: &mut impl XofReader, modulus: &NonZero) -> Self; fn add_mod(&self, rhs: &Self, modulus: &NonZero) -> Self; fn sub_mod(&self, rhs: &Self, modulus: &NonZero) -> Self; fn trailing_zeros(&self) -> usize; @@ -79,25 +78,21 @@ pub trait HasWide: Sized + Zero { } } -impl UintLike for Uint { - fn hash_into_mod(reader: &mut impl XofReader, modulus: &NonZero) -> Self { - // TODO: The algorithm taken from `impl RandomMod for crypto_bigint::Uint`. - // Consider if this functionality can be added to `crypto_bigint`. - let mut n = Uint::::ZERO; +impl UintLike for Uint +where + Uint: Encoding, +{ + fn from_xof(reader: &mut impl XofReader, modulus: &NonZero) -> Self { let backend_modulus = modulus.as_ref(); let n_bits = backend_modulus.bits_vartime(); - let n_limbs = (n_bits + Limb::BITS - 1) / Limb::BITS; - let mask = Limb::MAX >> (Limb::BITS * n_limbs - n_bits); + let n_bytes = (n_bits + 7) / 8; // ceiling division by 8 - let mut limb_bytes = [0u8; Limb::BYTES]; + let mut bytes = Uint::::ZERO.to_le_bytes(); loop { - for i in 0..n_limbs { - reader.read(&mut limb_bytes); - n.as_limbs_mut()[i] = Limb(Word::from_be_bytes(limb_bytes)); - } - n.as_limbs_mut()[n_limbs - 1] = n.as_limbs()[n_limbs - 1] & mask; + reader.read(&mut (bytes.as_mut()[0..n_bytes])); + let n = Uint::::from_le_bytes(bytes); if n.ct_lt(backend_modulus).into() { return n; @@ -167,21 +162,12 @@ impl UintLike for Uint { } } -impl Hashable for Uint { +impl Hashable for Uint +where + Uint: Encoding, +{ fn chain(&self, digest: C) -> C { - // NOTE: This relies on the fact that `as_words()` returns words - // starting from the least significant one. - // So when we hash them like that it is equivalent to hashing the whole thing - // in the big endian bytes representation. - // We don't need the length into the digest since it is fixed. - // TODO: This may be replaced with `to_be_bytes()` call when we have it; - // right now `Encoding::to_be_bytes()` is only implemented for specific - // `crypto_bigint::Uint`, but there is no generic implementation. - let mut digest = digest; - for word in self.as_words().iter().rev() { - digest = digest.chain_constant_sized_bytes(&word.to_be_bytes()); - } - digest + digest.chain_constant_sized_bytes(&self.to_be_bytes()) } } @@ -242,7 +228,10 @@ pub trait UintModLike: fn square(&self) -> Self; } -impl UintModLike for DynResidue { +impl UintModLike for DynResidue +where + Uint: Encoding, +{ type RawUint = Uint; type Precomputed = DynResidueParams; @@ -319,7 +308,10 @@ where T::conditional_select(&abs_result, &inv_result, exponent.is_negative()) } -impl Hashable for DynResidue { +impl Hashable for DynResidue +where + Uint: Encoding, +{ fn chain(&self, digest: C) -> C { // TODO: I don't think we really need `retrieve()` here, // but `DynResidue` objects are not serializable at the moment.