From a731a6255abb128a23e3d366294aea594abbd080 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Mon, 9 Dec 2024 16:09:01 -0800 Subject: [PATCH 1/4] Split Signed/Bounded into SecretSigned/PublicSigned/SecretUnsigned --- synedrion/src/cggmp21/entities.rs | 6 +- synedrion/src/cggmp21/interactive_signing.rs | 73 +- synedrion/src/cggmp21/key_refresh.rs | 4 +- synedrion/src/cggmp21/params.rs | 85 +-- synedrion/src/cggmp21/sigma/aff_g.rs | 70 +- synedrion/src/cggmp21/sigma/dec.rs | 40 +- synedrion/src/cggmp21/sigma/enc.rs | 38 +- synedrion/src/cggmp21/sigma/fac.rs | 101 ++- synedrion/src/cggmp21/sigma/log_star.rs | 38 +- synedrion/src/cggmp21/sigma/mod_.rs | 11 +- synedrion/src/cggmp21/sigma/mul.rs | 42 +- synedrion/src/cggmp21/sigma/mul_star.rs | 40 +- synedrion/src/cggmp21/sigma/prm.rs | 23 +- synedrion/src/curve/arithmetic.rs | 60 +- synedrion/src/paillier/encryption.rs | 288 ++++---- synedrion/src/paillier/keys.rs | 72 +- synedrion/src/paillier/params.rs | 18 +- synedrion/src/paillier/ring_pedersen.rs | 99 +-- synedrion/src/paillier/rsa.rs | 38 +- synedrion/src/tools/secret.rs | 326 ++++----- synedrion/src/tools/sss.rs | 2 +- synedrion/src/uint.rs | 16 +- synedrion/src/uint/bounded.rs | 201 ------ synedrion/src/uint/public_signed.rs | 248 +++++++ synedrion/src/uint/secret_signed.rs | 710 +++++++++++++++++++ synedrion/src/uint/secret_unsigned.rs | 114 +++ synedrion/src/uint/signed.rs | 689 ------------------ synedrion/src/uint/traits.rs | 227 ++---- 28 files changed, 1781 insertions(+), 1898 deletions(-) delete mode 100644 synedrion/src/uint/bounded.rs create mode 100644 synedrion/src/uint/public_signed.rs create mode 100644 synedrion/src/uint/secret_signed.rs create mode 100644 synedrion/src/uint/secret_unsigned.rs delete mode 100644 synedrion/src/uint/signed.rs diff --git a/synedrion/src/cggmp21/entities.rs b/synedrion/src/cggmp21/entities.rs index cb247d1..b2aa0a5 100644 --- a/synedrion/src/cggmp21/entities.rs +++ b/synedrion/src/cggmp21/entities.rs @@ -18,7 +18,7 @@ use crate::{ SecretKeyPaillier, SecretKeyPaillierWire, }, tools::Secret, - uint::Signed, + uint::SecretSigned, }; /// The result of the KeyInit protocol. @@ -103,7 +103,7 @@ pub(crate) struct PresigningData { // Values generated during presigning, // kept in case we need to generate a proof of correctness. - pub(crate) product_share_nonreduced: Secret::Uint>>, + pub(crate) product_share_nonreduced: SecretSigned<::Uint>, // $K_i$. pub(crate) cap_k: Ciphertext, @@ -114,7 +114,7 @@ pub(crate) struct PresigningData { #[derive(Debug, Clone)] pub(crate) struct PresigningValues { - pub(crate) hat_beta: Secret::Uint>>, + pub(crate) hat_beta: SecretSigned<::Uint>, pub(crate) hat_r: Randomizer, pub(crate) hat_s: Randomizer, pub(crate) cap_k: Ciphertext, diff --git a/synedrion/src/cggmp21/interactive_signing.rs b/synedrion/src/cggmp21/interactive_signing.rs index 7ab056d..5cd0f47 100644 --- a/synedrion/src/cggmp21/interactive_signing.rs +++ b/synedrion/src/cggmp21/interactive_signing.rs @@ -20,8 +20,8 @@ use serde::{Deserialize, Serialize}; use super::{ entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues, PublicAuxInfoPrecomputed}, params::{ - bounded_from_scalar, secret_bounded_from_scalar, secret_scalar_from_signed, secret_signed_from_scalar, - secret_uint_from_scalar, signed_from_scalar, SchemeParams, + public_signed_from_scalar, secret_scalar_from_signed, secret_signed_from_scalar, secret_unsigned_from_scalar, + SchemeParams, }, sigma::{AffGProof, DecProof, EncProof, LogStarProof, MulProof, MulStarProof}, }; @@ -32,7 +32,7 @@ use crate::{ hashing::{Chain, FofHasher, HashOutput}, DowncastMap, Secret, Without, }, - uint::Signed, + uint::SecretSigned, }; /// A protocol for creating all the data necessary for signing @@ -162,10 +162,10 @@ impl EntryPoint for InteractiveSigning { let pk = aux_info.secret_aux.paillier_sk.public_key(); let nu = Randomizer::::random(rng, pk); - let cap_g = Ciphertext::new_with_randomizer(pk, &secret_uint_from_scalar::

