Skip to content

Commit

Permalink
Decrypt into Secret
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Dec 1, 2024
1 parent 3de9f00 commit 06c4ca1
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 62 deletions.
28 changes: 17 additions & 11 deletions synedrion/src/cggmp21/interactive_signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ struct Round2Artifact<P: SchemeParams> {

struct Round2Payload<P: SchemeParams> {
cap_gamma: Point,
alpha: Signed<<P::Paillier as PaillierParams>::Uint>,
hat_alpha: Signed<<P::Paillier as PaillierParams>::Uint>,
alpha: Secret<Signed<<P::Paillier as PaillierParams>::Uint>>,
hat_alpha: Secret<Signed<<P::Paillier as PaillierParams>::Uint>>,
cap_d: Ciphertext<P::Paillier>,
hat_cap_d: Ciphertext<P::Paillier>,
}
Expand Down Expand Up @@ -612,12 +612,18 @@ impl<P: SchemeParams, I: PartyId> Round<I> for Round2<P, I> {
// `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::<P> {
cap_gamma: direct_message.cap_gamma,
Expand All @@ -642,14 +648,14 @@ impl<P: SchemeParams, I: PartyId> Round<I> for Round2<P, I> {

let cap_delta = cap_gamma * &self.context.k;

let alpha_sum: Signed<_> = payloads.values().map(|p| p.alpha).sum();
let beta_sum: Secret<Signed<_>> = artifacts.values().map(|p| &p.beta).sum();
let alpha_sum: Secret<Signed<_>> = payloads.values().map(|payload| &payload.alpha).sum();
let beta_sum: Secret<Signed<_>> = artifacts.values().map(|artifact| &artifact.beta).sum();
let delta = secret_signed_from_scalar::<P>(&self.context.gamma)
* secret_signed_from_scalar::<P>(&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<Signed<_>> = payloads.values().map(|payload| &payload.hat_alpha).sum();
let hat_beta_sum: Secret<Signed<_>> = artifacts.values().map(|artifact| &artifact.hat_beta).sum();
let chi = secret_signed_from_scalar::<P>(&self.context.key_share.secret_share)
* secret_signed_from_scalar::<P>(&self.context.k)
Expand Down
4 changes: 2 additions & 2 deletions synedrion/src/cggmp21/key_refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -651,7 +651,7 @@ impl<P: SchemeParams, I: PartyId> Round<I> for Round3<P, I> {
.paillier_enc_x
.to_precomputed(&self.context.data_precomp.paillier_pk);

let x = Secret::init_with(|| scalar_from_uint::<P>(&enc_x.decrypt(&self.context.paillier_sk)));
let x = secret_scalar_from_uint::<P>(&enc_x.decrypt(&self.context.paillier_sk));

let my_idx = self.context.ids_ordering[&self.context.my_id];

Expand Down
16 changes: 16 additions & 0 deletions synedrion/src/cggmp21/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ pub(crate) fn scalar_from_wide_signed<P: SchemeParams>(
Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative())
}

pub(crate) fn secret_scalar_from_uint<P: SchemeParams>(
value: &Secret<<P::Paillier as PaillierParams>::Uint>,
) -> Secret<Scalar> {
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<P: SchemeParams>(
value: &Secret<Scalar>,
) -> Secret<<P::Paillier as PaillierParams>::Uint> {
Expand Down
68 changes: 37 additions & 31 deletions synedrion/src/paillier/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -16,7 +16,7 @@ use super::{
use crate::{
tools::Secret,
uint::{
subtle::{Choice, ConditionallyNegatable, ConditionallySelectable},
subtle::{Choice, ConditionallyNegatable},
Bounded, Exponentiable, HasWide, Retrieve, Signed, ToMontgomery,
},
};
Expand Down Expand Up @@ -225,7 +225,7 @@ impl<P: PaillierParams> Ciphertext<P> {
) -> 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())
Expand All @@ -246,7 +246,7 @@ impl<P: PaillierParams> Ciphertext<P> {
}

/// Decrypts this ciphertext assuming that the plaintext is in range `[0, N)`.
pub fn decrypt(&self, sk: &SecretKeyPaillier<P>) -> P::Uint {
pub fn decrypt(&self, sk: &SecretKeyPaillier<P>) -> Secret<P::Uint> {
assert_eq!(sk.public_key(), &self.pk);

let pk = sk.public_key();
Expand All @@ -260,33 +260,39 @@ impl<P: PaillierParams> Ciphertext<P> {
// `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<P>) -> Signed<P::Uint> {
pub fn decrypt_signed(&self, sk: &SecretKeyPaillier<P>) -> Secret<Signed<P::Uint>> {
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<Secret>` 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::<P::Uint>::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
Expand All @@ -306,7 +312,7 @@ impl<P: PaillierParams> Ciphertext<P> {
// = 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());

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -552,7 +558,7 @@ mod tests {
Secret::init_with(|| <PaillierTest as PaillierParams>::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero()));
let ciphertext = Ciphertext::<PaillierTest>::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);
Expand All @@ -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::<PaillierTest>(plaintext.expose_secret(), &pk.modulus_nonzero());
assert_eq!(plaintext_reduced, plaintext_back);
assert_eq!(&plaintext_reduced, plaintext_back.expose_secret());
}

#[test]
Expand Down Expand Up @@ -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()
);
}

Expand All @@ -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()
);
}

Expand All @@ -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()
);
}
}
4 changes: 2 additions & 2 deletions synedrion/src/paillier/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions synedrion/src/paillier/params.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -43,6 +43,7 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn
+ Bounded
+ Gcd<Output = Self::Uint>
+ ConditionallySelectable
+ ConstantTimeGreater
+ Encoding<Repr: Zeroize>
+ Hashable
+ HasWide<Wide = Self::WideUint>
Expand Down Expand Up @@ -82,7 +83,8 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn
+ ConditionallyNegatable
+ ConditionallySelectable
+ Invert<Output = CtOption<Self::WideUintMod>>
+ Retrieve<Output = Self::WideUint>;
+ Retrieve<Output = Self::WideUint>
+ Zeroize;

/// An integer that fits the squared RSA modulus times a small factor.
/// Used in some ZK proofs.
Expand Down
Loading

0 comments on commit 06c4ca1

Please sign in to comment.