diff --git a/synedrion/Cargo.toml b/synedrion/Cargo.toml index c4040c9..e2e27ec 100644 --- a/synedrion/Cargo.toml +++ b/synedrion/Cargo.toml @@ -37,9 +37,10 @@ displaydoc = { version = "0.2", default-features = false} getrandom = { version = "0.2", features = ["js"]} [dev-dependencies] +rand_chacha = "0.3" +serde_assert = "0.8" tokio = { version = "1", features = ["rt", "sync", "time", "macros"] } rand = "0.8" -rand_chacha = "0.3" criterion = "0.5" k256 = {version = "0.14.0-pre.2", default-features = false, features = ["ecdsa", "arithmetic", "pem", "serde"]} impls = "1" diff --git a/synedrion/src/cggmp21/entities.rs b/synedrion/src/cggmp21/entities.rs index 56e360f..903c91f 100644 --- a/synedrion/src/cggmp21/entities.rs +++ b/synedrion/src/cggmp21/entities.rs @@ -113,7 +113,7 @@ pub struct PresigningData { #[derive(Debug, Clone)] pub(crate) struct PresigningValues { - pub(crate) hat_beta: Signed<::Uint>, + pub(crate) hat_beta: SecretBox::Uint>>, pub(crate) hat_r: Randomizer, pub(crate) hat_s: Randomizer, pub(crate) cap_k: CiphertextMod, @@ -276,7 +276,11 @@ impl AuxInfo { } } -impl PresigningData { +impl PresigningData +where + P: SchemeParams, + I: Ord + Clone + PartialEq, +{ /// Creates a consistent set of presigning data for testing purposes. #[cfg(any(test, feature = "bench-internals"))] pub(crate) fn new_centralized( @@ -346,7 +350,7 @@ impl PresigningData { let id_ji = (id_j.clone(), id_i.clone()); hat_betas.insert(id_ij.clone(), hat_beta); - hat_ss.insert(id_ij.clone(), hat_s); + hat_ss.insert(id_ij.clone(), hat_s.clone()); hat_rs.insert(id_ij.clone(), hat_r); hat_cap_ds.insert(id_ji.clone(), hat_cap_d); @@ -368,7 +372,7 @@ impl PresigningData { values.insert( id_j.clone(), PresigningValues { - hat_beta: hat_betas[&id_ij], + hat_beta: SecretBox::new(Box::new(hat_betas[&id_ij])), hat_r: hat_rs[&id_ij].clone(), hat_s: hat_ss[&id_ij].clone(), hat_cap_d_received: hat_cap_ds[&id_ij].clone(), diff --git a/synedrion/src/cggmp21/protocols/presigning.rs b/synedrion/src/cggmp21/protocols/presigning.rs index 7005b00..7414e84 100644 --- a/synedrion/src/cggmp21/protocols/presigning.rs +++ b/synedrion/src/cggmp21/protocols/presigning.rs @@ -295,12 +295,12 @@ pub struct Round2Message { #[derive(Debug, Clone)] pub struct Round2Artifact { - beta: Signed<::Uint>, // TODO (#77): secret - hat_beta: Signed<::Uint>, // TODO (#77): secret - r: Randomizer, // TODO (#77): secret - s: Randomizer, // TODO (#77): secret - hat_r: Randomizer, // TODO (#77): secret - hat_s: Randomizer, // TODO (#77): secret + beta: SecretBox::Uint>>, + hat_beta: SecretBox::Uint>>, + r: Randomizer, + s: Randomizer, + hat_r: Randomizer, + hat_s: Randomizer, cap_d: CiphertextMod, cap_f: CiphertextMod, hat_cap_d: CiphertextMod, @@ -344,25 +344,38 @@ impl Round for Round2 Round for Round2 Round for Round2 FinalizableToNextRound let cap_delta = cap_gamma * self.context.k; let alpha_sum: Signed<_> = payloads.values().map(|p| p.alpha).sum(); - let beta_sum: Signed<_> = artifacts.values().map(|p| p.beta).sum(); + let beta_sum: Signed<_> = artifacts.values().map(|p| p.beta.expose_secret()).sum(); let delta = P::signed_from_scalar(&self.context.gamma) * P::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_beta_sum: Signed<_> = artifacts.values().map(|artifact| artifact.hat_beta).sum(); + let hat_beta_sum: Signed<_> = artifacts + .values() + .map(|artifact| artifact.hat_beta.expose_secret()) + .sum(); let chi = P::signed_from_scalar(self.context.key_share.secret_share.expose_secret()) * P::signed_from_scalar(&self.context.k) + hat_alpha_sum @@ -770,8 +786,8 @@ impl FinalizableToResult rng, &P::signed_from_scalar(&self.context.gamma), beta, - &s.to_mod(target_pk), - &r.to_mod(pk), + s.to_mod(target_pk), + r.to_mod(pk), target_pk, pk, &self.all_cap_k[id_j], diff --git a/synedrion/src/cggmp21/protocols/signing.rs b/synedrion/src/cggmp21/protocols/signing.rs index 7617124..bdd8d80 100644 --- a/synedrion/src/cggmp21/protocols/signing.rs +++ b/synedrion/src/cggmp21/protocols/signing.rs @@ -182,14 +182,14 @@ impl FinalizableToResult let target_pk = &self.aux_info.public_aux[id_j].paillier_pk; let rp = &self.aux_info.public_aux[id_l].rp_params; - let values = &self.inputs.presigning.values.get(id_j).unwrap(); + let values = self.inputs.presigning.values.get(id_j).unwrap(); let p_aff_g = AffGProof::

::new( rng, &P::signed_from_scalar(self.inputs.key_share.secret_share.expose_secret()), &values.hat_beta, - &values.hat_s.to_mod(target_pk), - &values.hat_r.to_mod(pk), + values.hat_s.to_mod(target_pk), + values.hat_r.to_mod(pk), target_pk, pk, &values.cap_k, diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index aebcd3b..2a76cfc 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -1,6 +1,7 @@ //! Paillier Affine Operation with Group Commitment in Range ($\Pi^{aff-g}$, Section 6.2, Fig. 15) use rand_core::CryptoRngCore; +use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; @@ -59,9 +60,9 @@ impl AffGProof

{ pub fn new( rng: &mut impl CryptoRngCore, x: &Signed<::Uint>, - y: &Signed<::Uint>, - rho: &RandomizerMod, - rho_y: &RandomizerMod, + y: &SecretBox::Uint>>, + rho: RandomizerMod, + rho_y: RandomizerMod, pk0: &PublicKeyPaillierPrecomputed, pk1: &PublicKeyPaillierPrecomputed, cap_c: &CiphertextMod, @@ -72,7 +73,7 @@ impl AffGProof

{ aux: &impl Hashable, ) -> Self { x.assert_bound(P::L_BOUND); - y.assert_bound(P::LP_BOUND); + y.expose_secret().assert_bound(P::LP_BOUND); assert!(cap_c.public_key() == pk0); assert!(cap_d.public_key() == pk0); assert!(cap_y.public_key() == pk1); @@ -96,14 +97,14 @@ impl AffGProof

{ let cap_b_x = P::scalar_from_signed(&alpha).mul_by_generator(); let cap_b_y = CiphertextMod::new_with_randomizer_signed(pk1, &beta, &r_y_mod.retrieve()).retrieve(); - let cap_e = setup.commit(&alpha, &gamma).retrieve(); - let cap_s = setup.commit(x, &m).retrieve(); - let cap_f = setup.commit(&beta, &delta).retrieve(); + let cap_e = setup.commit(&alpha.into(), &gamma).retrieve(); + let cap_s = setup.commit(&x.into(), &m).retrieve(); + let cap_f = setup.commit(&beta.into(), &delta).retrieve(); // NOTE: deviation from the paper to support a different $D$ // (see the comment in `AffGProof`) // Original: $s^y$. Modified: $s^{-y}$ - let cap_t = setup.commit(&-y, &mu).retrieve(); + let cap_t = setup.commit(&(-y.expose_secret()).into(), &mu).retrieve(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -135,7 +136,7 @@ impl AffGProof

{ // (see the comment in `AffGProof`) // Original: $z_2 = \beta + e y$ // Modified: $z_2 = \beta - e y$ - let z2 = beta + e * (-y); + let z2 = beta + e * (-y.expose_secret()); let z3 = gamma + e_wide * m; let z4 = delta + e_wide * mu; @@ -249,14 +250,16 @@ impl AffGProof

{ // s^{z_1} t^{z_3} = E S^e \mod \hat{N} let cap_e_mod = self.cap_e.to_mod(aux_pk); let cap_s_mod = self.cap_s.to_mod(aux_pk); - if setup.commit(&self.z1, &self.z3) != &cap_e_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit(&self.z1.into(), &self.z3) != &cap_e_mod * &cap_s_mod.pow_signed_vartime(&e) + { return false; } // s^{z_2} t^{z_4} = F T^e \mod \hat{N} let cap_f_mod = self.cap_f.to_mod(aux_pk); let cap_t_mod = self.cap_t.to_mod(aux_pk); - if setup.commit(&self.z2, &self.z4) != &cap_f_mod * &cap_t_mod.pow_signed_vartime(&e) { + if setup.commit(&self.z2.into(), &self.z4) != &cap_f_mod * &cap_t_mod.pow_signed_vartime(&e) + { return false; } @@ -267,6 +270,7 @@ impl AffGProof

{ #[cfg(test)] mod tests { use rand_core::OsRng; + use secrecy::{ExposeSecret, SecretBox}; use super::AffGProof; use crate::cggmp21::{SchemeParams, TestParams}; @@ -290,21 +294,24 @@ mod tests { let aux: &[u8] = b"abcde"; let x = Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND); - let y = Signed::random_bounded_bits(&mut OsRng, Params::LP_BOUND); + let y = SecretBox::new(Box::new(Signed::random_bounded_bits( + &mut OsRng, + Params::LP_BOUND, + ))); let rho = RandomizerMod::random(&mut OsRng, pk0); let rho_y = RandomizerMod::random(&mut OsRng, pk1); let secret = Signed::random(&mut OsRng); let cap_c = CiphertextMod::new_signed(&mut OsRng, pk0, &secret); - let cap_d = - &cap_c * x + CiphertextMod::new_with_randomizer_signed(pk0, &-y, &rho.retrieve()); - let cap_y = CiphertextMod::new_with_randomizer_signed(pk1, &y, &rho_y.retrieve()); + let cap_d = &cap_c * x + + CiphertextMod::new_with_randomizer_signed(pk0, &-y.expose_secret(), &rho.retrieve()); + let cap_y = + CiphertextMod::new_with_randomizer_signed(pk1, y.expose_secret(), &rho_y.retrieve()); let cap_x = Params::scalar_from_signed(&x).mul_by_generator(); let proof = AffGProof::::new( - &mut OsRng, &x, &y, &rho, &rho_y, pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &setup, - &aux, + &mut OsRng, &x, &y, rho, rho_y, pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &setup, &aux, ); assert!(proof.verify(pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &setup, &aux)); } diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index 9687aa4..147d7cd 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -61,8 +61,8 @@ impl DecProof

{ let nu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let r = RandomizerMod::random(rng, pk0); - let cap_s = setup.commit(y, &mu).retrieve(); - let cap_t = setup.commit(&alpha, &nu).retrieve(); + let cap_s = setup.commit(&y.into(), &mu).retrieve(); + let cap_t = setup.commit(&alpha.into(), &nu).retrieve(); let cap_a = CiphertextMod::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()).retrieve(); let gamma = P::scalar_from_signed(&alpha); @@ -150,7 +150,9 @@ impl DecProof

{ // s^{z_1} t^{z_2} == T S^e let cap_s_mod = self.cap_s.to_mod(setup.public_key()); let cap_t_mod = self.cap_t.to_mod(setup.public_key()); - if setup.commit_wide(&self.z1, &self.z2) != &cap_t_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit_wide(&self.z1.into(), &self.z2) + != &cap_t_mod * &cap_s_mod.pow_signed_vartime(&e) + { return false; } diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index 93770ec..5a62bbf 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -58,10 +58,10 @@ impl EncProof

{ let r = RandomizerMod::random(rng, pk0); let gamma = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); - let cap_s = setup.commit(k, &mu).retrieve(); + let cap_s = setup.commit(&k.into(), &mu).retrieve(); let cap_a = CiphertextMod::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()).retrieve(); - let cap_c = setup.commit(&alpha, &gamma).retrieve(); + let cap_c = setup.commit(&alpha.into(), &gamma).retrieve(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -135,7 +135,8 @@ impl EncProof

{ // s^{z_1} t^{z_3} == C S^e \mod \hat{N} let cap_c_mod = self.cap_c.to_mod(setup.public_key()); let cap_s_mod = self.cap_s.to_mod(setup.public_key()); - if setup.commit(&self.z1, &self.z3) != &cap_c_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit(&self.z1.into(), &self.z3) != &cap_c_mod * &cap_s_mod.pow_signed_vartime(&e) + { return false; } diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index d235d33..7fe7533 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -1,6 +1,7 @@ //! No small factor proof ($\Pi^{fac}$, Section C.5, Fig. 28) use rand_core::CryptoRngCore; +use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; @@ -89,8 +90,8 @@ impl FacProof

{ let cap_p = setup.commit(&p, &mu).retrieve(); let cap_q = setup.commit(&q, &nu); - let cap_a = setup.commit_wide(&alpha, &x).retrieve(); - let cap_b = setup.commit_wide(&beta, &y).retrieve(); + let cap_a = setup.commit_wide(&alpha.into(), &x).retrieve(); + let cap_b = setup.commit_wide(&beta.into(), &y).retrieve(); let cap_t = (&cap_q.pow_signed_wide(&alpha) * &setup.commit_base_xwide(&r)).retrieve(); let cap_q = cap_q.retrieve(); @@ -112,9 +113,9 @@ impl FacProof

{ let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.into_wide(); - let hat_sigma = sigma - (nu * p.into_wide()).into_wide(); - let z1 = alpha + (e * p).into_wide(); - let z2 = beta + (e * q).into_wide(); + let hat_sigma = sigma - (nu * p.expose_secret().into_wide()).into_wide(); + let z1 = alpha + (e * p.expose_secret()).into_wide(); + let z2 = beta + (e * q.expose_secret()).into_wide(); let omega1 = x + e_wide * mu; let omega2 = y + e_wide * nu; let v = r + (e_wide.into_wide() * hat_sigma); @@ -165,12 +166,12 @@ impl FacProof

{ let aux_pk = setup.public_key(); // R = s^{N_0} t^\sigma - let cap_r = &setup.commit_xwide(&pk0.modulus_bounded(), &self.sigma); + let cap_r = &setup.commit_xwide(&pk0.modulus_bounded().into(), &self.sigma); // s^{z_1} t^{\omega_1} == A * P^e \mod \hat{N} let cap_a_mod = self.cap_a.to_mod(aux_pk); let cap_p_mod = self.cap_p.to_mod(aux_pk); - if setup.commit_wide(&self.z1, &self.omega1) + if setup.commit_wide(&self.z1.into(), &self.omega1) != &cap_a_mod * &cap_p_mod.pow_signed_vartime(&e) { return false; @@ -179,7 +180,7 @@ impl FacProof

{ // s^{z_2} t^{\omega_2} == B * Q^e \mod \hat{N} let cap_b_mod = self.cap_b.to_mod(aux_pk); let cap_q_mod = self.cap_q.to_mod(aux_pk); - if setup.commit_wide(&self.z2, &self.omega2) + if setup.commit_wide(&self.z2.into(), &self.omega2) != &cap_b_mod * &cap_q_mod.pow_signed_vartime(&e) { return false; diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index be8852a..ea9ab14 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -63,11 +63,11 @@ impl LogStarProof

{ let r = RandomizerMod::random(rng, pk0); let gamma = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); - let cap_s = setup.commit(x, &mu).retrieve(); + let cap_s = setup.commit(&x.into(), &mu).retrieve(); let cap_a = CiphertextMod::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()).retrieve(); let cap_y = g * &P::scalar_from_signed(&alpha); - let cap_d = setup.commit(&alpha, &gamma).retrieve(); + let cap_d = setup.commit(&alpha.into(), &gamma).retrieve(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -156,7 +156,8 @@ impl LogStarProof

{ // s^{z_1} t^{z_3} == D S^e \mod \hat{N} let cap_d_mod = self.cap_d.to_mod(setup.public_key()); let cap_s_mod = self.cap_s.to_mod(setup.public_key()); - if setup.commit(&self.z1, &self.z3) != &cap_d_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit(&self.z1.into(), &self.z3) != &cap_d_mod * &cap_s_mod.pow_signed_vartime(&e) + { return false; } diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 0c8ffb2..bef5dfb 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -73,8 +73,8 @@ impl MulStarProof

{ let cap_a = (cap_c * alpha).mul_randomizer(&r.retrieve()).retrieve(); let cap_b_x = P::scalar_from_signed(&alpha).mul_by_generator(); - let cap_e = setup.commit(&alpha, &gamma).retrieve(); - let cap_s = setup.commit(x, &m).retrieve(); + let cap_e = setup.commit(&alpha.into(), &gamma).retrieve(); + let cap_s = setup.commit(&x.into(), &m).retrieve(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -168,7 +168,8 @@ impl MulStarProof

{ // s^{z_1} t^{z_2} == E S^e let cap_e_mod = self.cap_e.to_mod(aux_pk); let cap_s_mod = self.cap_s.to_mod(aux_pk); - if setup.commit(&self.z1, &self.z2) != &cap_e_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit(&self.z1.into(), &self.z2) != &cap_e_mod * &cap_s_mod.pow_signed_vartime(&e) + { return false; } diff --git a/synedrion/src/paillier/encryption.rs b/synedrion/src/paillier/encryption.rs index 311a229..90d1b73 100644 --- a/synedrion/src/paillier/encryption.rs +++ b/synedrion/src/paillier/encryption.rs @@ -3,8 +3,9 @@ use core::ops::{Add, Mul}; use crypto_bigint::{Invert, Monty, PowBoundedExp, ShrVartime, WrappingSub}; use rand_core::CryptoRngCore; +use secrecy::ExposeSecret; use serde::{Deserialize, Serialize}; -use zeroize::ZeroizeOnDrop; +use zeroize::{Zeroize, ZeroizeOnDrop}; use super::keys::{PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; use super::params::PaillierParams; @@ -15,7 +16,7 @@ use crate::uint::{ }; // A ciphertext randomizer (an invertible element of $\mathbb{Z}_N$). -#[derive(Debug, Clone, Serialize, Deserialize, ZeroizeOnDrop)] +#[derive(Debug, Clone, Serialize, Deserialize, ZeroizeOnDrop, Default, Zeroize)] pub(crate) struct Randomizer(P::Uint); impl Randomizer

{ @@ -28,7 +29,7 @@ impl Randomizer

{ } } -#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop)] +#[derive(Debug, Clone, PartialEq, Eq, ZeroizeOnDrop, Zeroize)] pub(crate) struct RandomizerMod(P::UintMod); impl RandomizerMod

{ @@ -147,9 +148,6 @@ impl CiphertextMod

{ // `SchemeParameters`/`PaillierParameters` values in tests, which can only // be overcome by fixing #27 and using a small 32- or 64-bit curve for tests) - // TODO (#77): wrap in Secret - let randomizer = randomizer.0.into_wide(); - // Calculate the ciphertext `C = (N + 1)^m * rho^N mod N^2` // where `N` is the Paillier composite modulus, `m` is the plaintext, // and `rho` is the randomizer. @@ -163,6 +161,7 @@ impl CiphertextMod

{ let factor1 = prod_mod + P::WideUintMod::one(pk.precomputed_modulus_squared().clone()); + let randomizer = randomizer.0.into_wide(); let pk_mod_bound = pk.modulus_bounded().into_wide(); let factor2 = randomizer .to_montgomery(pk.precomputed_modulus_squared()) @@ -228,7 +227,7 @@ impl CiphertextMod

{ assert_eq!(sk.public_key(), &self.pk); let pk = sk.public_key(); - let totient_wide = sk.totient().into_wide(); + let totient_wide = sk.totient().expose_secret().into_wide(); let modulus_wide = NonZero::new(pk.modulus().into_wide()).unwrap(); // Calculate the plaintext `m = ((C^phi mod N^2 - 1) / N) * mu mod N`, @@ -248,7 +247,7 @@ impl CiphertextMod

{ .unwrap(); let x_mod = x.to_montgomery(pk.precomputed_modulus()); - (x_mod * sk.inv_totient()).retrieve() + (x_mod * sk.inv_totient().expose_secret()).retrieve() } /// Decrypts this ciphertext assuming that the plaintext is in range `[-N/2, N/2)`. diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index 6d157bc..0293996 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -1,8 +1,9 @@ +use alloc::boxed::Box; use core::fmt::Debug; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; -use zeroize::ZeroizeOnDrop; +use zeroize::{Zeroize, ZeroizeOnDrop}; use super::params::PaillierParams; use crate::uint::{ @@ -13,11 +14,19 @@ use crate::uint::{ use crypto_bigint::{ Bounded as TraitBounded, InvMod, Monty, Odd, ShrVartime, Square, WrappingAdd, WrappingSub, }; +use secrecy::{ExposeSecret, SecretBox}; -#[derive(Clone, Serialize, Deserialize, ZeroizeOnDrop)] +#[derive(Deserialize, ZeroizeOnDrop, Zeroize)] pub(crate) struct SecretKeyPaillier { - p: P::HalfUint, - q: P::HalfUint, + p: SecretBox, + q: SecretBox, +} + +impl PartialEq for SecretKeyPaillier

{ + fn eq(&self, other: &Self) -> bool { + self.p.expose_secret() == other.p.expose_secret() + && self.q.expose_secret() == other.q.expose_secret() + } } impl Debug for SecretKeyPaillier

{ @@ -28,6 +37,24 @@ impl Debug for SecretKeyPaillier

{ } } +impl Clone for SecretKeyPaillier

{ + fn clone(&self) -> Self { + Self { + p: Box::new(self.p.expose_secret().clone()).into(), + q: Box::new(self.q.expose_secret().clone()).into(), + } + } +} + +impl Serialize for SecretKeyPaillier

{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + (self.p.expose_secret(), self.q.expose_secret()).serialize(serializer) + } +} + impl SecretKeyPaillier

{ pub fn random(rng: &mut impl CryptoRngCore) -> Self { let p = P::HalfUint::generate_safe_prime_with_rng( @@ -41,7 +68,10 @@ impl SecretKeyPaillier

{

::HalfUint::BITS, ); - Self { p, q } + Self { + p: Box::new(p).into(), + q: Box::new(q).into(), + } } pub fn to_precomputed(&self) -> SecretKeyPaillierPrecomputed

{ @@ -51,24 +81,28 @@ impl SecretKeyPaillier

{ let one = ::one(); let p_minus_one = self .p + .expose_secret() .checked_sub(&one) .expect("`p` is prime, so greater than one"); let q_minus_one = self .q + .expose_secret() .checked_sub(&one) .expect("`q` is prime, so greater than one"); let totient = Bounded::new(p_minus_one.mul_wide(&q_minus_one), P::MODULUS_BITS as u32) .expect("The pre-configured bound set in `P::MODULUS_BITS` is assumed to be valid"); let precomputed_mod_p = P::HalfUintMod::new_params_vartime( - Odd::new(self.p.clone()).expect("`p` is assumed to be a prime greater than 2"), + Odd::new(self.p.expose_secret().clone()) + .expect("`p` is assumed to be a prime greater than 2"), ); let precomputed_mod_q = P::HalfUintMod::new_params_vartime( - Odd::new(self.q.clone()).expect("`q` is assumed to be a prime greater than 2"), + Odd::new(self.q.expose_secret().clone()) + .expect("`q` is assumed to be a prime greater than 2"), ); let public_key = PublicKeyPaillier { - modulus: self.p.mul_wide(&self.q), + modulus: self.p.expose_secret().mul_wide(self.q.expose_secret()), }; let public_key = public_key.to_precomputed(); @@ -89,6 +123,7 @@ impl SecretKeyPaillier

{ let inv_p_mod_q = self .p + .expose_secret() .clone() .to_montgomery(&precomputed_mod_q) .invert() @@ -96,6 +131,7 @@ impl SecretKeyPaillier

{ let inv_q_mod_p = self .q + .expose_secret() .clone() .to_montgomery(&precomputed_mod_p) .invert() @@ -109,8 +145,8 @@ impl SecretKeyPaillier

{ .retrieve(); // Note that the wrapping add/sub won't overflow by construction. let nonsquare_sampling_constant = t - .mul_wide(&self.q) - .wrapping_add(&self.q.clone().into_wide()) + .mul_wide(self.q.expose_secret()) + .wrapping_add(&self.q.expose_secret().clone().into_wide()) .wrapping_sub(&::one()); let nonsquare_sampling_constant = P::UintMod::new( @@ -148,36 +184,45 @@ pub(crate) struct SecretKeyPaillierPrecomputed { public_key: PublicKeyPaillierPrecomputed

, } -impl SecretKeyPaillierPrecomputed

{ +impl

SecretKeyPaillierPrecomputed

+where + P: PaillierParams, +{ pub fn to_minimal(&self) -> SecretKeyPaillier

{ self.sk.clone() } - pub fn primes(&self) -> (Signed, Signed) { + #[allow(clippy::type_complexity)] + pub fn primes(&self) -> (SecretBox>, SecretBox>) { // The primes are positive, but where this method is used Signed is needed, // so we return that for convenience. - // TODO (#77): must be wrapped in a Secret ( - Signed::new_positive(self.sk.p.clone().into_wide(), P::PRIME_BITS as u32).unwrap(), - Signed::new_positive(self.sk.q.clone().into_wide(), P::PRIME_BITS as u32).unwrap(), + SecretBox::new(Box::new(Signed::new_positive(self.sk.p.expose_secret().clone().into_wide(), P::PRIME_BITS as u32) + .expect("The primes in the `SecretKeyPaillier` are 'safe primes' and positive by construction; the bound is assumed to be configured correctly by the user.") + )), + SecretBox::new(Box::new(Signed::new_positive(self.sk.q.expose_secret().clone().into_wide(), P::PRIME_BITS as u32) + .expect("The primes in the `SecretKeyPaillier` are 'safe primes' and positive by construction; the bound is assumed to be configured correctly by the user.") + )), ) } - pub fn totient(&self) -> &Bounded { - // TODO (#77): must be wrapped in a Secret - &self.totient + /// Returns Euler's totient function (`φ(n)`) of the modulus, wrapped in a [`SecretBox`]. + pub fn totient(&self) -> SecretBox> { + self.totient.into() } - /// Returns Euler's totient function of the modulus. + /// Returns Euler's totient function (`φ(n)`) of the modulus as a [`NonZero`] pub fn totient_nonzero(&self) -> NonZero { - // TODO (#77): must be wrapped in a Secret - NonZero::new(*self.totient.as_ref()).unwrap() + // TODO (#77): must be wrapped in a Secret – This must await the release of crypto-bigint + // v0.6, because `NonZero` doesn't implement `Zeroize` in prior versions. Without `Zeroize` + // we cannot build a `SecretBox`. + + NonZero::new(*self.totient.as_ref()).expect("φ(n) is a positive, non-zero number") } /// Returns $\phi(N)^{-1} \mod N$ - pub fn inv_totient(&self) -> &P::UintMod { - // TODO (#77): must be wrapped in a Secret - &self.inv_totient + pub fn inv_totient(&self) -> SecretBox { + Box::new(self.inv_totient).into() } /// Returns $N^{-1} \mod \phi(N)$ @@ -198,17 +243,23 @@ impl SecretKeyPaillierPrecomputed

{ } pub fn rns_split(&self, elem: &P::Uint) -> (P::HalfUintMod, P::HalfUintMod) { - // TODO (#77): zeroize intermediate values - // May be some speed up potential here since we know p and q are small, // but it needs to be supported by `crypto-bigint`. - let p_rem = *elem % NonZero::new(self.sk.p.clone().into_wide()).unwrap(); - let q_rem = *elem % NonZero::new(self.sk.q.clone().into_wide()).unwrap(); + let mut p_rem = + *elem % NonZero::new(self.sk.p.expose_secret().clone().into_wide()).unwrap(); + let mut q_rem = + *elem % NonZero::new(self.sk.q.expose_secret().clone().into_wide()).unwrap(); let p_rem_half = P::HalfUint::try_from_wide(p_rem).unwrap(); let q_rem_half = P::HalfUint::try_from_wide(q_rem).unwrap(); - let p_rem_mod = P::HalfUintMod::new(p_rem_half, self.precomputed_mod_p().clone()); - let q_rem_mod = P::HalfUintMod::new(q_rem_half, self.precomputed_mod_q().clone()); + let p_rem_mod = p_rem_half.to_montgomery(self.precomputed_mod_p()); + let q_rem_mod = q_rem_half.to_montgomery(self.precomputed_mod_q()); + + // crypto_bigint::Uint does not impl `ZeroizeOnDrop` (only + // `DefaultIsZeroes`) so we're stuck with this rather clunky way of zeroizing. + p_rem.zeroize(); + q_rem.zeroize(); + (p_rem_mod, q_rem_mod) } @@ -237,8 +288,8 @@ impl SecretKeyPaillierPrecomputed

{ // TODO (#73): when we can extract the modulus from `HalfUintMod`, this can be moved there. // For now we have to keep this a method of SecretKey to have access to `p` and `q`. let (p_part, q_part) = rns; - let p_res = self.sqrt_part(p_part, &self.sk.p); - let q_res = self.sqrt_part(q_part, &self.sk.q); + let p_res = self.sqrt_part(p_part, self.sk.p.expose_secret()); + let q_res = self.sqrt_part(q_part, self.sk.q.expose_secret()); match (p_res, q_res) { (Some(p), Some(q)) => Some((p, q)), _ => None, @@ -258,7 +309,7 @@ impl SecretKeyPaillierPrecomputed

{ let a = a_half.into_wide(); // Will not overflow since 0 <= x < q, and 0 <= a < p. - a.checked_add(&self.sk.p.mul_wide(&x)) + a.checked_add(&self.sk.p.expose_secret().mul_wide(&x)) .expect("Will not overflow since 0 <= x < q, and 0 <= a < p.") } @@ -391,7 +442,10 @@ impl Eq for PublicKeyPaillierPrecomputed

{} #[cfg(test)] mod tests { + use rand::SeedableRng; use rand_core::OsRng; + use serde::Serialize; + use serde_assert::Token; use super::super::params::PaillierTest; use super::SecretKeyPaillier; @@ -401,4 +455,32 @@ mod tests { let sk = SecretKeyPaillier::::random(&mut OsRng).to_precomputed(); let _pk = sk.public_key(); } + + #[test] + fn debug_redacts_secrets() { + let sk = SecretKeyPaillier::::random(&mut OsRng); + + let debug_output = format!("Sikrit {:?}", sk); + assert_eq!(debug_output, "Sikrit [REDACTED synedrion::paillier::keys::SecretKeyPaillier]"); + } + + #[test] + fn serialization_and_clone_works() { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123456); + let sk = SecretKeyPaillier::::random(&mut rng); + + let serializer = serde_assert::Serializer::builder().build(); + let sk_ser = sk.serialize(&serializer).unwrap(); + let expected_tokens = [ + Token::Tuple { len: 2 }, + Token::Str("d30b226b6f3a29a048826fa4cf85f83a7aa03d097ec89aea7b1f35633f5719e180b93af2508fc289c196078937d9d8a61af6d7768301d231bafdf87c10f28f8a".into()), + Token::Str("7f0e0796291488cf87ed167109d9daf34e4ad5cc1399c9d034803b953652598963abf19b9675653a51e619651f1ab15e66256829c250903fae3ab96683b5aff9".into()), + Token::TupleEnd, + ]; + assert_eq!(sk_ser, expected_tokens); + + // Clone works + let clone = sk.clone(); + assert_eq!(sk, clone); + } } diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index 58ca18e..3c620bf 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -31,7 +31,8 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn /// A modulo-residue counterpart of `HalfUint`. type HalfUintMod: Monty + Retrieve - + Invert>; + + Invert> + + Zeroize; /// An integer that fits the RSA modulus. type Uint: Integer @@ -65,7 +66,8 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn + RandomMod + Serialize + for<'de> Deserialize<'de> - + ToMontgomery; + + ToMontgomery + + Zeroize; /// A modulo-residue counterpart of `WideUint`. type WideUintMod: Monty @@ -79,7 +81,6 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn // Technically, it doesn't have to be that large, but the time spent multiplying these // is negligible, and when it is used as an exponent, it is bounded anyway. // So it is easier to keep it as a double of `WideUint`. - // type ExtraWideUint: UintLike + Serialize + for<'de> Deserialize<'de>; type ExtraWideUint: Bounded + ConditionallySelectable + Encoding @@ -92,7 +93,7 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn /// Paillier parameters for unit tests in this submodule. #[cfg(test)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Zeroize)] pub(crate) struct PaillierTest; #[cfg(test)] diff --git a/synedrion/src/paillier/ring_pedersen.rs b/synedrion/src/paillier/ring_pedersen.rs index 6084150..92326fb 100644 --- a/synedrion/src/paillier/ring_pedersen.rs +++ b/synedrion/src/paillier/ring_pedersen.rs @@ -1,6 +1,7 @@ use core::ops::Mul; use rand_core::CryptoRngCore; +use secrecy::{ExposeSecret, SecretBox}; use serde::{Deserialize, Serialize}; use super::{PaillierParams, PublicKeyPaillierPrecomputed, SecretKeyPaillierPrecomputed}; @@ -72,36 +73,44 @@ impl RPParamsMod

{ // - this will match the order in the paper pub fn commit( &self, - secret: &Signed, + secret: &SecretBox>, randomizer: &Signed, ) -> RPCommitmentMod

{ // $t^\rho * s^m mod N$ where $\rho$ is the randomizer and $m$ is the secret. RPCommitmentMod( - pow_signed_wide::(self.base, randomizer) * pow_signed(self.power, secret), + pow_signed_wide::(self.base, randomizer) + * pow_signed(self.power, secret.expose_secret()), ) } pub fn commit_wide( &self, - secret: &Signed, + // TODO(dp): @reviewers Question unrelated to the PR, just something I noticed: Why is the + // `secret` a `P::WideUint` in this method but a `P::Uint` in `commit_xwide` below? Should + // it be the same here? Or `P::ExtraWide` there? Maybe it's like it should, because while + // `commit` and `commit_wide` take a `Signed` secret, `commit_xwide` takes a `Bounded`? + secret: &SecretBox>, randomizer: &Signed, ) -> RPCommitmentMod

{ // $t^\rho * s^m mod N$ where $\rho$ is the randomizer and $m$ is the secret. RPCommitmentMod( pow_signed_wide::(self.base, randomizer) - * pow_signed_wide::(self.power, secret), + * pow_signed_wide::(self.power, secret.expose_secret()), ) } pub fn commit_xwide( &self, - secret: &Bounded, + secret: &SecretBox>, randomizer: &Signed, ) -> RPCommitmentMod

{ // $t^\rho * s^m mod N$ where $\rho$ is the randomizer and $m$ is the secret. RPCommitmentMod( pow_signed_extra_wide::(self.base, randomizer) - * self.power.pow_bounded_exp(secret.as_ref(), secret.bound()), + * self.power.pow_bounded_exp( + secret.expose_secret().as_ref(), + secret.expose_secret().bound(), + ), ) } diff --git a/synedrion/src/uint/bounded.rs b/synedrion/src/uint/bounded.rs index 57c4ef9..c4d520f 100644 --- a/synedrion/src/uint/bounded.rs +++ b/synedrion/src/uint/bounded.rs @@ -2,7 +2,9 @@ use alloc::boxed::Box; use alloc::format; use alloc::string::String; +use secrecy::SecretBox; use serde::{Deserialize, Serialize}; +use zeroize::DefaultIsZeroes; use super::{ subtle::{Choice, ConditionallySelectable, ConstantTimeLess, CtOption}, @@ -59,7 +61,7 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] #[serde( try_from = "PackedBounded", into = "PackedBounded", @@ -180,6 +182,16 @@ where } } +impl DefaultIsZeroes for Bounded where T: Integer + Copy {} + +impl From> for SecretBox> +where + T: Integer + Copy, +{ + fn from(value: Bounded) -> Self { + Box::new(value).into() + } +} #[cfg(test)] mod tests { use crypto_bigint::{CheckedMul, U1024, U128, U2048}; diff --git a/synedrion/src/uint/signed.rs b/synedrion/src/uint/signed.rs index 7d14521..5d42b04 100644 --- a/synedrion/src/uint/signed.rs +++ b/synedrion/src/uint/signed.rs @@ -1,10 +1,12 @@ -use alloc::string::String; +use alloc::{boxed::Box, string::String}; use core::ops::{Add, Mul, Neg, Sub}; #[cfg(test)] use crypto_bigint::Random; use digest::XofReader; use rand_core::CryptoRngCore; +use secrecy::SecretBox; use serde::{Deserialize, Serialize}; +use zeroize::Zeroize; use super::{ bounded::PackedBounded, @@ -63,7 +65,6 @@ where into = "PackedSigned", bound = "T: Integer + Encoding + crypto_bigint::Bounded + ConditionallySelectable" )] - pub struct Signed { /// bound on the bit size of the absolute value bound: u32, @@ -320,6 +321,35 @@ impl Default for Signed { } } +impl Zeroize for Signed +where + T: Integer + Zeroize, +{ + fn zeroize(&mut self) { + self.value.zeroize(); + } +} + +impl secrecy::CloneableSecret for Signed where T: Clone + Integer + Zeroize {} + +impl From> for SecretBox> +where + T: Integer + Zeroize, +{ + fn from(value: Signed) -> Self { + Box::new(value).into() + } +} + +impl From<&Signed> for SecretBox> +where + T: Integer + Zeroize, +{ + fn from(value: &Signed) -> Self { + SecretBox::new(Box::new(value.clone())) + } +} + impl ConditionallySelectable for Signed where T: Integer + ConditionallySelectable,