(&gamma), &nu); + let cap_g = Ciphertext::new_with_randomizer(pk, &secret_unsigned_from_scalar::

(&gamma), &nu); let rho = Randomizer::::random(rng, pk); - let cap_k = Ciphertext::new_with_randomizer(pk, &secret_uint_from_scalar::

(&k), &rho); + let cap_k = Ciphertext::new_with_randomizer(pk, &secret_unsigned_from_scalar::

(&k), &rho); Ok(BoxedRound::new_dynamic(Round1 { context: Context { @@ -421,8 +421,8 @@ struct Round2Message { #[derive(Debug, Clone)] struct Round2Artifact { - beta: Secret::Uint>>, - hat_beta: Secret::Uint>>, + beta: SecretSigned<::Uint>, + hat_beta: SecretSigned<::Uint>, r: Randomizer, s: Randomizer, hat_r: Randomizer, @@ -435,8 +435,8 @@ struct Round2Artifact { struct Round2Payload { cap_gamma: Point, - alpha: Secret::Uint>>, - hat_alpha: Secret::Uint>>, + alpha: SecretSigned<::Uint>, + hat_alpha: SecretSigned<::Uint>, cap_d: Ciphertext, hat_cap_d: Ciphertext, } @@ -473,8 +473,8 @@ impl Round for Round2 { let target_pk = &self.context.public_aux(destination)?.paillier_pk; - let beta = Secret::init_with(|| Signed::random_bounded_bits(rng, P::LP_BOUND)); - let hat_beta = Secret::init_with(|| Signed::random_bounded_bits(rng, P::LP_BOUND)); + let beta = SecretSigned::random_in_exp_range(rng, P::LP_BOUND); + let hat_beta = SecretSigned::random_in_exp_range(rng, P::LP_BOUND); let r = Randomizer::random(rng, pk); let s = Randomizer::random(rng, target_pk); let hat_r = Randomizer::random(rng, pk); @@ -661,18 +661,10 @@ 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 = 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)) - })?; + let alpha = Option::from(alpha.ensure_bound(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1)) + .ok_or_else(|| ReceiveError::protocol(InteractiveSigningError::OutOfBoundsAlpha))?; + let hat_alpha = Option::from(hat_alpha.ensure_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, @@ -697,15 +689,15 @@ impl Round for Round2 { let cap_delta = cap_gamma * &self.context.k; - let alpha_sum: Secret> = payloads.values().map(|payload| &payload.alpha).sum(); - let beta_sum: Secret> = artifacts.values().map(|artifact| &artifact.beta).sum(); + let alpha_sum: SecretSigned<_> = payloads.values().map(|payload| &payload.alpha).sum(); + let beta_sum: SecretSigned<_> = 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: Secret> = payloads.values().map(|payload| &payload.hat_alpha).sum(); - let hat_beta_sum: Secret> = artifacts.values().map(|artifact| &artifact.hat_beta).sum(); + let hat_alpha_sum: SecretSigned<_> = payloads.values().map(|payload| &payload.hat_alpha).sum(); + let hat_beta_sum: SecretSigned<_> = 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) + &hat_alpha_sum @@ -734,8 +726,8 @@ impl Round for Round2 { #[derive(Debug)] struct Round3 { context: Context, - delta: Secret::Uint>>, - chi: Secret::Uint>>, + delta: SecretSigned<::Uint>, + chi: SecretSigned<::Uint>, cap_delta: Point, cap_gamma: Point, all_cap_k: BTreeMap>, @@ -999,15 +991,15 @@ impl Round for Round3 { // Mul proof let my_id = &self.context.my_id; let rho = Randomizer::random(rng, pk); - let cap_x = self + let cap_k = self .all_cap_k .get(my_id) .ok_or_else(|| LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?; - let cap_y = self + let cap_g = self .all_cap_g .get(my_id) .ok_or_else(|| LocalError::new("my_id={my_id:?} is missing in all_cap_g"))?; - let cap_h = (cap_y * secret_bounded_from_scalar::

(&self.context.k)).mul_randomizer(&rho); + let cap_h = (cap_g * &secret_unsigned_from_scalar::

(&self.context.k)).mul_randomizer(&rho); let p_mul = MulProof::

::new( rng, @@ -1015,12 +1007,12 @@ impl Round for Round3 { &self.context.rho, &rho, pk, - cap_x, - cap_y, + cap_k, + cap_g, &cap_h, &aux, ); - assert!(p_mul.verify(pk, cap_x, cap_y, &cap_h, &aux)); + assert!(p_mul.verify(pk, cap_k, cap_g, &cap_h, &aux)); // Dec proof @@ -1229,7 +1221,7 @@ impl Round for Round4 { let cap_x = self.context.public_share(&my_id)?; let rho = Randomizer::random(rng, pk); - let hat_cap_h = (&self.presigning.cap_k * secret_bounded_from_scalar::

(x)).mul_randomizer(&rho); + let hat_cap_h = (&self.presigning.cap_k * &secret_unsigned_from_scalar::

(x)).mul_randomizer(&rho); let aux = (&self.context.ssid_hash, &my_id); @@ -1267,16 +1259,17 @@ impl Round for Round4 { } let r = self.presigning.nonce; + let signed_r = public_signed_from_scalar::

(&r); + let signed_message = public_signed_from_scalar::

(&self.context.message); - let ciphertext = ciphertext * bounded_from_scalar::

(&r) - + &self.presigning.cap_k * bounded_from_scalar::

(&self.context.message); + let ciphertext = ciphertext * &signed_r + &self.presigning.cap_k * &signed_message; let rho = ciphertext.derive_randomizer(sk); // This is the same as `s_part` but if all the calculations were performed // without reducing modulo curve order. let s_part_nonreduced = secret_signed_from_scalar::

(&self.presigning.ephemeral_scalar_share) - * signed_from_scalar::

(&self.context.message) - + self.presigning.product_share_nonreduced.clone() * signed_from_scalar::

(&r); + * signed_message + + &self.presigning.product_share_nonreduced * signed_r; let mut dec_proofs = Vec::new(); for id_l in self.context.other_ids.iter() { diff --git a/synedrion/src/cggmp21/key_refresh.rs b/synedrion/src/cggmp21/key_refresh.rs index 3442a5b..70a5bc4 100644 --- a/synedrion/src/cggmp21/key_refresh.rs +++ b/synedrion/src/cggmp21/key_refresh.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; use super::{ entities::{AuxInfo, KeyShareChange, PublicAuxInfo, SecretAuxInfo}, - params::{secret_scalar_from_uint, secret_uint_from_scalar, SchemeParams}, + params::{secret_scalar_from_uint, secret_unsigned_from_scalar, SchemeParams}, sigma::{FacProof, ModProof, PrmProof, SchCommitment, SchProof, SchSecret}, }; use crate::{ @@ -619,7 +619,7 @@ impl Round for Round3 { .cap_x_to_send .get(destination_idx) .ok_or_else(|| LocalError::new("destination_idx={destination_idx} is missing in cap_x_to_send"))?; - let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &secret_uint_from_scalar::

(x_secret)); + let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &secret_unsigned_from_scalar::

(x_secret)); let proof_secret = self .context .tau_x diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index 44be41a..3ba2de2 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -4,6 +4,7 @@ use core::fmt::Debug; // and `k256` depends on the released one. // So as long as that is the case, `k256` `Uint` is separate // from the one used throughout the crate. +use crypto_bigint::{Encoding, NonZero, Uint, Zero, U1024, U2048, U4096, U512, U8192}; use k256::elliptic_curve::bigint::Uint as K256Uint; use serde::{Deserialize, Serialize}; @@ -14,10 +15,7 @@ use crate::{ hashing::{Chain, HashableType}, Secret, }, - uint::{ - subtle::ConditionallySelectable, Bounded, Encoding, NonZero, Signed, U1024Mod, U2048Mod, U4096Mod, U512Mod, - Uint, Zero, U1024, U2048, U4096, U512, U8192, - }, + uint::{PublicSigned, SecretSigned, SecretUnsigned, U1024Mod, U2048Mod, U4096Mod, U512Mod}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -145,17 +143,11 @@ pub(crate) fn uint_from_scalar(value: &Scalar) -> ::Uint::from_be_bytes(repr) } -/// Converts a curve scalar to the associated integer type, wrapped in `Bounded`. -pub(crate) fn bounded_from_scalar(value: &Scalar) -> Bounded<::Uint> { - Bounded::new(uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ - "a curve scalar value is smaller than the curve order, ", - "and the curve order fits in `PaillierParams::Uint`" - ]) -} - /// Converts a curve scalar to the associated integer type, wrapped in `Signed`. -pub(crate) fn signed_from_scalar(value: &Scalar) -> Signed<::Uint> { - bounded_from_scalar::

(value).into_signed().expect(concat![ +pub(crate) fn public_signed_from_scalar( + value: &Scalar, +) -> PublicSigned<::Uint> { + PublicSigned::new_positive(uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ "a curve scalar value is smaller than the half of `PaillierParams::Uint` range, ", "so it is still positive when treated as a 2-complement signed value" ]) @@ -179,9 +171,15 @@ pub(crate) fn scalar_from_uint(value: &(value: &Signed<::Uint>) -> Scalar { +pub(crate) fn scalar_from_signed( + value: &PublicSigned<::Uint>, +) -> Scalar { let abs_value = scalar_from_uint::

(&value.abs()); - Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative()) + if value.is_negative() { + -abs_value + } else { + abs_value + } } /// Converts a wide integer to the associated curve scalar type. @@ -203,10 +201,14 @@ pub(crate) fn scalar_from_wide_uint(value: &( - value: &Signed<::WideUint>, + value: &PublicSigned<::WideUint>, ) -> Scalar { let abs_value = scalar_from_wide_uint::

(&value.abs()); - Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative()) + if value.is_negative() { + -abs_value + } else { + abs_value + } } pub(crate) fn secret_scalar_from_uint( @@ -230,9 +232,7 @@ pub(crate) fn secret_scalar_from_uint( }) } -pub(crate) fn secret_uint_from_scalar( - value: &Secret, -) -> Secret<::Uint> { +fn secret_uint_from_scalar(value: &Secret) -> Secret<::Uint> { let scalar_bytes = Secret::init_with(|| value.expose_secret().to_be_bytes()); let mut repr = Secret::init_with(|| ::Uint::zero().to_be_bytes()); @@ -248,42 +248,29 @@ pub(crate) fn secret_uint_from_scalar( Secret::init_with(|| ::Uint::from_be_bytes(*repr.expose_secret())) } -pub(crate) fn secret_signed_from_scalar( +pub(crate) fn secret_unsigned_from_scalar( value: &Secret, -) -> Secret::Uint>> { - Secret::init_with(|| { - Signed::new_positive( - *secret_uint_from_scalar::

(value).expose_secret(), - ORDER.bits_vartime() as u32, - ) - .expect(concat![ - "a curve scalar value is smaller than the curve order, ", - "and the curve order fits in `PaillierParams::Uint`" - ]) - }) +) -> SecretUnsigned<::Uint> { + SecretUnsigned::new(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ + "a curve scalar value is smaller than the curve order, ", + "and the curve order fits in `PaillierParams::Uint`" + ]) } -pub(crate) fn secret_bounded_from_scalar( +pub(crate) fn secret_signed_from_scalar( value: &Secret, -) -> Secret::Uint>> { - Secret::init_with(|| { - Bounded::new( - *secret_uint_from_scalar::

(value).expose_secret(), - ORDER.bits_vartime() as u32, - ) - .expect(concat![ - "a curve scalar value is smaller than the curve order, ", - "and the curve order fits in `PaillierParams::Uint`" - ]) - }) +) -> SecretSigned<::Uint> { + SecretSigned::new_positive(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ + "a curve scalar value is smaller than the curve order, ", + "and the curve order fits in `PaillierParams::Uint`" + ]) } pub(crate) fn secret_scalar_from_signed( - value: &Secret::Uint>>, + value: &SecretSigned<::Uint>, ) -> Secret { - // TODO: wrap in secrets properly - let abs_value = scalar_from_uint::

(&value.expose_secret().abs()); - Secret::init_with(|| Scalar::conditional_select(&abs_value, &-abs_value, value.expose_secret().is_negative())) + let abs_value = secret_scalar_from_uint::

(&value.abs_value()); + Secret::::conditional_select(&abs_value, &-&abs_value, value.is_negative()) } impl HashableType for P { diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index fc14cd4..9c7bf69 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -13,11 +13,8 @@ use crate::{ Ciphertext, CiphertextWire, MaskedRandomizer, PaillierParams, PublicKeyPaillier, RPCommitmentWire, RPParams, Randomizer, }, - tools::{ - hashing::{Chain, Hashable, XofHasher}, - Secret, - }, - uint::Signed, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::{PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_aff_g"; @@ -46,7 +43,7 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct AffGProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_a: CiphertextWire, cap_b_x: Point, cap_b_y: CiphertextWire, @@ -54,10 +51,10 @@ pub(crate) struct AffGProof { cap_s: RPCommitmentWire, cap_f: RPCommitmentWire, cap_t: RPCommitmentWire, - z1: Signed<::Uint>, - z2: Signed<::Uint>, - z3: Signed<::WideUint>, - z4: Signed<::WideUint>, + z1: PublicSigned<::Uint>, + z2: PublicSigned<::Uint>, + z3: PublicSigned<::WideUint>, + z4: PublicSigned<::WideUint>, omega: MaskedRandomizer, omega_y: MaskedRandomizer, } @@ -66,8 +63,8 @@ impl AffGProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Secret::Uint>>, - y: &Secret::Uint>>, + x: &SecretSigned<::Uint>, + y: &SecretSigned<::Uint>, rho: &Randomizer, rho_y: &Randomizer, pk0: &PublicKeyPaillier, @@ -79,24 +76,24 @@ impl AffGProof

{ setup: &RPParams, aux: &impl Hashable, ) -> Self { - x.expose_secret().assert_bound(P::L_BOUND); - y.expose_secret().assert_bound(P::LP_BOUND); + x.assert_exp_range(P::L_BOUND); + y.assert_exp_range(P::LP_BOUND); assert!(cap_c.public_key() == pk0); assert!(cap_d.public_key() == pk0); assert!(cap_y.public_key() == pk1); - let hat_cap_n = &setup.modulus_bounded(); + let hat_cap_n = setup.modulus(); - let alpha = Secret::init_with(|| Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND)); - let beta = Secret::init_with(|| Signed::random_bounded_bits(rng, P::LP_BOUND + P::EPS_BOUND)); + let alpha = SecretSigned::random_in_exp_range(rng, P::L_BOUND + P::EPS_BOUND); + let beta = SecretSigned::random_in_exp_range(rng, P::LP_BOUND + P::EPS_BOUND); let r = Randomizer::random(rng, pk0); let r_y = Randomizer::random(rng, pk1); - let gamma = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); - let m = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); - let delta = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); - let mu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); + let gamma = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); + let m = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); + let delta = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); + let mu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); let cap_a = (cap_c * &alpha + Ciphertext::new_with_randomizer_signed(pk0, &beta, &r)).to_wire(); let cap_b_x = secret_scalar_from_signed::

(&alpha).mul_by_generator(); @@ -131,19 +128,19 @@ impl AffGProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.to_wide(); - let z1 = *(alpha + x * e).expose_secret(); + let z1 = (alpha + x * e).to_public(); // NOTE: deviation from the paper to support a different $D$ // (see the comment in `AffGProof`) // Original: $z_2 = \beta + e y$ // Modified: $z_2 = \beta - e y$ - let z2 = *(beta + (-y) * e).expose_secret(); + let z2 = (beta + (-y) * e).to_public(); - let z3 = *(gamma + m * e_wide).expose_secret(); - let z4 = *(delta + mu * e_wide).expose_secret(); + let z3 = (gamma + m * e_wide).to_public(); + let z4 = (delta + mu * e_wide).to_public(); let omega = rho.to_masked(&r, &e); @@ -207,7 +204,7 @@ impl AffGProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; @@ -225,8 +222,8 @@ impl AffGProof

{ // 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 - if cap_c * self.z1 + Ciphertext::new_public_with_randomizer_signed(pk0, &self.z2, &self.omega) - != cap_d * e + self.cap_a.to_precomputed(pk0) + if cap_c * &self.z1 + Ciphertext::new_public_with_randomizer_signed(pk0, &self.z2, &self.omega) + != cap_d * &e + self.cap_a.to_precomputed(pk0) { return false; } @@ -242,7 +239,7 @@ impl AffGProof

{ // (1 + N_1)^{z_2} \omega_y^{N_1} = B_y Y^(-e) \mod N_1^2 // => encrypt_1(z_2, \omega_y) = B_y (+) Y (*) (-e) if Ciphertext::new_public_with_randomizer_signed(pk1, &self.z2, &self.omega_y) - != cap_y * (-e) + self.cap_b_y.to_precomputed(pk1) + != cap_y * &(-e) + self.cap_b_y.to_precomputed(pk1) { return false; } @@ -250,14 +247,14 @@ impl AffGProof

{ // s^{z_1} t^{z_3} = E S^e \mod \hat{N} let cap_e = self.cap_e.to_precomputed(setup); let cap_s = self.cap_s.to_precomputed(setup); - if setup.commit_public(&self.z1, &self.z3) != &cap_e * &cap_s.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.z3) != &cap_e * &cap_s.pow(&e) { return false; } // s^{z_2} t^{z_4} = F T^e \mod \hat{N} let cap_f = self.cap_f.to_precomputed(setup); let cap_t = self.cap_t.to_precomputed(setup); - if setup.commit_public(&self.z2, &self.z4) != &cap_f * &cap_t.pow_signed_vartime(&e) { + if setup.commit(&self.z2, &self.z4) != &cap_f * &cap_t.pow(&e) { return false; } @@ -273,8 +270,7 @@ mod tests { use crate::{ cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -292,12 +288,12 @@ mod tests { let aux: &[u8] = b"abcde"; - let x = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); - let y = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::LP_BOUND)); + let x = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); + let y = SecretSigned::random_in_exp_range(&mut OsRng, Params::LP_BOUND); let rho = Randomizer::random(&mut OsRng, pk0); let rho_y = Randomizer::random(&mut OsRng, pk1); - let secret = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); + let secret = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); let cap_c = Ciphertext::new_signed(&mut OsRng, pk0, &secret); let cap_d = &cap_c * &x + Ciphertext::new_with_randomizer_signed(pk0, &-&y, &rho); diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index a241010..88e44b9 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -14,8 +14,7 @@ use crate::{ Randomizer, }, tools::hashing::{Chain, Hashable, XofHasher}, - tools::Secret, - uint::Signed, + uint::{PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_dec"; @@ -36,13 +35,13 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DecProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_s: RPCommitmentWire, cap_t: RPCommitmentWire, cap_a: CiphertextWire, gamma: Scalar, - z1: Signed<::WideUint>, - z2: Signed<::WideUint>, + z1: PublicSigned<::WideUint>, + z2: PublicSigned<::WideUint>, omega: MaskedRandomizer, } @@ -50,7 +49,7 @@ impl DecProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - y: &Secret::Uint>>, + y: &SecretSigned<::Uint>, rho: &Randomizer, pk0: &PublicKeyPaillier, x: &Scalar, @@ -60,11 +59,11 @@ impl DecProof

{ ) -> Self { assert_eq!(cap_c.public_key(), pk0); - let hat_cap_n = &setup.modulus_bounded(); // $\hat{N}$ + let hat_cap_n = setup.modulus(); // $\hat{N}$ - let alpha = Secret::init_with(|| Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND)); - let mu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); - let nu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); + let alpha = SecretSigned::random_in_exp_range(rng, P::L_BOUND + P::EPS_BOUND); + let mu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); + let nu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let r = Randomizer::random(rng, pk0); let cap_s = setup.commit(y, &mu).to_wire(); @@ -93,10 +92,10 @@ impl DecProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = *(alpha.to_wide() + y.mul_wide(&e)).expose_secret(); - let z2 = *(nu + mu * e.to_wide()).expose_secret(); + let z1 = (alpha.to_wide() + y.mul_wide(&e)).to_public(); + let z2 = (nu + mu * e.to_wide()).to_public(); let omega = rho.to_masked(&r, &e); @@ -137,7 +136,7 @@ impl DecProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; @@ -145,7 +144,7 @@ impl DecProof

{ // enc(z_1, \omega) == A (+) C (*) e if Ciphertext::new_public_with_randomizer_wide(pk0, &self.z1, &self.omega) - != self.cap_a.to_precomputed(pk0) + cap_c * e + != self.cap_a.to_precomputed(pk0) + cap_c * &e { return false; } @@ -158,7 +157,7 @@ impl DecProof

{ // s^{z_1} t^{z_2} == T S^e let cap_s = self.cap_s.to_precomputed(setup); let cap_t = self.cap_t.to_precomputed(setup); - if setup.commit_public_wide(&self.z1, &self.z2) != &cap_t * &cap_s.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.z2) != &cap_t * &cap_s.pow(&e) { return false; } @@ -172,10 +171,9 @@ mod tests { use super::DecProof; use crate::{ - cggmp21::{params::scalar_from_signed, SchemeParams, TestParams}, + cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, PaillierParams, RPParams, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -191,8 +189,8 @@ mod tests { let aux: &[u8] = b"abcde"; // We need something within the range -N/2..N/2 so that it doesn't wrap around. - let y = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Paillier::PRIME_BITS * 2 - 2)); - let x = scalar_from_signed::(y.expose_secret()); + let y = SecretSigned::random_in_exp_range(&mut OsRng, Paillier::PRIME_BITS * 2 - 2); + let x = *secret_scalar_from_signed::(&y).expose_secret(); let rho = Randomizer::random(&mut OsRng, pk); let cap_c = Ciphertext::new_with_randomizer_signed(pk, &y, &rho); diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index aab28e3..fafe3e3 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -10,8 +10,7 @@ use crate::{ Randomizer, }, tools::hashing::{Chain, Hashable, XofHasher}, - tools::Secret, - uint::Signed, + uint::{PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_enc"; @@ -30,36 +29,36 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct EncProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_s: RPCommitmentWire, cap_a: CiphertextWire, cap_c: RPCommitmentWire, - z1: Signed<::Uint>, + z1: PublicSigned<::Uint>, z2: MaskedRandomizer, - z3: Signed<::WideUint>, + z3: PublicSigned<::WideUint>, } impl EncProof

{ pub fn new( rng: &mut impl CryptoRngCore, - k: &Secret::Uint>>, + k: &SecretSigned<::Uint>, rho: &Randomizer, pk0: &PublicKeyPaillier, cap_k: &Ciphertext, setup: &RPParams, aux: &impl Hashable, ) -> Self { - k.expose_secret().assert_bound(P::L_BOUND); + k.assert_exp_range(P::L_BOUND); assert_eq!(cap_k.public_key(), pk0); - let hat_cap_n = &setup.modulus_bounded(); // $\hat{N}$ + let hat_cap_n = setup.modulus(); // $\hat{N}$ // TODO (#86): should we instead sample in range $+- 2^{\ell + \eps} - q 2^\ell$? // This will ensure that the range check on the prover side will pass. - let alpha = Secret::init_with(|| Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND)); - let mu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); + let alpha = SecretSigned::random_in_exp_range(rng, P::L_BOUND + P::EPS_BOUND); + let mu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); let r = Randomizer::random(rng, pk0); - let gamma = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); + let gamma = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let cap_s = setup.commit(k, &mu).to_wire(); let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r).to_wire(); @@ -78,11 +77,11 @@ impl EncProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = *(alpha + k * e).expose_secret(); + let z1 = (alpha + k * e).to_public(); let z2 = rho.to_masked(&r, &e); - let z3 = *(gamma + mu * e.to_wide()).expose_secret(); + let z3 = (gamma + mu * e.to_wide()).to_public(); Self { e, @@ -117,7 +116,7 @@ impl EncProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; @@ -130,14 +129,14 @@ impl EncProof

{ // enc_0(z1, z2) == A (+) K (*) e let c = Ciphertext::new_public_with_randomizer_signed(pk0, &self.z1, &self.z2); - if c != self.cap_a.to_precomputed(pk0) + cap_k * e { + if c != self.cap_a.to_precomputed(pk0) + cap_k * &e { return false; } // s^{z_1} t^{z_3} == C S^e \mod \hat{N} let cap_c = self.cap_c.to_precomputed(setup); let cap_s = self.cap_s.to_precomputed(setup); - if setup.commit_public(&self.z1, &self.z3) != &cap_c * &cap_s.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.z3) != &cap_c * &cap_s.pow(&e) { return false; } @@ -153,8 +152,7 @@ mod tests { use crate::{ cggmp21::{SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -169,7 +167,7 @@ mod tests { let aux: &[u8] = b"abcde"; - let secret = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); + let secret = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); let randomizer = Randomizer::random(&mut OsRng, pk); let ciphertext = Ciphertext::new_with_randomizer_signed(pk, &secret, &randomizer); diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index 740c992..6968e38 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -1,16 +1,14 @@ //! No small factor proof ($\Pi^{fac}$, Section C.5, Fig. 28) +use crypto_bigint::Integer; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; use crate::{ paillier::{PaillierParams, PublicKeyPaillier, RPCommitmentWire, RPParams, SecretKeyPaillier}, - tools::{ - hashing::{Chain, Hashable, XofHasher}, - Secret, - }, - uint::{Bounded, Integer, Signed}, + tools::hashing::{Chain, Hashable, XofHasher}, + uint::{HasWide, PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_fac"; @@ -27,18 +25,18 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct FacProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_p: RPCommitmentWire, cap_q: RPCommitmentWire, cap_a: RPCommitmentWire, cap_b: RPCommitmentWire, cap_t: RPCommitmentWire, - sigma: Signed<::ExtraWideUint>, - z1: Signed<::WideUint>, - z2: Signed<::WideUint>, - omega1: Signed<::WideUint>, - omega2: Signed<::WideUint>, - v: Signed<::ExtraWideUint>, + sigma: PublicSigned<::ExtraWideUint>, + z1: PublicSigned<::WideUint>, + z2: PublicSigned<::WideUint>, + omega1: PublicSigned<::WideUint>, + omega2: PublicSigned<::WideUint>, + v: PublicSigned<::ExtraWideUint>, } impl FacProof

{ @@ -50,7 +48,7 @@ impl FacProof

{ ) -> Self { let pk0 = sk0.public_key(); - let hat_cap_n = &setup.modulus_bounded(); // $\hat{N}$ + let hat_cap_n = setup.modulus(); // $\hat{N}$ // NOTE: using `2^(Paillier::PRIME_BITS - 2)` as $\sqrt{N_0}$ (which is its lower bound) // According to the authors of the paper, it is acceptable. @@ -58,42 +56,39 @@ impl FacProof

{ // and really they should be `~ sqrt{N_0}`. // Note that it has to be matched when we check the range of // `z1` and `z2` during verification. - let sqrt_cap_n = Bounded::new( - ::Uint::one() << (::PRIME_BITS - 2), - ::PRIME_BITS, - ) - .expect("the value is bounded by `2^PRIME_BITS` by construction"); + let sqrt_cap_n = + ::Uint::one() << (::PRIME_BITS - 2); - let alpha = - Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n)); - let beta = - Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n)); - let mu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); - let nu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); + let alpha = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n); + let beta = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n); + let mu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); + let nu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); // N_0 \hat{N} - let scale = pk0.modulus_bounded().mul_wide(hat_cap_n); - - let sigma = - Signed::<::Uint>::random_bounded_bits_scaled_wide(rng, P::L_BOUND, &scale); - let r = Secret::init_with(|| { - Signed::<::Uint>::random_bounded_bits_scaled_wide( - rng, - P::L_BOUND + P::EPS_BOUND, - &scale, - ) - }); - let x = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); - let y = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); + let scale = pk0.modulus().mul_wide(hat_cap_n); + + let sigma = SecretSigned::<::Uint>::random_in_exp_range_scaled_wide( + rng, + P::L_BOUND, + &scale, + ) + .to_public(); + let r = SecretSigned::<::Uint>::random_in_exp_range_scaled_wide( + rng, + P::L_BOUND + P::EPS_BOUND, + &scale, + ); + let x = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); + let y = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let p = sk0.p_signed(); let q = sk0.q_signed(); let cap_p = setup.commit(&p, &mu).to_wire(); let cap_q = setup.commit(&q, &nu); - let cap_a = setup.commit_wide(&alpha, &x).to_wire(); - let cap_b = setup.commit_wide(&beta, &y).to_wire(); - let cap_t = (&cap_q.pow_signed_wide(&alpha) * &setup.commit_zero_xwide(&r)).to_wire(); + let cap_a = setup.commit(&alpha, &x).to_wire(); + let cap_b = setup.commit(&beta, &y).to_wire(); + let cap_t = (&cap_q.pow(&alpha) * &setup.commit_zero(&r)).to_wire(); let cap_q = cap_q.to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) @@ -111,17 +106,17 @@ impl FacProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.to_wide(); let p_wide = sk0.p_wide_signed(); - let hat_sigma = sigma - (p_wide * &nu).expose_secret().to_wide(); - let z1 = *(alpha + (p * e).to_wide()).expose_secret(); - let z2 = *(beta + (q * e).to_wide()).expose_secret(); - let omega1 = *(x + mu * e_wide).expose_secret(); - let omega2 = *(nu * e_wide + &y).expose_secret(); - let v = *(r + &(hat_sigma * e_wide.to_wide())).expose_secret(); + let hat_sigma = sigma - (p_wide * &nu).to_public().to_wide(); + let z1 = (alpha + (p * e).to_wide()).to_public(); + let z2 = (beta + (q * e).to_wide()).to_public(); + let omega1 = (x + mu * e_wide).to_public(); + let omega2 = (nu * e_wide + y).to_public(); + let v = (r + (hat_sigma * e_wide.to_wide())).to_public(); Self { e, @@ -160,34 +155,32 @@ impl FacProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; } // R = s^{N_0} t^\sigma - let cap_r = &setup.commit_public_xwide(&pk0.modulus_bounded(), &self.sigma); + let cap_r = &setup.commit(&pk0.modulus_signed(), &self.sigma); // s^{z_1} t^{\omega_1} == A * P^e \mod \hat{N} let cap_a = self.cap_a.to_precomputed(setup); let cap_p = self.cap_p.to_precomputed(setup); - if setup.commit_public_wide(&self.z1, &self.omega1) != &cap_a * &cap_p.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.omega1) != &cap_a * &cap_p.pow(&e) { return false; } // s^{z_2} t^{\omega_2} == B * Q^e \mod \hat{N} let cap_b = self.cap_b.to_precomputed(setup); let cap_q = self.cap_q.to_precomputed(setup); - if setup.commit_public_wide(&self.z2, &self.omega2) != &cap_b * &cap_q.pow_signed_vartime(&e) { + if setup.commit(&self.z2, &self.omega2) != &cap_b * &cap_q.pow(&e) { return false; } // Q^{z_1} * t^v == T * R^e \mod \hat{N} let cap_t = self.cap_t.to_precomputed(setup); - if &cap_q.pow_signed_wide_vartime(&self.z1) * &setup.commit_public_base_xwide(&self.v) - != &cap_t * &cap_r.pow_signed_vartime(&e) - { + if &cap_q.pow(&self.z1) * &setup.commit_zero(&self.v) != &cap_t * &cap_r.pow(&e) { return false; } diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index 287fa44..1e66631 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -14,8 +14,7 @@ use crate::{ Randomizer, }, tools::hashing::{Chain, Hashable, XofHasher}, - tools::Secret, - uint::Signed, + uint::{PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_log*"; @@ -36,21 +35,21 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct LogStarProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_s: RPCommitmentWire, cap_a: CiphertextWire, cap_y: Point, cap_d: RPCommitmentWire, - z1: Signed<::Uint>, + z1: PublicSigned<::Uint>, z2: MaskedRandomizer, - z3: Signed<::WideUint>, + z3: PublicSigned<::WideUint>, } impl LogStarProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Secret::Uint>>, + x: &SecretSigned<::Uint>, rho: &Randomizer, pk0: &PublicKeyPaillier, cap_c: &Ciphertext, @@ -59,15 +58,15 @@ impl LogStarProof

{ setup: &RPParams, aux: &impl Hashable, ) -> Self { - x.expose_secret().assert_bound(P::L_BOUND); + x.assert_exp_range(P::L_BOUND); assert_eq!(cap_c.public_key(), pk0); - let hat_cap_n = &setup.modulus_bounded(); // $\hat{N}$ + let hat_cap_n = setup.modulus(); // $\hat{N}$ - let alpha = Secret::init_with(|| Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND)); - let mu = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); + let alpha = SecretSigned::random_in_exp_range(rng, P::L_BOUND + P::EPS_BOUND); + let mu = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); let r = Randomizer::random(rng, pk0); - let gamma = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); + let gamma = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let cap_s = setup.commit(x, &mu).to_wire(); let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r).to_wire(); @@ -90,11 +89,11 @@ impl LogStarProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = *(alpha + x * e).expose_secret(); + let z1 = (alpha + x * e).to_public(); let z2 = rho.to_masked(&r, &e); - let z3 = *(gamma + mu * e.to_wide()).expose_secret(); + let z3 = (gamma + mu * e.to_wide()).to_public(); Self { e, @@ -136,7 +135,7 @@ impl LogStarProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; @@ -149,7 +148,7 @@ impl LogStarProof

{ // enc_0(z1, z2) == A (+) C (*) e let c = Ciphertext::new_public_with_randomizer_signed(pk0, &self.z1, &self.z2); - if c != self.cap_a.to_precomputed(pk0) + cap_c * e { + if c != self.cap_a.to_precomputed(pk0) + cap_c * &e { return false; } @@ -161,7 +160,7 @@ impl LogStarProof

{ // s^{z_1} t^{z_3} == D S^e \mod \hat{N} let cap_d = self.cap_d.to_precomputed(setup); let cap_s = self.cap_s.to_precomputed(setup); - if setup.commit_public(&self.z1, &self.z3) != &cap_d * &cap_s.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.z3) != &cap_d * &cap_s.pow(&e) { return false; } @@ -178,8 +177,7 @@ mod tests { cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, curve::{Point, Scalar}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -195,7 +193,7 @@ mod tests { let aux: &[u8] = b"abcde"; let g = Point::GENERATOR * Scalar::random(&mut OsRng); - let x = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); + let x = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); let rho = Randomizer::random(&mut OsRng, pk); let cap_c = Ciphertext::new_with_randomizer_signed(pk, &x, &rho); let cap_x = g * secret_scalar_from_signed::(&x); diff --git a/synedrion/src/cggmp21/sigma/mod_.rs b/synedrion/src/cggmp21/sigma/mod_.rs index a378866..b4d70d8 100644 --- a/synedrion/src/cggmp21/sigma/mod_.rs +++ b/synedrion/src/cggmp21/sigma/mod_.rs @@ -2,7 +2,8 @@ use alloc::vec::Vec; -use crypto_bigint::Square; +use crypto_bigint::{modular::Retrieve, Square}; +use crypto_primes::RandomPrimeWithRng; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -10,7 +11,7 @@ use super::super::SchemeParams; use crate::{ paillier::{PaillierParams, PublicKeyPaillier, SecretKeyPaillier}, tools::hashing::{uint_from_xof, Chain, Hashable, XofHasher}, - uint::{Exponentiable, RandomPrimeWithRng, Retrieve, ToMontgomery}, + uint::{Exponentiable, ToMontgomery}, }; const HASH_TAG: &[u8] = b"P_mod"; @@ -116,7 +117,7 @@ impl ModProof

{ let y = y.to_montgomery(pk.monty_params_mod_n()); let sk_inv_modulus = sk.inv_modulus(); - let z = y.pow_bounded(sk_inv_modulus.expose_secret()); + let z = y.pow(sk_inv_modulus); ModProofElem { x: y_4th, @@ -162,8 +163,8 @@ impl ModProof

{ for (elem, y) in self.proof.iter().zip(self.challenge.0.iter()) { let z_m = elem.z.to_montgomery(monty_params); let mut y_m = y.to_montgomery(monty_params); - let pk_modulus_bounded = pk.modulus_bounded(); - if z_m.pow_bounded(&pk_modulus_bounded) != y_m { + let pk_modulus = pk.modulus_signed(); + if z_m.pow(&pk_modulus) != y_m { return false; } diff --git a/synedrion/src/cggmp21/sigma/mul.rs b/synedrion/src/cggmp21/sigma/mul.rs index 3150a65..5fc7f36 100644 --- a/synedrion/src/cggmp21/sigma/mul.rs +++ b/synedrion/src/cggmp21/sigma/mul.rs @@ -10,17 +10,17 @@ use crate::{ hashing::{Chain, Hashable, XofHasher}, Secret, }, - uint::{Bounded, Signed}, + uint::{PublicSigned, SecretSigned, SecretUnsigned}, }; const HASH_TAG: &[u8] = b"P_mul"; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_a: CiphertextWire, cap_b: CiphertextWire, - z: Signed<::WideUint>, + z: PublicSigned<::WideUint>, u: MaskedRandomizer, v: MaskedRandomizer, } @@ -45,7 +45,7 @@ impl MulProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Secret::Uint>>, + x: &SecretSigned<::Uint>, rho_x: &Randomizer, rho: &Randomizer, pk: &PublicKeyPaillier, @@ -59,19 +59,14 @@ impl MulProof

{ assert_eq!(cap_c.public_key(), pk); let alpha_uint = Secret::init_with(|| pk.random_invertible_residue(rng)); - let alpha = Secret::init_with(|| { - Bounded::new( - *alpha_uint.expose_secret(), - ::MODULUS_BITS, - ) - .expect("the value is bounded by `MODULUS_BITS` by construction") - }); + let alpha = SecretUnsigned::new(alpha_uint, ::MODULUS_BITS) + .expect("the value is bounded by `MODULUS_BITS` by construction"); let r = Randomizer::random(rng, pk); let s = Randomizer::random(rng, pk); let cap_a = (cap_y * &alpha).mul_randomizer(&r).to_wire(); - let cap_b = Ciphertext::new_with_randomizer_bounded(pk, &alpha, &s).to_wire(); + let cap_b = Ciphertext::new_with_randomizer(pk, &alpha, &s).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -86,14 +81,14 @@ impl MulProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z = *(alpha + let z = (alpha .to_wide() .to_signed() .expect("conversion to `WideUint` provides enough space for a sign bit") + x.mul_wide(&e)) - .expose_secret(); + .to_public(); let u = rho.to_masked(&r, &e); let v = rho_x.to_masked(&s, &e); @@ -132,15 +127,17 @@ impl MulProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; } // Y^z u^N = A * C^e \mod N^2 - if cap_y.homomorphic_mul_wide(&self.z).mul_masked_randomizer(&self.u) - != self.cap_a.to_precomputed(pk) + cap_c * e + if cap_y + .homomorphic_mul_wide_public(&self.z) + .mul_masked_randomizer(&self.u) + != self.cap_a.to_precomputed(pk) + cap_c * &e { return false; } @@ -148,7 +145,7 @@ impl MulProof

{ // enc(z, v) == B * X^e \mod N^2 // (Note: typo in the paper, it uses `c` and not `v` here) if Ciphertext::new_public_with_randomizer_wide(pk, &self.z, &self.v) - != self.cap_b.to_precomputed(pk) + cap_x * e + != self.cap_b.to_precomputed(pk) + cap_x * &e { return false; } @@ -165,8 +162,7 @@ mod tests { use crate::{ cggmp21::{SchemeParams, TestParams}, paillier::{Ciphertext, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -179,8 +175,8 @@ mod tests { let aux: &[u8] = b"abcde"; - let x = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); - let y = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); + let x = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); + let y = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); let rho_x = Randomizer::random(&mut OsRng, pk); let rho = Randomizer::random(&mut OsRng, pk); diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index f43d120..4964a75 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -14,8 +14,7 @@ use crate::{ Randomizer, }, tools::hashing::{Chain, Hashable, XofHasher}, - tools::Secret, - uint::Signed, + uint::{PublicSigned, SecretSigned}, }; const HASH_TAG: &[u8] = b"P_mul*"; @@ -36,13 +35,13 @@ Public inputs: */ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulStarProof { - e: Signed<::Uint>, + e: PublicSigned<::Uint>, cap_a: CiphertextWire, cap_b_x: Point, cap_e: RPCommitmentWire, cap_s: RPCommitmentWire, - z1: Signed<::Uint>, - z2: Signed<::WideUint>, + z1: PublicSigned<::Uint>, + z2: PublicSigned<::WideUint>, omega: MaskedRandomizer, } @@ -50,7 +49,7 @@ impl MulStarProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Secret::Uint>>, + x: &SecretSigned<::Uint>, rho: &Randomizer, pk0: &PublicKeyPaillier, cap_c: &Ciphertext, @@ -66,16 +65,16 @@ impl MulStarProof

{ - $\beta$ used to create $A$ is not mentioned anywhere else - a typo, it is effectively == 0 */ - x.expose_secret().assert_bound(P::L_BOUND); + x.assert_exp_range(P::L_BOUND); assert_eq!(cap_c.public_key(), pk0); assert_eq!(cap_d.public_key(), pk0); - let hat_cap_n = &setup.modulus_bounded(); // $\hat{N}$ + let hat_cap_n = setup.modulus(); // $\hat{N}$ let r = Randomizer::random(rng, pk0); - let alpha = Secret::init_with(|| Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND)); - let gamma = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n)); - let m = Secret::init_with(|| Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n)); + let alpha = SecretSigned::random_in_exp_range(rng, P::L_BOUND + P::EPS_BOUND); + let gamma = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); + let m = SecretSigned::random_in_exp_range_scaled(rng, P::L_BOUND, hat_cap_n); let cap_a = (cap_c * &alpha).mul_randomizer(&r).to_wire(); let cap_b_x = secret_scalar_from_signed::

(&alpha).mul_by_generator(); @@ -98,10 +97,10 @@ impl MulStarProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = *(alpha + x * e).expose_secret(); - let z2 = *(gamma + m * e.to_wide()).expose_secret(); + let z1 = (alpha + x * e).to_public(); + let z2 = (gamma + m * e.to_wide()).to_public(); let omega = rho.to_masked(&r, &e); Self { @@ -146,7 +145,7 @@ impl MulStarProof

{ .finalize_to_reader(); // Non-interactive challenge - let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); if e != self.e { return false; @@ -158,7 +157,7 @@ impl MulStarProof

{ } // C (*) z_1 * \omega^{N_0} == A (+) D (*) e - if (cap_c * self.z1).mul_masked_randomizer(&self.omega) != self.cap_a.to_precomputed(pk0) + cap_d * e { + if (cap_c * &self.z1).mul_masked_randomizer(&self.omega) != self.cap_a.to_precomputed(pk0) + cap_d * &e { return false; } @@ -170,7 +169,7 @@ impl MulStarProof

{ // s^{z_1} t^{z_2} == E S^e let cap_e = self.cap_e.to_precomputed(setup); let cap_s = self.cap_s.to_precomputed(setup); - if setup.commit_public(&self.z1, &self.z2) != &cap_e * &cap_s.pow_signed_vartime(&e) { + if setup.commit(&self.z1, &self.z2) != &cap_e * &cap_s.pow(&e) { return false; } @@ -186,8 +185,7 @@ mod tests { use crate::{ cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, - tools::Secret, - uint::Signed, + uint::SecretSigned, }; #[test] @@ -202,8 +200,8 @@ mod tests { let aux: &[u8] = b"abcde"; - let x = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); - let secret = Secret::init_with(|| Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND)); + let x = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); + let secret = SecretSigned::random_in_exp_range(&mut OsRng, Params::L_BOUND); let rho = Randomizer::random(&mut OsRng, pk); let cap_c = Ciphertext::new_signed(&mut OsRng, pk, &secret); let cap_d = (&cap_c * &x).mul_randomizer(&rho); diff --git a/synedrion/src/cggmp21/sigma/prm.rs b/synedrion/src/cggmp21/sigma/prm.rs index 35846f9..2ce9b94 100644 --- a/synedrion/src/cggmp21/sigma/prm.rs +++ b/synedrion/src/cggmp21/sigma/prm.rs @@ -5,6 +5,7 @@ use alloc::{vec, vec::Vec}; +use crypto_bigint::modular::Retrieve; use digest::XofReader; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -13,17 +14,14 @@ use super::super::SchemeParams; use crate::{ paillier::{PaillierParams, RPParams, RPSecret}, tools::hashing::{Chain, Hashable, XofHasher}, - uint::{ - subtle::{Choice, ConditionallySelectable}, - Bounded, Exponentiable, Retrieve, ToMontgomery, - }, + uint::{Exponentiable, PublicSigned, SecretUnsigned, ToMontgomery}, }; const HASH_TAG: &[u8] = b"P_prm"; /// Secret data the proof is based on ($a_i$). #[derive(Clone)] -struct PrmSecret(Vec::Uint>>); +struct PrmSecret(Vec::Uint>>); impl PrmSecret

{ fn random(rng: &mut impl CryptoRngCore, secret: &RPSecret) -> Self { @@ -39,7 +37,7 @@ struct PrmCommitment(Vec<::Uint> impl PrmCommitment

{ fn new(secret: &PrmSecret

, base: &::UintMod) -> Self { - let commitment = secret.0.iter().map(|a| base.pow_bounded(a).retrieve()).collect(); + let commitment = secret.0.iter().map(|a| base.pow(a).retrieve()).collect(); Self(commitment) } } @@ -77,7 +75,7 @@ Public inputs: pub(crate) struct PrmProof { commitment: PrmCommitment

, challenge: PrmChallenge, - proof: Vec::Uint>>, + proof: Vec::Uint>>, } impl PrmProof

{ @@ -100,9 +98,12 @@ impl PrmProof

{ .iter() .zip(challenge.0.iter()) .map(|(a, e)| { - let x = a.add_mod(secret.lambda().expose_secret(), totient.expose_secret()); - let choice = Choice::from(*e as u8); - Bounded::conditional_select(a, &x, choice) + let x = a.add_mod(secret.lambda(), &totient); + + let p = if *e { x.expose_secret() } else { a.expose_secret() }; + + PublicSigned::new_positive(*p, P::Paillier::MODULUS_BITS) + .expect("the value is modulo totient and therefore fits the bound") }) .collect(); Self { @@ -123,7 +124,7 @@ impl PrmProof

{ for ((e, z), a) in challenge.0.iter().zip(self.proof.iter()).zip(self.commitment.0.iter()) { let a = a.to_montgomery(monty_params); - let pwr = setup.base_randomizer().pow_bounded(z); + let pwr = setup.base_randomizer().pow(z); let test = if *e { pwr == a * setup.base_value() } else { pwr == a }; if !test { return false; diff --git a/synedrion/src/curve/arithmetic.rs b/synedrion/src/curve/arithmetic.rs index edf31d7..089c44b 100644 --- a/synedrion/src/curve/arithmetic.rs +++ b/synedrion/src/curve/arithmetic.rs @@ -1,7 +1,7 @@ use alloc::{format, string::String, vec, vec::Vec}; use core::{ default::Default, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + ops::{Add, Mul, Neg, Sub}, }; use digest::Digest; @@ -260,90 +260,88 @@ impl Neg for Scalar { impl Add for Scalar { type Output = Scalar; - fn add(self, other: Scalar) -> Scalar { - Scalar(self.0.add(&other.0)) + fn add(self, rhs: Scalar) -> Scalar { + Scalar(self.0.add(&rhs.0)) } } impl Add<&Scalar> for &Scalar { type Output = Scalar; - fn add(self, other: &Scalar) -> Scalar { - Scalar(self.0.add(&other.0)) + fn add(self, rhs: &Scalar) -> Scalar { + Scalar(self.0.add(&rhs.0)) } } -impl AddAssign<&Scalar> for Scalar { - fn add_assign(&mut self, other: &Scalar) { - self.0.add_assign(&other.0) +impl Add<&Scalar> for Scalar { + type Output = Scalar; + + fn add(self, rhs: &Scalar) -> Scalar { + Scalar(self.0.add(&rhs.0)) } } impl Add for Point { type Output = Point; - fn add(self, other: Point) -> Point { - Point(self.0.add(&(other.0))) + fn add(self, rhs: Point) -> Point { + Point(self.0.add(&(rhs.0))) } } impl Sub for Scalar { type Output = Scalar; - fn sub(self, other: Scalar) -> Scalar { - Scalar(self.0.sub(&(other.0))) + fn sub(self, rhs: Scalar) -> Scalar { + Scalar(self.0.sub(&(rhs.0))) } } -impl SubAssign<&Scalar> for Scalar { - fn sub_assign(&mut self, other: &Scalar) { - self.0.sub_assign(&other.0) +impl Sub<&Scalar> for Scalar { + type Output = Scalar; + + fn sub(self, rhs: &Scalar) -> Scalar { + Scalar(self.0.sub(&(rhs.0))) } } impl Mul for Point { type Output = Point; - fn mul(self, other: Scalar) -> Point { - Point(self.0.mul(&(other.0))) + fn mul(self, rhs: Scalar) -> Point { + Point(self.0.mul(&(rhs.0))) } } impl Mul<&Scalar> for Point { type Output = Point; - fn mul(self, other: &Scalar) -> Point { - Point(self.0.mul(&(other.0))) + fn mul(self, rhs: &Scalar) -> Point { + Point(self.0.mul(&(rhs.0))) } } impl Mul<&Scalar> for &Point { type Output = Point; - fn mul(self, other: &Scalar) -> Point { - Point(self.0.mul(&(other.0))) + fn mul(self, rhs: &Scalar) -> Point { + Point(self.0.mul(&(rhs.0))) } } impl Mul for Scalar { type Output = Scalar; - fn mul(self, other: Scalar) -> Scalar { - Scalar(self.0.mul(&(other.0))) + fn mul(self, rhs: Scalar) -> Scalar { + Scalar(self.0.mul(&(rhs.0))) } } impl Mul<&Scalar> for Scalar { type Output = Scalar; - fn mul(self, other: &Scalar) -> Scalar { - Scalar(self.0.mul(&(other.0))) - } -} - -impl MulAssign<&Scalar> for Scalar { - fn mul_assign(&mut self, other: &Scalar) { - self.0.mul_assign(&other.0) + fn mul(self, rhs: &Scalar) -> Scalar { + Scalar(self.0.mul(&(rhs.0))) } } diff --git a/synedrion/src/paillier/encryption.rs b/synedrion/src/paillier/encryption.rs index ae3a55f..dee336a 100644 --- a/synedrion/src/paillier/encryption.rs +++ b/synedrion/src/paillier/encryption.rs @@ -3,7 +3,11 @@ use core::{ ops::{Add, Mul}, }; -use crypto_bigint::{subtle::ConstantTimeGreater, Monty, ShrVartime}; +use crypto_bigint::{ + modular::Retrieve, + subtle::{Choice, ConditionallyNegatable, ConstantTimeGreater}, + Monty, ShrVartime, +}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -13,10 +17,7 @@ use super::{ }; use crate::{ tools::Secret, - uint::{ - subtle::{Choice, ConditionallyNegatable}, - Bounded, Exponentiable, HasWide, Retrieve, Signed, ToMontgomery, - }, + uint::{Exponentiable, HasWide, PublicSigned, SecretSigned, SecretUnsigned, ToMontgomery}, }; /// A public randomizer-like quantity used in ZK proofs. @@ -60,9 +61,9 @@ impl Randomizer

{ } /// Converts the randomizer to a publishable form by masking it with another randomizer and a public exponent. - pub fn to_masked(&self, coeff: &Self, exponent: &Signed) -> MaskedRandomizer

{ + pub fn to_masked(&self, coeff: &Self, exponent: &PublicSigned) -> MaskedRandomizer

{ MaskedRandomizer( - (self.randomizer_mod.pow_signed_vartime(exponent) * &coeff.randomizer_mod) + (self.randomizer_mod.pow(exponent) * &coeff.randomizer_mod) .expose_secret() .retrieve(), ) @@ -100,7 +101,7 @@ impl Ciphertext

{ /// Encrypts the plaintext with the provided randomizer. fn new_with_randomizer_inner( pk: &PublicKeyPaillier

, - abs_plaintext: &Secret, + abs_plaintext: &SecretUnsigned, randomizer: &Randomizer

, plaintext_is_negative: Choice, ) -> Self { @@ -129,10 +130,10 @@ impl Ciphertext

{ let factor1 = prod_mod + &P::WideUintMod::one(pk.monty_params_mod_n_squared().clone()); let randomizer = randomizer.randomizer.to_wide(); - let pk_mod_bound = pk.modulus_bounded().to_wide(); + let pk_modulus = pk.modulus_signed(); let factor2 = randomizer .to_montgomery(pk.monty_params_mod_n_squared()) - .pow_bounded(&pk_mod_bound); + .pow(&pk_modulus); let ciphertext = *(factor1 * factor2).expose_secret(); @@ -146,21 +147,23 @@ impl Ciphertext

{ pk: &PublicKeyPaillier

, abs_plaintext: &P::Uint, randomizer: &MaskedRandomizer

, - plaintext_is_negative: Choice, + plaintext_is_negative: bool, ) -> Self { // Same as `new_with_randomizer_inner`, but works on public data. let prod = abs_plaintext.mul_wide(pk.modulus()); let mut prod_mod = prod.to_montgomery(pk.monty_params_mod_n_squared()); - prod_mod.conditional_negate(plaintext_is_negative); + if plaintext_is_negative { + prod_mod = -prod_mod; + } let factor1 = prod_mod + P::WideUintMod::one(pk.monty_params_mod_n_squared().clone()); let randomizer = randomizer.0.to_wide(); - let pk_mod_bound = pk.modulus_bounded().to_wide(); + let pk_modulus = pk.modulus_signed(); let factor2 = randomizer .to_montgomery(pk.monty_params_mod_n_squared()) - .pow_bounded(&pk_mod_bound); + .pow(&pk_modulus); let ciphertext = factor1 * factor2; @@ -173,42 +176,23 @@ impl Ciphertext

{ /// Encrypts the plaintext with the provided randomizer. pub fn new_with_randomizer( pk: &PublicKeyPaillier

, - plaintext: &Secret, + plaintext: &SecretUnsigned, randomizer: &Randomizer

, ) -> Self { Self::new_with_randomizer_inner(pk, plaintext, randomizer, Choice::from(0)) } - pub fn new_with_randomizer_bounded( - pk: &PublicKeyPaillier

, - plaintext: &Secret>, - randomizer: &Randomizer

, - ) -> Self { - Self::new_with_randomizer_inner( - pk, - &Secret::init_with(|| *plaintext.expose_secret().as_ref()), - randomizer, - Choice::from(0), - ) - } - pub fn new_with_randomizer_signed( pk: &PublicKeyPaillier

, - plaintext: &Secret>, + plaintext: &SecretSigned, randomizer: &Randomizer

, ) -> Self { - let plaintext = *plaintext.expose_secret(); - Self::new_with_randomizer_inner( - pk, - &Secret::init_with(|| plaintext.abs()), - randomizer, - plaintext.is_negative(), - ) + Self::new_with_randomizer_inner(pk, &plaintext.abs(), randomizer, plaintext.is_negative()) } pub fn new_public_with_randomizer_signed( pk: &PublicKeyPaillier

, - plaintext: &Signed, + plaintext: &PublicSigned, randomizer: &MaskedRandomizer

, ) -> Self { Self::new_public_with_randomizer_inner(pk, &plaintext.abs(), randomizer, plaintext.is_negative()) @@ -216,7 +200,7 @@ impl Ciphertext

{ pub fn new_public_with_randomizer_wide( pk: &PublicKeyPaillier

, - plaintext: &Signed, + plaintext: &PublicSigned, randomizer: &MaskedRandomizer

, ) -> Self { let plaintext_reduced = P::Uint::try_from_wide(&(plaintext.abs() % pk.modulus_wide_nonzero())) @@ -225,7 +209,7 @@ impl Ciphertext

{ } /// Encrypts the plaintext with a random randomizer. - pub fn new(rng: &mut impl CryptoRngCore, pk: &PublicKeyPaillier

, plaintext: &Secret) -> Self { + pub fn new(rng: &mut impl CryptoRngCore, pk: &PublicKeyPaillier

, plaintext: &SecretUnsigned) -> Self { Self::new_with_randomizer(pk, plaintext, &Randomizer::random(rng, pk)) } @@ -233,7 +217,7 @@ impl Ciphertext

{ pub fn new_signed( rng: &mut impl CryptoRngCore, pk: &PublicKeyPaillier

, - plaintext: &Secret>, + plaintext: &SecretSigned, ) -> Self { Self::new_with_randomizer_signed(pk, plaintext, &Randomizer::random(rng, pk)) } @@ -243,7 +227,7 @@ impl Ciphertext

{ assert_eq!(sk.public_key(), &self.pk); let pk = sk.public_key(); - let totient_wide = sk.totient_wide_bounded(); + let totient_wide = sk.totient_wide_unsigned(); // Calculate the plaintext `m = ((C^phi mod N^2 - 1) / N) * mu mod N`, // where `m` is the plaintext, `C` is the ciphertext, @@ -255,9 +239,7 @@ impl Ciphertext

{ // (because `N` itself fits into `Uint`). // 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 t = Secret::init_with(|| self.ciphertext.pow(&totient_wide)); 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(|| { @@ -270,7 +252,7 @@ impl Ciphertext

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

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

) -> SecretSigned { assert_eq!(sk.public_key(), &self.pk); let pk = sk.public_key(); @@ -285,13 +267,10 @@ impl Ciphertext

{ .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") - }); + let result = SecretSigned::new_from_unsigned(uint_result, 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 + SecretSigned::conditional_select(&result, &-result.clone(), is_negative) } /// Derive the randomizer used to create this ciphertext. @@ -315,7 +294,7 @@ impl Ciphertext

{ // To isolate `rho`, calculate `(rho^N)^(N^(-1)) mod N`. // The order of `Z_N` is `phi(N)`, so the inversion in the exponent is modulo `phi(N)`. let sk_inv_modulus = sk.inv_modulus(); - let randomizer_mod = Secret::init_with(|| ciphertext_mod_n.pow_bounded(sk_inv_modulus.expose_secret())); + let randomizer_mod = Secret::init_with(|| ciphertext_mod_n.pow(sk_inv_modulus)); Randomizer::new_mod(randomizer_mod) } @@ -325,43 +304,49 @@ impl Ciphertext

{ // compared to what we would get if we used the signed `rhs` faithfully in the original formula. // So if we want to replicate the Paillier encryption manually and get the same ciphertext // (e.g. in the P_enc sigma-protocol), we need to process the sign correctly. - fn homomorphic_mul(self, rhs: &Signed) -> Self { + fn homomorphic_mul(self, rhs: &SecretSigned) -> Self { Self { pk: self.pk, - ciphertext: self.ciphertext.pow_signed(&rhs.to_wide()), + ciphertext: self.ciphertext.pow(&rhs.to_wide()), } } - fn homomorphic_mul_ref(&self, rhs: &Signed) -> Self { + fn homomorphic_mul_ref_public(&self, rhs: &PublicSigned) -> Self { Self { pk: self.pk.clone(), - ciphertext: self.ciphertext.pow_signed(&rhs.to_wide()), + ciphertext: self.ciphertext.pow(&rhs.to_wide()), } } - pub fn homomorphic_mul_wide(&self, rhs: &Signed) -> Self { - // Unfortunately we cannot implement `Mul` for `Signed` and `Signed` - // at the same time, since they can be the same type. - // But this method is only used once, so it's not a problem to spell it out. + fn homomorphic_mul_public(self, rhs: &PublicSigned) -> Self { + Self { + pk: self.pk, + ciphertext: self.ciphertext.pow(&rhs.to_wide()), + } + } + + fn homomorphic_mul_ref(&self, rhs: &SecretSigned) -> Self { Self { pk: self.pk.clone(), - ciphertext: self.ciphertext.pow_signed(rhs), + ciphertext: self.ciphertext.pow(&rhs.to_wide()), } } - fn homomorphic_mul_unsigned(self, rhs: &Bounded) -> Self { - let rhs_wide = rhs.to_wide(); + pub fn homomorphic_mul_wide_public(&self, rhs: &PublicSigned) -> Self { + // Unfortunately we cannot implement `Mul` for `SecretSigned` and `SecretSigned` + // at the same time, since they can be the same type. + // But this method is only used once, so it's not a problem to spell it out. Self { - pk: self.pk, - ciphertext: self.ciphertext.pow_bounded(&rhs_wide), + pk: self.pk.clone(), + ciphertext: self.ciphertext.pow(rhs), } } - fn homomorphic_mul_unsigned_ref(&self, rhs: &Bounded) -> Self { + fn homomorphic_mul_unsigned_ref(&self, rhs: &SecretUnsigned) -> Self { let rhs_wide = rhs.to_wide(); Self { pk: self.pk.clone(), - ciphertext: self.ciphertext.pow_bounded(&rhs_wide), + ciphertext: self.ciphertext.pow(&rhs_wide), } } @@ -378,8 +363,8 @@ impl Ciphertext

{ .0 .to_wide() .to_montgomery(self.pk.monty_params_mod_n_squared()); - let pk_modulus_wide = self.pk.modulus_bounded().to_wide(); - let ciphertext = self.ciphertext * randomizer_mod.pow_bounded(&pk_modulus_wide); + let pk_modulus = self.pk.modulus_signed(); + let ciphertext = self.ciphertext * randomizer_mod.pow(&pk_modulus); Self { pk: self.pk, ciphertext, @@ -391,8 +376,8 @@ impl Ciphertext

{ .randomizer .to_wide() .to_montgomery(self.pk.monty_params_mod_n_squared()); - let pk_modulus_wide = self.pk.modulus_bounded().to_wide(); - let ciphertext = self.ciphertext * randomizer_mod.pow_bounded(&pk_modulus_wide).expose_secret(); + let pk_modulus = self.pk.modulus_signed(); + let ciphertext = self.ciphertext * randomizer_mod.pow(&pk_modulus).expose_secret(); Self { pk: self.pk, ciphertext, @@ -409,85 +394,67 @@ impl Ciphertext

{ impl Add> for Ciphertext

{ type Output = Ciphertext

; - fn add(self, other: Ciphertext

) -> Ciphertext

{ - self + &other + fn add(self, rhs: Ciphertext

) -> Ciphertext

{ + self + &rhs } } impl Add<&Ciphertext

> for Ciphertext

{ type Output = Ciphertext

; - fn add(self, other: &Ciphertext

) -> Ciphertext

{ - self.homomorphic_add(other) + fn add(self, rhs: &Ciphertext

) -> Ciphertext

{ + self.homomorphic_add(rhs) } } -impl Mul> for Ciphertext

{ +impl Mul<&PublicSigned> for Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: Signed) -> Ciphertext

{ - self.homomorphic_mul(&other) + fn mul(self, rhs: &PublicSigned) -> Ciphertext

{ + self.homomorphic_mul_public(rhs) } } -impl Mul> for &Ciphertext

{ +impl Mul<&PublicSigned> for &Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: Signed) -> Ciphertext

{ - self.homomorphic_mul_ref(&other) + fn mul(self, rhs: &PublicSigned) -> Ciphertext

{ + self.homomorphic_mul_ref_public(rhs) } } -impl<'a, P: PaillierParams> Mul<&'a Signed> for &Ciphertext

{ +impl Mul> for Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: &'a Signed) -> Ciphertext

{ - self.homomorphic_mul_ref(other) + fn mul(self, rhs: SecretSigned) -> Ciphertext

{ + self.homomorphic_mul(&rhs) } } -impl Mul>> for &Ciphertext

{ +impl Mul> for &Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: Secret>) -> Ciphertext

{ - self * other.expose_secret() + fn mul(self, rhs: SecretSigned) -> Ciphertext

{ + self.homomorphic_mul_ref(&rhs) } } -impl<'a, P: PaillierParams> Mul<&'a Secret>> for &Ciphertext

{ +impl<'a, P: PaillierParams> Mul<&'a SecretSigned> for &Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: &'a Secret>) -> Ciphertext

{ - self * other.expose_secret() + fn mul(self, rhs: &'a SecretSigned) -> Ciphertext

{ + self.homomorphic_mul_ref(rhs) } } -impl Mul> for Ciphertext

{ +impl<'a, P: PaillierParams> Mul<&'a SecretUnsigned> for &Ciphertext

{ type Output = Ciphertext

; - fn mul(self, other: Bounded) -> Ciphertext

{ - self.homomorphic_mul_unsigned(&other) - } -} - -impl Mul> for &Ciphertext

{ - type Output = Ciphertext

; - fn mul(self, other: Bounded) -> Ciphertext

{ - self.homomorphic_mul_unsigned_ref(&other) - } -} - -impl Mul>> for &Ciphertext

{ - type Output = Ciphertext

; - fn mul(self, other: Secret>) -> Ciphertext

{ - self.homomorphic_mul_unsigned_ref(other.expose_secret()) - } -} - -impl<'a, P: PaillierParams> Mul<&'a Secret>> for &Ciphertext

{ - type Output = Ciphertext

; - fn mul(self, other: &'a Secret>) -> Ciphertext

{ - self.homomorphic_mul_unsigned_ref(other.expose_secret()) + fn mul(self, rhs: &'a SecretUnsigned) -> Ciphertext

{ + self.homomorphic_mul_unsigned_ref(rhs) } } #[cfg(test)] mod tests { - use crypto_bigint::{Encoding, Integer, ShrVartime, WrappingSub}; + use crypto_bigint::{ + subtle::ConditionallySelectable, Encoding, Integer, NonZero, RandomMod, ShrVartime, WrappingSub, + }; use rand_core::OsRng; + use zeroize::Zeroize; use super::{ super::{params::PaillierTest, PaillierParams, SecretKeyPaillierWire}, @@ -495,22 +462,19 @@ mod tests { }; use crate::{ tools::Secret, - uint::{ - subtle::{ConditionallyNegatable, ConditionallySelectable}, - HasWide, NonZero, RandomMod, Signed, - }, + uint::{HasWide, SecretSigned, SecretUnsigned}, }; - fn mul_mod(lhs: &T, rhs: &Signed, modulus: &NonZero) -> T + fn mul_mod(lhs: &T, rhs: &SecretSigned, modulus: &NonZero) -> T where - T: Integer + HasWide + crypto_bigint::Bounded + Encoding + ConditionallySelectable, + T: Zeroize + Integer + HasWide + crypto_bigint::Bounded + Encoding + ConditionallySelectable, { // There may be more efficient ways to do this (e.g. Barrett reduction), // but it's only used in tests. // Note that modulus here may be even, so we can't use Montgomery representation - let wide_product = lhs.mul_wide(&rhs.abs()); + let wide_product = lhs.mul_wide(rhs.abs().expose_secret()); let wide_modulus = modulus.as_ref().to_wide(); let result = T::try_from_wide(&(wide_product % NonZero::new(wide_modulus).unwrap())).unwrap(); if rhs.is_negative().into() { @@ -520,24 +484,31 @@ mod tests { } } - fn reduce(val: &Signed, modulus: &NonZero) -> Signed { - let result = val.abs() % *modulus; + fn reduce(val: &SecretSigned, modulus: &NonZero) -> SecretSigned { + let result = *val.abs().expose_secret() % modulus; let twos_complement_result = if result > modulus.as_ref().wrapping_shr_vartime(1) { result.wrapping_sub(modulus.as_ref()) } else { result }; - let mut signed_result = Signed::new_from_unsigned(twos_complement_result, P::MODULUS_BITS - 1).unwrap(); - signed_result.conditional_negate(val.is_negative()); - signed_result + let signed_result = + SecretSigned::new_from_unsigned(Secret::init_with(|| twos_complement_result), P::MODULUS_BITS - 1).unwrap(); + if val.is_negative().into() { + -signed_result + } else { + signed_result + } } #[test] fn roundtrip() { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let ciphertext = Ciphertext::::new(&mut OsRng, pk, &plaintext); let plaintext_back = ciphertext.decrypt(&sk); assert_eq!(plaintext.expose_secret(), plaintext_back.expose_secret()); @@ -551,21 +522,22 @@ mod tests { fn signed_roundtrip() { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext = Secret::init_with(|| { - Signed::random_bounded_bits(&mut OsRng, ::Uint::BITS - 2) - }); + let plaintext = SecretSigned::random_in_exp_range(&mut OsRng, ::Uint::BITS - 2); 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.expose_secret()); + let plaintext_reduced = reduce::(&plaintext, &pk.modulus_nonzero()); + assert_eq!(plaintext_reduced.to_public(), plaintext_back.to_public()); } #[test] fn derive_randomizer() { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let randomizer = Randomizer::random(&mut OsRng, pk); let ciphertext = Ciphertext::::new_with_randomizer(pk, &plaintext, &randomizer); let randomizer_back = ciphertext.derive_randomizer(&sk); @@ -576,12 +548,15 @@ mod tests { fn homomorphic_mul() { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let ciphertext = Ciphertext::::new(&mut OsRng, pk, &plaintext); - let coeff = Signed::random_bounded_bits(&mut OsRng, ::Uint::BITS - 2); - let new_ciphertext = ciphertext * coeff; + let coeff = SecretSigned::random_in_exp_range(&mut OsRng, ::Uint::BITS - 2); + let new_ciphertext = ciphertext * coeff.clone(); let new_plaintext = new_ciphertext.decrypt(&sk); assert_eq!( @@ -595,12 +570,18 @@ mod tests { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext1 = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext1 = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let ciphertext1 = Ciphertext::::new(&mut OsRng, pk, &plaintext1); - let plaintext2 = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext2 = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let ciphertext2 = Ciphertext::::new(&mut OsRng, pk, &plaintext2); let new_ciphertext = ciphertext1 + ciphertext2; @@ -619,15 +600,22 @@ mod tests { let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); let pk = sk.public_key(); - let plaintext1 = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); - let plaintext2 = Signed::random_bounded_bits(&mut OsRng, ::Uint::BITS - 2); - let plaintext3 = - Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())); + let plaintext1 = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); + let plaintext2 = + SecretSigned::random_in_exp_range(&mut OsRng, ::Uint::BITS - 2); + let plaintext3 = SecretUnsigned::new( + Secret::init_with(|| ::Uint::random_mod(&mut OsRng, &pk.modulus_nonzero())), + PaillierTest::MODULUS_BITS, + ) + .unwrap(); let ciphertext1 = Ciphertext::::new(&mut OsRng, pk, &plaintext1); let ciphertext3 = Ciphertext::::new(&mut OsRng, pk, &plaintext3); - let result = ciphertext1 * plaintext2 + ciphertext3; + let result = ciphertext1 * plaintext2.clone() + ciphertext3; let plaintext_back = result.decrypt(&sk); assert_eq!( diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index 59ea0b1..8c05796 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -1,9 +1,11 @@ -use core::{ - fmt::Debug, - ops::{AddAssign, SubAssign}, -}; +use core::fmt::Debug; -use crypto_bigint::{InvMod, Monty, Odd, ShrVartime, Square, WrappingAdd}; +use crypto_bigint::{ + modular::Retrieve, + subtle::{Choice, ConditionallySelectable}, + CheckedAdd, CheckedSub, Integer, InvMod, Invert, Monty, NonZero, Odd, PowBoundedExp, ShrVartime, Square, + WrappingAdd, +}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -13,11 +15,7 @@ use super::{ }; use crate::{ tools::Secret, - uint::{ - subtle::{Choice, ConditionallySelectable}, - Bounded, CheckedAdd, CheckedSub, HasWide, Integer, Invert, NonZero, PowBoundedExp, Retrieve, Signed, - ToMontgomery, - }, + uint::{HasWide, PublicSigned, SecretSigned, SecretUnsigned, ToMontgomery}, }; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -50,7 +48,7 @@ pub(crate) struct SecretKeyPaillier { /// The inverse of the totient modulo the modulus ($\phi(N)^{-1} \mod N$). inv_totient: Secret, /// The inverse of the modulus modulo the totient ($N^{-1} \mod \phi(N)$). - inv_modulus: Secret>, + inv_modulus: SecretUnsigned, /// $p^{-1} \mod q$, a constant used when joining an RNS-represented number using Garner's algorithm. inv_p_mod_q: Secret, // $u$ such that $u = -1 \mod p$ and $u = 1 \mod q$. Used for sampling of non-square residues. @@ -88,24 +86,21 @@ where ]) }); - let inv_modulus = Secret::init_with(|| { - Bounded::new( - (*modulus.modulus()) + let inv_modulus = SecretUnsigned::new( + Secret::init_with(|| { + modulus + .modulus() .inv_mod(primes.totient().expose_secret()) - .expect("pq is invertible mod ϕ(pq) because gcd(pq, (p-1)(q-1)) = 1"), - P::MODULUS_BITS, - ) - .expect("We assume `P::MODULUS_BITS` is properly configured") - }); + .expect("pq is invertible mod ϕ(pq) because gcd(pq, (p-1)(q-1)) = 1") + }), + P::MODULUS_BITS, + ) + .expect("We assume `P::MODULUS_BITS` is properly configured"); + let p_mod = primes.p_half().to_montgomery(&monty_params_mod_q); let inv_p_mod_q = Secret::init_with(|| { - primes - .p_half() + p_mod .expose_secret() - .clone() - // NOTE: `monty_params_mod_q` is cloned here and can remain on the stack. - // See https://github.com/RustCrypto/crypto-bigint/issues/704 - .to_montgomery(&monty_params_mod_q) .invert() .expect("All non-zero integers mod a prime have a multiplicative inverse") }); @@ -121,10 +116,7 @@ where // See https://github.com/RustCrypto/crypto-bigint/issues/704 P::HalfUintMod::one(monty_params_mod_q.clone()) }); - let mut t_mod = inv_p_mod_q.clone(); - t_mod.expose_secret_mut().add_assign(inv_p_mod_q.expose_secret()); - t_mod.expose_secret_mut().sub_assign(one.expose_secret()); - let t = Secret::init_with(|| t_mod.expose_secret().retrieve()); + let t = (&inv_p_mod_q + &inv_p_mod_q - one).retrieve(); // Calculate $u$ // I am not entirely sure if it can be used to learn something about `p` and `q`, @@ -164,21 +156,21 @@ where } } - pub fn p_signed(&self) -> Secret> { + pub fn p_signed(&self) -> SecretSigned { self.primes.p_signed() } - pub fn q_signed(&self) -> Secret> { + pub fn q_signed(&self) -> SecretSigned { self.primes.q_signed() } - pub fn p_wide_signed(&self) -> Secret> { + pub fn p_wide_signed(&self) -> SecretSigned { self.primes.p_wide_signed() } /// Returns Euler's totient function (`φ(n)`) of the modulus, wrapped in a [`Secret`]. - pub fn totient_wide_bounded(&self) -> Secret> { - self.primes.totient_wide_bounded() + pub fn totient_wide_unsigned(&self) -> SecretUnsigned { + self.primes.totient_wide_unsigned() } /// Returns $\phi(N)^{-1} \mod N$ @@ -187,7 +179,7 @@ where } /// Returns $N^{-1} \mod \phi(N)$ - pub fn inv_modulus(&self) -> &Secret> { + pub fn inv_modulus(&self) -> &SecretUnsigned { &self.inv_modulus } @@ -319,7 +311,7 @@ pub(crate) struct PublicKeyPaillier { impl PublicKeyPaillier

{ fn new(modulus: PublicModulus

) -> Self { let monty_params_mod_n_squared = P::WideUintMod::new_params_vartime( - Odd::new(modulus.modulus().square_wide()).expect("Square of odd number is odd"), + Odd::new(modulus.modulus().mul_wide(modulus.modulus())).expect("Square of odd number is odd"), ); let public_key_wire = PublicKeyPaillierWire { @@ -345,8 +337,8 @@ impl PublicKeyPaillier

{ self.modulus.modulus() } - pub fn modulus_bounded(&self) -> Bounded { - self.modulus.modulus_bounded() + pub fn modulus_signed(&self) -> PublicSigned { + self.modulus.modulus_signed() } pub fn modulus_nonzero(&self) -> NonZero { @@ -375,8 +367,8 @@ impl PublicKeyPaillier

{ } impl PartialEq for PublicKeyPaillier

{ - fn eq(&self, other: &Self) -> bool { - self.modulus.eq(&other.modulus) + fn eq(&self, rhs: &Self) -> bool { + self.modulus.eq(&rhs.modulus) } } diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index 61d8590..7d63921 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -3,19 +3,23 @@ use core::ops::RemAssign; use crypto_bigint::{ modular::Retrieve, subtle::{ConditionallyNegatable, ConditionallySelectable, ConstantTimeGreater, CtOption}, - Bounded, Encoding, Gcd, Integer, InvMod, Invert, Monty, NonZero, RandomMod, + Bounded, Encoding, Gcd, Integer, InvMod, Invert, Monty, NonZero, PowBoundedExp, RandomMod, }; use crypto_primes::RandomPrimeWithRng; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -#[cfg(test)] -use crate::uint::{U1024Mod, U2048Mod, U512Mod, U1024, U2048, U4096, U512}; use crate::{ tools::hashing::Hashable, - uint::{Exponentiable, HasWide, ToMontgomery}, + uint::{HasWide, ToMontgomery}, }; +#[cfg(test)] +use crypto_bigint::{U1024, U2048, U4096, U512}; + +#[cfg(test)] +use crate::uint::{U1024Mod, U2048Mod, U512Mod}; + pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Sync { /// The size of one of the pair of RSA primes. const PRIME_BITS: u32; @@ -61,7 +65,9 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn /// A modulo-residue counterpart of `Uint`. type UintMod: ConditionallySelectable - + Exponentiable + + PowBoundedExp + + PowBoundedExp + + PowBoundedExp + Monty + Retrieve + Invert> @@ -83,7 +89,7 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn /// A modulo-residue counterpart of `WideUint`. type WideUintMod: Monty - + Exponentiable + + PowBoundedExp + ConditionallyNegatable + ConditionallySelectable + Invert> diff --git a/synedrion/src/paillier/ring_pedersen.rs b/synedrion/src/paillier/ring_pedersen.rs index 0b19560..d4b7e3d 100644 --- a/synedrion/src/paillier/ring_pedersen.rs +++ b/synedrion/src/paillier/ring_pedersen.rs @@ -1,7 +1,7 @@ /// Implements the Definition 3.3 from the CGGMP'21 paper and related operations. use core::ops::Mul; -use crypto_bigint::{Monty, NonZero, RandomMod, ShrVartime}; +use crypto_bigint::{modular::Retrieve, Monty, NonZero, RandomMod, ShrVartime}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -11,14 +11,14 @@ use super::{ }; use crate::{ tools::Secret, - uint::{Bounded, Exponentiable, Retrieve, Signed, ToMontgomery}, + uint::{Exponentiable, SecretUnsigned, ToMontgomery}, }; /// Ring-Pedersen secret. #[derive(Debug, Clone)] pub(crate) struct RPSecret { primes: SecretPrimes

, - lambda: Secret>, + lambda: SecretUnsigned, } impl RPSecret

{ @@ -29,19 +29,20 @@ impl RPSecret

{ NonZero::new(primes.totient().expose_secret().wrapping_shr_vartime(2)) .expect("totient / 4 is still non-zero because p, q >= 5") }); - let lambda = Secret::init_with(|| { - Bounded::new(P::Uint::random_mod(rng, bound.expose_secret()), P::MODULUS_BITS - 2) - .expect("totient < N < 2^MODULUS_BITS, so totient / 4 < 2^(MODULUS_BITS - 2)") - }); + let lambda = SecretUnsigned::new( + Secret::init_with(|| P::Uint::random_mod(rng, bound.expose_secret())), + P::MODULUS_BITS - 2, + ) + .expect("totient < N < 2^MODULUS_BITS, so totient / 4 < 2^(MODULUS_BITS - 2)"); Self { primes, lambda } } - pub fn lambda(&self) -> &Secret> { + pub fn lambda(&self) -> &SecretUnsigned { &self.lambda } - pub fn random_residue_mod_totient(&self, rng: &mut impl CryptoRngCore) -> Bounded { + pub fn random_residue_mod_totient(&self, rng: &mut impl CryptoRngCore) -> SecretUnsigned { self.primes.random_residue_mod_totient(rng) } @@ -78,7 +79,7 @@ impl RPParams

{ let modulus = secret.primes.modulus_wire().into_precomputed(); let base_randomizer = modulus.random_quadratic_residue(rng); // $t$ - let base_value = base_randomizer.pow_bounded(secret.lambda.expose_secret()); // $s$ + let base_value = base_randomizer.pow(&secret.lambda); // $s$ Self { modulus, @@ -99,61 +100,24 @@ impl RPParams

{ self.modulus.modulus() } - pub fn modulus_bounded(&self) -> Bounded { - self.modulus.modulus_bounded() - } - pub fn monty_params_mod_n(&self) -> &::Params { self.modulus.monty_params_mod_n() } /// Creates a commitment for a secret `value` with a secret `randomizer`. - pub fn commit(&self, value: &Secret>, randomizer: &Secret>) -> RPCommitment

{ - RPCommitment( - self.base_value.pow_signed(value.expose_secret()) - * self.base_randomizer.pow_signed_wide(randomizer.expose_secret()), - ) - } - - /// Creates a commitment for a secret `value` with a secret `randomizer`. - pub fn commit_wide( - &self, - value: &Secret>, - randomizer: &Secret>, - ) -> RPCommitment

{ - RPCommitment( - self.base_value.pow_signed_wide(value.expose_secret()) - * self.base_randomizer.pow_signed_wide(randomizer.expose_secret()), - ) + pub fn commit(&self, value: &V, randomizer: &R) -> RPCommitment

+ where + P::UintMod: Exponentiable + Exponentiable, + { + RPCommitment(self.base_value.pow(value) * self.base_randomizer.pow(randomizer)) } /// Creates a commitment for a secret `randomizer` and the value 0. - pub fn commit_zero_xwide(&self, randomizer: &Secret>) -> RPCommitment

{ - RPCommitment(self.base_randomizer.pow_signed_extra_wide(randomizer.expose_secret())) - } - - /// Creates a commitment for a public `value` with a public `randomizer`. - pub fn commit_public(&self, value: &Signed, randomizer: &Signed) -> RPCommitment

{ - RPCommitment(self.base_value.pow_signed(value) * self.base_randomizer.pow_signed_wide(randomizer)) - } - - /// Creates a commitment for a public `value` with a public `randomizer`. - pub fn commit_public_wide(&self, value: &Signed, randomizer: &Signed) -> RPCommitment

{ - RPCommitment(self.base_value.pow_signed_wide(value) * self.base_randomizer.pow_signed_wide(randomizer)) - } - - /// Creates a commitment for a public `value` with a public `randomizer`. - pub fn commit_public_xwide( - &self, - value: &Bounded, - randomizer: &Signed, - ) -> RPCommitment

{ - RPCommitment(self.base_value.pow_bounded(value) * self.base_randomizer.pow_signed_extra_wide(randomizer)) - } - - /// Creates a commitment for a public `randomizer` and the value 0. - pub fn commit_public_base_xwide(&self, randomizer: &Signed) -> RPCommitment

{ - RPCommitment(self.base_randomizer.pow_signed_extra_wide(randomizer)) + pub fn commit_zero(&self, randomizer: &R) -> RPCommitment

+ where + P::UintMod: Exponentiable, + { + RPCommitment(self.base_randomizer.pow(randomizer)) } pub fn to_wire(&self) -> RPParamsWire

{ @@ -201,22 +165,11 @@ impl RPCommitment

{ } /// Raise to the power of `exponent`. - /// - /// Note: this is variable time in `exponent`. - pub fn pow_signed_vartime(&self, exponent: &Signed) -> Self { - Self(self.0.pow_signed_vartime(exponent)) - } - - /// Raise to the power of `exponent`. - /// - /// Note: this is variable time in `exponent`. - pub fn pow_signed_wide_vartime(&self, exponent: &Signed) -> Self { - Self(self.0.pow_signed_wide_vartime(exponent)) - } - - /// Raise to the power of `exponent`. - pub fn pow_signed_wide(&self, exponent: &Secret>) -> Self { - Self(self.0.pow_signed_wide(exponent.expose_secret())) + pub fn pow(&self, exponent: &V) -> Self + where + P::UintMod: Exponentiable, + { + Self(self.0.pow(exponent)) } } diff --git a/synedrion/src/paillier/rsa.rs b/synedrion/src/paillier/rsa.rs index 89c1183..f342568 100644 --- a/synedrion/src/paillier/rsa.rs +++ b/synedrion/src/paillier/rsa.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use super::params::PaillierParams; use crate::{ tools::Secret, - uint::{Bounded, HasWide, Signed, ToMontgomery}, + uint::{HasWide, PublicSigned, SecretSigned, SecretUnsigned, ToMontgomery}, }; fn random_paillier_blum_prime(rng: &mut impl CryptoRngCore) -> P::HalfUint { @@ -133,16 +133,12 @@ impl SecretPrimes

{ Secret::init_with(|| self.primes.q.expose_secret().to_wide()) } - pub fn p_signed(&self) -> Secret> { - Secret::init_with(|| { - Signed::new_positive(*self.p().expose_secret(), P::PRIME_BITS).expect("`P::PRIME_BITS` is valid") - }) + pub fn p_signed(&self) -> SecretSigned { + SecretSigned::new_positive(self.p(), P::PRIME_BITS).expect("`P::PRIME_BITS` is valid") } - pub fn q_signed(&self) -> Secret> { - Secret::init_with(|| { - Signed::new_positive(*self.q().expose_secret(), P::PRIME_BITS).expect("`P::PRIME_BITS` is valid") - }) + pub fn q_signed(&self) -> SecretSigned { + SecretSigned::new_positive(self.q(), P::PRIME_BITS).expect("`P::PRIME_BITS` is valid") } pub fn p_nonzero(&self) -> Secret> { @@ -153,7 +149,7 @@ impl SecretPrimes

{ Secret::init_with(|| NonZero::new(*self.q().expose_secret()).expect("`q` is non-zero")) } - pub fn p_wide_signed(&self) -> Secret> { + pub fn p_wide_signed(&self) -> SecretSigned { self.p_signed().to_wide() } @@ -161,14 +157,12 @@ impl SecretPrimes

{ &self.totient } - pub fn totient_bounded(&self) -> Secret> { - Secret::init_with(|| { - Bounded::new(*self.totient.expose_secret(), P::MODULUS_BITS).expect("`P::MODULUS_BITS` is valid") - }) + fn totient_unsigned(&self) -> SecretUnsigned { + SecretUnsigned::new(self.totient.clone(), P::MODULUS_BITS).expect("`P::MODULUS_BITS` is valid") } - pub fn totient_wide_bounded(&self) -> Secret> { - self.totient_bounded().to_wide() + pub fn totient_wide_unsigned(&self) -> SecretUnsigned { + self.totient_unsigned().to_wide() } pub fn totient_nonzero(&self) -> Secret> { @@ -182,9 +176,9 @@ impl SecretPrimes

{ } /// Returns a random in range `[0, \phi(N))`. - pub fn random_residue_mod_totient(&self, rng: &mut impl CryptoRngCore) -> Bounded { - Bounded::new( - P::Uint::random_mod(rng, self.totient_nonzero().expose_secret()), + pub fn random_residue_mod_totient(&self, rng: &mut impl CryptoRngCore) -> SecretUnsigned { + SecretUnsigned::new( + Secret::init_with(|| P::Uint::random_mod(rng, self.totient_nonzero().expose_secret())), P::MODULUS_BITS, ) .expect(concat![ @@ -253,8 +247,10 @@ impl PublicModulus

{ NonZero::new(self.modulus.0).expect("the modulus is non-zero") } - pub fn modulus_bounded(&self) -> Bounded { - Bounded::new(self.modulus.0, P::MODULUS_BITS).expect("the modulus can be bounded by 2^MODULUS_BITS") + pub fn modulus_signed(&self) -> PublicSigned { + // Have to return WideUint, since Uint::BITS == P::MODULUS_BITS, so it won't fit in a Signed. + PublicSigned::new_positive(self.modulus.0.to_wide(), P::MODULUS_BITS) + .expect("the modulus can be bounded by 2^MODULUS_BITS") } pub fn monty_params_mod_n(&self) -> &::Params { diff --git a/synedrion/src/tools/secret.rs b/synedrion/src/tools/secret.rs index 7647214..218c83e 100644 --- a/synedrion/src/tools/secret.rs +++ b/synedrion/src/tools/secret.rs @@ -1,12 +1,12 @@ use core::{ fmt::Debug, - ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, + ops::{Add, Div, DivAssign, Mul, Neg, Rem, RemAssign, Sub}, }; use crypto_bigint::{ modular::Retrieve, subtle::{Choice, ConditionallyNegatable, ConditionallySelectable}, - Encoding, Integer, Monty, NonZero, + Integer, Monty, NonZero, WrappingAdd, WrappingMul, WrappingNeg, WrappingSub, }; use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -14,7 +14,7 @@ use zeroize::Zeroize; use crate::{ curve::{Point, Scalar}, - uint::{Bounded, Exponentiable, HasWide, Signed}, + uint::{Exponentiable, HasWide}, }; /// A helper wrapper for managing secret values. @@ -49,12 +49,6 @@ impl Secret { } } -impl Default for Secret { - fn default() -> Self { - Self::init_with(|| T::default()) - } -} - impl Clone for Secret { fn clone(&self) -> Self { Self::init_with(|| self.0.expose_secret().clone()) @@ -79,213 +73,171 @@ impl Debug for Secret { } } -impl Secret -where - T: Zeroize + Clone + HasWide, - T::Wide: Zeroize, -{ - pub fn to_wide(&self) -> Secret<::Wide> { - Secret::init_with(|| self.expose_secret().to_wide()) +impl> Neg for &Secret { + type Output = Secret; + fn neg(self) -> Self::Output { + Secret::init_with(|| self.expose_secret().clone().neg()) } +} - pub fn mul_wide(&self, rhs: &T) -> Secret { - Secret::init_with(|| self.expose_secret().mul_wide(rhs)) +impl WrappingNeg for Secret { + fn wrapping_neg(&self) -> Self { + Secret::init_with(|| self.expose_secret().wrapping_neg()) } } -impl Secret> -where - T: Zeroize + Clone + Encoding + Integer + HasWide + ConditionallySelectable + crypto_bigint::Bounded, - T::Wide: ConditionallySelectable + crypto_bigint::Bounded + Zeroize, -{ - pub fn to_wide(&self) -> Secret::Wide>> { - Secret::init_with(|| self.expose_secret().to_wide()) +impl Add<&'a T, Output = T>> WrappingAdd for Secret { + fn wrapping_add(&self, rhs: &Self) -> Self { + Secret::init_with(|| self.expose_secret().wrapping_add(rhs.expose_secret())) } +} - pub fn mul_wide(&self, rhs: &Signed) -> Secret> { - Secret::init_with(|| self.expose_secret().mul_wide(rhs)) +impl Sub<&'a T, Output = T>> WrappingSub for Secret { + fn wrapping_sub(&self, rhs: &Self) -> Self { + Secret::init_with(|| self.expose_secret().wrapping_sub(rhs.expose_secret())) } } -impl Secret> +impl Mul<&'a T, Output = T>> WrappingMul for Secret { + fn wrapping_mul(&self, rhs: &Self) -> Self { + Secret::init_with(|| self.expose_secret().wrapping_mul(rhs.expose_secret())) + } +} + +impl Secret where - T: Zeroize + Clone + Encoding + Integer + HasWide + crypto_bigint::Bounded, - T::Wide: crypto_bigint::Bounded + Zeroize, + T: Zeroize + Clone + HasWide, + T::Wide: Zeroize, { - pub fn to_wide(&self) -> Secret::Wide>> { + pub fn to_wide(&self) -> Secret<::Wide> { Secret::init_with(|| self.expose_secret().to_wide()) } - - pub fn to_signed(&self) -> Option>> { - Secret::maybe_init_with(|| self.expose_secret().clone().into_signed()) - } } // Addition -impl<'a, T: Zeroize + AddAssign<&'a T>> AddAssign<&'a T> for Secret { - fn add_assign(&mut self, other: &'a T) { - self.expose_secret_mut().add_assign(other); - } -} - -impl<'a, T: Zeroize + AddAssign<&'a T>> AddAssign<&'a Secret> for Secret { - fn add_assign(&mut self, other: &'a Secret) { - self.add_assign(other.expose_secret()); +impl<'a, T: Zeroize + Clone + Add<&'a T, Output = T>> Add<&'a T> for &Secret { + type Output = Secret; + fn add(self, rhs: &'a T) -> Self::Output { + Secret::init_with(|| self.expose_secret().clone() + rhs) } } -impl<'a, T: Zeroize + AddAssign<&'a T>> Add<&'a T> for Secret { +impl<'a, T: Zeroize + Clone + Add<&'a T, Output = T>> Add<&'a T> for Secret { type Output = Secret; - - fn add(mut self, other: &'a T) -> Self::Output { - self += other; - self + fn add(self, rhs: &'a T) -> Self::Output { + &self + rhs } } -impl AddAssign<&'a T>> Add> for Secret { +impl Add<&'a T, Output = T>> Add> for Secret { type Output = Secret; - - fn add(mut self, other: Secret) -> Self::Output { - self += &other; - self + fn add(self, rhs: Secret) -> Self::Output { + &self + rhs.expose_secret() } } -impl<'a, T: Zeroize + AddAssign<&'a T>> Add<&'a Secret> for Secret { +impl<'a, T: Zeroize + Clone + Add<&'a T, Output = T>> Add<&'a Secret> for Secret { type Output = Secret; - - fn add(mut self, other: &'a Secret) -> Self::Output { - self += other.expose_secret(); - self + fn add(self, rhs: &'a Secret) -> Self::Output { + &self + rhs.expose_secret() } } -impl AddAssign<&'a T>> Add> for &Secret { +impl Add<&'a T, Output = T>> Add> for &Secret { type Output = Secret; - - fn add(self, other: Secret) -> Self::Output { - let mut result = other; - result += self; - result + fn add(self, rhs: Secret) -> Self::Output { + self + rhs.expose_secret() } } -// Negation - -impl> Neg for &Secret { +impl<'a, T: Zeroize + Clone + Add<&'a T, Output = T>> Add<&'a Secret> for &Secret { type Output = Secret; - fn neg(self) -> Self::Output { - Secret::init_with(|| self.expose_secret().clone().neg()) + fn add(self, rhs: &'a Secret) -> Self::Output { + self + rhs.expose_secret() } } // Subtraction -impl<'a, T: Zeroize + SubAssign<&'a T>> SubAssign<&'a T> for Secret { - fn sub_assign(&mut self, other: &'a T) { - self.expose_secret_mut().sub_assign(other); - } -} - -impl<'a, T: Zeroize + SubAssign<&'a T>> SubAssign<&'a Secret> for Secret { - fn sub_assign(&mut self, other: &'a Secret) { - self.sub_assign(other.expose_secret()); +impl<'a, T: Zeroize + Clone + Sub<&'a T, Output = T>> Sub<&'a T> for &Secret { + type Output = Secret; + fn sub(self, rhs: &'a T) -> Self::Output { + Secret::init_with(|| self.expose_secret().clone() - rhs) } } -impl<'a, T: Zeroize + SubAssign<&'a T>> Sub<&'a T> for Secret { +impl<'a, T: Zeroize + Clone + Sub<&'a T, Output = T>> Sub<&'a T> for Secret { type Output = Secret; - - fn sub(mut self, other: &'a T) -> Self::Output { - self -= other; - self + fn sub(self, rhs: &'a T) -> Self::Output { + &self - rhs } } -impl SubAssign<&'a T>> Sub> for Secret { +impl Sub<&'a T, Output = T>> Sub> for Secret { type Output = Secret; - - fn sub(mut self, other: Secret) -> Self::Output { - self -= &other; - self + fn sub(self, rhs: Secret) -> Self::Output { + &self - rhs.expose_secret() } } // Multiplication -impl<'a, T: Zeroize + MulAssign<&'a T>> MulAssign<&'a T> for Secret { - fn mul_assign(&mut self, other: &'a T) { - self.expose_secret_mut().mul_assign(other) - } -} - -impl<'a, T: Zeroize + MulAssign<&'a T>> MulAssign<&'a Secret> for Secret { - fn mul_assign(&mut self, other: &'a Secret) { - self.mul_assign(other.expose_secret()) +impl<'a, T: Zeroize + Clone + Mul<&'a T, Output = T>> Mul<&'a T> for &Secret { + type Output = Secret; + fn mul(self, rhs: &'a T) -> Self::Output { + Secret::init_with(|| self.expose_secret().clone() * rhs) } } -impl<'a, T: Zeroize + MulAssign<&'a T>> Mul<&'a T> for Secret { +impl Mul<&'a T, Output = T>> Mul for &Secret { type Output = Secret; - - fn mul(mut self, other: &'a T) -> Self::Output { - self *= other; - self + fn mul(self, rhs: T) -> Self::Output { + Secret::init_with(|| self.expose_secret().clone() * &rhs) } } -impl MulAssign<&'a T>> Mul for Secret { +impl<'a, T: Zeroize + Clone + Mul<&'a T, Output = T>> Mul<&'a T> for Secret { type Output = Secret; - - fn mul(mut self, other: T) -> Self::Output { - self *= &other; - self + fn mul(self, rhs: &'a T) -> Self::Output { + &self * rhs } } -impl MulAssign<&'a T>> Mul for &Secret { +impl Mul<&'a T, Output = T>> Mul for Secret { type Output = Secret; - - fn mul(self, other: T) -> Self::Output { - let mut result: Secret = self.clone(); - result *= &other; - result + fn mul(self, rhs: T) -> Self::Output { + &self * &rhs } } -impl MulAssign<&'a T>> Mul> for Secret { +impl Mul<&'a T, Output = T>> Mul> for Secret { type Output = Secret; - - fn mul(mut self, other: Secret) -> Self::Output { - self *= &other; - self + fn mul(self, rhs: Secret) -> Self::Output { + &self * rhs.expose_secret() } } -impl<'a, T: Zeroize + MulAssign<&'a T>> Mul<&'a Secret> for Secret { +impl<'a, T: Zeroize + Clone + Mul<&'a T, Output = T>> Mul<&'a Secret> for Secret { type Output = Secret; - - fn mul(mut self, other: &'a Secret) -> Self::Output { - self *= other.expose_secret(); - self + fn mul(self, rhs: &'a Secret) -> Self::Output { + &self * rhs.expose_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) + fn div_assign(&mut self, rhs: &'a NonZero) { + self.expose_secret_mut().div_assign(rhs) } } impl DivAssign<&'a NonZero>> Div> for Secret { type Output = Secret; - fn div(mut self, other: NonZero) -> Self::Output { - self /= &other; + fn div(mut self, rhs: NonZero) -> Self::Output { + self /= &rhs; self } } @@ -293,66 +245,21 @@ impl DivAssign<&'a NonZero>> Div> for Secret< // Remainder impl<'a, T: Zeroize + Clone + RemAssign<&'a NonZero>> RemAssign<&'a NonZero> for Secret { - fn rem_assign(&mut self, other: &'a NonZero) { - self.expose_secret_mut().rem_assign(other) + fn rem_assign(&mut self, rhs: &'a NonZero) { + self.expose_secret_mut().rem_assign(rhs) } } impl<'a, T: Zeroize + Clone + RemAssign<&'a NonZero>> Rem<&'a NonZero> for &Secret { type Output = Secret; - fn rem(self, other: &'a NonZero) -> Self::Output { + fn rem(self, rhs: &'a NonZero) -> Self::Output { let mut result = self.clone(); - result %= other; + result %= rhs; result } } -// Summation - -impl AddAssign<&'a T> + Default> core::iter::Sum for Secret { - fn sum>(iter: I) -> Self { - iter.reduce(Add::add).unwrap_or(Secret::::default()) - } -} - -impl<'b, T: Zeroize + Clone + for<'a> AddAssign<&'a T> + Default> core::iter::Sum<&'b Secret> for Secret { - fn sum>>(iter: I) -> Self { - iter.fold(Secret::::default(), |accum, x| accum + x) - } -} - -impl Secret { - pub fn mul_by_generator(&self) -> Point { - self.expose_secret().mul_by_generator() - } - - pub fn invert(&self) -> Option> { - Secret::maybe_init_with(|| Option::from(self.expose_secret().invert())) - } -} - -impl Mul> for Point { - type Output = Point; - fn mul(self, scalar: Secret) -> Self::Output { - self * scalar.expose_secret() - } -} - -impl Mul<&Secret> for Point { - type Output = Point; - fn mul(self, scalar: &Secret) -> Self::Output { - self * scalar.expose_secret() - } -} - -impl Mul> for &Point { - type Output = Point; - fn mul(self, scalar: Secret) -> Self::Output { - self * scalar.expose_secret() - } -} - impl> Retrieve for Secret { type Output = Secret; fn retrieve(&self) -> Self::Output { @@ -360,27 +267,17 @@ impl> Retrieve for Secret { } } -impl Secret { - pub fn pow_bounded(&self, exponent: &Bounded) -> Self - where - T: Exponentiable, - V: Integer + crypto_bigint::Bounded + Encoding + ConditionallySelectable, - { - // TODO: do we need to implement our own windowed exponentiation to hide the secret? - Secret::init_with(|| self.expose_secret().pow_bounded(exponent)) - } - - pub fn pow_signed_vartime(&self, exponent: &Signed) -> Self +impl Secret { + pub fn pow(&self, exponent: &V) -> Self where T: Exponentiable, - V: Integer + crypto_bigint::Bounded + Encoding + ConditionallySelectable, { // TODO: do we need to implement our own windowed exponentiation to hide the secret? // The exponent will be put in a stack array when it's decomposed with a small radix // for windowed exponentiation. So if it's secret, it's going to leave traces on the stack. // With the multiplication, for example, there's less danger since Uints implement *Assign traits which we use, // so theoretically anything secret will be overwritten. - Secret::init_with(|| self.expose_secret().pow_signed_vartime(exponent)) + Secret::init_with(|| self.expose_secret().pow(exponent)) } } @@ -388,6 +285,10 @@ 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. + + // NOTE: `params` is cloned here and can remain on the stack. + // See https://github.com/RustCrypto/crypto-bigint/issues/704; + // after that's fixed, we can properly treat secret `params`. Secret::init_with(|| ::new(self.expose_secret().clone(), params.clone())) } } @@ -404,3 +305,48 @@ impl ConditionallyNegatable for Secret { self.0.expose_secret_mut().conditional_negate(choice) } } + +// Scalar-specific impls + +impl core::iter::Sum> for Secret { + fn sum>(iter: I) -> Self { + iter.reduce(Add::add).unwrap_or(Secret::init_with(|| Scalar::ZERO)) + } +} + +impl<'a> core::iter::Sum<&'a Secret> for Secret { + fn sum>>(iter: I) -> Self { + iter.fold(Secret::init_with(|| Scalar::ZERO), |accum, x| accum + x) + } +} + +impl Secret { + pub fn mul_by_generator(&self) -> Point { + self.expose_secret().mul_by_generator() + } + + pub fn invert(&self) -> Option> { + Secret::maybe_init_with(|| Option::from(self.expose_secret().invert())) + } +} + +impl Mul> for Point { + type Output = Point; + fn mul(self, scalar: Secret) -> Self::Output { + self * scalar.expose_secret() + } +} + +impl Mul<&Secret> for Point { + type Output = Point; + fn mul(self, scalar: &Secret) -> Self::Output { + self * scalar.expose_secret() + } +} + +impl Mul> for &Point { + type Output = Point; + fn mul(self, scalar: Secret) -> Self::Output { + self * scalar.expose_secret() + } +} diff --git a/synedrion/src/tools/sss.rs b/synedrion/src/tools/sss.rs index 4961839..84294e2 100644 --- a/synedrion/src/tools/sss.rs +++ b/synedrion/src/tools/sss.rs @@ -124,7 +124,7 @@ pub(crate) fn shamir_join_scalars(pairs: BTreeMap>) -> S let mut sum = Secret::init_with(|| Scalar::ZERO); for (share_id, val) in pairs.into_iter() { - sum += &(val * interpolation_coeff(&share_ids, &share_id)); + sum = sum + &(val * interpolation_coeff(&share_ids, &share_id)); } sum diff --git a/synedrion/src/uint.rs b/synedrion/src/uint.rs index 759f642..9bb4805 100644 --- a/synedrion/src/uint.rs +++ b/synedrion/src/uint.rs @@ -1,13 +1,9 @@ -mod bounded; -mod signed; +mod public_signed; +mod secret_signed; +mod secret_unsigned; mod traits; -pub(crate) use crypto_bigint::{ - modular::Retrieve, subtle, CheckedAdd, CheckedMul, CheckedSub, Encoding, Integer, Invert, NonZero, PowBoundedExp, - RandomMod, ShlVartime, Uint, WrappingSub, Zero, U1024, U2048, U4096, U512, U8192, -}; -pub(crate) use crypto_primes::RandomPrimeWithRng; - -pub(crate) use bounded::Bounded; -pub(crate) use signed::Signed; +pub(crate) use public_signed::PublicSigned; +pub(crate) use secret_signed::SecretSigned; +pub(crate) use secret_unsigned::SecretUnsigned; pub(crate) use traits::{Exponentiable, HasWide, ToMontgomery, U1024Mod, U2048Mod, U4096Mod, U512Mod}; diff --git a/synedrion/src/uint/bounded.rs b/synedrion/src/uint/bounded.rs deleted file mode 100644 index cbd0e14..0000000 --- a/synedrion/src/uint/bounded.rs +++ /dev/null @@ -1,201 +0,0 @@ -use alloc::{boxed::Box, format, string::String}; - -use serde::{Deserialize, Serialize}; -use serde_encoded_bytes::{Hex, SliceLike}; -use zeroize::Zeroize; - -use super::{ - subtle::{Choice, ConditionallySelectable, ConstantTimeLess, CtOption}, - CheckedMul, Encoding, HasWide, Integer, NonZero, Signed, -}; - -/// A packed representation for serializing Bounded objects. -/// Usually they have the bound much lower than the full size of the integer, -/// so this way we avoid serializing a bunch of zeros. -#[derive(Serialize, Deserialize)] -pub(crate) struct PackedBounded { - bound: u32, - #[serde(with = "SliceLike::")] - bytes: Box<[u8]>, -} - -impl From> for PackedBounded -where - T: Integer + Encoding + crypto_bigint::Bounded, -{ - fn from(val: Bounded) -> Self { - let repr = val.as_ref().to_be_bytes(); - let bound_bytes = (val.bound() + 7) / 8; - let slice = repr - .as_ref() - .get((repr.as_ref().len() - bound_bytes as usize)..) - .expect("val has a valid bound that was checked when it was created"); - Self { - bound: val.bound(), - bytes: slice.into(), - } - } -} - -impl TryFrom for Bounded -where - T: Integer + Encoding + crypto_bigint::Bounded, -{ - type Error = String; - fn try_from(val: PackedBounded) -> Result { - let mut repr = T::zero().to_be_bytes(); - let bytes_len: usize = val.bytes.len(); - let repr_len: usize = repr.as_ref().len(); - - if repr_len < bytes_len { - return Err(format!( - "The bytestring of length {} does not fit the expected integer size {}", - bytes_len, repr_len - )); - } - - repr.as_mut() - .get_mut((repr_len - bytes_len)..) - .expect("Just checked that val's data all fit in a T") - .copy_from_slice(&val.bytes); - let abs_value = T::from_be_bytes(repr); - - Self::new(abs_value, val.bound).ok_or_else(|| "Invalid values for the signed integer".into()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Zeroize)] -#[serde( - try_from = "PackedBounded", - into = "PackedBounded", - bound = "T: Integer + Encoding + crypto_bigint::Bounded" -)] -pub(crate) struct Bounded { - /// bound on the bit size of the value - bound: u32, - value: T, -} - -impl Bounded -where - T: Integer + crypto_bigint::Bounded, -{ - pub fn bound(&self) -> u32 { - self.bound - } - - /// Creates a new [`Bounded`] wrapper around `T`, restricted to `bound`. - /// - /// Returns `None` if the bound is invalid, i.e.: - /// - The bound is bigger than a `T` can represent. - /// - The value of `T` is too big to be bounded by the provided bound. - pub fn new(value: T, bound: u32) -> Option { - if bound > T::BITS || value.bits() > bound { - return None; - } - Some(Self { value, bound }) - } - - pub fn add_mod(&self, rhs: &Self, modulus: &NonZero) -> Self { - // Note: assuming that the bit size of the modulus is not secret - // (although the modulus itself might be) - Self { - value: self.value.add_mod(&rhs.value, modulus), - bound: modulus.bits_vartime(), - } - } - - pub fn into_signed(self) -> Option> { - Signed::new_positive(self.value, self.bound) - } -} - -impl AsRef for Bounded { - fn as_ref(&self) -> &T { - &self.value - } -} - -impl Bounded -where - T: HasWide, -{ - pub fn to_wide(&self) -> Bounded { - Bounded { - value: self.value.to_wide(), - bound: self.bound, - } - } - - pub fn mul_wide(&self, rhs: &Self) -> Bounded { - let result = self.value.mul_wide(&rhs.value); - Bounded { - value: result, - bound: self.bound + rhs.bound, - } - } -} - -impl CheckedMul for Bounded -where - T: Integer + crypto_bigint::Bounded, -{ - fn checked_mul(&self, rhs: &Self) -> CtOption { - let bound = self.bound + rhs.bound; - let in_range = bound.ct_lt(&::BITS); - - let result = Self { - bound, - value: self.value.wrapping_mul(&rhs.value), - }; - CtOption::new(result, in_range) - } -} - -impl ConditionallySelectable for Bounded -where - T: ConditionallySelectable, -{ - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Self { - bound: u32::conditional_select(&a.bound, &b.bound, choice), - value: T::conditional_select(&a.value, &b.value, choice), - } - } -} - -#[cfg(test)] -mod tests { - use crypto_bigint::{CheckedMul, U1024, U128, U2048}; - - use super::Bounded; - - #[test] - fn checked_mul_fails_when_operands_have_max_bounds() { - let bound = 88; - let b1 = Bounded::new(U128::from_u8(10), bound).unwrap(); - let b2 = Bounded::new(U128::from_u8(10), bound).unwrap(); - let b3 = b1.checked_mul(&b2); - - // Bounds are summed up, so 88 + 88 = 176 ==> OoB - assert!(bool::from(b3.is_none())); - - let b4 = Bounded::new(U128::from_u8(10), 20).unwrap(); - let b5 = b1.checked_mul(&b4); - // This is fine, because 88 + 20 < MAX BOUND (127) - assert!(bool::from(b5.is_some())); - assert_eq!(b5.unwrap(), Bounded::new(U128::from_u8(100), 108).unwrap()); - } - - #[test] - fn mul_wide_sums_the_bounds_of_the_operands() { - let bound = 678; - let b1 = Bounded::new(U1024::from_u8(10), bound).unwrap(); - let b2 = Bounded::new(U1024::from_u8(10), bound).unwrap(); - let b3 = b1.mul_wide(&b2); - - // Bounds are summed up, so 678 + 678 = 1356 - assert_eq!(b3.bound(), 1356); - assert_eq!(b3, Bounded::new(U2048::from_u8(100), 1356).unwrap()); - } -} diff --git a/synedrion/src/uint/public_signed.rs b/synedrion/src/uint/public_signed.rs new file mode 100644 index 0000000..76f8548 --- /dev/null +++ b/synedrion/src/uint/public_signed.rs @@ -0,0 +1,248 @@ +use alloc::{boxed::Box, format, string::String}; +use core::ops::{Mul, Neg, Sub}; + +use crypto_bigint::{Bounded, Encoding, Integer, NonZero}; +use digest::XofReader; +use serde::{Deserialize, Serialize}; +use serde_encoded_bytes::{Hex, SliceLike}; + +use super::HasWide; +use crate::tools::hashing::uint_from_xof; + +/// A packed representation for serializing Signed objects. +/// Usually they have the bound much lower than the full size of the integer, +/// so this way we avoid serializing a bunch of zeros. +#[derive(Serialize, Deserialize)] +struct PackedSigned { + bound: u32, + is_negative: bool, + #[serde(with = "SliceLike::")] + abs_bytes: Box<[u8]>, +} + +impl From> for PackedSigned +where + T: Integer + Encoding + Bounded, +{ + fn from(val: PublicSigned) -> Self { + let repr = val.abs().to_be_bytes(); + let bound_bytes = (val.bound + 7) / 8; + let slice = repr + .as_ref() + .get((repr.as_ref().len() - bound_bytes as usize)..) + .expect("val has a valid bound that was checked when it was created"); + Self { + bound: val.bound, + is_negative: val.is_negative(), + abs_bytes: slice.into(), + } + } +} + +impl TryFrom for PublicSigned +where + T: Integer + Encoding + Bounded, +{ + type Error = String; + fn try_from(val: PackedSigned) -> Result { + let mut repr = T::zero().to_be_bytes(); + let bytes_len: usize = val.abs_bytes.len(); + let repr_len: usize = repr.as_ref().len(); + + if repr_len < bytes_len { + return Err(format!( + "The bytestring of length {} does not fit the expected integer size {}", + bytes_len, repr_len + )); + } + + repr.as_mut() + .get_mut((repr_len - bytes_len)..) + .expect("Just checked that val's data all fit in a T") + .copy_from_slice(&val.abs_bytes); + let abs_value = T::from_be_bytes(repr); + + Self::new_from_abs(abs_value, val.bound, val.is_negative) + .ok_or_else(|| "Invalid values for the signed integer".into()) + } +} + +/// A wrapper over unsigned integers that treats two's complement numbers as negative. +// In principle, Bounded could be separate from Signed, but we only use it internally, +// and pretty much every time we need a bounded value, it's also signed. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde( + try_from = "PackedSigned", + into = "PackedSigned", + bound = "T: Integer + Encoding + Bounded" +)] +pub(crate) struct PublicSigned { + /// bound on the bit size of the absolute value + bound: u32, + value: T, +} + +impl PublicSigned +where + T: Integer + Bounded, +{ + fn new_from_abs(abs_value: T, bound: u32, is_negative: bool) -> Option { + if bound >= T::BITS || abs_value.bits_vartime() > bound { + return None; + } + + let value = if is_negative { + abs_value.wrapping_neg() + } else { + abs_value + }; + + Some(Self { value, bound }) + } + + pub fn new_positive(value: T, bound: u32) -> Option { + Self::new_from_abs(value, bound, false) + } + + pub fn new_from_unsigned(value: T, bound: u32) -> Option { + let result = Self { value, bound }; + if bound >= T::BITS || result.abs().bits_vartime() > bound { + return None; + } + Some(result) + } + + pub fn is_negative(&self) -> bool { + self.value.bit_vartime(T::BITS - 1) + } + + pub fn abs(&self) -> T { + if self.is_negative() { + self.neg().value + } else { + self.value.clone() + } + } + + pub fn bound(&self) -> u32 { + self.bound + } + + pub fn value(&self) -> &T { + &self.value + } + + /// Returns `true` if the value is within `[-2^bound_bits, 2^bound_bits]`. + pub fn in_range_bits(&self, bound_bits: u32) -> bool { + self.abs() <= T::one() << bound_bits + } + + fn checked_sub(&self, rhs: &Self) -> Option { + let bound = core::cmp::max(self.bound, rhs.bound) + 1; + if bound < T::BITS { + Some(Self { + bound, + value: self.value.wrapping_sub(&rhs.value), + }) + } else { + None + } + } + + /// Constant-time checked multiplication. The product must fit in a `T`; use [`Signed::mul_wide`] if widening is desired. + /// Note: when multiplying two [`Signed`], the bound on the result is equal to the sum of the bounds of the operands. + fn checked_mul(&self, rhs: &Self) -> Option { + let bound = self.bound + rhs.bound; + if bound < T::BITS { + Some(Self { + bound, + value: self.value.wrapping_mul(&rhs.value), + }) + } else { + None + } + } + + /// Performs the unary - operation. + pub fn neg(&self) -> Self { + Self { + value: T::zero().wrapping_sub(&self.value), + bound: self.bound, + } + } +} + +impl PublicSigned +where + T: Integer + Bounded + Encoding, +{ + /// 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. + 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() + .overflowing_shl_vartime(1) + .expect("Just asserted that bound is smaller than precision; qed") + .checked_add(&T::one()) + .expect("does not overflow since we're adding 1 to an even number"); + let positive_result = uint_from_xof( + rng, + &NonZero::new(positive_bound).expect("Guaranteed to be greater than zero because we added 1"), + ); + Self::new_from_unsigned(positive_result.wrapping_sub(bound.as_ref()), bound_bits) + .expect("Guaranteed to be Some because we checked the bounds just above") + } +} + +impl PublicSigned +where + T: Bounded + HasWide + Encoding + Integer, + T::Wide: Bounded, +{ + /// Returns a [`PublicSigned`] with the same value, but twice the bit-width. + pub fn to_wide(&self) -> PublicSigned { + let abs_result = self.abs().to_wide(); + PublicSigned::new_from_abs(abs_result, self.bound, self.is_negative()) + .expect("the value fit the bound before, and the bound won't overflow for `WideUint`") + } +} + +impl Neg for PublicSigned +where + T: Integer + Bounded, +{ + type Output = PublicSigned; + + fn neg(self) -> Self::Output { + PublicSigned::neg(&self) + } +} + +impl Sub> for PublicSigned +where + T: Integer + Bounded, +{ + type Output = PublicSigned; + + fn sub(self, rhs: PublicSigned) -> Self::Output { + self.checked_sub(&rhs) + .expect("the calling code ensured the bound is not overflown") + } +} + +impl Mul> for PublicSigned +where + T: Integer + Bounded, +{ + type Output = PublicSigned; + + fn mul(self, rhs: PublicSigned) -> Self::Output { + self.checked_mul(&rhs) + .expect("the calling code ensured the bound is not overflown") + } +} diff --git a/synedrion/src/uint/secret_signed.rs b/synedrion/src/uint/secret_signed.rs new file mode 100644 index 0000000..c1b789b --- /dev/null +++ b/synedrion/src/uint/secret_signed.rs @@ -0,0 +1,710 @@ +use core::ops::{Add, Mul, Neg, Sub}; + +use crypto_bigint::{ + rand_core::CryptoRngCore, + subtle::{Choice, ConditionallySelectable, ConstantTimeLess, CtOption}, + zeroize::Zeroize, + BitOps, Bounded, CheckedAdd, CheckedMul, CheckedSub, Integer, NonZero, RandomMod, ShlVartime, WrappingAdd, + WrappingMul, WrappingNeg, WrappingSub, +}; + +use super::{HasWide, PublicSigned, SecretUnsigned}; +use crate::tools::Secret; + +/// A wrapper over secret unsigned integers that treats two's complement numbers as negative. +#[derive(Debug, Clone)] +pub(crate) struct SecretSigned { + /// bound on the bit size of the absolute value (that is, `abs(value) < 2^bound`) + bound: u32, + value: Secret, +} + +impl SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + /// A constructor for internal use where we already checked the bound. + /// Creates a [`SignedSecret`] from an unsigned value, treating it as if it encodes the sign as two's complement. + /// + /// Extracting in a method to make the intent clear. + fn new_from_unsigned_unchecked(value: Secret, bound: u32) -> Self { + Self { value, bound } + } + + /// Creates a signed value from an unsigned one, assuming that it encodes a positive value + /// treated as two's complement. + /// + /// Panics if it is not the case. + pub fn new_positive(value: Secret, bound: u32) -> Option { + // Reserving one bit as the sign bit (MSB) + if bound >= T::BITS || value.expose_secret().bits() > bound { + return None; + } + let result = Self::new_from_unsigned_unchecked(value, bound); + if result.is_negative().into() { + return None; + } + Some(result) + } + + pub fn zero() -> Self { + Self { + value: Secret::init_with(|| T::zero()), + bound: 0, + } + } + + /// Returns a truthy `Choice` if this number is negative. + pub fn is_negative(&self) -> Choice { + // Check the MSB, `1` indicates that it is negative. + Choice::from(self.value.expose_secret().bit_vartime(T::BITS - 1) as u8) + } + + pub fn bound(&self) -> u32 { + self.bound + } + + pub fn to_public(&self) -> PublicSigned { + PublicSigned::new_from_unsigned(self.value.expose_secret().clone(), self.bound).expect("the bound is valid") + } + + /// Performs the unary `-` operation. + pub fn neg(&self) -> Self { + Self { + value: self.value.wrapping_neg(), + bound: self.bound, + } + } +} + +impl SecretSigned +where + T: ConditionallySelectable + Zeroize + Integer + Bounded, +{ + pub fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self { + bound: u32::conditional_select(&a.bound, &b.bound, choice), + value: Secret::::conditional_select(&a.value, &b.value, choice), + } + } + + pub fn abs_value(&self) -> Secret { + Secret::::conditional_select(&self.value, &self.value.wrapping_neg(), self.is_negative()) + } + + /// Computes the absolute value of [`self`] + pub fn abs(&self) -> SecretUnsigned { + SecretUnsigned::new( + Secret::::conditional_select(&self.value, &self.value.wrapping_neg(), self.is_negative()), + self.bound, + ) + .expect("the absolute value is within the same bound") + } + + /// Creates a [`SignedSecret`] from an unsigned value, treating it as if it encodes the sign as two's complement. + /// + /// Returns `None` if the requested bound is too large, or if `abs(value)` is actually larger than the bound. + pub fn new_from_unsigned(value: Secret, bound: u32) -> Option { + let is_negative = Choice::from(value.expose_secret().bit_vartime(T::BITS - 1) as u8); + let abs = Secret::::conditional_select(&value, &value.wrapping_neg(), is_negative); + if bound >= T::BITS || abs.expose_secret().bits() > bound { + return None; + } + Some(Self::new_from_unsigned_unchecked(value, bound)) + } + + /// Creates a [`SignedSecret`] from an unsigned value, treating it as if it is the absolute value. + /// If `is_negative` is truthy, crates a negative [`SignedSecret`] with the given absolute value. + /// + /// Returns `None` if the bound is too large, or if `abs_value` is actually larger than the bound. + fn new_from_abs(abs_value: Secret, bound: u32, is_negative: Choice) -> Option { + Self::new_from_unsigned( + Secret::::conditional_select(&abs_value, &abs_value.wrapping_neg(), is_negative), + bound, + ) + } + + #[cfg(test)] + fn test_new_from_abs(abs_value: T, bound: u32, is_negative: bool) -> Option { + Self::new_from_abs(Secret::init_with(|| abs_value), bound, Choice::from(is_negative as u8)) + } + + #[cfg(test)] + fn test_new_from_unsigned(abs_value: T, bound: u32) -> Option { + Self::new_from_unsigned(Secret::init_with(|| abs_value), bound) + } + + /// Returns a truthy `Choice` if the absolute value is within the bit bound `bound`. + fn in_bound(&self, bound: u32) -> Choice { + let abs = self.abs(); + let mask = T::one().wrapping_neg().wrapping_shl_vartime(bound); + let masked = abs & mask; + masked.is_zero() + } + + /// Asserts that the absolute value is within the bit bound `bound`. + /// If that is the case, returns the value with the bound set to it. + pub fn ensure_bound(&self, bound: u32) -> CtOption { + let value = Self { + value: self.value.clone(), + bound, + }; + CtOption::new(value, self.in_bound(bound)) + } + + /// Asserts that the value is within the interval the paper denotes as $\pm 2^exp$. + /// Panics if it is not the case. + /// + /// That is, the value must be within $[-2^{exp}, 2^{exp}]$ + /// (See Section 2). + pub fn assert_exp_range(&self, exp: u32) { + let in_bound = self.in_bound(exp); + // Have to check for the ends of the range too + let is_end = self.abs().expose_secret().ct_eq(&(T::one() << exp)); + assert!(bool::from(in_bound | is_end), "out of bounds $\\pm 2^{exp}$",) + } +} + +impl SecretSigned +where + T: ConditionallySelectable + Zeroize + Bounded + HasWide, + T::Wide: ConditionallySelectable + Zeroize + Bounded, +{ + /// Returns a [`SecretSigned`] with the same value, but twice the bit-width. + pub fn to_wide(&self) -> SecretSigned { + let abs_result = self.abs_value().to_wide(); + SecretSigned::new_from_abs(abs_result, self.bound(), self.is_negative()) + .expect("the value fit the bound before, and the bound won't overflow for `WideUint`") + } + + /// Multiplies two [`SecretSigned`] and returns a new [`SecretSigned`] of twice the bit-width. + pub fn mul_wide(&self, rhs: &PublicSigned) -> SecretSigned { + let abs_value = Secret::init_with(|| self.abs_value().expose_secret().mul_wide(&rhs.abs())); + SecretSigned::new_from_abs( + abs_value, + self.bound() + rhs.bound(), + self.is_negative() ^ Choice::from(rhs.is_negative() as u8), + ) + .expect("the new bound is valid since the constituent ones were") + } +} + +impl CheckedAdd> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn checked_add(&self, rhs: &SecretSigned) -> CtOption { + let bound = core::cmp::max(self.bound, rhs.bound) + 1; + let in_range = bound.ct_lt(&T::BITS); + let result = Self { + bound, + value: self.value.wrapping_add(&rhs.value), + }; + CtOption::new(result, in_range) + } +} + +impl CheckedAdd> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn checked_add(&self, rhs: &PublicSigned) -> CtOption { + let bound = core::cmp::max(self.bound, rhs.bound()) + 1; + let in_range = bound.ct_lt(&T::BITS); + let result = Self { + bound, + value: Secret::init_with(|| self.value.expose_secret().wrapping_add(rhs.value())), + }; + CtOption::new(result, in_range) + } +} + +impl CheckedSub> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn checked_sub(&self, rhs: &SecretSigned) -> CtOption { + let bound = core::cmp::max(self.bound, rhs.bound()) + 1; + let in_range = bound.ct_lt(&T::BITS); + let result = Self { + bound, + value: self.value.wrapping_sub(&rhs.value), + }; + CtOption::new(result, in_range) + } +} + +impl CheckedMul> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn checked_mul(&self, rhs: &SecretSigned) -> CtOption { + let bound = self.bound + rhs.bound; + let in_range = bound.ct_lt(&T::BITS); + let result = Self { + bound, + value: self.value.wrapping_mul(&rhs.value), + }; + CtOption::new(result, in_range) + } +} + +impl CheckedMul> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn checked_mul(&self, rhs: &PublicSigned) -> CtOption { + let bound = self.bound + rhs.bound(); + let in_range = bound.ct_lt(&T::BITS); + let result = Self { + bound, + value: Secret::init_with(|| self.value.expose_secret().wrapping_mul(rhs.value())), + }; + CtOption::new(result, in_range) + } +} + +impl SecretSigned +where + T: ConditionallySelectable + Zeroize + Integer + Bounded + RandomMod, +{ + // Returns a random value in range `[-range, range]`. + // + // Note: variable time in bit size of `range`. + fn random_in_range(rng: &mut impl CryptoRngCore, range: &NonZero) -> Self { + let range_bits = range.as_ref().bits_vartime(); + assert!( + range_bits < T::BITS, + "Out of bounds: range_bits was {} but must be smaller or equal to {}", + range_bits, + T::BITS - 1 + ); + // Will not overflow because of the assertion above + let positive_bound = range + .as_ref() + .overflowing_shl_vartime(1) + .expect("Just asserted that range is smaller than precision; qed") + .checked_add(&T::one()) + .expect("Checked bounds above"); + let positive_result = Secret::init_with(|| { + T::random_mod( + rng, + &NonZero::new(positive_bound).expect("the range is non-zero by construction"), + ) + }); + + Self::new_from_unsigned_unchecked( + Secret::init_with(|| positive_result.expose_secret().wrapping_sub(range.as_ref())), + range_bits, + ) + } + + /// Returns a random value in range `[-2^bound_bits, 2^bound_bits]`. + /// + /// Note: variable time in `bound_bits`. + pub fn random_in_exp_range(rng: &mut impl CryptoRngCore, range_bits: u32) -> Self { + assert!( + range_bits < T::BITS - 1, + "Out of bounds: bound_bits was {} but must be smaller than {}", + range_bits, + T::BITS - 1 + ); + + let bound = NonZero::new(T::one() << range_bits).expect("Checked bound_bits just above; qed"); + Self::random_in_range(rng, &bound) + } +} + +impl SecretSigned +where + T: Zeroize + Integer + Bounded + HasWide, + T::Wide: Zeroize + ConditionallySelectable + Bounded + RandomMod, +{ + /// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`. + /// + /// Note: variable time in `bound_bits` and bit size of `scale`. + pub fn random_in_exp_range_scaled( + rng: &mut impl CryptoRngCore, + bound_bits: u32, + scale: &T, + ) -> SecretSigned { + assert!( + bound_bits < T::BITS - 1, + "Out of bounds: bound_bits was {} but must be smaller than {}", + bound_bits, + T::BITS - 1 + ); + let scaled_bound: ::Wide = scale + .to_wide() + .overflowing_shl_vartime(bound_bits) + .expect("Just asserted that bound bits is smaller than T's bit precision"); + + // Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range. + let positive_bound = scaled_bound + .overflowing_shl_vartime(1) + .expect(concat![ + "`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ", + "will not cause overflow in T ⇒ it's safe to left-shift 1 step ", + "(aka multiply by 2)." + ]) + .checked_add(&T::Wide::one()) + .expect(concat![ + "`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ", + "will not cause overflow in T ⇒ it's safe to add 1." + ]); + let positive_result = Secret::init_with(|| { + T::Wide::random_mod( + rng, + &NonZero::new(positive_bound) + .expect("Input guaranteed to be positive and it's non-zero because we added 1"), + ) + }); + let value = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&scaled_bound)); + + SecretSigned::new_from_unsigned_unchecked(value, bound_bits + scale.bits_vartime()) + } +} + +impl SecretSigned +where + T: Zeroize + Integer + Bounded + HasWide, + T::Wide: Zeroize + HasWide, + ::Wide: Zeroize + ConditionallySelectable + Bounded, +{ + /// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`. + /// + /// Note: variable time in `bound_bits` and `scale`. + pub fn random_in_exp_range_scaled_wide( + rng: &mut impl CryptoRngCore, + bound_bits: u32, + scale: &T::Wide, + ) -> SecretSigned<::Wide> { + assert!( + bound_bits < T::BITS - 1, + "Out of bounds: bound_bits was {} but must be smaller than {}", + bound_bits, + T::BITS - 1 + ); + let scaled_bound = scale + .to_wide() + .overflowing_shl_vartime(bound_bits) + .expect("Just asserted that bound_bits is smaller than bit precision of T"); + + // Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range. + let positive_bound = scaled_bound + .overflowing_shl_vartime(1) + .expect(concat![ + "`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ", + "will not cause overflow in T::Wide ⇒ it's safe to left-shift 1 step ", + "(aka multiply by 2)." + ]) + .checked_add(&::Wide::one()) + .expect(concat![ + "`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ", + "will not cause overflow in T::Wide ⇒ it's safe to add 1." + ]); + let positive_result = Secret::init_with(|| { + ::Wide::random_mod( + rng, + &NonZero::new(positive_bound) + .expect("Input guaranteed to be positive and it's non-zero because we added 1"), + ) + }); + let result = Secret::init_with(|| positive_result.expose_secret().wrapping_sub(&scaled_bound)); + + SecretSigned::new_from_unsigned_unchecked(result, bound_bits + scale.bits_vartime()) + } +} + +impl Neg for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn neg(self) -> Self::Output { + SecretSigned::neg(&self) + } +} + +impl Neg for &SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = SecretSigned; + fn neg(self) -> Self::Output { + SecretSigned::neg(self) + } +} + +impl Add> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + self.checked_add(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Add<&SecretSigned> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn add(self, rhs: &SecretSigned) -> Self::Output { + self.checked_add(rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Sub> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + self.checked_sub(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Mul> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn mul(self, rhs: Self) -> Self::Output { + self.checked_mul(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Mul<&SecretSigned> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn mul(self, rhs: &Self) -> Self::Output { + self.checked_mul(rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Add> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = SecretSigned; + + fn add(self, rhs: PublicSigned) -> Self::Output { + self.checked_add(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Mul> for &SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = SecretSigned; + + fn mul(self, rhs: PublicSigned) -> Self::Output { + self.checked_mul(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl Mul> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = SecretSigned; + + fn mul(self, rhs: PublicSigned) -> Self::Output { + self.checked_mul(&rhs) + .expect("the caller ensured the bounds will not overflow") + } +} + +impl<'b, T> core::iter::Sum<&'b SecretSigned> for SecretSigned +where + T: Zeroize + Integer + Bounded, +{ + fn sum>>(iter: I) -> Self { + iter.fold(Self::zero(), |accum, x| accum + x) + } +} + +#[cfg(test)] +mod tests { + use std::ops::Neg; + + use crypto_bigint::{CheckedMul, CheckedSub, U1024, U128}; + use rand::SeedableRng; + use rand_chacha::{self, ChaCha8Rng}; + + use super::SecretSigned; + use crate::uint::PublicSigned; + + const SEED: u64 = 123; + + #[test] + fn adding_signed_numbers_increases_the_bound() { + let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 13).unwrap(); + let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 10).unwrap(); + // The sum has a bound that is equal to the biggest bound of the operands + 1 + assert_eq!((s1 + s2).bound(), 14); + } + + #[test] + #[should_panic] + fn adding_signed_numbers_with_max_bounds_panics() { + let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); + let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 127).unwrap(); + + let _ = s1 + s2; + } + + #[test] + fn checked_mul_sums_bounds() { + let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 27).unwrap(); + let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let mul = s1.checked_mul(&s2).unwrap(); + + assert_eq!(mul.bound(), 44); + } + + #[test] + fn checked_mul_fails_when_sum_of_bounds_is_too_large() { + let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); + let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let mul = s1.checked_mul(&s2); + + assert!(bool::from(mul.is_none())); + } + + #[test] + fn mul_wide_sums_bounds() { + let s = SecretSigned::test_new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); + let s1 = PublicSigned::new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); + let mul = s.mul_wide(&s1); + assert_eq!(mul.bound(), 2046); + + let s2 = PublicSigned::new_from_unsigned(U1024::from_u8(8), 4).unwrap(); + let mul = s.mul_wide(&s2); + assert_eq!(mul.bound(), 1027); + } + + #[test] + fn checked_mul_handles_sign() { + let n = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 27) + .unwrap() + .neg(); + let p = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let neg_pos = n.checked_mul(&p).unwrap(); + let pos_neg = p.checked_mul(&n).unwrap(); + let pos_pos = p.checked_mul(&p).unwrap(); + let neg_neg = n.checked_mul(&n).unwrap(); + // negative * positive ⇒ negative + assert!(bool::from(neg_pos.is_negative())); + // positive * negative ⇒ negative + assert!(bool::from(pos_neg.is_negative())); + // positive * positive ⇒ positive + assert!(!bool::from(pos_pos.is_negative())); + // negative * negative ⇒ positive + assert!(!bool::from(neg_neg.is_negative())); + } + + #[test] + fn random_bounded_bits_is_sane() { + let mut rng = ChaCha8Rng::seed_from_u64(SEED); + for bound_bits in 1..U1024::BITS - 1 { + let signed: SecretSigned = SecretSigned::random_in_exp_range(&mut rng, bound_bits); + assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound_bits)); + signed.assert_exp_range(bound_bits); + } + } + + #[test] + fn signed_with_low_bounds() { + // a 2 bit bound means numbers must be smaller or equal to 3 + let bound = 2; + let value = U1024::from_u8(3); + let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); + signed.assert_exp_range(bound); + // 4 is too big + let value = U1024::from_u8(4); + let signed = SecretSigned::test_new_from_unsigned(value, bound); + assert!(signed.is_none()); + + // a 1 bit bound means numbers must be smaller or equal to 1 + let bound = 1; + let value = U1024::from_u8(1); + let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); + signed.assert_exp_range(bound); + // 2 is too big + let value = U1024::from_u8(2); + let signed = SecretSigned::test_new_from_unsigned(value, bound); + assert!(signed.is_none()); + + // a 0 bit bound means only 0 is a valid value + let bound = 0; + let value = U1024::from_u8(0); + let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); + signed.assert_exp_range(bound); + // 1 is too big + let value = U1024::from_u8(1); + let signed = SecretSigned::test_new_from_unsigned(value, bound); + assert!(signed.is_none()); + } + + #[test] + fn neg_u128() { + let n = + SecretSigned::test_new_from_unsigned(U128::from_be_hex("fffffffffffffffffffffffffffffff0"), 127).unwrap(); + let neg_n = + SecretSigned::test_new_from_unsigned(U128::from_be_hex("00000000000000000000000000000010"), 127).unwrap(); + assert!(bool::from(n.is_negative())); + assert!(!bool::from(neg_n.is_negative())); + assert_eq!(n.clone().neg().to_public(), neg_n.to_public()); + assert_eq!(n.clone().neg().neg().to_public(), n.to_public()); + } + + #[test] + #[should_panic(expected = "the caller ensured the bounds will not overflow")] + fn sub_panics_on_underflow() { + // Biggest/smallest SecretSigned is |2^127|: + use crypto_bigint::U128; + let max_uint = U128::from_u128(u128::MAX >> 1); + let one_signed = SecretSigned::test_new_from_abs(U128::ONE, U128::BITS - 1, false).unwrap(); + let min_signed = + SecretSigned::test_new_from_abs(max_uint, U128::BITS - 1, true).expect("|2^127| is a valid SecretSigned"); + let _ = min_signed - one_signed; + } + #[test] + #[should_panic(expected = "the caller ensured the bounds will not overflow")] + fn sub_panics_on_underflow_1024() { + // Biggest/smallest SecretSigned is |2^1023|: + let max_uint = U1024::MAX >> 1; + let one_signed = SecretSigned::test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); + let min_signed = + SecretSigned::test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); + let _ = min_signed - one_signed; + } + + #[test] + fn checked_sub_handles_underflow() { + // Biggest/smallest SecretSigned is |2^1023| + let max_uint = U1024::MAX >> 1; + let one_signed = SecretSigned::test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); + let min_signed = + SecretSigned::test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); + + let result = min_signed.checked_sub(&one_signed); + assert!(bool::from(result.is_none())) + } +} diff --git a/synedrion/src/uint/secret_unsigned.rs b/synedrion/src/uint/secret_unsigned.rs new file mode 100644 index 0000000..b6c6329 --- /dev/null +++ b/synedrion/src/uint/secret_unsigned.rs @@ -0,0 +1,114 @@ +use core::ops::BitAnd; + +use crypto_bigint::{subtle::Choice, Bounded, Integer, Monty, NonZero}; +use zeroize::Zeroize; + +use super::{HasWide, SecretSigned}; +use crate::tools::Secret; + +/// A bounded unsigned integer with sensitive data. +#[derive(Debug, Clone)] +pub(crate) struct SecretUnsigned { + /// bound on the bit size of the value (that is, `value < 2^bound`) + bound: u32, + value: Secret, +} + +impl SecretUnsigned +where + T: Zeroize + Integer + Bounded, +{ + pub fn is_zero(&self) -> Choice { + self.value.expose_secret().is_zero() + } + + pub fn bound(&self) -> u32 { + self.bound + } + + /// Creates a new [`Bounded`] wrapper around `T`, restricted to `bound`. + /// + /// Returns `None` if the bound is invalid, i.e.: + /// - The bound is bigger than a `T` can represent. + /// - The value of `T` is too big to be bounded by the provided bound. + pub fn new(value: Secret, bound: u32) -> Option { + if bound > T::BITS || value.expose_secret().bits() > bound { + return None; + } + Some(Self { value, bound }) + } + + pub fn add_mod(&self, rhs: &Self, modulus: &Secret>) -> Self { + // Note: assuming that the bit size of the modulus is not secret + // (although the modulus itself might be) + Self { + value: Secret::init_with(|| { + self.value + .expose_secret() + .add_mod(rhs.value.expose_secret(), modulus.expose_secret()) + }), + bound: modulus.expose_secret().bits_vartime(), + } + } + + pub fn to_signed(&self) -> Option> { + SecretSigned::new_positive(self.value.clone(), self.bound) + } + + pub fn expose_secret(&self) -> &T { + self.value.expose_secret() + } +} + +impl BitAnd for SecretUnsigned +where + T: Zeroize + Integer + Bounded, +{ + type Output = Self; + fn bitand(self, rhs: T) -> Self::Output { + Self { + value: Secret::init_with(|| { + let mut result = self.value.expose_secret().clone(); + result &= rhs; + result + }), + bound: self.bound, + } + } +} + +impl SecretUnsigned +where + T: Zeroize + Integer + Bounded, +{ + pub fn to_montgomery(&self, params: &::Params) -> Secret { + Secret::init_with(|| ::new(self.expose_secret().clone(), params.clone())) + } +} + +impl SecretUnsigned +where + T: Zeroize + Integer + Bounded + HasWide, + T::Wide: Zeroize + Integer + Bounded, +{ + pub fn mul_wide(&self, rhs: &T) -> SecretUnsigned { + SecretUnsigned::new( + Secret::init_with(|| self.value.expose_secret().mul_wide(rhs)), + self.bound + rhs.bits_vartime(), + ) + .expect("the new bound is valid since the constituent ones were") + } +} + +impl SecretUnsigned +where + T: Zeroize + Clone + HasWide, + T::Wide: Zeroize, +{ + pub fn to_wide(&self) -> SecretUnsigned { + SecretUnsigned { + value: self.value.to_wide(), + bound: self.bound, + } + } +} diff --git a/synedrion/src/uint/signed.rs b/synedrion/src/uint/signed.rs deleted file mode 100644 index 6831d5f..0000000 --- a/synedrion/src/uint/signed.rs +++ /dev/null @@ -1,689 +0,0 @@ -use alloc::string::String; -use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub}; - -use digest::XofReader; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; -use zeroize::Zeroize; - -use super::{ - bounded::PackedBounded, - subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, ConstantTimeLess, CtOption}, - Bounded, CheckedAdd, CheckedSub, Encoding, HasWide, Integer, NonZero, RandomMod, ShlVartime, WrappingSub, -}; -use crate::tools::hashing::uint_from_xof; - -/// A packed representation for serializing Signed objects. -/// Usually they have the bound much lower than the full size of the integer, -/// so this way we avoid serializing a bunch of zeros. -#[derive(Serialize, Deserialize)] -struct PackedSigned { - is_negative: bool, - abs_value: PackedBounded, -} - -impl From> for PackedSigned -where - T: ConditionallySelectable + Integer + Encoding + crypto_bigint::Bounded, -{ - fn from(val: Signed) -> Self { - Self { - is_negative: val.is_negative().into(), - abs_value: PackedBounded::from(val.abs_bounded()), - } - } -} - -impl TryFrom for Signed -where - T: ConditionallySelectable + Integer + Encoding + crypto_bigint::Bounded, -{ - type Error = String; - fn try_from(val: PackedSigned) -> Result { - let abs_value = Bounded::try_from(val.abs_value)?; - Self::new_from_abs( - *abs_value.as_ref(), - abs_value.bound(), - Choice::from(val.is_negative as u8), - ) - .ok_or_else(|| "Invalid values for the signed integer".into()) - } -} - -/// A wrapper over unsigned integers that treats two's complement numbers as negative. -// In principle, Bounded could be separate from Signed, but we only use it internally, -// and pretty much every time we need a bounded value, it's also signed. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Zeroize)] -#[serde( - try_from = "PackedSigned", - into = "PackedSigned", - bound = "T: Integer + Encoding + crypto_bigint::Bounded + ConditionallySelectable" -)] -pub(crate) struct Signed { - /// bound on the bit size of the absolute value - bound: u32, - value: T, -} - -impl Signed -where - T: crypto_bigint::Bounded + Integer, -{ - /// Note: when adding two [`Signed`], the bound on the result is equal to the biggest bound of - /// the two operands plus 1. - fn checked_add(&self, rhs: &Self) -> CtOption { - let bound = core::cmp::max(self.bound, rhs.bound) + 1; - let in_range = bound.ct_lt(&T::BITS); - - let result = Self { - bound, - value: self.value.wrapping_add(&rhs.value), - }; - let lhs_neg = self.is_negative(); - let rhs_neg = rhs.is_negative(); - let res_neg = result.is_negative(); - - // Cannot get overflow from adding values of different signs, - // and if for two values of the same sign the sign of the result remains the same - // it means there was no overflow. - CtOption::new(result, !(lhs_neg.ct_eq(&rhs_neg) & !lhs_neg.ct_eq(&res_neg)) & in_range) - } - - /// Checks if a [`Signed`] is negative by checking the MSB: if it's `1` then the [`Signed`] is - /// negative; if it's `0` it's positive. Returns a [`Choice`]. - pub fn is_negative(&self) -> Choice { - Choice::from(self.value.bit_vartime(T::BITS - 1) as u8) - } - - pub fn bound(&self) -> u32 { - self.bound - } - - /// Creates a signed value from an unsigned one, - /// assuming that it encodes a positive value. - pub fn new_positive(value: T, bound: u32) -> Option { - // Reserving one bit as the sign bit (MSB) - if bound >= T::BITS || value.bits() > bound { - return None; - } - let result = Self { value, bound }; - if result.is_negative().into() { - return None; - } - Some(result) - } -} - -impl Signed -where - T: ConditionallySelectable + crypto_bigint::Bounded + Encoding + Integer, -{ - /// Constant-time checked multiplication. The product must fit in a `T`; use [`Signed::mul_wide`] if widening is desired. - /// Note: when multiplying two [`Signed`], the bound on the result is equal to the sum of the bounds of the operands. - fn checked_mul(&self, rhs: &Self) -> CtOption { - let bound = self.bound + rhs.bound; - let in_range = bound.ct_lt(&T::BITS); - - let lhs_neg = self.is_negative(); - let rhs_neg = rhs.is_negative(); - let lhs = T::conditional_select(&self.value, &T::zero().wrapping_sub(&self.value), lhs_neg); - let rhs = T::conditional_select(&rhs.value, &T::zero().wrapping_sub(&rhs.value), rhs_neg); - let result = lhs.checked_mul(&rhs); - result.and_then(|val| { - let result_neg = lhs_neg ^ rhs_neg; - let val_neg = T::zero().wrapping_sub(&val); - let value = T::conditional_select(&val, &val_neg, result_neg); - CtOption::new(Self { bound, value }, in_range) - }) - } - - /// Performs the unary - operation. - pub fn neg(&self) -> Self { - Self { - value: T::zero().wrapping_sub(&self.value), - bound: self.bound, - } - } - - /// Computes the absolute value of [`self`] - pub fn abs(&self) -> T { - T::conditional_select(&self.value, &self.neg().value, self.is_negative()) - } - - // Asserts that the value lies in the interval `[-2^bound, 2^bound]`. - // Panics if it is not the case. - pub fn assert_bound(self, bound: u32) { - assert!( - T::one() - .overflowing_shl_vartime(bound) - .map(|b| self.abs() <= b) - .expect("Out of bounds"), - "Out of bounds" - ); - } - - /// Creates a [`Bounded`] from the absolute value of `self`. - pub fn abs_bounded(&self) -> Bounded { - // Can unwrap here since the maximum bound on the positive Bounded - // is always greater than the maximum bound on Signed - Bounded::new(self.abs(), self.bound) - .expect("Max bound for a positive Bounded is always greater than max bound for a Signed; qed") - } - - /// Creates a signed value from an unsigned one, - /// treating it as if the sign is encoded in the MSB. - pub fn new_from_unsigned(value: T, bound: u32) -> Option { - let result = Self { value, bound }; - if bound >= T::BITS || result.abs().bits() > bound { - return None; - } - Some(result) - } - - /// Creates a signed value from an unsigned one, treating it as if it is the absolute value. - /// Returns `None` if `abs_value` is actually negative or if the bounds are invalid. - fn new_from_abs(abs_value: T, bound: u32, is_negative: Choice) -> Option { - Self::new_positive(abs_value, bound).map(|x| { - let mut x = x; - x.conditional_negate(is_negative); - x - }) - } - - // Asserts that the value has bound less or equal to `bound` - // (or, in other words, the value lies in the interval `(-(2^bound-1), 2^bound-1)`). - // Returns the value with the bound set to `bound`. - pub fn assert_bit_bound(self, bound: u32) -> Option { - if self.abs().bits_vartime() <= bound { - Some(Self { - value: self.value, - bound, - }) - } else { - None - } - } - /// Returns `true` if the value is within `[-2^bound_bits, 2^bound_bits]`. - pub fn in_range_bits(&self, bound_bits: u32) -> bool { - self.abs() <= T::one() << bound_bits - } - - /// 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() - .overflowing_shl_vartime(1) - .expect("Just asserted that bound is smaller than precision; qed") - .checked_add(&T::one()) - .expect("does not overflow since we're adding 1 to an even number"); - let positive_result = uint_from_xof( - rng, - &NonZero::new(positive_bound).expect("Guaranteed to be greater than zero because we added 1"), - ); - Self::new_from_unsigned(positive_result.wrapping_sub(bound.as_ref()), bound_bits) - .expect("Guaranteed to be Some because we checked the bounds just above") - } -} - -impl Signed -where - T: ConditionallySelectable + crypto_bigint::Bounded + Encoding + Integer + RandomMod, -{ - // Returns a random value in range `[-bound, bound]`. - // - // Note: variable time in bit size of `bound`. - fn random_bounded(rng: &mut impl CryptoRngCore, bound: &NonZero) -> Self { - let bound_bits = bound.as_ref().bits_vartime(); - assert!( - bound_bits < T::BITS, - "Out of bounds: bound_bits was {} but must be smaller than {}", - bound_bits, - T::BITS - 1 - ); - // Will not overflow because of the assertion above - let positive_bound = bound - .as_ref() - .overflowing_shl_vartime(1) - .expect("Just asserted that bound is smaller than precision; qed") - .checked_add(&T::one()) - .expect("Checked bounds above"); - let positive_result = T::random_mod( - rng, - &NonZero::new(positive_bound).expect("the bound is non-zero by construction"), - ); - // Will not panic because of the assertion above - Self::new_from_unsigned(positive_result.wrapping_sub(bound.as_ref()), bound_bits) - .expect("bounded by `bound_bits` by construction") - } - - /// Returns a random value in range `[-2^bound_bits, 2^bound_bits]`. - /// - /// Note: variable time in `bound_bits`. - pub fn random_bounded_bits(rng: &mut impl CryptoRngCore, bound_bits: u32) -> Self { - assert!( - bound_bits < T::BITS - 1, - "Out of bounds: bound_bits was {} but must be smaller than {}", - bound_bits, - T::BITS - 1 - ); - - let bound = NonZero::new(T::one() << bound_bits).expect("Checked bound_bits just above; qed"); - Self::random_bounded(rng, &bound) - } -} - -impl Default for Signed { - fn default() -> Self { - Self { - bound: 0, - value: T::default(), - } - } -} - -impl ConditionallySelectable for Signed -where - T: Integer + ConditionallySelectable, -{ - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Self { - bound: u32::conditional_select(&a.bound, &b.bound, choice), - value: T::conditional_select(&a.value, &b.value, choice), - } - } -} - -impl Neg for Signed -where - T: Integer + crypto_bigint::Bounded + ConditionallySelectable + Encoding, -{ - type Output = Self; - fn neg(self) -> Self::Output { - Signed::neg(&self) - } -} - -impl Neg for &Signed -where - T: Integer + crypto_bigint::Bounded + ConditionallySelectable + Encoding, -{ - type Output = Signed; - fn neg(self) -> Self::Output { - Signed::neg(self) - } -} - -impl Signed -where - T: crypto_bigint::Bounded + HasWide + Integer, - ::Wide: RandomMod, -{ - /// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`. - /// - /// Note: variable time in `bound_bits` and bit size of `scale`. - pub fn random_bounded_bits_scaled( - rng: &mut impl CryptoRngCore, - bound_bits: u32, - scale: &Bounded, - ) -> Signed { - assert!( - bound_bits < T::BITS - 1, - "Out of bounds: bound_bits was {} but must be smaller than {}", - bound_bits, - T::BITS - 1 - ); - let scaled_bound: ::Wide = scale - .clone() - .to_wide() - .as_ref() - .overflowing_shl_vartime(bound_bits) - .expect("Just asserted that bound bits is smaller than T's bit precision"); - - // Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range. - let positive_bound = scaled_bound - .overflowing_shl_vartime(1) - .expect(concat![ - "`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ", - "will not cause overflow in T ⇒ it's safe to left-shift 1 step ", - "(aka multiply by 2)." - ]) - .checked_add(&T::Wide::one()) - .expect(concat![ - "`scaled_bound` is double the size of a T; we asserted that the `bound_bits` ", - "will not cause overflow in T ⇒ it's safe to add 1." - ]); - let positive_result = T::Wide::random_mod( - rng, - &NonZero::new(positive_bound) - .expect("Input guaranteed to be positive and it's non-zero because we added 1"), - ); - let result = positive_result.wrapping_sub(&scaled_bound); - - Signed { - bound: bound_bits + scale.bound(), - value: result, - } - } -} - -impl Signed -where - T: ConditionallySelectable + crypto_bigint::Bounded + HasWide + Encoding + Integer, - T::Wide: ConditionallySelectable + crypto_bigint::Bounded, -{ - /// Returns a [`Signed`] with the same value, but twice the bit-width. - /// Consumes `self`, but under the hood this method clones. - pub fn to_wide(self) -> Signed { - let abs_result = self.abs().to_wide(); - Signed::new_from_abs(abs_result, self.bound(), self.is_negative()) - .expect("the value fit the bound before, and the bound won't overflow for `WideUint`") - } - - /// Multiplies two [`Signed`] and returns a new [`Signed`] of twice the bit-width - pub fn mul_wide(&self, rhs: &Self) -> Signed { - let abs_value = self.abs().mul_wide(&rhs.abs()); - Signed::new_from_abs( - abs_value, - self.bound() + rhs.bound(), - self.is_negative() ^ rhs.is_negative(), - ) - .expect("The call to new_positive cannot fail when the input is the absolute value ") - } -} - -impl Signed -where - T: crypto_bigint::Bounded + HasWide + Integer, - T::Wide: ConditionallySelectable + crypto_bigint::Bounded + HasWide, -{ - /// Returns a random value in range `[-2^bound_bits * scale, 2^bound_bits * scale]`. - /// - /// Note: variable time in `bound_bits` and `scale`. - pub fn random_bounded_bits_scaled_wide( - rng: &mut impl CryptoRngCore, - bound_bits: u32, - scale: &Bounded, - ) -> Signed<::Wide> { - assert!( - bound_bits < T::BITS - 1, - "Out of bounds: bound_bits was {} but must be smaller than {}", - bound_bits, - T::BITS - 1 - ); - let scaled_bound = scale - .as_ref() - .to_wide() - .overflowing_shl_vartime(bound_bits) - .expect("Just asserted that bound_bits is smaller than bit precision of T"); - - // Sampling in range [0, 2^bound_bits * scale * 2 + 1) and translating to the desired range. - let positive_bound = scaled_bound - .overflowing_shl_vartime(1) - .expect(concat![ - "`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ", - "will not cause overflow in T::Wide ⇒ it's safe to left-shift 1 step ", - "(aka multiply by 2)." - ]) - .checked_add(&::Wide::one()) - .expect(concat![ - "`scaled_bound` is double the size of a T::Wide; we asserted that the `bound_bits` ", - "will not cause overflow in T::Wide ⇒ it's safe to add 1." - ]); - let positive_result = ::Wide::random_mod( - rng, - &NonZero::new(positive_bound) - .expect("Input guaranteed to be positive and it's non-zero because we added 1"), - ); - let result = positive_result.wrapping_sub(&scaled_bound); - - Signed { - bound: bound_bits + scale.bound(), - value: result, - } - } -} - -impl Add> for Signed -where - T: Integer + crypto_bigint::Bounded, -{ - type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - self.checked_add(&rhs) - .expect("does not overflow by the construction of the arguments") - } -} - -impl CheckedSub> for Signed -where - T: crypto_bigint::Bounded + ConditionallySelectable + Integer, -{ - /// Performs subtraction that returns `None` instead of wrapping around on underflow. - /// The bound of the result is the bound of `self` (lhs). - fn checked_sub(&self, rhs: &Signed) -> CtOption { - self.value.checked_sub(&rhs.value).and_then(|v| { - let signed = Signed::new_positive(v, self.bound); - if let Some(signed) = signed { - CtOption::new(signed, 1u8.into()) - } else { - CtOption::new(Signed::default(), 0u8.into()) - } - }) - } -} - -impl Sub> for Signed -where - T: crypto_bigint::Bounded + ConditionallySelectable + Encoding + Integer, -{ - type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - self.checked_add(&-rhs) - .expect("does not overflow by the construction of the arguments") - } -} - -impl<'a, T> AddAssign<&'a Signed> for Signed -where - T: ConditionallySelectable + crypto_bigint::Bounded + Encoding + Integer, -{ - fn add_assign(&mut self, rhs: &'a Signed) { - // TODO: implement properly - *self = self.checked_add(rhs).unwrap() - } -} - -impl<'a, T> MulAssign<&'a Signed> for Signed -where - T: ConditionallySelectable + crypto_bigint::Bounded + Encoding + Integer, -{ - fn mul_assign(&mut self, rhs: &'a Signed) { - // TODO: implement properly - *self = self.checked_mul(rhs).unwrap() - } -} - -impl Mul> for Signed -where - T: Integer + Encoding + crypto_bigint::Bounded + ConditionallySelectable, -{ - type Output = Self; - fn mul(self, rhs: Self) -> Self::Output { - self.checked_mul(&rhs) - .expect("does not overflow by the construction of the arguments") - } -} - -#[cfg(test)] -mod tests { - use std::ops::Neg; - - use crypto_bigint::{CheckedSub, U128}; - use rand::SeedableRng; - use rand_chacha::{self, ChaCha8Rng}; - - use super::Signed; - use crate::uint::U1024; - const SEED: u64 = 123; - - #[test] - fn adding_signed_numbers_increases_the_bound() { - let s1 = Signed::new_from_unsigned(U128::from_u8(5), 13).unwrap(); - let s2 = Signed::new_from_unsigned(U128::from_u8(3), 10).unwrap(); - // The sum has a bound that is equal to the biggest bound of the operands + 1 - assert_eq!((s1 + s2).bound(), 14); - } - - #[test] - #[should_panic] - fn adding_signed_numbers_with_max_bounds_panics() { - let s1 = Signed::new_from_unsigned(U128::from_u8(5), 127).unwrap(); - let s2 = Signed::new_from_unsigned(U128::from_u8(3), 127).unwrap(); - - let _ = s1 + s2; - } - - #[test] - fn checked_mul_sums_bounds() { - let s1 = Signed::new_from_unsigned(U128::from_u8(5), 27).unwrap(); - let s2 = Signed::new_from_unsigned(U128::from_u8(3), 17).unwrap(); - let mul = s1.checked_mul(&s2).unwrap(); - - assert_eq!(mul.bound(), 44); - } - - #[test] - fn checked_mul_fails_when_sum_of_bounds_is_too_large() { - let s1 = Signed::new_from_unsigned(U128::from_u8(5), 127).unwrap(); - let s2 = Signed::new_from_unsigned(U128::from_u8(3), 17).unwrap(); - let mul = s1.checked_mul(&s2); - - assert!(bool::from(mul.is_none())); - } - - #[test] - fn mul_wide_sums_bounds() { - let s1 = Signed::new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); - let mul = s1.mul_wide(&s1); - assert_eq!(mul.bound(), 2046); - - let s2 = Signed::new_from_unsigned(U1024::from_u8(8), 4).unwrap(); - let mul = s1.mul_wide(&s2); - assert_eq!(mul.bound(), 1027); - } - - #[test] - fn checked_mul_handles_sign() { - let n = Signed::new_from_unsigned(U128::from_u8(5), 27).unwrap().neg(); - let p = Signed::new_from_unsigned(U128::from_u8(3), 17).unwrap(); - let neg_pos = n.checked_mul(&p).unwrap(); - let pos_neg = p.checked_mul(&n).unwrap(); - let pos_pos = p.checked_mul(&p).unwrap(); - let neg_neg = n.checked_mul(&n).unwrap(); - // negative * positive ⇒ negative - assert!(bool::from(neg_pos.is_negative())); - // positive * negative ⇒ negative - assert!(bool::from(pos_neg.is_negative())); - // positive * positive ⇒ positive - assert!(!bool::from(pos_pos.is_negative())); - // negative * negative ⇒ positive - assert!(!bool::from(neg_neg.is_negative())); - } - - #[test] - fn random_bounded_bits_is_sane() { - let mut rng = ChaCha8Rng::seed_from_u64(SEED); - for bound_bits in 1..U1024::BITS - 1 { - let signed: Signed = Signed::random_bounded_bits(&mut rng, bound_bits); - assert!(signed.abs() < U1024::MAX >> (U1024::BITS - 1 - bound_bits)); - signed.assert_bound(bound_bits); - } - } - - #[test] - fn signed_with_low_bounds() { - // a 2 bit bound means numbers must be smaller or equal to 3 - let bound = 2; - let value = U1024::from_u8(3); - let signed = Signed::new_from_unsigned(value, bound).unwrap(); - assert!(signed.abs() < U1024::MAX >> (U1024::BITS - 1 - bound)); - signed.assert_bound(bound); - // 4 is too big - let value = U1024::from_u8(4); - let signed = Signed::new_from_unsigned(value, bound); - assert!(signed.is_none()); - - // a 1 bit bound means numbers must be smaller or equal to 1 - let bound = 1; - let value = U1024::from_u8(1); - let signed = Signed::new_from_unsigned(value, bound).unwrap(); - assert!(signed.abs() < U1024::MAX >> (U1024::BITS - 1 - bound)); - signed.assert_bound(bound); - // 2 is too big - let value = U1024::from_u8(2); - let signed = Signed::new_from_unsigned(value, bound); - assert!(signed.is_none()); - - // a 0 bit bound means only 0 is a valid value - let bound = 0; - let value = U1024::from_u8(0); - let signed = Signed::new_from_unsigned(value, bound).unwrap(); - assert!(signed.abs() < U1024::MAX >> (U1024::BITS - 1 - bound)); - signed.assert_bound(bound); - // 1 is too big - let value = U1024::from_u8(1); - let signed = Signed::new_from_unsigned(value, bound); - assert!(signed.is_none()); - } - - #[test] - fn neg_u128() { - let n = Signed::new_from_unsigned(U128::from_be_hex("fffffffffffffffffffffffffffffff0"), 127).unwrap(); - let neg_n = Signed::new_from_unsigned(U128::from_be_hex("00000000000000000000000000000010"), 127).unwrap(); - assert!(bool::from(n.is_negative())); - assert!(!bool::from(neg_n.is_negative())); - assert_eq!(n.neg(), neg_n); - assert_eq!(n.neg().neg(), n); - } - - #[test] - #[should_panic(expected = "does not overflow by the construction of the arguments")] - fn sub_panics_on_underflow() { - // Biggest/smallest Signed is |2^127|: - use crypto_bigint::U128; - let max_uint = U128::from_u128(u128::MAX >> 1); - let one_signed = Signed::new_from_abs(U128::ONE, U128::BITS - 1, 0u8.into()).unwrap(); - let min_signed = Signed::new_from_abs(max_uint, U128::BITS - 1, 1u8.into()).expect("|2^127| is a valid Signed"); - let _ = min_signed - one_signed; - } - #[test] - #[should_panic(expected = "does not overflow by the construction of the arguments")] - fn sub_panics_on_underflow_1024() { - // Biggest/smallest Signed is |2^1023|: - let max_uint = U1024::MAX >> 1; - let one_signed = Signed::new_from_abs(U1024::ONE, U1024::BITS - 1, 0u8.into()).unwrap(); - let min_signed = - Signed::new_from_abs(max_uint, U1024::BITS - 1, 1u8.into()).expect("|2^1023| is a valid Signed"); - let _ = min_signed - one_signed; - } - - #[test] - fn checked_sub_handles_underflow() { - // Biggest/smallest Signed is |2^1023| - let max_uint = U1024::MAX >> 1; - let one_signed = Signed::new_from_abs(U1024::ONE, U1024::BITS - 1, 0u8.into()).unwrap(); - let min_signed = - Signed::new_from_abs(max_uint, U1024::BITS - 1, 1u8.into()).expect("|2^1023| is a valid Signed"); - - let result = min_signed.checked_sub(&one_signed); - assert!(bool::from(result.is_none())) - } -} diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index c2fc62f..d06fd71 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -1,10 +1,12 @@ use crypto_bigint::{ modular::MontyForm, subtle::{ConditionallySelectable, CtOption}, - Encoding, Integer, Invert, Limb, PowBoundedExp, RandomMod, Square, Uint, Zero, U1024, U2048, U4096, U512, U8192, + Bounded, ConcatMixed, Encoding, Integer, Invert, Limb, PowBoundedExp, RandomMod, SplitMixed, WideningMul, Zero, + U1024, U2048, U4096, U512, U8192, }; +use zeroize::Zeroize; -use crate::uint::{Bounded, Signed}; +use crate::uint::{PublicSigned, SecretSigned, SecretUnsigned}; pub trait ToMontgomery: Integer { fn to_montgomery( @@ -15,129 +17,42 @@ pub trait ToMontgomery: Integer { } } -/// Exponentiation functions for generic integers (in our case used for integers in Montgomery form -/// with `Signed` exponents). -pub trait Exponentiable: - PowBoundedExp + Invert> + ConditionallySelectable + Square + core::ops::Mul +pub trait Exponentiable { + fn pow(&self, exp: &Exponent) -> Self; +} + +impl Exponentiable> for T where - T: Integer + crypto_bigint::Bounded + Encoding + ConditionallySelectable, + T: ConditionallySelectable + PowBoundedExp + Invert>, + V: ConditionallySelectable + Zeroize + Integer + Bounded, { - fn pow_bounded(&self, exponent: &Bounded) -> Self { - self.pow_bounded_exp(exponent.as_ref(), exponent.bound()) - } - - /// Constant-time exponentiation of an integer in Montgomery form by a signed exponent. - /// - /// #Panics - /// - /// Panics if `self` is not invertible. - fn pow_signed(&self, exponent: &Signed) -> Self { - let abs_exponent = exponent.abs(); - let abs_result = self.pow_bounded_exp(&abs_exponent, exponent.bound()); + fn pow(&self, exp: &SecretSigned) -> Self { + let abs_exp = exp.abs(); + let abs_result = self.pow_bounded_exp(abs_exp.expose_secret(), exp.bound()); let inv_result = abs_result.invert().expect("`self` is assumed to be invertible"); - Self::conditional_select(&abs_result, &inv_result, exponent.is_negative()) - } - - /// Constant-time exponentiation of an integer in Montgomery form by a "wide" and signed exponent. - /// - /// #Panics - /// - /// Panics if `self` is not invertible. - fn pow_signed_wide(&self, exp: &Signed<::Wide>) -> Self - where - T: HasWide, - ::Wide: crypto_bigint::Bounded + ConditionallySelectable, - { - let exp_abs = exp.abs(); - let abs = self.pow_wide(&exp_abs, exp.bound()); - let inv = abs.invert().expect("self is assumed to be invertible"); - Self::conditional_select(&abs, &inv, exp.is_negative()) - } - - fn pow_wide(self, exp: &::Wide, bound: u32) -> Self - where - T: HasWide, - { - let bits = ::BITS; - let bound = bound % (2 * bits + 1); - - 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 - // exponents of any size - it keeps the self^(2^k) already. - if bound > bits { - let mut hi_res = self.pow_bounded_exp(&hi, bound - bits); - for _ in 0..bits { - hi_res = hi_res.square() - } - hi_res * lo_res - } else { - lo_res - } + Self::conditional_select(&abs_result, &inv_result, exp.is_negative()) } +} - /// Constant-time exponentiation of an integer in Montgomery form by an "extra wide" and signed exponent. - /// - /// #Panics - /// - /// Panics if `self` is not invertible. - fn pow_signed_extra_wide(&self, exp: &Signed<<::Wide as HasWide>::Wide>) -> Self - where - T: HasWide, - ::Wide: crypto_bigint::Bounded + ConditionallySelectable + HasWide, - <::Wide as HasWide>::Wide: crypto_bigint::Bounded + ConditionallySelectable, - { - let bits = <::Wide as crypto_bigint::Bounded>::BITS; - let bound = exp.bound(); - - let abs_exponent = exp.abs(); - let (wlo, whi) = ::Wide::from_wide(&abs_exponent); - - let lo_res = self.pow_wide(&wlo, core::cmp::min(bits, bound)); - - let abs_result = if bound > bits { - let mut hi_res = self.pow_wide(&whi, bound - bits); - for _ in 0..bits { - hi_res = hi_res.square(); - } - hi_res * lo_res - } else { - lo_res - }; - - let inv_result = abs_result.invert().expect("`self` is assumed invertible"); - Self::conditional_select(&abs_result, &inv_result, exp.is_negative()) +impl Exponentiable> for T +where + T: PowBoundedExp + Invert>, + V: ConditionallySelectable + Zeroize + Integer + Bounded, +{ + fn pow(&self, exp: &SecretUnsigned) -> Self { + self.pow_bounded_exp(exp.expose_secret(), exp.bound()) } +} - /// Variable-time exponentiation of an integer in Montgomery form by a signed exponent. - /// - /// #Panics - /// - /// Panics if `self` is not invertible. - fn pow_signed_vartime(self, exp: &Signed) -> Self { +impl Exponentiable> for T +where + T: PowBoundedExp + Invert>, + V: Integer + Bounded, +{ + fn pow(&self, exp: &PublicSigned) -> Self { let abs_exp = exp.abs(); let abs_result = self.pow_bounded_exp(&abs_exp, exp.bound()); - if exp.is_negative().into() { - abs_result.invert().expect("`self` is assumed invertible") - } else { - abs_result - } - } - - /// Variable-time exponentiation of an integer in Montgomery form by a "wide" and signed exponent. - /// - /// #Panics - /// - /// Panics if `self` is not invertible. - fn pow_signed_wide_vartime(&self, exp: &Signed<::Wide>) -> Self - where - T: HasWide, - ::Wide: crypto_bigint::Bounded + ConditionallySelectable, - { - let exp_abs = exp.abs(); - let abs_result = self.pow_wide(&exp_abs, exp.bound()); - if exp.is_negative().into() { + if exp.is_negative() { abs_result.invert().expect("`self` is assumed invertible") } else { abs_result @@ -145,22 +60,27 @@ where } } -pub trait HasWide: Sized + Zero { - type Wide: Integer + Encoding + RandomMod; - fn mul_wide(&self, other: &Self) -> Self::Wide; - fn square_wide(&self) -> Self::Wide; +pub trait HasWide: + Sized + Zero + Integer + for<'a> WideningMul<&'a Self, Output = Self::Wide> + ConcatMixed +{ + type Wide: Integer + Encoding + RandomMod + SplitMixed; + + fn mul_wide(&self, other: &Self) -> Self::Wide { + self.widening_mul(other) + } /// Converts `self` to a new `Wide` uint, setting the higher half to `0`s. - /// Consumes `self`. - fn to_wide(&self) -> Self::Wide; + fn to_wide(&self) -> Self::Wide { + // Note that this minimizes the presense of `self` on the stack (to the extent we can ensure it), + // in case it is secret. + Self::concat_mixed(self, &Self::zero()) + } /// Splits a `Wide` in two halves and returns the halves (`Self` sized) in a /// tuple (lower half first). - /// - /// *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) { + value.split_mixed() + } /// 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. @@ -176,66 +96,18 @@ pub trait HasWide: Sized + Zero { impl HasWide for U512 { type Wide = U1024; - fn mul_wide(&self, other: &Self) -> Self::Wide { - self.widening_mul(other) - } - fn square_wide(&self) -> Self::Wide { - self.square_wide().into() - } - fn to_wide(&self) -> Self::Wide { - Uint::concat_mixed(self, &Self::ZERO) - } - fn from_wide(value: &Self::Wide) -> (Self, Self) { - value.split_mixed() - } } impl HasWide for U1024 { type Wide = U2048; - fn mul_wide(&self, other: &Self) -> Self::Wide { - self.widening_mul(other) - } - fn square_wide(&self) -> Self::Wide { - self.square_wide().into() - } - fn to_wide(&self) -> Self::Wide { - Uint::concat_mixed(self, &Self::ZERO) - } - fn from_wide(value: &Self::Wide) -> (Self, Self) { - value.split_mixed() - } } impl HasWide for U2048 { type Wide = U4096; - fn mul_wide(&self, other: &Self) -> Self::Wide { - self.widening_mul(other) - } - fn square_wide(&self) -> Self::Wide { - self.square_wide().into() - } - fn to_wide(&self) -> Self::Wide { - Uint::concat_mixed(self, &Self::ZERO) - } - fn from_wide(value: &Self::Wide) -> (Self, Self) { - value.split_mixed() - } } impl HasWide for U4096 { type Wide = U8192; - fn mul_wide(&self, other: &Self) -> Self::Wide { - self.widening_mul(other) - } - fn square_wide(&self) -> Self::Wide { - self.square_wide().into() - } - fn to_wide(&self) -> Self::Wide { - Uint::concat_mixed(self, &Self::ZERO) - } - fn from_wide(value: &Self::Wide) -> (Self, Self) { - value.split_mixed() - } } // TODO(dp): Suggest crypto-bigint update nlimbs! macro. @@ -249,8 +121,3 @@ impl ToMontgomery for U1024 {} impl ToMontgomery for U2048 {} impl ToMontgomery for U4096 {} impl ToMontgomery for U8192 {} - -impl Exponentiable for U512Mod {} -impl Exponentiable for U1024Mod {} -impl Exponentiable for U2048Mod {} -impl Exponentiable for U4096Mod {} From 64b7866001d895153f01350b5110ec6037e12e61 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Thu, 12 Dec 2024 22:31:34 -0800 Subject: [PATCH 2/4] Move conversions to their own module --- synedrion/src/cggmp21.rs | 1 + synedrion/src/cggmp21/conversion.rs | 152 ++++++++++++++++++ synedrion/src/cggmp21/interactive_signing.rs | 6 +- synedrion/src/cggmp21/key_refresh.rs | 3 +- synedrion/src/cggmp21/params.rs | 156 +------------------ synedrion/src/cggmp21/sigma/aff_g.rs | 4 +- synedrion/src/cggmp21/sigma/dec.rs | 4 +- synedrion/src/cggmp21/sigma/log_star.rs | 4 +- synedrion/src/cggmp21/sigma/mul_star.rs | 4 +- 9 files changed, 170 insertions(+), 164 deletions(-) create mode 100644 synedrion/src/cggmp21/conversion.rs diff --git a/synedrion/src/cggmp21.rs b/synedrion/src/cggmp21.rs index cd2537a..0657134 100644 --- a/synedrion/src/cggmp21.rs +++ b/synedrion/src/cggmp21.rs @@ -8,6 +8,7 @@ //! refers to the version of the paper published at mod aux_gen; +mod conversion; mod entities; mod interactive_signing; mod key_init; diff --git a/synedrion/src/cggmp21/conversion.rs b/synedrion/src/cggmp21/conversion.rs new file mode 100644 index 0000000..835c5ff --- /dev/null +++ b/synedrion/src/cggmp21/conversion.rs @@ -0,0 +1,152 @@ +use crypto_bigint::{Encoding, Zero}; + +use super::params::SchemeParams; +use crate::{ + curve::{Scalar, ORDER}, + paillier::PaillierParams, + tools::Secret, + uint::{PublicSigned, SecretSigned, SecretUnsigned}, +}; + +fn uint_from_scalar(value: &Scalar) -> ::Uint { + let scalar_bytes = value.to_be_bytes(); + let mut repr = ::Uint::zero().to_be_bytes(); + + let uint_len = repr.as_ref().len(); + let scalar_len = scalar_bytes.len(); + + repr.as_mut() + .get_mut(uint_len - scalar_len..) + .expect("PaillierParams::Uint is expected to be bigger than a Scalar") + .copy_from_slice(&scalar_bytes); + ::Uint::from_be_bytes(repr) +} + +pub(crate) fn public_signed_from_scalar( + value: &Scalar, +) -> PublicSigned<::Uint> { + PublicSigned::new_positive(uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ + "a curve scalar value is smaller than the half of `PaillierParams::Uint` range, ", + "so it is still positive when treated as a 2-complement signed value" + ]) +} + +/// Converts an integer to the associated curve scalar type. +pub(crate) fn scalar_from_uint(value: &::Uint) -> Scalar { + let r = *value % P::CURVE_ORDER; + + let repr = r.to_be_bytes(); + let uint_len = repr.as_ref().len(); + let scalar_len = Scalar::repr_len(); + + // Can unwrap here since the value is within the Scalar range + Scalar::try_from_be_bytes( + repr.as_ref() + .get(uint_len - scalar_len..) + .expect("Uint is assumed to be bigger than Scalar"), + ) + .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") +} + +/// Converts a `Signed`-wrapped integer to the associated curve scalar type. +pub(crate) fn scalar_from_signed( + value: &PublicSigned<::Uint>, +) -> Scalar { + let abs_value = scalar_from_uint::

(&value.abs()); + if value.is_negative() { + -abs_value + } else { + abs_value + } +} + +/// Converts a wide integer to the associated curve scalar type. +pub(crate) fn scalar_from_wide_uint(value: &::WideUint) -> Scalar { + let r = *value % P::CURVE_ORDER_WIDE; + + let repr = r.to_be_bytes(); + let uint_len = repr.as_ref().len(); + let scalar_len = Scalar::repr_len(); + + // Can unwrap here since the value is within the Scalar range + Scalar::try_from_be_bytes( + repr.as_ref() + .get(uint_len - scalar_len..) + .expect("WideUint is assumed to be bigger than Scalar"), + ) + .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") +} + +/// Converts a `Signed`-wrapped wide integer to the associated curve scalar type. +pub(crate) fn scalar_from_wide_signed( + value: &PublicSigned<::WideUint>, +) -> Scalar { + let abs_value = scalar_from_wide_uint::

(&value.abs()); + if value.is_negative() { + -abs_value + } else { + abs_value + } +} + +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_be_bytes( + repr.expose_secret() + .as_ref() + .get(uint_len - scalar_len..) + .expect("Uint is assumed to be bigger than Scalar"), + ) + .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") + }) +} + +fn secret_uint_from_scalar(value: &Secret) -> Secret<::Uint> { + let scalar_bytes = Secret::init_with(|| value.expose_secret().to_be_bytes()); + let mut repr = Secret::init_with(|| ::Uint::zero().to_be_bytes()); + + let uint_len = repr.expose_secret().as_ref().len(); + let scalar_len = scalar_bytes.expose_secret().len(); + + debug_assert!(uint_len >= scalar_len); + repr.expose_secret_mut() + .as_mut() + .get_mut(uint_len - scalar_len..) + .expect("::Uint is assumed to be configured to be bigger than Scalar") + .copy_from_slice(scalar_bytes.expose_secret()); + Secret::init_with(|| ::Uint::from_be_bytes(*repr.expose_secret())) +} + +pub(crate) fn secret_unsigned_from_scalar( + value: &Secret, +) -> SecretUnsigned<::Uint> { + SecretUnsigned::new(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ + "a curve scalar value is smaller than the curve order, ", + "and the curve order fits in `PaillierParams::Uint`" + ]) +} + +pub(crate) fn secret_signed_from_scalar( + value: &Secret, +) -> SecretSigned<::Uint> { + SecretSigned::new_positive(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ + "a curve scalar value is smaller than the curve order, ", + "and the curve order fits in `PaillierParams::Uint`" + ]) +} + +pub(crate) fn secret_scalar_from_signed( + value: &SecretSigned<::Uint>, +) -> Secret { + let abs_value = secret_scalar_from_uint::

(&value.abs_value()); + Secret::::conditional_select(&abs_value, &-&abs_value, value.is_negative()) +} diff --git a/synedrion/src/cggmp21/interactive_signing.rs b/synedrion/src/cggmp21/interactive_signing.rs index 5cd0f47..7f47451 100644 --- a/synedrion/src/cggmp21/interactive_signing.rs +++ b/synedrion/src/cggmp21/interactive_signing.rs @@ -18,11 +18,11 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::{ - entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues, PublicAuxInfoPrecomputed}, - params::{ + conversion::{ public_signed_from_scalar, secret_scalar_from_signed, secret_signed_from_scalar, secret_unsigned_from_scalar, - SchemeParams, }, + entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues, PublicAuxInfoPrecomputed}, + params::SchemeParams, sigma::{AffGProof, DecProof, EncProof, LogStarProof, MulProof, MulStarProof}, }; use crate::{ diff --git a/synedrion/src/cggmp21/key_refresh.rs b/synedrion/src/cggmp21/key_refresh.rs index 70a5bc4..3999a26 100644 --- a/synedrion/src/cggmp21/key_refresh.rs +++ b/synedrion/src/cggmp21/key_refresh.rs @@ -20,8 +20,9 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::{ + conversion::{secret_scalar_from_uint, secret_unsigned_from_scalar}, entities::{AuxInfo, KeyShareChange, PublicAuxInfo, SecretAuxInfo}, - params::{secret_scalar_from_uint, secret_unsigned_from_scalar, SchemeParams}, + params::SchemeParams, sigma::{FacProof, ModProof, PrmProof, SchCommitment, SchProof, SchSecret}, }; use crate::{ diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index 3ba2de2..f4ff566 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -4,18 +4,15 @@ use core::fmt::Debug; // and `k256` depends on the released one. // So as long as that is the case, `k256` `Uint` is separate // from the one used throughout the crate. -use crypto_bigint::{Encoding, NonZero, Uint, Zero, U1024, U2048, U4096, U512, U8192}; +use crypto_bigint::{NonZero, Uint, U1024, U2048, U4096, U512, U8192}; use k256::elliptic_curve::bigint::Uint as K256Uint; use serde::{Deserialize, Serialize}; use crate::{ - curve::{Curve, Scalar, ORDER}, + curve::{Curve, ORDER}, paillier::PaillierParams, - tools::{ - hashing::{Chain, HashableType}, - Secret, - }, - uint::{PublicSigned, SecretSigned, SecretUnsigned, U1024Mod, U2048Mod, U4096Mod, U512Mod}, + tools::hashing::{Chain, HashableType}, + uint::{U1024Mod, U2048Mod, U4096Mod, U512Mod}, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -128,151 +125,6 @@ pub trait SchemeParams: Debug + Clone + Send + PartialEq + Eq + Send + Sync + 's type Paillier: PaillierParams; } -/// Converts a curve scalar to the associated integer type. -pub(crate) fn uint_from_scalar(value: &Scalar) -> ::Uint { - let scalar_bytes = value.to_be_bytes(); - let mut repr = ::Uint::zero().to_be_bytes(); - - let uint_len = repr.as_ref().len(); - let scalar_len = scalar_bytes.len(); - - repr.as_mut() - .get_mut(uint_len - scalar_len..) - .expect("PaillierParams::Uint is expected to be bigger than a Scalar") - .copy_from_slice(&scalar_bytes); - ::Uint::from_be_bytes(repr) -} - -/// Converts a curve scalar to the associated integer type, wrapped in `Signed`. -pub(crate) fn public_signed_from_scalar( - value: &Scalar, -) -> PublicSigned<::Uint> { - PublicSigned::new_positive(uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ - "a curve scalar value is smaller than the half of `PaillierParams::Uint` range, ", - "so it is still positive when treated as a 2-complement signed value" - ]) -} - -/// Converts an integer to the associated curve scalar type. -pub(crate) fn scalar_from_uint(value: &::Uint) -> Scalar { - let r = *value % P::CURVE_ORDER; - - let repr = r.to_be_bytes(); - let uint_len = repr.as_ref().len(); - let scalar_len = Scalar::repr_len(); - - // Can unwrap here since the value is within the Scalar range - Scalar::try_from_be_bytes( - repr.as_ref() - .get(uint_len - scalar_len..) - .expect("Uint is assumed to be bigger than Scalar"), - ) - .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") -} - -/// Converts a `Signed`-wrapped integer to the associated curve scalar type. -pub(crate) fn scalar_from_signed( - value: &PublicSigned<::Uint>, -) -> Scalar { - let abs_value = scalar_from_uint::

(&value.abs()); - if value.is_negative() { - -abs_value - } else { - abs_value - } -} - -/// Converts a wide integer to the associated curve scalar type. -pub(crate) fn scalar_from_wide_uint(value: &::WideUint) -> Scalar { - let r = *value % P::CURVE_ORDER_WIDE; - - let repr = r.to_be_bytes(); - let uint_len = repr.as_ref().len(); - let scalar_len = Scalar::repr_len(); - - // Can unwrap here since the value is within the Scalar range - Scalar::try_from_be_bytes( - repr.as_ref() - .get(uint_len - scalar_len..) - .expect("WideUint is assumed to be bigger than Scalar"), - ) - .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") -} - -/// Converts a `Signed`-wrapped wide integer to the associated curve scalar type. -pub(crate) fn scalar_from_wide_signed( - value: &PublicSigned<::WideUint>, -) -> Scalar { - let abs_value = scalar_from_wide_uint::

(&value.abs()); - if value.is_negative() { - -abs_value - } else { - abs_value - } -} - -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_be_bytes( - repr.expose_secret() - .as_ref() - .get(uint_len - scalar_len..) - .expect("Uint is assumed to be bigger than Scalar"), - ) - .expect("the value was reduced modulo `CURVE_ORDER`, so it's a valid curve scalar") - }) -} - -fn secret_uint_from_scalar(value: &Secret) -> Secret<::Uint> { - let scalar_bytes = Secret::init_with(|| value.expose_secret().to_be_bytes()); - let mut repr = Secret::init_with(|| ::Uint::zero().to_be_bytes()); - - let uint_len = repr.expose_secret().as_ref().len(); - let scalar_len = scalar_bytes.expose_secret().len(); - - debug_assert!(uint_len >= scalar_len); - repr.expose_secret_mut() - .as_mut() - .get_mut(uint_len - scalar_len..) - .expect("::Uint is assumed to be configured to be bigger than Scalar") - .copy_from_slice(scalar_bytes.expose_secret()); - Secret::init_with(|| ::Uint::from_be_bytes(*repr.expose_secret())) -} - -pub(crate) fn secret_unsigned_from_scalar( - value: &Secret, -) -> SecretUnsigned<::Uint> { - SecretUnsigned::new(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ - "a curve scalar value is smaller than the curve order, ", - "and the curve order fits in `PaillierParams::Uint`" - ]) -} - -pub(crate) fn secret_signed_from_scalar( - value: &Secret, -) -> SecretSigned<::Uint> { - SecretSigned::new_positive(secret_uint_from_scalar::

(value), ORDER.bits_vartime() as u32).expect(concat![ - "a curve scalar value is smaller than the curve order, ", - "and the curve order fits in `PaillierParams::Uint`" - ]) -} - -pub(crate) fn secret_scalar_from_signed( - value: &SecretSigned<::Uint>, -) -> Secret { - let abs_value = secret_scalar_from_uint::

(&value.abs_value()); - Secret::::conditional_select(&abs_value, &-&abs_value, value.is_negative()) -} - impl HashableType for P { fn chain_type(digest: C) -> C { digest.chain_type::() diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index 9c7bf69..4c9e545 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -4,7 +4,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::{ - params::{scalar_from_signed, secret_scalar_from_signed}, + conversion::{scalar_from_signed, secret_scalar_from_signed}, SchemeParams, }; use crate::{ @@ -268,7 +268,7 @@ mod tests { use super::AffGProof; use crate::{ - cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, uint::SecretSigned, }; diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index 88e44b9..6a2b20f 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -4,7 +4,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::{ - params::{scalar_from_signed, scalar_from_wide_signed, secret_scalar_from_signed}, + conversion::{scalar_from_signed, scalar_from_wide_signed, secret_scalar_from_signed}, SchemeParams, }; use crate::{ @@ -171,7 +171,7 @@ mod tests { use super::DecProof; use crate::{ - cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, PaillierParams, RPParams, Randomizer, SecretKeyPaillierWire}, uint::SecretSigned, }; diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index 1e66631..03b7567 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -4,7 +4,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::{ - params::{scalar_from_signed, secret_scalar_from_signed}, + conversion::{scalar_from_signed, secret_scalar_from_signed}, SchemeParams, }; use crate::{ @@ -174,7 +174,7 @@ mod tests { use super::LogStarProof; use crate::{ - cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, curve::{Point, Scalar}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, uint::SecretSigned, diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 4964a75..18f638a 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -4,7 +4,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::{ - params::{scalar_from_signed, secret_scalar_from_signed}, + conversion::{scalar_from_signed, secret_scalar_from_signed}, SchemeParams, }; use crate::{ @@ -183,7 +183,7 @@ mod tests { use super::MulStarProof; use crate::{ - cggmp21::{params::secret_scalar_from_signed, SchemeParams, TestParams}, + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, uint::SecretSigned, }; From 31173064b8f2fdfe074dd22312f55d1bdd37cdd6 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 13 Dec 2024 18:20:40 -0800 Subject: [PATCH 3/4] Add comments to conversions --- synedrion/src/cggmp21/conversion.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/synedrion/src/cggmp21/conversion.rs b/synedrion/src/cggmp21/conversion.rs index 835c5ff..c97bdc9 100644 --- a/synedrion/src/cggmp21/conversion.rs +++ b/synedrion/src/cggmp21/conversion.rs @@ -22,6 +22,9 @@ fn uint_from_scalar(value: &Scalar) -> ::Uint::from_be_bytes(repr) } +/// Converts a [`Scalar`] to a [`PublicSigned`]. +/// +/// Assumes using a curve whose order is at most the width of `Uint` minus 1 bit. pub(crate) fn public_signed_from_scalar( value: &Scalar, ) -> PublicSigned<::Uint> { @@ -45,10 +48,10 @@ pub(crate) fn scalar_from_uint(value: &( value: &PublicSigned<::Uint>, ) -> Scalar { @@ -74,10 +77,10 @@ pub(crate) fn scalar_from_wide_uint(value: &( value: &PublicSigned<::WideUint>, ) -> Scalar { @@ -89,6 +92,7 @@ pub(crate) fn scalar_from_wide_signed( } } +/// Converts a secret-wrapped uint to a secret-wrapped [`Scalar`], reducing the value modulo curve order. pub(crate) fn secret_scalar_from_uint( value: &Secret<::Uint>, ) -> Secret { @@ -126,6 +130,9 @@ fn secret_uint_from_scalar(value: &Secret) -> Secret<

::Uint::from_be_bytes(*repr.expose_secret())) } +/// Converts a secret-wrapped [`Scalar`] to a [`SecretUnsigned`]. +/// +/// Assumes using a curve whose order fits in a [`PaillierParams::Uint`]. pub(crate) fn secret_unsigned_from_scalar( value: &Secret, ) -> SecretUnsigned<::Uint> { @@ -135,6 +142,9 @@ pub(crate) fn secret_unsigned_from_scalar( ]) } +/// Converts a secret-wrapped [`Scalar`] to a [`SecretSigned`]. +/// +/// Assumes using a curve whose order is at most the width of `Uint` minus 1 bit. pub(crate) fn secret_signed_from_scalar( value: &Secret, ) -> SecretSigned<::Uint> { @@ -144,6 +154,7 @@ pub(crate) fn secret_signed_from_scalar( ]) } +/// Converts a [`SecretSigned`] to a secret-wrapped [`Scalar`]. pub(crate) fn secret_scalar_from_signed( value: &SecretSigned<::Uint>, ) -> Secret { From 283cc7e48ead44f5b4a007cf3830218c60d6c524 Mon Sep 17 00:00:00 2001 From: Bogdan Opanchuk Date: Fri, 13 Dec 2024 18:20:54 -0800 Subject: [PATCH 4/4] Some of RFCs --- synedrion/src/paillier/keys.rs | 7 +-- synedrion/src/tools/secret.rs | 5 +- synedrion/src/uint/public_signed.rs | 19 ++++-- synedrion/src/uint/secret_signed.rs | 91 ++++++++++++++------------- synedrion/src/uint/secret_unsigned.rs | 12 +--- synedrion/src/uint/traits.rs | 7 +++ 6 files changed, 72 insertions(+), 69 deletions(-) diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index 8c05796..9102728 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -54,7 +54,6 @@ pub(crate) struct SecretKeyPaillier { // $u$ such that $u = -1 \mod p$ and $u = 1 \mod q$. Used for sampling of non-square residues. nonsquare_sampling_constant: Secret, // TODO (#162): these should be secret, but they are not zeroizable. - // See https://github.com/RustCrypto/crypto-bigint/issues/704 /// Montgomery parameters for operations modulo $p$. monty_params_mod_p: ::Params, /// Montgomery parameters for operations modulo $q$. @@ -112,8 +111,7 @@ where // Calculate $t = 2 p^{-1} - 1 \mod q$ let one = Secret::init_with(|| { - // NOTE: `monty_params_mod_q` is cloned here and can remain on the stack. - // See https://github.com/RustCrypto/crypto-bigint/issues/704 + // TODO (#162): `monty_params_mod_q` is cloned here and can remain on the stack. P::HalfUintMod::one(monty_params_mod_q.clone()) }); let t = (&inv_p_mod_q + &inv_p_mod_q - one).retrieve(); @@ -195,8 +193,7 @@ where 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 + // TODO (#162): `monty_params_mod_q` is cloned here and can remain on the stack. let p_rem_mod = p_rem_half.to_montgomery(&self.monty_params_mod_p); let q_rem_mod = q_rem_half.to_montgomery(&self.monty_params_mod_q); diff --git a/synedrion/src/tools/secret.rs b/synedrion/src/tools/secret.rs index 218c83e..9fd08de 100644 --- a/synedrion/src/tools/secret.rs +++ b/synedrion/src/tools/secret.rs @@ -285,10 +285,7 @@ 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. - - // NOTE: `params` is cloned here and can remain on the stack. - // See https://github.com/RustCrypto/crypto-bigint/issues/704; - // after that's fixed, we can properly treat secret `params`. + // TODO (#162): `params` is cloned here and can remain on the stack. Secret::init_with(|| ::new(self.expose_secret().clone(), params.clone())) } } diff --git a/synedrion/src/uint/public_signed.rs b/synedrion/src/uint/public_signed.rs index 76f8548..be80de2 100644 --- a/synedrion/src/uint/public_signed.rs +++ b/synedrion/src/uint/public_signed.rs @@ -10,10 +10,11 @@ use super::HasWide; use crate::tools::hashing::uint_from_xof; /// A packed representation for serializing Signed objects. -/// Usually they have the bound much lower than the full size of the integer, +/// Usually they have the bound set much lower than the full size of the integer, /// so this way we avoid serializing a bunch of zeros. #[derive(Serialize, Deserialize)] struct PackedSigned { + /// Bound on the bit size of the absolute value (that is, `abs(value) < 2^bound`). bound: u32, is_negative: bool, #[serde(with = "SliceLike::")] @@ -67,9 +68,7 @@ where } } -/// A wrapper over unsigned integers that treats two's complement numbers as negative. -// In principle, Bounded could be separate from Signed, but we only use it internally, -// and pretty much every time we need a bounded value, it's also signed. +/// A wrapper over bounded unsigned integers. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde( try_from = "PackedSigned", @@ -100,10 +99,16 @@ where Some(Self { value, bound }) } + /// Creates a new [`PublicSigned`] from an integer that is assumed to be positive + /// (that is, has its MSB set to 0). + /// Returns `None` if the bound is invalid. pub fn new_positive(value: T, bound: u32) -> Option { Self::new_from_abs(value, bound, false) } + /// Creates a new [`PublicSigned`] from an integer that will be treated as a negative number in two's complement + /// if its MSB is set to 1. + /// Returns `None` if the bound is invalid. pub fn new_from_unsigned(value: T, bound: u32) -> Option { let result = Self { value, bound }; if bound >= T::BITS || result.abs().bits_vartime() > bound { @@ -149,8 +154,10 @@ where } } - /// Constant-time checked multiplication. The product must fit in a `T`; use [`Signed::mul_wide`] if widening is desired. - /// Note: when multiplying two [`Signed`], the bound on the result is equal to the sum of the bounds of the operands. + /// Constant-time checked multiplication. The product must fit in a `T`; + /// use [`Signed::mul_wide`] if widening is desired. + /// Note: when multiplying two [`PublicSigned`], the bound on the result + /// is equal to the sum of the bounds of the operands. fn checked_mul(&self, rhs: &Self) -> Option { let bound = self.bound + rhs.bound; if bound < T::BITS { diff --git a/synedrion/src/uint/secret_signed.rs b/synedrion/src/uint/secret_signed.rs index c1b789b..8b3c947 100644 --- a/synedrion/src/uint/secret_signed.rs +++ b/synedrion/src/uint/secret_signed.rs @@ -14,7 +14,7 @@ use crate::tools::Secret; /// A wrapper over secret unsigned integers that treats two's complement numbers as negative. #[derive(Debug, Clone)] pub(crate) struct SecretSigned { - /// bound on the bit size of the absolute value (that is, `abs(value) < 2^bound`) + /// Bound on the bit size of the absolute value (that is, `abs(value) < 2^bound`). bound: u32, value: Secret, } @@ -124,16 +124,6 @@ where ) } - #[cfg(test)] - fn test_new_from_abs(abs_value: T, bound: u32, is_negative: bool) -> Option { - Self::new_from_abs(Secret::init_with(|| abs_value), bound, Choice::from(is_negative as u8)) - } - - #[cfg(test)] - fn test_new_from_unsigned(abs_value: T, bound: u32) -> Option { - Self::new_from_unsigned(Secret::init_with(|| abs_value), bound) - } - /// Returns a truthy `Choice` if the absolute value is within the bit bound `bound`. fn in_bound(&self, bound: u32) -> Choice { let abs = self.abs(); @@ -540,19 +530,37 @@ where mod tests { use std::ops::Neg; - use crypto_bigint::{CheckedMul, CheckedSub, U1024, U128}; + use crypto_bigint::{ + subtle::{Choice, ConditionallySelectable}, + Bounded, CheckedMul, CheckedSub, Integer, U1024, U128, + }; use rand::SeedableRng; use rand_chacha::{self, ChaCha8Rng}; + use zeroize::Zeroize; use super::SecretSigned; - use crate::uint::PublicSigned; + use crate::{tools::Secret, uint::PublicSigned}; const SEED: u64 = 123; + fn test_new_from_abs(abs_value: T, bound: u32, is_negative: bool) -> Option> + where + T: Zeroize + ConditionallySelectable + Integer + Bounded, + { + SecretSigned::new_from_abs(Secret::init_with(|| abs_value), bound, Choice::from(is_negative as u8)) + } + + fn test_new_from_unsigned(abs_value: T, bound: u32) -> Option> + where + T: Zeroize + ConditionallySelectable + Integer + Bounded, + { + SecretSigned::new_from_unsigned(Secret::init_with(|| abs_value), bound) + } + #[test] fn adding_signed_numbers_increases_the_bound() { - let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 13).unwrap(); - let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 10).unwrap(); + let s1 = test_new_from_unsigned(U128::from_u8(5), 13).unwrap(); + let s2 = test_new_from_unsigned(U128::from_u8(3), 10).unwrap(); // The sum has a bound that is equal to the biggest bound of the operands + 1 assert_eq!((s1 + s2).bound(), 14); } @@ -560,16 +568,16 @@ mod tests { #[test] #[should_panic] fn adding_signed_numbers_with_max_bounds_panics() { - let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); - let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 127).unwrap(); + let s1 = test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); + let s2 = test_new_from_unsigned(U128::from_u8(3), 127).unwrap(); let _ = s1 + s2; } #[test] fn checked_mul_sums_bounds() { - let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 27).unwrap(); - let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let s1 = test_new_from_unsigned(U128::from_u8(5), 27).unwrap(); + let s2 = test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); let mul = s1.checked_mul(&s2).unwrap(); assert_eq!(mul.bound(), 44); @@ -577,8 +585,8 @@ mod tests { #[test] fn checked_mul_fails_when_sum_of_bounds_is_too_large() { - let s1 = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); - let s2 = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let s1 = test_new_from_unsigned(U128::from_u8(5), 127).unwrap(); + let s2 = test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); let mul = s1.checked_mul(&s2); assert!(bool::from(mul.is_none())); @@ -586,7 +594,7 @@ mod tests { #[test] fn mul_wide_sums_bounds() { - let s = SecretSigned::test_new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); + let s = test_new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); let s1 = PublicSigned::new_from_unsigned(U1024::MAX >> 1, 1023).unwrap(); let mul = s.mul_wide(&s1); assert_eq!(mul.bound(), 2046); @@ -598,10 +606,8 @@ mod tests { #[test] fn checked_mul_handles_sign() { - let n = SecretSigned::test_new_from_unsigned(U128::from_u8(5), 27) - .unwrap() - .neg(); - let p = SecretSigned::test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); + let n = test_new_from_unsigned(U128::from_u8(5), 27).unwrap().neg(); + let p = test_new_from_unsigned(U128::from_u8(3), 17).unwrap(); let neg_pos = n.checked_mul(&p).unwrap(); let pos_neg = p.checked_mul(&n).unwrap(); let pos_pos = p.checked_mul(&p).unwrap(); @@ -631,43 +637,41 @@ mod tests { // a 2 bit bound means numbers must be smaller or equal to 3 let bound = 2; let value = U1024::from_u8(3); - let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + let signed = test_new_from_unsigned(value, bound).unwrap(); assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); signed.assert_exp_range(bound); // 4 is too big let value = U1024::from_u8(4); - let signed = SecretSigned::test_new_from_unsigned(value, bound); + let signed = test_new_from_unsigned(value, bound); assert!(signed.is_none()); // a 1 bit bound means numbers must be smaller or equal to 1 let bound = 1; let value = U1024::from_u8(1); - let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + let signed = test_new_from_unsigned(value, bound).unwrap(); assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); signed.assert_exp_range(bound); // 2 is too big let value = U1024::from_u8(2); - let signed = SecretSigned::test_new_from_unsigned(value, bound); + let signed = test_new_from_unsigned(value, bound); assert!(signed.is_none()); // a 0 bit bound means only 0 is a valid value let bound = 0; let value = U1024::from_u8(0); - let signed = SecretSigned::test_new_from_unsigned(value, bound).unwrap(); + let signed = test_new_from_unsigned(value, bound).unwrap(); assert!(*signed.abs().expose_secret() < U1024::MAX >> (U1024::BITS - 1 - bound)); signed.assert_exp_range(bound); // 1 is too big let value = U1024::from_u8(1); - let signed = SecretSigned::test_new_from_unsigned(value, bound); + let signed = test_new_from_unsigned(value, bound); assert!(signed.is_none()); } #[test] fn neg_u128() { - let n = - SecretSigned::test_new_from_unsigned(U128::from_be_hex("fffffffffffffffffffffffffffffff0"), 127).unwrap(); - let neg_n = - SecretSigned::test_new_from_unsigned(U128::from_be_hex("00000000000000000000000000000010"), 127).unwrap(); + let n = test_new_from_unsigned(U128::from_be_hex("fffffffffffffffffffffffffffffff0"), 127).unwrap(); + let neg_n = test_new_from_unsigned(U128::from_be_hex("00000000000000000000000000000010"), 127).unwrap(); assert!(bool::from(n.is_negative())); assert!(!bool::from(neg_n.is_negative())); assert_eq!(n.clone().neg().to_public(), neg_n.to_public()); @@ -680,9 +684,8 @@ mod tests { // Biggest/smallest SecretSigned is |2^127|: use crypto_bigint::U128; let max_uint = U128::from_u128(u128::MAX >> 1); - let one_signed = SecretSigned::test_new_from_abs(U128::ONE, U128::BITS - 1, false).unwrap(); - let min_signed = - SecretSigned::test_new_from_abs(max_uint, U128::BITS - 1, true).expect("|2^127| is a valid SecretSigned"); + let one_signed = test_new_from_abs(U128::ONE, U128::BITS - 1, false).unwrap(); + let min_signed = test_new_from_abs(max_uint, U128::BITS - 1, true).expect("|2^127| is a valid SecretSigned"); let _ = min_signed - one_signed; } #[test] @@ -690,9 +693,8 @@ mod tests { fn sub_panics_on_underflow_1024() { // Biggest/smallest SecretSigned is |2^1023|: let max_uint = U1024::MAX >> 1; - let one_signed = SecretSigned::test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); - let min_signed = - SecretSigned::test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); + let one_signed = test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); + let min_signed = test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); let _ = min_signed - one_signed; } @@ -700,9 +702,8 @@ mod tests { fn checked_sub_handles_underflow() { // Biggest/smallest SecretSigned is |2^1023| let max_uint = U1024::MAX >> 1; - let one_signed = SecretSigned::test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); - let min_signed = - SecretSigned::test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); + let one_signed = test_new_from_abs(U1024::ONE, U1024::BITS - 1, false).unwrap(); + let min_signed = test_new_from_abs(max_uint, U1024::BITS - 1, true).expect("|2^1023| is a valid SecretSigned"); let result = min_signed.checked_sub(&one_signed); assert!(bool::from(result.is_none())) diff --git a/synedrion/src/uint/secret_unsigned.rs b/synedrion/src/uint/secret_unsigned.rs index b6c6329..b49e1cd 100644 --- a/synedrion/src/uint/secret_unsigned.rs +++ b/synedrion/src/uint/secret_unsigned.rs @@ -9,7 +9,7 @@ use crate::tools::Secret; /// A bounded unsigned integer with sensitive data. #[derive(Debug, Clone)] pub(crate) struct SecretUnsigned { - /// bound on the bit size of the value (that is, `value < 2^bound`) + /// Bound on the bit size of the value (that is, `value < 2^bound`). bound: u32, value: Secret, } @@ -39,15 +39,13 @@ where } pub fn add_mod(&self, rhs: &Self, modulus: &Secret>) -> Self { - // Note: assuming that the bit size of the modulus is not secret - // (although the modulus itself might be) Self { value: Secret::init_with(|| { self.value .expose_secret() .add_mod(rhs.value.expose_secret(), modulus.expose_secret()) }), - bound: modulus.expose_secret().bits_vartime(), + bound: modulus.expose_secret().bits(), } } @@ -67,11 +65,7 @@ where type Output = Self; fn bitand(self, rhs: T) -> Self::Output { Self { - value: Secret::init_with(|| { - let mut result = self.value.expose_secret().clone(); - result &= rhs; - result - }), + value: Secret::init_with(|| self.value.expose_secret().clone() & rhs), bound: self.bound, } } diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index d06fd71..9bdf74c 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -17,6 +17,13 @@ pub trait ToMontgomery: Integer { } } +/// Exponentiation to the power of bounded integers. +/// +/// Constant-time for secret exponents, although not constant-time wrt the bound. +/// +/// Assumes that the result exists, panics otherwise (e.g., when trying to raise 0 to a negative power). +// We cannot use the `crypto_bigint::Pow` trait since we cannot implement it for the foreign types +// (namely, `crypto_bigint::modular::MontyForm`). pub trait Exponentiable { fn pow(&self, exp: &Exponent) -> Self; }