From 06c4ca1e8a7caeb7e1596ab61617e5b951b33233 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Sun, 1 Dec 2024 11:59:52 -0800 Subject: [PATCH] Decrypt into Secret --- synedrion/src/cggmp21/interactive_signing.rs | 28 +++-- synedrion/src/cggmp21/key_refresh.rs | 4 +- synedrion/src/cggmp21/params.rs | 16 +++ synedrion/src/paillier/encryption.rs | 68 +++++++------ synedrion/src/paillier/keys.rs | 4 +- synedrion/src/paillier/params.rs | 6 +- synedrion/src/tools/secret.rs | 101 ++++++++++++++++++- synedrion/src/uint/traits.rs | 24 ++--- 8 files changed, 189 insertions(+), 62 deletions(-) diff --git a/synedrion/src/cggmp21/interactive_signing.rs b/synedrion/src/cggmp21/interactive_signing.rs index 6f390583..adc147ab 100644 --- a/synedrion/src/cggmp21/interactive_signing.rs +++ b/synedrion/src/cggmp21/interactive_signing.rs @@ -404,8 +404,8 @@ struct Round2Artifact { struct Round2Payload { cap_gamma: Point, - alpha: Signed<::Uint>, - hat_alpha: Signed<::Uint>, + alpha: Secret::Uint>>, + hat_alpha: Secret::Uint>>, cap_d: Ciphertext, hat_cap_d: Ciphertext, } @@ -612,12 +612,18 @@ impl Round for Round2 { // `alpha == x * y + z` where `0 <= x, y < q`, and `-2^l' <= z <= 2^l'`, // where `q` is the curve order. // We will need this bound later, so we're asserting it. - let alpha = alpha - .assert_bit_bound(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) - .ok_or_else(|| ReceiveError::protocol(InteractiveSigningError::OutOfBoundsAlpha))?; - let hat_alpha = hat_alpha - .assert_bit_bound(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) - .ok_or_else(|| ReceiveError::protocol(InteractiveSigningError::OutOfBoundsHatAlpha))?; + let alpha = Secret::try_init_with(|| { + alpha + .expose_secret() + .assert_bit_bound(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) + .ok_or_else(|| ReceiveError::protocol(InteractiveSigningError::OutOfBoundsAlpha)) + })?; + let hat_alpha = Secret::try_init_with(|| { + hat_alpha + .expose_secret() + .assert_bit_bound(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) + .ok_or_else(|| ReceiveError::protocol(InteractiveSigningError::OutOfBoundsHatAlpha)) + })?; Ok(Payload::new(Round2Payload::

{ cap_gamma: direct_message.cap_gamma, @@ -642,14 +648,14 @@ impl Round for Round2 { let cap_delta = cap_gamma * &self.context.k; - let alpha_sum: Signed<_> = payloads.values().map(|p| p.alpha).sum(); - let beta_sum: Secret> = artifacts.values().map(|p| &p.beta).sum(); + let alpha_sum: Secret> = payloads.values().map(|payload| &payload.alpha).sum(); + let beta_sum: Secret> = artifacts.values().map(|artifact| &artifact.beta).sum(); let delta = secret_signed_from_scalar::

(&self.context.gamma) * secret_signed_from_scalar::

(&self.context.k) + &alpha_sum + &beta_sum; - let hat_alpha_sum: Signed<_> = payloads.values().map(|payload| payload.hat_alpha).sum(); + let hat_alpha_sum: Secret> = payloads.values().map(|payload| &payload.hat_alpha).sum(); let hat_beta_sum: Secret> = artifacts.values().map(|artifact| &artifact.hat_beta).sum(); let chi = secret_signed_from_scalar::

(&self.context.key_share.secret_share) * secret_signed_from_scalar::

(&self.context.k) diff --git a/synedrion/src/cggmp21/key_refresh.rs b/synedrion/src/cggmp21/key_refresh.rs index 1bf97b2a..3bf950e7 100644 --- a/synedrion/src/cggmp21/key_refresh.rs +++ b/synedrion/src/cggmp21/key_refresh.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; use super::{ entities::{AuxInfo, KeyShareChange, PublicAuxInfo, SecretAuxInfo}, - params::{scalar_from_uint, secret_uint_from_scalar, SchemeParams}, + params::{secret_scalar_from_uint, secret_uint_from_scalar, SchemeParams}, sigma::{FacProof, ModProof, PrmProof, SchCommitment, SchProof, SchSecret}, }; use crate::{ @@ -651,7 +651,7 @@ impl Round for Round3 { .paillier_enc_x .to_precomputed(&self.context.data_precomp.paillier_pk); - let x = Secret::init_with(|| scalar_from_uint::

(&enc_x.decrypt(&self.context.paillier_sk))); + let x = secret_scalar_from_uint::

(&enc_x.decrypt(&self.context.paillier_sk)); let my_idx = self.context.ids_ordering[&self.context.my_id]; diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index d8d2013b..4844e6d8 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -199,6 +199,22 @@ pub(crate) fn scalar_from_wide_signed( Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative()) } +pub(crate) fn secret_scalar_from_uint( + value: &Secret<::Uint>, +) -> Secret { + let r = value % &P::CURVE_ORDER; + + let repr = Secret::init_with(|| r.expose_secret().to_be_bytes()); + let uint_len = repr.expose_secret().as_ref().len(); + let scalar_len = Scalar::repr_len(); + + // Can unwrap here since the value is within the Scalar range + Secret::init_with(|| { + Scalar::try_from_bytes(&repr.expose_secret().as_ref()[uint_len - scalar_len..]) + .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") + }) +} + pub(crate) fn secret_uint_from_scalar( value: &Secret, ) -> Secret<::Uint> { diff --git a/synedrion/src/paillier/encryption.rs b/synedrion/src/paillier/encryption.rs index a9650a82..ae8ab2bf 100644 --- a/synedrion/src/paillier/encryption.rs +++ b/synedrion/src/paillier/encryption.rs @@ -3,7 +3,7 @@ use core::{ ops::{Add, Mul}, }; -use crypto_bigint::{Monty, ShrVartime, WrappingSub}; +use crypto_bigint::{subtle::ConstantTimeGreater, Monty, ShrVartime}; use rand_core::CryptoRngCore; use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; @@ -16,7 +16,7 @@ use super::{ use crate::{ tools::Secret, uint::{ - subtle::{Choice, ConditionallyNegatable, ConditionallySelectable}, + subtle::{Choice, ConditionallyNegatable}, Bounded, Exponentiable, HasWide, Retrieve, Signed, ToMontgomery, }, }; @@ -225,7 +225,7 @@ impl Ciphertext

{ ) -> Self { let plaintext = *plaintext.expose_secret(); let plaintext_reduced = Secret::init_with(|| { - P::Uint::try_from_wide(plaintext.abs() % pk.modulus_wide_nonzero()) + P::Uint::try_from_wide(&(plaintext.abs() % pk.modulus_wide_nonzero())) .expect("the number within range after reducing modulo N") }); Self::new_with_randomizer_inner(pk, &plaintext_reduced, randomizer, plaintext.is_negative()) @@ -246,7 +246,7 @@ impl Ciphertext

{ } /// Decrypts this ciphertext assuming that the plaintext is in range `[0, N)`. - pub fn decrypt(&self, sk: &SecretKeyPaillier

) -> P::Uint { + pub fn decrypt(&self, sk: &SecretKeyPaillier

) -> Secret { assert_eq!(sk.public_key(), &self.pk); let pk = sk.public_key(); @@ -260,33 +260,39 @@ impl Ciphertext

{ // `C^phi mod N^2` may be 0 if `C == N`, which is very unlikely for large `N`. // Note that `C^phi mod N^2 / N < N`, so we can unwrap when converting to `Uint` // (because `N` itself fits into `Uint`). - let x = P::Uint::try_from_wide( - (self.ciphertext.pow_bounded(totient_wide.expose_secret()) - - P::WideUintMod::one(pk.monty_params_mod_n_squared().clone())) - .retrieve() - / pk.modulus_wide_nonzero(), - ) - .expect("the value is within `Uint` limtis by construction"); + + // Calculate `C^phi mod N^2`. The result is already secret. + // The exponent may leave traces on the stack in the `pow` implementation in `crypto-bigint` + // (since it'll probably be decomposed with a small radix), but we can't do much about that. + let t = Secret::init_with(|| self.ciphertext.pow_bounded(totient_wide.expose_secret())); + let one = P::WideUintMod::one(pk.monty_params_mod_n_squared().clone()); + let x = (t - &one).retrieve() / pk.modulus_wide_nonzero(); + let x = Secret::init_with(|| { + P::Uint::try_from_wide(x.expose_secret()).expect("the value is within `Uint` limtis by construction") + }); let x_mod = x.to_montgomery(pk.monty_params_mod_n()); - (x_mod * sk.inv_totient().expose_secret()).retrieve() + (x_mod * sk.inv_totient()).retrieve() } /// Decrypts this ciphertext assuming that the plaintext is in range `[-N/2, N/2)`. - pub fn decrypt_signed(&self, sk: &SecretKeyPaillier

) -> Signed { + pub fn decrypt_signed(&self, sk: &SecretKeyPaillier

) -> Secret> { assert_eq!(sk.public_key(), &self.pk); let pk = sk.public_key(); let positive_result = self.decrypt(sk); // Note that this is in range `[0, N)` - let negative_result = pk.modulus().wrapping_sub(&positive_result); - let is_negative = Choice::from((positive_result > pk.modulus().wrapping_shr_vartime(1)) as u8); - - let mut result = Signed::new_from_unsigned( - P::Uint::conditional_select(&positive_result, &negative_result, is_negative), - P::MODULUS_BITS - 1, - ) - .expect("the value is within `[-2^(MODULUS_BITS-1), 2^(MODULUS_BITS-1)]` by construction"); + // Can't define a `Sub` for `Uint`, so have to re-wrap manually. + let negative_result = Secret::init_with(|| *pk.modulus() - positive_result.expose_secret()); + let is_negative = positive_result + .expose_secret() + .ct_gt(&pk.modulus().wrapping_shr_vartime(1)); + + let uint_result = Secret::::conditional_select(&positive_result, &negative_result, is_negative); + let mut result = Secret::init_with(|| { + Signed::new_from_unsigned(*uint_result.expose_secret(), P::MODULUS_BITS - 1) + .expect("the value is within `[-2^(MODULUS_BITS-1), 2^(MODULUS_BITS-1)]` by construction") + }); result.conditional_negate(is_negative); result @@ -306,7 +312,7 @@ impl Ciphertext

{ // = rho^N + m * N * rho^N + k * N^2, // where `k` is some integer. // Therefore `C mod N = rho^N mod N`. - let ciphertext_mod_n = P::Uint::try_from_wide(self.ciphertext.retrieve() % pk.modulus_wide_nonzero()) + let ciphertext_mod_n = P::Uint::try_from_wide(&(self.ciphertext.retrieve() % pk.modulus_wide_nonzero())) .expect("a value reduced modulo N fits into `Uint`"); let ciphertext_mod_n = ciphertext_mod_n.to_montgomery(pk.monty_params_mod_n()); @@ -524,7 +530,7 @@ mod tests { let wide_product = lhs.mul_wide(&rhs.abs()); let wide_modulus = modulus.as_ref().to_wide(); - let result = T::try_from_wide(wide_product % NonZero::new(wide_modulus).unwrap()).unwrap(); + let result = T::try_from_wide(&(wide_product % NonZero::new(wide_modulus).unwrap())).unwrap(); if rhs.is_negative().into() { modulus.as_ref().checked_sub(&result).unwrap() } else { @@ -552,7 +558,7 @@ mod tests { Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); let ciphertext = Ciphertext::::new(&mut OsRng, pk, &plaintext); let plaintext_back = ciphertext.decrypt(&sk); - assert_eq!(plaintext.expose_secret(), &plaintext_back); + assert_eq!(plaintext.expose_secret(), plaintext_back.expose_secret()); let ciphertext_wire = ciphertext.to_wire(); let ciphertext_back = ciphertext_wire.to_precomputed(pk); @@ -569,7 +575,7 @@ mod tests { let ciphertext = Ciphertext::new_signed(&mut OsRng, pk, &plaintext); let plaintext_back = ciphertext.decrypt_signed(&sk); let plaintext_reduced = reduce::(plaintext.expose_secret(), &pk.modulus_nonzero()); - assert_eq!(plaintext_reduced, plaintext_back); + assert_eq!(&plaintext_reduced, plaintext_back.expose_secret()); } #[test] @@ -597,8 +603,8 @@ mod tests { let new_plaintext = new_ciphertext.decrypt(&sk); assert_eq!( - mul_mod(plaintext.expose_secret(), &coeff, &pk.modulus_nonzero()), - new_plaintext + &mul_mod(plaintext.expose_secret(), &coeff, &pk.modulus_nonzero()), + new_plaintext.expose_secret() ); } @@ -619,10 +625,10 @@ mod tests { let new_plaintext = new_ciphertext.decrypt(&sk); assert_eq!( - plaintext1 + &plaintext1 .expose_secret() .add_mod(plaintext2.expose_secret(), pk.modulus()), - new_plaintext + new_plaintext.expose_secret() ); } @@ -643,9 +649,9 @@ mod tests { let plaintext_back = result.decrypt(&sk); assert_eq!( - mul_mod(plaintext1.expose_secret(), &plaintext2, &pk.modulus_nonzero()) + &mul_mod(plaintext1.expose_secret(), &plaintext2, &pk.modulus_nonzero()) .add_mod(plaintext3.expose_secret(), pk.modulus()), - plaintext_back + plaintext_back.expose_secret() ); } } diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index 51366d7c..e6d62e82 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -201,8 +201,8 @@ where // but it needs to be supported by `crypto-bigint`. let p_rem = *elem % self.primes.p_nonzero().expose_secret(); let q_rem = *elem % self.primes.q_nonzero().expose_secret(); - let p_rem_half = P::HalfUint::try_from_wide(p_rem).expect("`p` fits into `HalfUint`"); - let q_rem_half = P::HalfUint::try_from_wide(q_rem).expect("`q` fits into `HalfUint`"); + let p_rem_half = P::HalfUint::try_from_wide(&p_rem).expect("`p` fits into `HalfUint`"); + let q_rem_half = P::HalfUint::try_from_wide(&q_rem).expect("`q` fits into `HalfUint`"); // NOTE: `monty_params_mod_q` is cloned here and can remain on the stack. // See https://github.com/RustCrypto/crypto-bigint/issues/704 diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index d53ee1fb..d85dcf4c 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -1,6 +1,6 @@ use crypto_bigint::{ modular::Retrieve, - subtle::{ConditionallyNegatable, ConditionallySelectable, CtOption}, + subtle::{ConditionallyNegatable, ConditionallySelectable, ConstantTimeGreater, CtOption}, Bounded, Encoding, Gcd, Integer, InvMod, Invert, Monty, RandomMod, }; use crypto_primes::RandomPrimeWithRng; @@ -43,6 +43,7 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn + Bounded + Gcd + ConditionallySelectable + + ConstantTimeGreater + Encoding + Hashable + HasWide @@ -82,7 +83,8 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn + ConditionallyNegatable + ConditionallySelectable + Invert> - + Retrieve; + + Retrieve + + Zeroize; /// An integer that fits the squared RSA modulus times a small factor. /// Used in some ZK proofs. diff --git a/synedrion/src/tools/secret.rs b/synedrion/src/tools/secret.rs index bb9785c8..7678fcfc 100644 --- a/synedrion/src/tools/secret.rs +++ b/synedrion/src/tools/secret.rs @@ -1,9 +1,13 @@ use core::{ fmt::Debug, - ops::{Add, AddAssign, Deref, DerefMut, Mul, MulAssign, Neg, Sub, SubAssign}, + ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, }; -use crypto_bigint::{subtle::ConditionallySelectable, Encoding, Integer}; +use crypto_bigint::{ + modular::Retrieve, + subtle::{Choice, ConditionallyNegatable, ConditionallySelectable}, + Encoding, Integer, Monty, NonZero, +}; use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; @@ -306,6 +310,71 @@ impl<'a, T: Zeroize + MulAssign<&'a T>> Mul<&'a Secret> for Secret { } } +// Division + +impl<'a, T: Zeroize + DivAssign<&'a NonZero>> DivAssign<&'a NonZero> for Secret { + fn div_assign(&mut self, other: &'a NonZero) { + self.expose_secret_mut().div_assign(other) + } +} + +impl<'a, T: Zeroize + DivAssign<&'a NonZero>> Div<&'a NonZero> for Secret { + type Output = Secret; + + fn div(mut self, other: &'a NonZero) -> Self::Output { + self /= other; + self + } +} + +impl DivAssign<&'a NonZero>> Div> for Secret { + type Output = Secret; + + fn div(mut self, other: NonZero) -> Self::Output { + self /= &other; + self + } +} + +// Remainder + +// TODO: `crypto_bigint::Uint` does not impl `RemAssign`, so we have to use `Rem` +impl<'a, T: Zeroize + Clone + Rem<&'a NonZero, Output = T>> RemAssign<&'a NonZero> for Secret { + fn rem_assign(&mut self, other: &'a NonZero) { + let mut result = self.expose_secret().clone() % other; + core::mem::swap(self.expose_secret_mut(), &mut result); + result.zeroize(); + } +} + +impl<'a, T: Zeroize + Clone + Rem<&'a NonZero, Output = T>> Rem<&'a NonZero> for Secret { + type Output = Secret; + + fn rem(mut self, other: &'a NonZero) -> Self::Output { + self %= other; + self + } +} + +impl<'a, T: Zeroize + Clone + Rem<&'a NonZero, Output = T>> Rem<&'a NonZero> for &Secret { + type Output = Secret; + + fn rem(self, other: &'a NonZero) -> Self::Output { + let mut result = self.clone(); + result %= other; + result + } +} + +impl Rem<&'a NonZero, Output = T>> Rem> for Secret { + type Output = Secret; + + fn rem(mut self, other: NonZero) -> Self::Output { + self %= &other; + self + } +} + // Summation impl AddAssign<&'a T> + Default> core::iter::Sum for Secret { @@ -371,3 +440,31 @@ impl Mul<&Secret> for &Point { self * scalar.expose_secret() } } + +impl> Retrieve for Secret { + type Output = Secret; + fn retrieve(&self) -> Self::Output { + Secret::init_with(|| self.expose_secret().retrieve()) + } +} + +impl> Secret { + pub fn to_montgomery(&self, params: &::Params) -> Secret { + // `self` has to be cloned and passed by value, which means it may be retained on the stack. + // Can't help it with the current `Monty::new()` signature. + Secret::init_with(|| ::new(self.expose_secret().clone(), params.clone())) + } +} + +// Can't implement `ConditionallySelectable` itself since it is bounded on `Copy`. +impl Secret { + pub fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Secret::init_with(|| T::conditional_select(a.expose_secret(), b.expose_secret(), choice)) + } +} + +impl ConditionallyNegatable for Secret { + fn conditional_negate(&mut self, choice: Choice) { + self.0.expose_secret_mut().conditional_negate(choice) + } +} diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index ba691829..1eeee5cc 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -62,7 +62,7 @@ where let bits = ::BITS; let bound = bound % (2 * bits + 1); - let (lo, hi) = ::from_wide(exp.clone()); + let (lo, hi) = ::from_wide(exp); let lo_res = self.pow_bounded_exp(&lo, core::cmp::min(bits, bound)); // TODO (#34): this may be faster if we could get access to Uint's pow_bounded_exp() that takes @@ -93,7 +93,7 @@ where let bound = exp.bound(); let abs_exponent = exp.abs(); - let (wlo, whi) = ::Wide::from_wide(abs_exponent); + let (wlo, whi) = ::Wide::from_wide(&abs_exponent); let lo_res = self.pow_wide(&wlo, core::cmp::min(bits, bound)); @@ -142,12 +142,12 @@ pub trait HasWide: Sized + Zero { /// *Note*: The behaviour of this method has changed in v0.2. Previously, /// the order of the halves was `(hi, lo)` but after v0.2 the order is `(lo, /// hi)`. - fn from_wide(value: Self::Wide) -> (Self, Self); + fn from_wide(value: &Self::Wide) -> (Self, Self); /// Tries to convert a `Wide` into a `Self` sized uint. Splits a `Wide` /// value in two halves and returns the lower half if the high half is zero. /// Otherwise returns `None`. - fn try_from_wide(value: Self::Wide) -> Option { + fn try_from_wide(value: &Self::Wide) -> Option { let (lo, hi) = Self::from_wide(value); if hi.is_zero().into() { return Some(lo); @@ -167,8 +167,8 @@ impl HasWide for U512 { fn to_wide(&self) -> Self::Wide { Uint::concat_mixed(self, &Self::ZERO) } - fn from_wide(value: Self::Wide) -> (Self, Self) { - value.into() + fn from_wide(value: &Self::Wide) -> (Self, Self) { + value.split_mixed() } } @@ -183,8 +183,8 @@ impl HasWide for U1024 { fn to_wide(&self) -> Self::Wide { Uint::concat_mixed(self, &Self::ZERO) } - fn from_wide(value: Self::Wide) -> (Self, Self) { - value.into() + fn from_wide(value: &Self::Wide) -> (Self, Self) { + value.split_mixed() } } @@ -199,8 +199,8 @@ impl HasWide for U2048 { fn to_wide(&self) -> Self::Wide { Uint::concat_mixed(self, &Self::ZERO) } - fn from_wide(value: Self::Wide) -> (Self, Self) { - value.into() + fn from_wide(value: &Self::Wide) -> (Self, Self) { + value.split_mixed() } } @@ -215,8 +215,8 @@ impl HasWide for U4096 { fn to_wide(&self) -> Self::Wide { Uint::concat_mixed(self, &Self::ZERO) } - fn from_wide(value: Self::Wide) -> (Self, Self) { - value.into() + fn from_wide(value: &Self::Wide) -> (Self, Self) { + value.split_mixed() } }