diff --git a/synedrion/src/cggmp21/params.rs b/synedrion/src/cggmp21/params.rs index 06bfe7af..267f62f3 100644 --- a/synedrion/src/cggmp21/params.rs +++ b/synedrion/src/cggmp21/params.rs @@ -1,45 +1,57 @@ +use crate::curve::Scalar; use crate::curve::ORDER; use crate::paillier::PaillierParams; use crate::uint::{ - upcast_uint, U1024Mod, U2048Mod, U4096Mod, U512Mod, U1024, U2048, U4096, U512, U8192, + subtle::ConditionallySelectable, upcast_uint, Bounded, Encoding, NonZero, Signed, U1024Mod, + U2048Mod, U4096Mod, U512Mod, Zero, U1024, U2048, U4096, U512, U8192, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct PaillierTest; impl PaillierParams for PaillierTest { - // We need 257-bit primes because we need MODULUS_BITS to accommodate all the possible - // values of curve scalar squared, which is 512 bits. - /* The prime size is chosen to be minimal for which the `TestSchemeParams` still work. In the presigning, we are effectively constructing a ciphertext of + d = x * sum(j=1..P) y_i + sum(j=1..2*(P-1)) z_j + where + 0 < x, y_i < q < 2^L, and -2^LP < z < 2^LP + (`q` is the curve order, `L` and `LP` are constants in `TestSchemeParams`, `P` is the number of parties). - This is `delta_i`, an additive share of the product of two secret values. + This is `delta_i` or `chi_i`. + + During signing `chi_i` gets additionally multiplied by `r` (nonce, a scalar). We need the final result to be `-N/2 < d < N/2` - (that is, it may be negative, and it cannot wrap around modulo N). + (that is, it may be negative, and it cannot wrap around modulo N), + so that it could fit in a Paillier ciphertext without wrapping around. + This is needed for ZK proofs to work. `N` is a product of two primes of the size `PRIME_BITS`, so `N > 2^(2 * PRIME_BITS - 2)`. - The upper bound on `log2(d)` is - max(2 * L, LP + 2) + ceil(log2(P)) + The upper bound on `log2(d * r)` is + + max(2 * L, LP + 2) + ceil(log2(CURVE_ORDER)) + ceil(log2(P)) (note that in reality, due to numbers being random, the distribution will have a distinct peak, and the upper bound will have a low probability of being reached) - Therefore we require `max(2 * L, LP + 2) + ceil(log2(P)) < 2 * PRIME_BITS - 2`. + Therefore we require + + max(2 * L, LP + 2) + ceil(log2(CURVE_ORDER)) + ceil(log2(P)) < 2 * PRIME_BITS - 2` + For tests we assume `ceil(log2(P)) = 5` (we won't run tests with more than 32 nodes), - and since in `TestSchemeParams` `L = LP = 256`, this leads to `PRIME_BITS >= L + 4`. + and since in `TestSchemeParams` `L = LP = 256`, this leads to `PRIME_BITS >= 397`. - For production it does not matter since both 2*L and LP are much smaller than 2*PRIME_BITS. + For production it does not matter since 2*L, LP, and log2(CURVE_ORDER) + are much smaller than 2*PRIME_BITS. */ - const PRIME_BITS: usize = 260; + const PRIME_BITS: usize = 397; type HalfUint = U512; type HalfUintMod = U512Mod; type Uint = U1024; @@ -68,7 +80,9 @@ impl PaillierParams for PaillierProduction { // but for now they are hardcoded to `k256`. pub trait SchemeParams: Clone + Send + PartialEq + Eq + core::fmt::Debug + 'static { /// The order of the curve. - const CURVE_ORDER: ::Uint; // $q$ + const CURVE_ORDER: NonZero<::Uint>; // $q$ + /// The order of the curve as a wide integer. + const CURVE_ORDER_WIDE: NonZero<::WideUint>; /// The sheme's statistical security parameter. const SECURITY_PARAMETER: usize; // $\kappa$ /// The bound for secret values. @@ -79,6 +93,68 @@ pub trait SchemeParams: Clone + Send + PartialEq + Eq + core::fmt::Debug + 'stat const EPS_BOUND: usize; // $\eps$, in paper $= 2 \ell$ (see Table 2) /// The parameters of the Paillier encryption. type Paillier: PaillierParams; + + /// Converts a curve scalar to the associated integer type. + fn uint_from_scalar(value: &Scalar) -> ::Uint { + let scalar_bytes = value.to_bytes(); + let mut repr = ::Uint::ZERO.to_be_bytes(); + + let uint_len = repr.as_ref().len(); + let scalar_len = scalar_bytes.len(); + + debug_assert!(uint_len >= scalar_len); + repr.as_mut()[uint_len - scalar_len..].copy_from_slice(&scalar_bytes); + ::Uint::from_be_bytes(repr) + } + + /// Converts a curve scalar to the associated integer type, wrapped in `Bounded`. + fn bounded_from_scalar(value: &Scalar) -> Bounded<::Uint> { + const ORDER_BITS: usize = ORDER.bits_vartime(); + Bounded::new(Self::uint_from_scalar(value), ORDER_BITS as u32).unwrap() + } + + /// Converts a curve scalar to the associated integer type, wrapped in `Signed`. + fn signed_from_scalar(value: &Scalar) -> Signed<::Uint> { + Self::bounded_from_scalar(value).into_signed().unwrap() + } + + /// Converts an integer to the associated curve scalar type. + fn scalar_from_uint(value: &::Uint) -> Scalar { + let r = *value % Self::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_bytes(&repr.as_ref()[uint_len - scalar_len..]).unwrap() + } + + /// Converts a `Signed`-wrapped integer to the associated curve scalar type. + fn scalar_from_signed(value: &Signed<::Uint>) -> Scalar { + let abs_value = Self::scalar_from_uint(&value.abs()); + Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative()) + } + + /// Converts a wide integer to the associated curve scalar type. + fn scalar_from_wide_uint(value: &::WideUint) -> Scalar { + let r = *value % Self::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_bytes(&repr.as_ref()[uint_len - scalar_len..]).unwrap() + } + + /// Converts a `Signed`-wrapped wide integer to the associated curve scalar type. + fn scalar_from_wide_signed( + value: &Signed<::WideUint>, + ) -> Scalar { + let abs_value = Self::scalar_from_wide_uint(&value.abs()); + Scalar::conditional_select(&abs_value, &-abs_value, value.is_negative()) + } } /// Scheme parameters **for testing purposes only**. @@ -100,7 +176,10 @@ impl SchemeParams for TestParams { const LP_BOUND: usize = 256; const EPS_BOUND: usize = 320; type Paillier = PaillierTest; - const CURVE_ORDER: ::Uint = upcast_uint(ORDER); + const CURVE_ORDER: NonZero<::Uint> = + NonZero::<::Uint>::const_new(upcast_uint(ORDER)).0; + const CURVE_ORDER_WIDE: NonZero<::WideUint> = + NonZero::<::WideUint>::const_new(upcast_uint(ORDER)).0; } /// Production strength parameters. @@ -113,5 +192,8 @@ impl SchemeParams for ProductionParams { const LP_BOUND: usize = Self::L_BOUND * 5; const EPS_BOUND: usize = Self::L_BOUND * 2; type Paillier = PaillierProduction; - const CURVE_ORDER: ::Uint = upcast_uint(ORDER); + const CURVE_ORDER: NonZero<::Uint> = + NonZero::<::Uint>::const_new(upcast_uint(ORDER)).0; + const CURVE_ORDER_WIDE: NonZero<::WideUint> = + NonZero::<::WideUint>::const_new(upcast_uint(ORDER)).0; } diff --git a/synedrion/src/cggmp21/protocols/interactive_signing.rs b/synedrion/src/cggmp21/protocols/interactive_signing.rs index e9a1e5d4..eac3384e 100644 --- a/synedrion/src/cggmp21/protocols/interactive_signing.rs +++ b/synedrion/src/cggmp21/protocols/interactive_signing.rs @@ -259,3 +259,67 @@ impl FinalizableToResult for Round4

{ .map_err(wrap_finalize_error) } } + +#[cfg(test)] +mod tests { + use k256::ecdsa::{signature::hazmat::PrehashVerifier, VerifyingKey}; + use rand_core::{OsRng, RngCore}; + + use super::{Context, Round1}; + use crate::cggmp21::TestParams; + use crate::common::KeyShare; + use crate::curve::Scalar; + use crate::rounds::{ + test_utils::{step_next_round, step_result, step_round}, + FirstRound, PartyIdx, + }; + + #[test] + fn execute_interactive_signing() { + let mut shared_randomness = [0u8; 32]; + OsRng.fill_bytes(&mut shared_randomness); + + let message = Scalar::random(&mut OsRng); + + let num_parties = 3; + let key_shares = KeyShare::new_centralized(&mut OsRng, num_parties, None); + let r1 = (0..num_parties) + .map(|idx| { + Round1::::new( + &mut OsRng, + &shared_randomness, + num_parties, + PartyIdx::from_usize(idx), + Context { + message, + key_share: key_shares[idx].clone(), + }, + ) + .unwrap() + }) + .collect(); + + let r1a = step_round(&mut OsRng, r1).unwrap(); + let r2 = step_next_round(&mut OsRng, r1a).unwrap(); + let r2a = step_round(&mut OsRng, r2).unwrap(); + let r3 = step_next_round(&mut OsRng, r2a).unwrap(); + let r3a = step_round(&mut OsRng, r3).unwrap(); + let r4 = step_next_round(&mut OsRng, r3a).unwrap(); + let r4a = step_round(&mut OsRng, r4).unwrap(); + let signatures = step_result(&mut OsRng, r4a).unwrap(); + + for signature in signatures { + let (sig, rec_id) = signature.to_backend(); + + let vkey = key_shares[0].verifying_key(); + + // Check that the signature can be verified + vkey.verify_prehash(&message.to_bytes(), &sig).unwrap(); + + // Check that the key can be recovered + let recovered_key = + VerifyingKey::recover_from_prehash(&message.to_bytes(), &sig, rec_id).unwrap(); + assert_eq!(recovered_key, vkey); + } + } +} diff --git a/synedrion/src/cggmp21/protocols/key_refresh.rs b/synedrion/src/cggmp21/protocols/key_refresh.rs index a22625c9..b8aa2d4f 100644 --- a/synedrion/src/cggmp21/protocols/key_refresh.rs +++ b/synedrion/src/cggmp21/protocols/key_refresh.rs @@ -18,8 +18,8 @@ use crate::cggmp21::{ use crate::common::{KeyShareChange, PublicAuxInfo, SecretAuxInfo}; use crate::curve::{Point, Scalar}; use crate::paillier::{ - Ciphertext, PaillierParams, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, - RPParamsMod, RPSecret, Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, + Ciphertext, PublicKeyPaillier, PublicKeyPaillierPrecomputed, RPParams, RPParamsMod, RPSecret, + Randomizer, SecretKeyPaillier, SecretKeyPaillierPrecomputed, }; use crate::rounds::{ all_parties_except, try_to_holevec, BaseRound, BroadcastRound, DirectRound, Finalizable, @@ -30,13 +30,7 @@ use crate::tools::collections::HoleVec; use crate::tools::hashing::{Chain, Hash, HashOutput, Hashable}; use crate::tools::random::random_bits; use crate::tools::serde_bytes; -use crate::uint::{FromScalar, UintLike}; - -fn uint_from_scalar( - x: &Scalar, -) -> <

::Paillier as PaillierParams>::Uint { - <

::Paillier as PaillierParams>::Uint::from_scalar(x) -} +use crate::uint::UintLike; /// Possible results of the KeyRefresh protocol. #[derive(Debug, Clone, Copy)] @@ -528,7 +522,7 @@ impl DirectRound for Round3

{ let x_secret = self.context.xs_secret[idx]; let x_public = self.context.data_precomp.data.xs_public[idx]; - let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &uint_from_scalar::

(&x_secret)); + let ciphertext = Ciphertext::new(rng, &data.paillier_pk, &P::uint_from_scalar(&x_secret)); let sch_proof_x = SchProof::new( &self.context.sch_secrets_x[idx], @@ -556,11 +550,8 @@ impl DirectRound for Round3

{ ) -> Result> { let sender_data = &self.datas.get(from.as_usize()).unwrap(); - let x_secret = msg - .data2 - .paillier_enc_x - .decrypt(&self.context.paillier_sk) - .to_scalar(); + let x_secret = + P::scalar_from_uint(&msg.data2.paillier_enc_x.decrypt(&self.context.paillier_sk)); if x_secret.mul_by_generator() != sender_data.data.xs_public[self.context.party_idx.as_usize()] diff --git a/synedrion/src/cggmp21/protocols/presigning.rs b/synedrion/src/cggmp21/protocols/presigning.rs index f48a306f..4cf19882 100644 --- a/synedrion/src/cggmp21/protocols/presigning.rs +++ b/synedrion/src/cggmp21/protocols/presigning.rs @@ -23,13 +23,7 @@ use crate::rounds::{ }; use crate::tools::collections::{HoleRange, HoleVec}; use crate::tools::hashing::{Chain, Hashable}; -use crate::uint::{Bounded, FromScalar, Signed}; - -fn uint_from_scalar( - x: &Scalar, -) -> <

::Paillier as PaillierParams>::Uint { - <

::Paillier as PaillierParams>::Uint::from_scalar(x) -} +use crate::uint::Signed; /// Possible results of the Presigning protocol. #[derive(Debug, Clone, Copy)] @@ -89,10 +83,10 @@ impl FirstRound for Round1

{ let nu = RandomizerMod::::random(rng, pk); let g_ciphertext = - Ciphertext::new_with_randomizer(pk, &uint_from_scalar::

(&gamma), &nu.retrieve()); + Ciphertext::new_with_randomizer(pk, &P::uint_from_scalar(&gamma), &nu.retrieve()); let k_ciphertext = Ciphertext::new_with_randomizer( pk, - &uint_from_scalar::

(&ephemeral_scalar_share), + &P::uint_from_scalar(&ephemeral_scalar_share), &rho.retrieve(), ); @@ -193,9 +187,10 @@ impl DirectRound for Round1

{ let aux = (&self.context.shared_randomness, &destination); let proof = EncProof::new( rng, - &Signed::from_scalar(&self.context.ephemeral_scalar_share), + &P::signed_from_scalar(&self.context.ephemeral_scalar_share), &self.context.rho, - &self.context.key_share.secret_aux.paillier_sk, + self.context.key_share.secret_aux.paillier_sk.public_key(), + &self.k_ciphertext, &self.context.key_share.public_aux[destination.as_usize()].rp_params, &aux, ); @@ -306,14 +301,16 @@ pub struct Round2Artifact { s: Randomizer, // TODO (#77): secret hat_r: Randomizer, // TODO (#77): secret hat_s: Randomizer, // TODO (#77): secret + cap_d: Ciphertext, cap_f: Ciphertext, + hat_cap_d: Ciphertext, hat_cap_f: Ciphertext, } pub struct Round2Payload { gamma: Point, alpha: Signed<::Uint>, - alpha_hat: Scalar, + alpha_hat: Signed<::Uint>, cap_d: Ciphertext, hat_cap_d: Ciphertext, } @@ -376,7 +373,7 @@ impl DirectRound for Round2

{ let cap_f = Ciphertext::new_with_randomizer_signed(pk, &beta, &r.retrieve()); let d = self.k_ciphertexts[idx] - .homomorphic_mul(target_pk, &Signed::from_scalar(&self.context.gamma)) + .homomorphic_mul(target_pk, &P::signed_from_scalar(&self.context.gamma)) .homomorphic_add( target_pk, &Ciphertext::new_with_randomizer_signed(target_pk, &-beta, &s.retrieve()), @@ -385,7 +382,7 @@ impl DirectRound for Round2

{ let d_hat = self.k_ciphertexts[idx] .homomorphic_mul( target_pk, - &Signed::from_scalar(&self.context.key_share.secret_share), + &P::signed_from_scalar(&self.context.key_share.secret_share), ) .homomorphic_add( target_pk, @@ -398,45 +395,53 @@ impl DirectRound for Round2

{ let psi = AffGProof::new( rng, - &Signed::from_scalar(&self.context.gamma), + &P::signed_from_scalar(&self.context.gamma), &beta, &s, &r, target_pk, pk, &self.k_ciphertexts[idx], + &d, + &cap_f, + &gamma, rp, &aux, ); let psi_hat = AffGProof::new( rng, - &Signed::from_scalar(&self.context.key_share.secret_share), + &P::signed_from_scalar(&self.context.key_share.secret_share), &beta_hat, &s_hat, &r_hat, target_pk, pk, &self.k_ciphertexts[idx], + &d_hat, + &f_hat, + &self.context.key_share.public_shares[self.party_idx().as_usize()], rp, &aux, ); let psi_hat_prime = LogStarProof::new( rng, - &Signed::from_scalar(&self.context.gamma), + &P::signed_from_scalar(&self.context.gamma), &self.context.nu, pk, + &self.g_ciphertexts[self.party_idx().as_usize()], &Point::GENERATOR, + &gamma, rp, &aux, ); let msg = Round2Direct { gamma, - d, + d: d.clone(), f: cap_f.clone(), - d_hat, + d_hat: d_hat.clone(), f_hat: f_hat.clone(), psi, psi_hat, @@ -450,7 +455,9 @@ impl DirectRound for Round2

{ s: s.retrieve(), hat_r: r_hat.retrieve(), hat_s: s_hat.retrieve(), + cap_d: d, cap_f, + hat_cap_d: d_hat, hat_cap_f: f_hat, }; @@ -518,6 +525,9 @@ impl DirectRound for Round2

{ let alpha = msg .d .decrypt_signed(&self.context.key_share.secret_aux.paillier_sk); + let alpha_hat = msg + .d_hat + .decrypt_signed(&self.context.key_share.secret_aux.paillier_sk); // `alpha == x * y + z` where `0 <= x, y < q`, and `-2^l' <= z <= 2^l'`, // where `q` is the curve order. @@ -525,11 +535,9 @@ impl DirectRound for Round2

{ let alpha = alpha .assert_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) .unwrap(); - - let alpha_hat = msg - .d_hat - .decrypt_signed(&self.context.key_share.secret_aux.paillier_sk) - .to_scalar(); + let alpha_hat = alpha_hat + .assert_bound_usize(core::cmp::max(2 * P::L_BOUND, P::LP_BOUND) + 1) + .unwrap(); Ok(Round2Payload { gamma: msg.gamma, @@ -572,20 +580,21 @@ impl FinalizableToNextRound for Round2

{ let gamma: Point = dm_payloads.iter().map(|payload| payload.gamma).sum(); let gamma = gamma + self.context.gamma.mul_by_generator(); - let big_delta = &gamma * &self.context.ephemeral_scalar_share; + let big_delta = gamma * self.context.ephemeral_scalar_share; - let delta = Signed::from_scalar(&self.context.gamma) - * Signed::from_scalar(&self.context.ephemeral_scalar_share) - + dm_payloads.iter().map(|p| p.alpha).sum() - + dm_artifacts.iter().map(|p| p.beta).sum(); + let alpha_sum: Signed<_> = dm_payloads.iter().map(|p| p.alpha).sum(); + let beta_sum: Signed<_> = dm_artifacts.iter().map(|p| p.beta).sum(); + let delta = P::signed_from_scalar(&self.context.gamma) + * P::signed_from_scalar(&self.context.ephemeral_scalar_share) + + alpha_sum + + beta_sum; - let alpha_hat_sum: Scalar = dm_payloads.iter().map(|payload| payload.alpha_hat).sum(); + let alpha_hat_sum: Signed<_> = dm_payloads.iter().map(|payload| payload.alpha_hat).sum(); let beta_hat_sum: Signed<_> = dm_artifacts.iter().map(|artifact| artifact.beta_hat).sum(); - - let product_share = self.context.key_share.secret_share - * self.context.ephemeral_scalar_share + let product_share = P::signed_from_scalar(&self.context.key_share.secret_share) + * P::signed_from_scalar(&self.context.ephemeral_scalar_share) + alpha_hat_sum - + beta_hat_sum.to_scalar(); + + beta_hat_sum; let cap_ds = dm_payloads.map_ref(|payload| payload.cap_d.clone()); let hat_cap_d = dm_payloads.map_ref(|payload| payload.hat_cap_d.clone()); @@ -617,7 +626,7 @@ pub struct Round3Direct { pub struct Round3 { context: Context

, delta: Signed<::Uint>, - product_share: Scalar, + product_share: Signed<::Uint>, big_delta: Point, big_gamma: Point, k_ciphertexts: Vec>, @@ -681,15 +690,17 @@ impl DirectRound for Round3

{ let psi_hat_pprime = LogStarProof::new( rng, - &Signed::from_scalar(&self.context.ephemeral_scalar_share), + &P::signed_from_scalar(&self.context.ephemeral_scalar_share), &self.context.rho, pk, + &self.k_ciphertexts[self.party_idx().as_usize()], &self.big_gamma, + &self.big_delta, rp, &aux, ); let message = Round3Direct { - delta: self.delta.to_scalar(), + delta: P::scalar_from_signed(&self.delta), big_delta: self.big_delta, psi_hat_pprime, }; @@ -762,7 +773,8 @@ impl FinalizableToResult for Round3

{ .unzip(); let delta: Scalar = deltas.iter().sum(); - let delta = delta + self.delta.to_scalar(); + let delta_i = P::scalar_from_signed(&self.delta); + let delta = delta + delta_i; let big_delta: Point = big_deltas.iter().sum(); let big_delta = big_delta + self.big_delta; @@ -771,7 +783,7 @@ impl FinalizableToResult for Round3

{ if delta.mul_by_generator() == big_delta { // TODO (#79): seems like we only need the x-coordinate of this (as a Scalar) - let nonce = &self.big_gamma * &delta.invert().unwrap(); + let nonce = self.big_gamma * delta.invert().unwrap(); let hat_beta = self.round2_artifacts.map_ref(|artifact| artifact.beta_hat); let hat_r = self @@ -780,6 +792,9 @@ impl FinalizableToResult for Round3

{ let hat_s = self .round2_artifacts .map_ref(|artifact| artifact.hat_s.clone()); + let hat_cap_d = self + .round2_artifacts + .map_ref(|artifact| artifact.hat_cap_d.clone()); let hat_cap_f = self .round2_artifacts .map_ref(|artifact| artifact.hat_cap_f.clone()); @@ -787,13 +802,15 @@ impl FinalizableToResult for Round3

{ return Ok(PresigningData { nonce, ephemeral_scalar_share: self.context.ephemeral_scalar_share, - product_share: self.product_share, + product_share: P::scalar_from_signed(&self.product_share), + product_share_nonreduced: self.product_share, hat_beta, hat_r, hat_s, - cap_k: self.k_ciphertexts[my_idx].clone(), - hat_cap_d: self.hat_cap_d, + cap_k: self.k_ciphertexts.into_boxed_slice(), + hat_cap_d_received: self.hat_cap_d, + hat_cap_d, hat_cap_f, }); } @@ -817,7 +834,11 @@ impl FinalizableToResult for Round3

{ let r = self.round2_artifacts.map_ref(|artifact| artifact.r.clone()); let s = self.round2_artifacts.map_ref(|artifact| artifact.s.clone()); + let cap_gamma = self.context.gamma.mul_by_generator(); + for j in HoleRange::new(num_parties, my_idx) { + let r2_artefacts = self.round2_artifacts.get(j).unwrap(); + for l in HoleRange::new(num_parties, my_idx) { if l == j { continue; @@ -827,17 +848,31 @@ impl FinalizableToResult for Round3

{ let p_aff_g = AffGProof::

::new( rng, - &Signed::from_scalar(&self.context.gamma), + &P::signed_from_scalar(&self.context.gamma), beta.get(j).unwrap(), &s.get(j).unwrap().to_mod(target_pk), &r.get(j).unwrap().to_mod(pk), target_pk, pk, &self.k_ciphertexts[j], + &r2_artefacts.cap_d, + &r2_artefacts.cap_f, + &cap_gamma, rp, &aux, ); + assert!(p_aff_g.verify( + target_pk, + pk, + &self.k_ciphertexts[j], + &r2_artefacts.cap_d, + &r2_artefacts.cap_f, + &cap_gamma, + rp, + &aux, + )); + aff_g_proofs.push((PartyIdx::from_usize(j), PartyIdx::from_usize(l), p_aff_g)); } } @@ -845,17 +880,22 @@ impl FinalizableToResult for Round3

{ // Mul proof let rho = RandomizerMod::random(rng, pk); - let cap_h = self.k_ciphertexts[my_idx] - .homomorphic_mul_unsigned(pk, &Bounded::from_scalar(&self.context.gamma)) + let cap_h = self.g_ciphertexts[my_idx] + .homomorphic_mul_unsigned( + pk, + &P::bounded_from_scalar(&self.context.ephemeral_scalar_share), + ) .mul_randomizer(pk, &rho.retrieve()); let p_mul = MulProof::

::new( rng, - &Signed::from_scalar(&self.context.ephemeral_scalar_share), + &P::signed_from_scalar(&self.context.ephemeral_scalar_share), &self.context.rho, &rho, pk, + &self.k_ciphertexts[my_idx], &self.g_ciphertexts[my_idx], + &cap_h, &aux, ); assert!(p_mul.verify( @@ -887,12 +927,14 @@ impl FinalizableToResult for Round3

{ &self.delta, &rho, pk, + &delta_i, + &ciphertext, &self.context.key_share.public_aux[j].rp_params, &aux, ); assert!(p_dec.verify( pk, - &self.delta.to_scalar(), + &delta_i, &ciphertext, &self.context.key_share.public_aux[j].rp_params, &aux @@ -915,7 +957,7 @@ mod tests { use super::Round1; use crate::cggmp21::TestParams; use crate::common::KeyShare; - use crate::curve::{Point, Scalar}; + use crate::curve::Scalar; use crate::rounds::{ test_utils::{step_next_round, step_result, step_round}, FirstRound, PartyIdx, @@ -961,7 +1003,7 @@ mod tests { let x: Scalar = key_shares.iter().map(|share| share.secret_share).sum(); assert_eq!(x * k, k_times_x); assert_eq!( - &Point::GENERATOR * &k.invert().unwrap(), + k.invert().unwrap().mul_by_generator(), presigning_datas[0].nonce ); } diff --git a/synedrion/src/cggmp21/protocols/signing.rs b/synedrion/src/cggmp21/protocols/signing.rs index 6ec53ed7..4274e057 100644 --- a/synedrion/src/cggmp21/protocols/signing.rs +++ b/synedrion/src/cggmp21/protocols/signing.rs @@ -22,7 +22,6 @@ use crate::rounds::{ ProtocolResult, ReceiveError, ToResult, }; use crate::tools::collections::HoleRange; -use crate::uint::{Bounded, FromScalar, Signed}; /// Possible results of the Signing protocol. #[derive(Debug, Clone, Copy)] @@ -181,7 +180,7 @@ impl FinalizableToResult for Round1

{ let p_aff_g = AffGProof::

::new( rng, - &Signed::from_scalar(&self.context.key_share.secret_share), + &P::signed_from_scalar(&self.context.key_share.secret_share), self.context.presigning.hat_beta.get(j).unwrap(), &self .context @@ -193,11 +192,25 @@ impl FinalizableToResult for Round1

{ &self.context.presigning.hat_r.get(j).unwrap().to_mod(pk), target_pk, pk, - &self.context.presigning.cap_k, + &self.context.presigning.cap_k[j], + self.context.presigning.hat_cap_d.get(j).unwrap(), + self.context.presigning.hat_cap_f.get(j).unwrap(), + &self.context.key_share.public_shares[my_idx], rp, &aux, ); + assert!(p_aff_g.verify( + target_pk, + pk, + &self.context.presigning.cap_k[j], + self.context.presigning.hat_cap_d.get(j).unwrap(), + self.context.presigning.hat_cap_f.get(j).unwrap(), + &self.context.key_share.public_shares[my_idx], + rp, + &aux, + )); + aff_g_proofs.push((PartyIdx::from_usize(j), PartyIdx::from_usize(l), p_aff_g)); } } @@ -205,13 +218,11 @@ impl FinalizableToResult for Round1

{ // mul* proofs let x = self.context.key_share.secret_share; + let cap_x = self.context.key_share.public_shares[self.party_idx().as_usize()]; let rho = RandomizerMod::random(rng, pk); - let hat_cap_h = self - .context - .presigning - .cap_k - .homomorphic_mul_unsigned(pk, &Bounded::from_scalar(&x)) + let hat_cap_h = self.context.presigning.cap_k[my_idx] + .homomorphic_mul_unsigned(pk, &P::bounded_from_scalar(&x)) .mul_randomizer(pk, &rho.retrieve()); let aux = ( @@ -224,46 +235,77 @@ impl FinalizableToResult for Round1

{ for l in HoleRange::new(num_parties, my_idx) { let p_mul = MulStarProof::

::new( rng, - &Signed::from_scalar(&x), + &P::signed_from_scalar(&x), &rho, pk, - &self.context.presigning.cap_k, + &self.context.presigning.cap_k[my_idx], + &hat_cap_h, + &cap_x, &self.context.key_share.public_aux[l].rp_params, &aux, ); + assert!(p_mul.verify( + pk, + &self.context.presigning.cap_k[my_idx], + &hat_cap_h, + &cap_x, + &self.context.key_share.public_aux[l].rp_params, + &aux, + )); + mul_star_proofs.push((PartyIdx::from_usize(l), p_mul)); } // dec proofs - let mut ciphertext = hat_cap_h.homomorphic_add( - pk, - &self - .context - .presigning - .cap_k - .homomorphic_mul_unsigned(pk, &Bounded::from_scalar(&self.context.message)), - ); - + let mut ciphertext = hat_cap_h.clone(); for j in HoleRange::new(num_parties, my_idx) { ciphertext = ciphertext - .homomorphic_add(pk, self.context.presigning.hat_cap_d.get(j).unwrap()) + .homomorphic_add( + pk, + self.context.presigning.hat_cap_d_received.get(j).unwrap(), + ) .homomorphic_add(pk, self.context.presigning.hat_cap_f.get(j).unwrap()); } + let r = self.context.presigning.nonce.x_coordinate(); + + let ciphertext = ciphertext + .homomorphic_mul_unsigned(pk, &P::bounded_from_scalar(&r)) + .homomorphic_add( + pk, + &self.context.presigning.cap_k[my_idx] + .homomorphic_mul_unsigned(pk, &P::bounded_from_scalar(&self.context.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 = + P::signed_from_scalar(&self.context.presigning.ephemeral_scalar_share) + * P::signed_from_scalar(&self.context.message) + + self.context.presigning.product_share_nonreduced * P::signed_from_scalar(&r); let mut dec_proofs = Vec::new(); for l in HoleRange::new(num_parties, my_idx) { let p_dec = DecProof::

::new( rng, - &Signed::from_scalar(&s), + &s_part_nonreduced, &rho, pk, + &self.s_part, + &ciphertext, &self.context.key_share.public_aux[l].rp_params, &aux, ); + assert!(p_dec.verify( + pk, + &self.s_part, + &ciphertext, + &self.context.key_share.public_aux[l].rp_params, + &aux, + )); dec_proofs.push((PartyIdx::from_usize(l), p_dec)); } diff --git a/synedrion/src/cggmp21/sigma.rs b/synedrion/src/cggmp21/sigma.rs index b8828fda..0abb6861 100644 --- a/synedrion/src/cggmp21/sigma.rs +++ b/synedrion/src/cggmp21/sigma.rs @@ -1,17 +1,5 @@ //! Sigma-protocols -/* -A general note about verification: - -In `verify()` methods we take both the auxiliary info (for reconstructing the challenge), -and a challenge itself (as received with the proof from the other party). -This is prescribed by Fig. 2 (ZK module for sigma-protocols). - -Taking `challenge` as a parameter when we are reconstructing it anyway -may not seem necessary from the security perspective, but it provides a quick way -to detect an invalid message at the cost of an increased message size. -*/ - mod aff_g; mod dec; mod enc; diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index 3d2e2f93..24f97ea2 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -10,12 +10,35 @@ use crate::paillier::{ Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{FromScalar, NonZero, Signed}; +use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_aff_g"; +/** +ZK proof: Paillier Affine Operation with Group Commitment in Range. + +NOTE: deviation from the paper here. +The proof in the paper assumes $D = C (*) x (+) enc_0(y, \rho)$. +But the way it is used in the Presigning, $D$ will actually be $... (+) enc_0(-y, \rho)$. +So we have to negate several variables when constructing the proof for the whole thing to work. + +Secret inputs: +- $x \in \pm 2^\ell$, +- $y \in \pm 2^{\ell^\prime}$, +- $\rho$, a Paillier randomizer for the public key $N_0$, +- $\rho_y$, a Paillier randomizer for the public key $N_1$. + +Public inputs: +- Paillier public keys $N_0$, $N_1$, +- Paillier ciphertext $C$ encrypted with $N_0$, +- Paillier ciphertext $D = C (*) x (+) enc_0(-y, \rho)$, +- Paillier ciphertext $Y = enc_1(y, \rho_y)$, +- Point $X = g * x$, where $g$ is the curve generator, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct AffGProof { + e: Signed<::Uint>, cap_a: Ciphertext, cap_b_x: Point, cap_b_y: Ciphertext, @@ -37,21 +60,30 @@ impl AffGProof

{ rng: &mut impl CryptoRngCore, x: &Signed<::Uint>, y: &Signed<::Uint>, - rho_mod: &RandomizerMod, // Paillier randomizer for the public key $N_0$ - rho_y_mod: &RandomizerMod, // Paillier randomizer for the public key $N_1$ - pk0: &PublicKeyPaillierPrecomputed, // $N_0$ - pk1: &PublicKeyPaillierPrecomputed, // $N_1$ - cap_c: &Ciphertext, // a ciphertext encrypted with `pk0` - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + rho: &RandomizerMod, + rho_y: &RandomizerMod, + pk0: &PublicKeyPaillierPrecomputed, + pk1: &PublicKeyPaillierPrecomputed, + cap_c: &Ciphertext, + cap_d: &Ciphertext, + cap_y: &Ciphertext, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(pk1) + .chain(cap_c) + .chain(cap_d) + .chain(cap_y) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.into_wide(); let hat_cap_n = &setup.public_key().modulus_bounded(); @@ -71,19 +103,21 @@ impl AffGProof

{ pk0, &Ciphertext::new_with_randomizer_signed(pk0, &beta, &r_mod.retrieve()), ); - let cap_b_x = &Point::GENERATOR * &alpha.to_scalar(); + let cap_b_x = P::scalar_from_signed(&alpha).mul_by_generator(); let cap_b_y = Ciphertext::new_with_randomizer_signed(pk1, &beta, &r_y_mod.retrieve()); let cap_e = setup.commit(&alpha, &gamma).retrieve(); let cap_s = setup.commit(x, &m).retrieve(); let cap_f = setup.commit(&beta, &delta).retrieve(); - // NOTE: deviation from the paper to support a different $D$ (see the comment in `verify()`) + // NOTE: deviation from the paper to support a different $D$ + // (see the comment in `AffGProof`) // Original: $s^y$. Modified: $s^{-y}$ let cap_t = setup.commit(&-y, &mu).retrieve(); - let z1 = alpha + e * *x; + let z1 = alpha + e * x; - // NOTE: deviation from the paper to support a different $D$ (see the comment in `verify()`) + // 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 + e * (-y); @@ -91,13 +125,15 @@ impl AffGProof

{ let z3 = gamma + e_wide * m; let z4 = delta + e_wide * mu; - let omega = (r_mod * rho_mod.pow_signed_vartime(&e)).retrieve(); + let omega = (r_mod * rho.pow_signed_vartime(&e)).retrieve(); - // NOTE: deviation from the paper to support a different $D$ (see the comment in `verify()`) + // NOTE: deviation from the paper to support a different $D$ + // (see the comment in `AffGProof`) // Original: $\rho_y^e$. Modified: $\rho_y^{-e}$. - let omega_y = (r_y_mod * rho_y_mod.pow_signed_vartime(&-e)).retrieve(); + let omega_y = (r_y_mod * rho_y.pow_signed_vartime(&-e)).retrieve(); Self { + e, cap_a, cap_b_x, cap_b_y, @@ -120,24 +156,29 @@ impl AffGProof

{ pk0: &PublicKeyPaillierPrecomputed, pk1: &PublicKeyPaillierPrecomputed, cap_c: &Ciphertext, - // NOTE: deviation from the paper here. - // The proof in the paper assumes $D = C (*) x (+) enc_0(y, \rho)$. - // But the way it is used in the Presigning, $D$ will actually be $... (+) enc_0(-y, \rho)$. - // So we have to negate several variables when constructing the proof - // for the whole thing to work. - cap_d: &Ciphertext, // $D = C (*) x (+) enc_0(-y, \rho)$ - cap_y: &Ciphertext, // $Y = enc_1(y, \rho_y)$ - cap_x: &Point, // $X = g * x$, where $g$ is the curve generator - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + cap_d: &Ciphertext, + cap_y: &Ciphertext, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(pk1) + .chain(cap_c) + .chain(cap_d) + .chain(cap_y) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } let aux_pk = setup.public_key(); @@ -154,11 +195,14 @@ impl AffGProof

{ } // g^{z_1} = B_x X^e - if &Point::GENERATOR * &self.z1.to_scalar() != self.cap_b_x + cap_x * &e.to_scalar() { + if P::scalar_from_signed(&self.z1).mul_by_generator() + != self.cap_b_x + cap_x * &P::scalar_from_signed(&e) + { return false; } - // NOTE: deviation from the paper to support a different `D` (see the comment in `verify()`) + // NOTE: deviation from the paper to support a different `D` + // (see the comment in `AffGProof`) // Original: `Y^e`. Modified `Y^{-e}`. // (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) @@ -194,9 +238,8 @@ mod tests { use super::AffGProof; use crate::cggmp21::{SchemeParams, TestParams}; - use crate::curve::Point; use crate::paillier::{Ciphertext, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::{FromScalar, Signed}; + use crate::uint::Signed; #[test] fn prove_and_verify() { @@ -227,10 +270,11 @@ mod tests { &Ciphertext::new_with_randomizer_signed(pk0, &-y, &rho.retrieve()), ); let cap_y = Ciphertext::new_with_randomizer_signed(pk1, &y, &rho_y.retrieve()); - let cap_x = &Point::GENERATOR * &x.to_scalar(); + let cap_x = Params::scalar_from_signed(&x).mul_by_generator(); let proof = AffGProof::::new( - &mut OsRng, &x, &y, &rho, &rho_y, pk0, pk1, &cap_c, &setup, &aux, + &mut OsRng, &x, &y, &rho, &rho_y, pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &setup, + &aux, ); assert!(proof.verify(pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &setup, &aux)); } diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index 9f2ae8c2..ba3fdd05 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -10,56 +10,78 @@ use crate::paillier::{ Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{FromScalar, NonZero, Signed}; +use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_dec"; +/** +ZK proof: Paillier decryption modulo $q$. + +Secret inputs: +- $y$ (technically any integer since it will be implicitly reduced modulo $q$ or $\phi(N_0)$, + but we limit its size to `Uint` since that's what we use in this library), +- $\rho$, a Paillier randomizer for the public key $N_0$. + +Public inputs: +- Paillier public key $N_0$, +- scalar $x = y \mod q$, where $q$ is the curve order, +- Paillier ciphertext $C = enc_0(y, \rho)$, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DecProof { + e: Signed<::Uint>, cap_s: RPCommitment, cap_t: RPCommitment, cap_a: Ciphertext, gamma: Scalar, - z1: Signed<::Uint>, + z1: Signed<::WideUint>, z2: Signed<::WideUint>, omega: Randomizer, } impl DecProof

{ + #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, y: &Signed<::Uint>, rho: &RandomizerMod, - pk: &PublicKeyPaillierPrecomputed, // $N$ - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + x: &Scalar, + cap_c: &Ciphertext, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(x) + .chain(cap_c) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let hat_cap_n = &setup.public_key().modulus_bounded(); // $\hat{N}$ let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND); let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n); let nu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); - let r = RandomizerMod::random(rng, pk); + let r = RandomizerMod::random(rng, pk0); let cap_s = setup.commit(y, &mu).retrieve(); let cap_t = setup.commit(&alpha, &nu).retrieve(); - let cap_a = Ciphertext::new_with_randomizer_signed(pk, &alpha, &r.retrieve()); - let gamma = alpha.to_scalar(); + let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()); + let gamma = P::scalar_from_signed(&alpha); - let z1 = alpha + e * *y; + let z1 = alpha.into_wide() + e.mul_wide(y); let z2 = nu + e.into_wide() * mu; let omega = (r * rho.pow_signed_vartime(&e)).retrieve(); Self { + e, cap_s, cap_t, cap_a, @@ -72,38 +94,45 @@ impl DecProof

{ pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, // $N$ - x: &Scalar, // $x = y \mod q$ - cap_c: &Ciphertext, // $C = enc(y, \rho)$ - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + x: &Scalar, + cap_c: &Ciphertext, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(x) + .chain(cap_c) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } // enc(z_1, \omega) == A (+) C (*) e - if Ciphertext::new_with_randomizer_signed(pk, &self.z1, &self.omega) + if Ciphertext::new_with_randomizer_wide(pk0, &self.z1, &self.omega) != self .cap_a - .homomorphic_add(pk, &cap_c.homomorphic_mul(pk, &e)) + .homomorphic_add(pk0, &cap_c.homomorphic_mul(pk0, &e)) { return false; } // z_1 == \gamma + e x \mod q - if self.z1.to_scalar() != self.gamma + e.to_scalar() * *x { + if P::scalar_from_wide_signed(&self.z1) != self.gamma + P::scalar_from_signed(&e) * *x { return false; } // s^{z_1} t^{z_2} == T S^e let cap_s_mod = self.cap_s.to_mod(setup.public_key()); let cap_t_mod = self.cap_t.to_mod(setup.public_key()); - if setup.commit(&self.z1, &self.z2) != &cap_t_mod * &cap_s_mod.pow_signed_vartime(&e) { + if setup.commit_wide(&self.z1, &self.z2) != &cap_t_mod * &cap_s_mod.pow_signed_vartime(&e) { return false; } @@ -120,7 +149,7 @@ mod tests { use crate::paillier::{ Ciphertext, PaillierParams, RPParamsMod, RandomizerMod, SecretKeyPaillier, }; - use crate::uint::{FromScalar, Signed}; + use crate::uint::Signed; #[test] fn prove_and_verify() { @@ -136,13 +165,13 @@ 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 = Signed::random_bounded_bits(&mut OsRng, Paillier::PRIME_BITS - 2); - let x = y.to_scalar(); + let y = Signed::random_bounded_bits(&mut OsRng, Paillier::PRIME_BITS * 2 - 2); + let x = Params::scalar_from_signed(&y); let rho = RandomizerMod::random(&mut OsRng, pk); let cap_c = Ciphertext::new_with_randomizer_signed(pk, &y, &rho.retrieve()); - let proof = DecProof::::new(&mut OsRng, &y, &rho, pk, &setup, &aux); + let proof = DecProof::::new(&mut OsRng, &y, &rho, pk, &x, &cap_c, &setup, &aux); assert!(proof.verify(pk, &x, &cap_c, &setup, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index 2baa01ce..f3fc43a6 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -6,15 +6,28 @@ use serde::{Deserialize, Serialize}; use super::super::SchemeParams; use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, RPCommitment, RPParamsMod, - Randomizer, RandomizerMod, SecretKeyPaillierPrecomputed, + Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{NonZero, Signed}; +use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_enc"; -#[derive(Clone, Serialize, Deserialize)] +/** +ZK proof: Paillier encryption in range. + +Secret inputs: +- $k \in \pm 2^\ell$, +- $\rho$, a Paillier randomizer for the public key $N_0$. + +Public inputs: +- Paillier public key $N_0$, +- Paillier ciphertext $K = enc_0(k, \rho)$, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct EncProof { + e: Signed<::Uint>, cap_s: RPCommitment, cap_a: Ciphertext, cap_c: RPCommitment, @@ -26,39 +39,42 @@ pub(crate) struct EncProof { impl EncProof

{ pub fn new( rng: &mut impl CryptoRngCore, - secret: &Signed<::Uint>, // $k$ - randomizer_mod: &RandomizerMod, // $\rho$ - sk: &SecretKeyPaillierPrecomputed, // $N_0$ - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + k: &Signed<::Uint>, + rho: &RandomizerMod, + pk0: &PublicKeyPaillierPrecomputed, + cap_k: &Ciphertext, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_k) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let pk = sk.public_key(); let hat_cap_n = &setup.public_key().modulus_bounded(); // $\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 = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND); let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n); - let r = RandomizerMod::random(rng, pk); + let r = RandomizerMod::random(rng, pk0); let gamma = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); - let cap_s = setup.commit(secret, &mu).retrieve(); - let cap_a = Ciphertext::new_with_randomizer_signed(pk, &alpha, &r.retrieve()); + let cap_s = setup.commit(k, &mu).retrieve(); + let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()); let cap_c = setup.commit(&alpha, &gamma).retrieve(); - let z1 = alpha + e * *secret; - let z2 = (r * randomizer_mod.pow_signed_vartime(&e)).retrieve(); + let z1 = alpha + e * k; + let z2 = (r * rho.pow_signed_vartime(&e)).retrieve(); let z3 = gamma + mu * e.into_wide(); Self { + e, cap_s, cap_a, cap_c, @@ -70,18 +86,24 @@ impl EncProof

{ pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, // `N_0` - ciphertext: &Ciphertext, // `K` - setup: &RPParamsMod, // $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + cap_k: &Ciphertext, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_k) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } // z_1 \in \pm 2^{\ell + \eps} if !self.z1.in_range_bits(P::L_BOUND + P::EPS_BOUND) { @@ -89,10 +111,10 @@ impl EncProof

{ } // enc_0(z1, z2) == A (+) K (*) e - let c = Ciphertext::new_with_randomizer_signed(pk, &self.z1, &self.z2); + let c = Ciphertext::new_with_randomizer_signed(pk0, &self.z1, &self.z2); if c != self .cap_a - .homomorphic_add(pk, &ciphertext.homomorphic_mul(pk, &e)) + .homomorphic_add(pk0, &cap_k.homomorphic_mul(pk0, &e)) { return false; } @@ -135,7 +157,15 @@ mod tests { let ciphertext = Ciphertext::new_with_randomizer_signed(pk, &secret, &randomizer.retrieve()); - let proof = EncProof::::new(&mut OsRng, &secret, &randomizer, &sk, &setup, &aux); + let proof = EncProof::::new( + &mut OsRng, + &secret, + &randomizer, + pk, + &ciphertext, + &setup, + &aux, + ); assert!(proof.verify(pk, &ciphertext, &setup, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index 0e157b39..bd4907cb 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -9,12 +9,23 @@ use crate::paillier::{ SecretKeyPaillierPrecomputed, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{Bounded, Integer, NonZero, Signed}; +use crate::uint::{Bounded, Integer, Signed}; const HASH_TAG: &[u8] = b"P_fac"; -#[derive(Clone, Serialize, Deserialize)] +/** +ZK proof: No small factor proof. + +Secret inputs: +- primes $p$, $q$ such that $p, q < \pm \sqrt{N_0} 2^\ell$. + +Public inputs: +- Paillier public key $N_0 = p * q$, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct FacProof { + e: Signed<::Uint>, cap_p: RPCommitment, cap_q: RPCommitment, cap_a: RPCommitment, @@ -31,20 +42,22 @@ pub(crate) struct FacProof { impl FacProof

{ pub fn new( rng: &mut impl CryptoRngCore, - sk: &SecretKeyPaillierPrecomputed, - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + sk0: &SecretKeyPaillierPrecomputed, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { + let pk0 = sk0.public_key(); + let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.into_wide(); - let pk = sk.public_key(); let hat_cap_n = &setup.public_key().modulus_bounded(); // $\hat{N}$ // NOTE: using `2^(Paillier::PRIME_BITS - 1)` as $\sqrt{N_0}$ (which is its lower bound) @@ -61,7 +74,7 @@ impl FacProof

{ let nu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n); // N_0 \hat{N} - let scale = pk.modulus_bounded().mul_wide(hat_cap_n); + let scale = pk0.modulus_bounded().mul_wide(hat_cap_n); let sigma = Signed::<::Uint>::random_bounded_bits_scaled_wide( @@ -77,7 +90,7 @@ impl FacProof

{ let x = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let y = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); - let (p, q) = sk.primes(); + let (p, q) = sk0.primes(); let cap_p = setup.commit(&p, &mu).retrieve(); let cap_q = setup.commit(&q, &nu); @@ -93,6 +106,7 @@ impl FacProof

{ let v = r + (e_wide.into_wide() * hat_sigma); Self { + e, cap_p, cap_q: cap_q.retrieve(), cap_a, @@ -109,22 +123,27 @@ impl FacProof

{ pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, - setup: &RPParamsMod, // $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } let aux_pk = setup.public_key(); // R = s^{N_0} t^\sigma - let cap_r = &setup.commit_xwide(&pk.modulus_bounded(), &self.sigma); + let cap_r = &setup.commit_xwide(&pk0.modulus_bounded(), &self.sigma); // s^{z_1} t^{\omega_1} == A * P^e \mod \hat{N} let cap_a_mod = self.cap_a.to_mod(aux_pk); diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index 31b97674..5ed3f09d 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -10,12 +10,27 @@ use crate::paillier::{ Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{FromScalar, NonZero, Signed}; +use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_log*"; -#[derive(Clone, Serialize, Deserialize)] +/** +ZK proof: Knowledge of Exponent vs Paillier Encryption. + +Secret inputs: +- $x \in \pm 2^\ell$, +- $\rho$, a Paillier randomizer for the public key $N_0$. + +Public inputs: +- Paillier public key $N_0$, +- Paillier ciphertext $C = enc_0(x, \rho)$, +- Point $g$, +- Point $X = g * x$, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct LogStarProof { + e: Signed<::Uint>, cap_s: RPCommitment, cap_a: Ciphertext, cap_y: Point, @@ -29,38 +44,45 @@ impl LogStarProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Signed<::Uint>, // $x \in +- 2^\ell$ - rho: &RandomizerMod, // $\rho$ - pk: &PublicKeyPaillierPrecomputed, // $N_0$ - g: &Point, // $g$ - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + x: &Signed<::Uint>, + rho: &RandomizerMod, + pk0: &PublicKeyPaillierPrecomputed, + cap_c: &Ciphertext, + g: &Point, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_c) + .chain(g) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let hat_cap_n = &setup.public_key().modulus_bounded(); // $\hat{N}$ let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND); let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n); - let r = RandomizerMod::random(rng, pk); + let r = RandomizerMod::random(rng, pk0); let gamma = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let cap_s = setup.commit(x, &mu).retrieve(); - let cap_a = Ciphertext::new_with_randomizer_signed(pk, &alpha, &r.retrieve()); - let cap_y = g * &alpha.to_scalar(); + let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r.retrieve()); + let cap_y = g * &P::scalar_from_signed(&alpha); let cap_d = setup.commit(&alpha, &gamma).retrieve(); - let z1 = alpha + e * *x; + let z1 = alpha + e * x; let z2 = (r * rho.pow_signed_vartime(&e)).retrieve(); let z3 = gamma + mu * e.into_wide(); Self { + e, cap_s, cap_a, cap_y, @@ -74,32 +96,40 @@ impl LogStarProof

{ #[allow(clippy::too_many_arguments)] pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, - cap_c: &Ciphertext, // $C = encrypt(x, \rho)$ - g: &Point, // $g$ - cap_x: &Point, // $X = g^x$ - setup: &RPParamsMod, // $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + cap_c: &Ciphertext, + g: &Point, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_c) + .chain(g) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } // enc_0(z1, z2) == A (+) C (*) e - let c = Ciphertext::new_with_randomizer_signed(pk, &self.z1, &self.z2); + let c = Ciphertext::new_with_randomizer_signed(pk0, &self.z1, &self.z2); if c != self .cap_a - .homomorphic_add(pk, &cap_c.homomorphic_mul(pk, &e)) + .homomorphic_add(pk0, &cap_c.homomorphic_mul(pk0, &e)) { return false; } // g^{z_1} == Y X^e - if g * &self.z1.to_scalar() != self.cap_y + cap_x * &e.to_scalar() { + if g * &P::scalar_from_signed(&self.z1) != self.cap_y + cap_x * &P::scalar_from_signed(&e) { return false; } @@ -122,7 +152,7 @@ mod tests { use crate::cggmp21::{SchemeParams, TestParams}; use crate::curve::{Point, Scalar}; use crate::paillier::{Ciphertext, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::{FromScalar, Signed}; + use crate::uint::Signed; #[test] fn prove_and_verify() { @@ -137,13 +167,14 @@ mod tests { let aux: &[u8] = b"abcde"; - let g = &Point::GENERATOR * &Scalar::random(&mut OsRng); + let g = Point::GENERATOR * Scalar::random(&mut OsRng); let x = Signed::random_bounded_bits(&mut OsRng, Params::L_BOUND); let rho = RandomizerMod::random(&mut OsRng, pk); let cap_c = Ciphertext::new_with_randomizer_signed(pk, &x, &rho.retrieve()); - let cap_x = &g * &x.to_scalar(); + let cap_x = g * Params::scalar_from_signed(&x); - let proof = LogStarProof::::new(&mut OsRng, &x, &rho, pk, &g, &setup, &aux); + let proof = + LogStarProof::::new(&mut OsRng, &x, &rho, pk, &cap_c, &g, &cap_x, &setup, &aux); assert!(proof.verify(pk, &cap_c, &g, &cap_x, &setup, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/mod_.rs b/synedrion/src/cggmp21/sigma/mod_.rs index 15887c30..401e5686 100644 --- a/synedrion/src/cggmp21/sigma/mod_.rs +++ b/synedrion/src/cggmp21/sigma/mod_.rs @@ -12,11 +12,11 @@ use crate::uint::{JacobiSymbol, JacobiSymbolTrait, RandomMod, Retrieve, UintLike const HASH_TAG: &[u8] = b"P_mod"; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] struct ModCommitment(::Uint); impl ModCommitment

{ - pub fn random( + fn random( rng: &mut impl CryptoRngCore, pk: &PublicKeyPaillierPrecomputed, ) -> Self { @@ -34,8 +34,9 @@ impl ModCommitment

{ struct ModChallenge(Vec<::Uint>); impl ModChallenge

{ - fn new(aux: &impl Hashable, pk: &PublicKeyPaillierPrecomputed) -> Self { + fn new(pk: &PublicKeyPaillierPrecomputed, aux: &impl Hashable) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk) .chain(aux) .finalize_to_reader(); @@ -47,7 +48,7 @@ impl ModChallenge

{ } } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] struct ModProofElem { x: P::Uint, a: bool, @@ -55,7 +56,16 @@ struct ModProofElem { z: P::Uint, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +/** +ZK proof: Proof of Paillier-Blum modulus. + +Secret inputs: +- primes $p$, $q$. + +Public inputs: +- Paillier public key $N = p q$, +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(bound(serialize = "ModCommitment

: Serialize, ModChallenge

: Serialize"))] #[serde(bound(deserialize = "ModCommitment

: for<'x> Deserialize<'x>, @@ -67,13 +77,13 @@ pub(crate) struct ModProof { } impl ModProof

{ - pub(crate) fn new( + pub fn new( rng: &mut impl CryptoRngCore, sk: &SecretKeyPaillierPrecomputed, aux: &impl Hashable, ) -> Self { let pk = sk.public_key(); - let challenge = ModChallenge::

::new(aux, pk); + let challenge = ModChallenge::

::new(pk, aux); let commitment = ModCommitment::

::random(rng, pk); @@ -127,12 +137,12 @@ impl ModProof

{ } } - pub(crate) fn verify( + pub fn verify( &self, pk: &PublicKeyPaillierPrecomputed, aux: &impl Hashable, ) -> bool { - let challenge = ModChallenge::new(aux, pk); + let challenge = ModChallenge::new(pk, aux); if challenge != self.challenge { return false; } diff --git a/synedrion/src/cggmp21/sigma/mul.rs b/synedrion/src/cggmp21/sigma/mul.rs index 98bce74b..c40c3fc5 100644 --- a/synedrion/src/cggmp21/sigma/mul.rs +++ b/synedrion/src/cggmp21/sigma/mul.rs @@ -8,12 +8,13 @@ use crate::paillier::{ Ciphertext, PaillierParams, PublicKeyPaillierPrecomputed, Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{Bounded, NonZero, Retrieve, Signed}; +use crate::uint::{Bounded, Retrieve, Signed}; const HASH_TAG: &[u8] = b"P_mul"; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulProof { + e: Signed<::Uint>, cap_a: Ciphertext, cap_b: Ciphertext, z: Signed<::WideUint>, @@ -21,24 +22,45 @@ pub(crate) struct MulProof { v: Randomizer, } +/** +ZK proof: Paillier multiplication. + +Secret inputs: +- $x$ (technically any integer since it will be implicitly reduced modulo $q$ or $\phi(N)$, + but we limit its size to `Uint` since that's what we use in this library), +- $\rho_x$, a Paillier randomizer for the public key $N$, +- $\rho$, a Paillier randomizer for the public key $N$. + +Public inputs: +- Paillier public key $N$, +- Paillier ciphertext $X = enc(x, \rho_x)$, +- Paillier ciphertext $Y$ encrypted with $N$, +- Paillier ciphertext $C = (Y (*) x) * \rho^N \mod N^2$, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ impl MulProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - secret: &Signed<::Uint>, // $x$ - rho_x_mod: &RandomizerMod, // $\rho_x$ - rho_mod: &RandomizerMod, // $\rho$ - pk: &PublicKeyPaillierPrecomputed, // $N$ - cap_y: &Ciphertext, // $Y$ + x: &Signed<::Uint>, + rho_x: &RandomizerMod, + rho: &RandomizerMod, + pk: &PublicKeyPaillierPrecomputed, + cap_x: &Ciphertext, + cap_y: &Ciphertext, + cap_c: &Ciphertext, aux: &impl Hashable, ) -> Self { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk) + .chain(cap_x) + .chain(cap_y) + .chain(cap_c) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let alpha_mod = pk.random_invertible_group_elem(rng); let r_mod = RandomizerMod::random(rng, pk); @@ -57,11 +79,12 @@ impl MulProof

{ .mul_randomizer(pk, &r); let cap_b = Ciphertext::new_with_randomizer(pk, alpha.as_ref(), &s); - let z = alpha.into_wide().into_signed().unwrap() + e.mul_wide(secret); - let u = (r_mod * rho_mod.pow_signed_vartime(&e)).retrieve(); - let v = (s_mod * rho_x_mod.pow_signed_vartime(&e)).retrieve(); + let z = alpha.into_wide().into_signed().unwrap() + e.mul_wide(x); + let u = (r_mod * rho.pow_signed_vartime(&e)).retrieve(); + let v = (s_mod * rho_x.pow_signed_vartime(&e)).retrieve(); Self { + e, cap_a, cap_b, z, @@ -72,19 +95,26 @@ impl MulProof

{ pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, // $N$ - cap_x: &Ciphertext, // $X = enc(x, \rho_x)$ - cap_y: &Ciphertext, // $Y$ - cap_c: &Ciphertext, // $C = (Y (*) x) * \rho^N$ + pk: &PublicKeyPaillierPrecomputed, + cap_x: &Ciphertext, + cap_y: &Ciphertext, + cap_c: &Ciphertext, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk) + .chain(cap_x) + .chain(cap_y) + .chain(cap_c) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::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 @@ -141,7 +171,9 @@ mod tests { .homomorphic_mul(pk, &x) .mul_randomizer(pk, &rho.retrieve()); - let proof = MulProof::::new(&mut OsRng, &x, &rho_x, &rho, pk, &cap_y, &aux); + let proof = MulProof::::new( + &mut OsRng, &x, &rho_x, &rho, pk, &cap_x, &cap_y, &cap_c, &aux, + ); assert!(proof.verify(pk, &cap_x, &cap_y, &cap_c, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 17edc60f..244118b9 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -10,30 +10,47 @@ use crate::paillier::{ Randomizer, RandomizerMod, }; use crate::tools::hashing::{Chain, Hashable, XofHash}; -use crate::uint::{FromScalar, NonZero, Signed}; +use crate::uint::Signed; const HASH_TAG: &[u8] = b"P_mul*"; +/** +ZK proof: Multiplication Paillier vs Group. + +Secret inputs: +- $x \in +- 2^\ell$, +- $\rho$, a Paillier randomizer for the public key $N_0$. + +Public inputs: +- Paillier public key $N_0$, +- Paillier ciphertext $C$ encrypted with $N_0$, +- Paillier ciphertext $D = (C (*) x) * \rho^{N_0} \mod N_0^2$, +- Point $X = g * x$, where $g$ is the curve generator, +- Setup parameters ($\hat{N}$, $s$, $t$). +*/ #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulStarProof { - cap_a: Ciphertext, // $A$ - cap_b_x: Point, // $B_x$ - cap_e: RPCommitment, // $E$ - cap_s: RPCommitment, // $S$ - z1: Signed<::Uint>, // $z_1$ - z2: Signed<::WideUint>, // $z_2$ - omega: Randomizer, // $\omega$ + e: Signed<::Uint>, + cap_a: Ciphertext, + cap_b_x: Point, + cap_e: RPCommitment, + cap_s: RPCommitment, + z1: Signed<::Uint>, + z2: Signed<::WideUint>, + omega: Randomizer, } impl MulStarProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &Signed<::Uint>, // $x \in +- 2^\ell$ - rho: &RandomizerMod, // $\rho \in \mathbb{Z}_{N_0}$ - pk: &PublicKeyPaillierPrecomputed, // $N_0$ - cap_c: &Ciphertext, // $C$, a ciphertext encrypted with `pk` - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + x: &Signed<::Uint>, + rho: &RandomizerMod, + pk0: &PublicKeyPaillierPrecomputed, + cap_c: &Ciphertext, + cap_d: &Ciphertext, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { /* @@ -44,32 +61,37 @@ impl MulStarProof

{ */ let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_c) + .chain(cap_d) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let hat_cap_n = &setup.public_key().modulus_bounded(); // $\hat{N}$ - let r = RandomizerMod::random(rng, pk); + let r = RandomizerMod::random(rng, pk0); let alpha = Signed::random_bounded_bits(rng, P::L_BOUND + P::EPS_BOUND); let gamma = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); let m = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n); let cap_a = cap_c - .homomorphic_mul(pk, &alpha) - .mul_randomizer(pk, &r.retrieve()); - let cap_b_x = &Point::GENERATOR * &alpha.to_scalar(); + .homomorphic_mul(pk0, &alpha) + .mul_randomizer(pk0, &r.retrieve()); + let cap_b_x = Point::GENERATOR * P::scalar_from_signed(&alpha); let cap_e = setup.commit(&alpha, &gamma).retrieve(); let cap_s = setup.commit(x, &m).retrieve(); - let z1 = alpha + e * *x; + let z1 = alpha + e * x; let z2 = gamma + e.into_wide() * m; let omega = (r * rho.pow_signed(&e)).retrieve(); Self { + e, cap_a, cap_b_x, cap_e, @@ -84,36 +106,46 @@ impl MulStarProof

{ #[allow(clippy::too_many_arguments)] pub fn verify( &self, - pk: &PublicKeyPaillierPrecomputed, - cap_c: &Ciphertext, // $C$, a ciphertext encrypted with `pk` - cap_d: &Ciphertext, // $D = C (*) x * \rho^{N_0} \mod N_0^2$ - cap_x: &Point, // $X = g * x$, where `g` is the curve generator - setup: &RPParamsMod, // $\hat{N}$, $s$, $t$ + pk0: &PublicKeyPaillierPrecomputed, + cap_c: &Ciphertext, + cap_d: &Ciphertext, + cap_x: &Point, + setup: &RPParamsMod, aux: &impl Hashable, ) -> bool { let mut reader = XofHash::new_with_dst(HASH_TAG) + .chain(pk0) + .chain(cap_c) + .chain(cap_d) + .chain(cap_x) + .chain(setup) .chain(aux) .finalize_to_reader(); // Non-interactive challenge - let e = - Signed::from_xof_reader_bounded(&mut reader, &NonZero::new(P::CURVE_ORDER).unwrap()); + let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); + + if e != self.e { + return false; + } let aux_pk = setup.public_key(); // C (*) z_1 * \omega^{N_0} == A (+) D (*) e if cap_c - .homomorphic_mul(pk, &self.z1) - .mul_randomizer(pk, &self.omega) + .homomorphic_mul(pk0, &self.z1) + .mul_randomizer(pk0, &self.omega) != self .cap_a - .homomorphic_add(pk, &cap_d.homomorphic_mul(pk, &e)) + .homomorphic_add(pk0, &cap_d.homomorphic_mul(pk0, &e)) { return false; } // g^{z_1} == B_x X^e - if &Point::GENERATOR * &self.z1.to_scalar() != self.cap_b_x + cap_x * &e.to_scalar() { + if Point::GENERATOR * P::scalar_from_signed(&self.z1) + != self.cap_b_x + cap_x * &P::scalar_from_signed(&e) + { return false; } @@ -136,7 +168,7 @@ mod tests { use crate::cggmp21::{SchemeParams, TestParams}; use crate::curve::Point; use crate::paillier::{Ciphertext, RPParamsMod, RandomizerMod, SecretKeyPaillier}; - use crate::uint::{FromScalar, Signed}; + use crate::uint::Signed; #[test] fn prove_and_verify() { @@ -158,9 +190,11 @@ mod tests { let cap_d = cap_c .homomorphic_mul(pk, &x) .mul_randomizer(pk, &rho.retrieve()); - let cap_x = &Point::GENERATOR * &x.to_scalar(); + let cap_x = Point::GENERATOR * Params::scalar_from_signed(&x); - let proof = MulStarProof::::new(&mut OsRng, &x, &rho, pk, &cap_c, &setup, &aux); + let proof = MulStarProof::::new( + &mut OsRng, &x, &rho, pk, &cap_c, &cap_d, &cap_x, &setup, &aux, + ); assert!(proof.verify(pk, &cap_c, &cap_d, &cap_x, &setup, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/prm.rs b/synedrion/src/cggmp21/sigma/prm.rs index b4fa9f2b..a170dd20 100644 --- a/synedrion/src/cggmp21/sigma/prm.rs +++ b/synedrion/src/cggmp21/sigma/prm.rs @@ -10,10 +10,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use super::super::SchemeParams; -use crate::paillier::{ - PaillierParams, PublicKeyPaillierPrecomputed, RPParamsMod, RPSecret, - SecretKeyPaillierPrecomputed, -}; +use crate::paillier::{PaillierParams, RPParamsMod, RPSecret, SecretKeyPaillierPrecomputed}; use crate::tools::hashing::{Chain, Hashable, XofHash}; use crate::uint::{ subtle::{Choice, ConditionallySelectable}, @@ -22,38 +19,29 @@ use crate::uint::{ const HASH_TAG: &[u8] = b"P_prm"; -/// Secret data the proof is based on (~ signing key) -#[derive(Debug, Clone, PartialEq, Eq)] -struct PrmSecret { - public_key: PublicKeyPaillierPrecomputed, - secret: Vec::Uint>>, // $a_i$ -} +/// Secret data the proof is based on ($a_i$). +#[derive(Clone)] +struct PrmSecret(Vec::Uint>>); impl PrmSecret

{ - pub(crate) fn random( + fn random( rng: &mut impl CryptoRngCore, sk: &SecretKeyPaillierPrecomputed, ) -> Self { let secret = (0..P::SECURITY_PARAMETER) .map(|_| sk.random_field_elem(rng)) .collect(); - Self { - public_key: sk.public_key().clone(), - secret, - } + Self(secret) } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] struct PrmCommitment(Vec<::Uint>); impl PrmCommitment

{ - pub(crate) fn new( - secret: &PrmSecret

, - base: &::UintMod, - ) -> Self { + fn new(secret: &PrmSecret

, base: &::UintMod) -> Self { let commitment = secret - .secret + .0 .iter() .map(|a| base.pow_bounded(a).retrieve()) .collect(); @@ -71,11 +59,16 @@ impl Hashable for PrmCommitment

{ struct PrmChallenge(Vec); impl PrmChallenge { - fn new(aux: &impl Hashable, commitment: &PrmCommitment

) -> Self { + fn new( + commitment: &PrmCommitment

, + setup: &RPParamsMod, + aux: &impl Hashable, + ) -> Self { // TODO (#61): generate m/8 random bytes instead and fill the vector bit by bit. let mut reader = XofHash::new_with_dst(HASH_TAG) - .chain(aux) .chain(commitment) + .chain(setup) + .chain(aux) .finalize_to_reader(); let mut bytes = vec![0u8; P::SECURITY_PARAMETER]; reader.read(&mut bytes); @@ -89,7 +82,17 @@ impl Hashable for PrmChallenge { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/** +ZK proof: Ring-Pedersen parameters. + +Secret inputs: +- integer $\lambda$, +- (not explicitly mentioned in the paper, but necessary to calculate the totient) primes $p$, $q$. + +Public inputs: +- Setup parameters $N$, $s$, $t$ such that $N = p q$, and $s = t^\lambda \mod N$. +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(bound(serialize = "PrmCommitment

: Serialize"))] #[serde(bound(deserialize = "PrmCommitment

: for<'x> Deserialize<'x>"))] pub(crate) struct PrmProof { @@ -101,10 +104,10 @@ pub(crate) struct PrmProof { impl PrmProof

{ /// Create a proof that we know the `secret` /// (the power that was used to create RP parameters). - pub(crate) fn new( + pub fn new( rng: &mut impl CryptoRngCore, sk: &SecretKeyPaillierPrecomputed, - setup_secret: &RPSecret, + lambda: &RPSecret, setup: &RPParamsMod, aux: &impl Hashable, ) -> Self { @@ -112,13 +115,13 @@ impl PrmProof

{ let commitment = PrmCommitment::new(&proof_secret, &setup.base); let totient = sk.totient_nonzero(); - let challenge = PrmChallenge::new(aux, &commitment); + let challenge = PrmChallenge::new(&commitment, setup, aux); let proof = proof_secret - .secret + .0 .iter() .zip(challenge.0.iter()) .map(|(a, e)| { - let x = a.add_mod(setup_secret.as_ref(), &totient); + let x = a.add_mod(lambda.as_ref(), &totient); let choice = Choice::from(*e as u8); Bounded::conditional_select(a, &x, choice) }) @@ -131,10 +134,10 @@ impl PrmProof

{ } /// Verify that the proof is correct for a secret corresponding to the given RP parameters. - pub(crate) fn verify(&self, setup: &RPParamsMod, aux: &impl Hashable) -> bool { + pub fn verify(&self, setup: &RPParamsMod, aux: &impl Hashable) -> bool { let precomputed = setup.public_key().precomputed_modulus(); - let challenge = PrmChallenge::new(aux, &self.commitment); + let challenge = PrmChallenge::new(&self.commitment, setup, aux); if challenge != self.challenge { return false; } @@ -175,12 +178,12 @@ mod tests { let sk = SecretKeyPaillier::::random(&mut OsRng).to_precomputed(); let pk = sk.public_key(); - let setup_secret = RPSecret::random(&mut OsRng, &sk); - let setup = RPParamsMod::random_with_secret(&mut OsRng, &setup_secret, pk); + let lambda = RPSecret::random(&mut OsRng, &sk); + let setup = RPParamsMod::random_with_secret(&mut OsRng, &lambda, pk); let aux: &[u8] = b"abcde"; - let proof = PrmProof::::new(&mut OsRng, &sk, &setup_secret, &setup, &aux); + let proof = PrmProof::::new(&mut OsRng, &sk, &lambda, &setup, &aux); assert!(proof.verify(&setup, &aux)); } } diff --git a/synedrion/src/cggmp21/sigma/sch.rs b/synedrion/src/cggmp21/sigma/sch.rs index 38fb201e..21f12885 100644 --- a/synedrion/src/cggmp21/sigma/sch.rs +++ b/synedrion/src/cggmp21/sigma/sch.rs @@ -19,17 +19,17 @@ pub(crate) struct SchSecret( ); impl SchSecret { - pub(crate) fn random(rng: &mut impl CryptoRngCore) -> Self { + pub fn random(rng: &mut impl CryptoRngCore) -> Self { Self(Scalar::random(rng)) } } /// Public data for the proof (~ verifying key) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct SchCommitment(Point); impl SchCommitment { - pub(crate) fn new(secret: &SchSecret) -> Self { + pub fn new(secret: &SchSecret) -> Self { Self(secret.0.mul_by_generator()) } } @@ -40,11 +40,11 @@ impl Hashable for SchCommitment { } } -#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct SchChallenge(Scalar); impl SchChallenge { - fn new(aux: &impl Hashable, public: &Point, commitment: &SchCommitment) -> Self { + fn new(public: &Point, commitment: &SchCommitment, aux: &impl Hashable) -> Self { Self( Hash::new_with_dst(HASH_TAG) .chain(aux) @@ -55,37 +55,38 @@ impl SchChallenge { } } -/// Schnorr PoK of a secret scalar. -#[derive(Clone, Serialize, Deserialize)] +/** +ZK proof: Schnorr proof of knowledge. + +Secret inputs: +- scalar $x$. + +Public inputs: +- Point $X = g * x$, where $g$ is the curve generator. +*/ +#[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct SchProof { challenge: SchChallenge, proof: Scalar, } impl SchProof { - /// Create a proof that we know the `secret`. - pub(crate) fn new( + pub fn new( proof_secret: &SchSecret, - secret: &Scalar, + x: &Scalar, commitment: &SchCommitment, - public: &Point, + cap_x: &Point, aux: &impl Hashable, ) -> Self { - let challenge = SchChallenge::new(aux, public, commitment); - let proof = proof_secret.0 + &challenge.0 * secret; + let challenge = SchChallenge::new(cap_x, commitment, aux); + let proof = proof_secret.0 + &challenge.0 * x; Self { challenge, proof } } - /// Verify that the proof is correct for a secret corresponding to the given `public`. - pub(crate) fn verify( - &self, - commitment: &SchCommitment, - public: &Point, - aux: &impl Hashable, - ) -> bool { - let challenge = SchChallenge::new(aux, public, commitment); + pub fn verify(&self, commitment: &SchCommitment, cap_x: &Point, aux: &impl Hashable) -> bool { + let challenge = SchChallenge::new(cap_x, commitment, aux); challenge == self.challenge - && self.proof.mul_by_generator() == commitment.0 + public * &challenge.0 + && self.proof.mul_by_generator() == commitment.0 + cap_x * &challenge.0 } } diff --git a/synedrion/src/common.rs b/synedrion/src/common.rs index 9377abc2..5860d15c 100644 --- a/synedrion/src/common.rs +++ b/synedrion/src/common.rs @@ -21,7 +21,6 @@ use crate::uint::Signed; use crate::{ paillier::RandomizerMod, tools::collections::{HoleRange, HoleVecAccum}, - uint::FromScalar, }; /// The result of the KeyInit protocol. @@ -112,17 +111,24 @@ pub struct KeyShareChange { #[derive(Debug, Clone)] pub struct PresigningData { // TODO (#79): can we store nonce as a scalar? - pub(crate) nonce: Point, // `R` - /// An additive share of the ephemeral scalar `k`. - pub(crate) ephemeral_scalar_share: Scalar, // `k_i` + pub(crate) nonce: Point, // $R$ + /// An additive share of the ephemeral scalar. + pub(crate) ephemeral_scalar_share: Scalar, // $k_i$ /// An additive share of `k * x` where `x` is the secret key. pub(crate) product_share: Scalar, + // Values generated during presigning, // kept in case we need to generate a proof of correctness. + + // We are keeping the non-reduced product share because we may need + pub(crate) product_share_nonreduced: Signed<::Uint>, pub(crate) hat_beta: HoleVec::Uint>>, pub(crate) hat_r: HoleVec>, pub(crate) hat_s: HoleVec>, - pub(crate) cap_k: Ciphertext, + pub(crate) cap_k: Box<[Ciphertext]>, + /// Received $\hat{D}$, that is $\hat{D}_{i,j}$, $j != i$, where $i$ is this party's index. + pub(crate) hat_cap_d_received: HoleVec>, + /// Sent $\hat{D}$, that is $\hat{D}_{j,i}$, $j != i$, where $i$ is this party's index. pub(crate) hat_cap_d: HoleVec>, pub(crate) hat_cap_f: HoleVec>, } @@ -280,13 +286,8 @@ impl PresigningData

{ key_shares: &[KeyShare

], ) -> Box<[Self]> { let ephemeral_scalar = Scalar::random(rng); - let nonce = &Point::GENERATOR * &ephemeral_scalar.invert().unwrap(); + let nonce = ephemeral_scalar.invert().unwrap().mul_by_generator(); let ephemeral_scalar_shares = ephemeral_scalar.split(rng, key_shares.len()); - let secret: Scalar = key_shares - .iter() - .map(|key_share| key_share.secret_share) - .sum(); - let product_shares = (ephemeral_scalar * secret).split(rng, key_shares.len()); let num_parties = key_shares.len(); let public_keys = key_shares[0] @@ -298,34 +299,26 @@ impl PresigningData

{ let cap_k = ephemeral_scalar_shares .iter() .enumerate() - .map(|(i, k)| { - Ciphertext::new( - rng, - &public_keys[i], - &<

::Paillier as PaillierParams>::Uint::from_scalar(k), - ) - }) + .map(|(i, k)| Ciphertext::new(rng, &public_keys[i], &P::uint_from_scalar(k))) .collect::>(); let mut presigning = Vec::new(); - for i in 0..key_shares.len() { + let mut hat_betas = Vec::new(); + let mut hat_ss = Vec::new(); + let mut hat_cap_ds = Vec::new(); + for (i, key_share) in key_shares.iter().enumerate() { + let x = key_share.secret_share; + let mut hat_beta_vec = HoleVecAccum::new(num_parties, i); - let mut hat_r_vec = HoleVecAccum::new(num_parties, i); let mut hat_s_vec = HoleVecAccum::new(num_parties, i); - let mut hat_cap_d_vec = HoleVecAccum::new(num_parties, i); - let mut hat_cap_f_vec = HoleVecAccum::new(num_parties, i); - - let x = key_shares[i].secret_share; - let k = ephemeral_scalar_shares[i]; + let mut hat_cap_d_vec = HoleVecAccum::>::new(num_parties, i); for j in HoleRange::new(num_parties, i) { let hat_beta = Signed::random_bounded_bits(rng, P::LP_BOUND); - let hat_r = RandomizerMod::random(rng, &public_keys[i]).retrieve(); let hat_s = RandomizerMod::random(rng, &public_keys[j]).retrieve(); - let hat_cap_d = cap_k[j] - .homomorphic_mul(&public_keys[j], &Signed::from_scalar(&x)) + .homomorphic_mul(&public_keys[j], &P::signed_from_scalar(&x)) .homomorphic_add( &public_keys[j], &Ciphertext::new_with_randomizer_signed( @@ -334,26 +327,63 @@ impl PresigningData

{ &hat_s, ), ); - let hat_cap_f = - Ciphertext::new_with_randomizer_signed(&public_keys[j], &hat_beta, &hat_r); hat_beta_vec.insert(j, hat_beta); - hat_r_vec.insert(j, hat_r); hat_s_vec.insert(j, hat_s); hat_cap_d_vec.insert(j, hat_cap_d); + } + hat_betas.push(hat_beta_vec.finalize().unwrap()); + hat_ss.push(hat_s_vec.finalize().unwrap()); + hat_cap_ds.push(hat_cap_d_vec.finalize().unwrap()); + } + + for i in 0..key_shares.len() { + let mut hat_r_vec = HoleVecAccum::new(num_parties, i); + let mut hat_cap_f_vec = HoleVecAccum::new(num_parties, i); + + let x = key_shares[i].secret_share; + let k = ephemeral_scalar_shares[i]; + + for j in HoleRange::new(num_parties, i) { + let hat_beta = hat_betas[i].get(j).unwrap(); + let hat_r = RandomizerMod::random(rng, &public_keys[i]).retrieve(); + + let hat_cap_f = + Ciphertext::new_with_randomizer_signed(&public_keys[i], hat_beta, &hat_r); + + hat_r_vec.insert(j, hat_r); hat_cap_f_vec.insert(j, hat_cap_f); } + let mut hat_cap_d_received_vec = HoleVecAccum::new(num_parties, i); + for j in HoleRange::new(num_parties, i) { + hat_cap_d_received_vec.insert(j, hat_cap_ds[j].get(i).unwrap().clone()); + } + let hat_cap_d_received = hat_cap_d_received_vec.finalize().unwrap(); + + let alpha_sum: Signed<_> = HoleRange::new(num_parties, i) + .map(|j| { + P::signed_from_scalar(&key_shares[j].secret_share) * P::signed_from_scalar(&k) + - hat_betas[j].get(i).unwrap() + }) + .sum(); + + let beta_sum: Signed<_> = hat_betas[i].iter().sum(); + let product_share_nonreduced = + P::signed_from_scalar(&x) * P::signed_from_scalar(&k) + alpha_sum + beta_sum; + presigning.push(PresigningData { nonce, ephemeral_scalar_share: k, - product_share: product_shares[i], - hat_beta: hat_beta_vec.finalize().unwrap(), + product_share: P::scalar_from_signed(&product_share_nonreduced), + product_share_nonreduced, + hat_beta: hat_betas[i].clone(), hat_r: hat_r_vec.finalize().unwrap(), - hat_s: hat_s_vec.finalize().unwrap(), - hat_cap_d: hat_cap_d_vec.finalize().unwrap(), + hat_s: hat_ss[i].clone(), + hat_cap_d_received, + hat_cap_d: hat_cap_ds[i].clone(), hat_cap_f: hat_cap_f_vec.finalize().unwrap(), - cap_k: cap_k[i].clone(), + cap_k: cap_k.clone().into_boxed_slice(), }); } diff --git a/synedrion/src/curve/arithmetic.rs b/synedrion/src/curve/arithmetic.rs index d874e176..a7b35dec 100644 --- a/synedrion/src/curve/arithmetic.rs +++ b/synedrion/src/curve/arithmetic.rs @@ -153,6 +153,12 @@ impl<'de> Deserialize<'de> for Scalar { } } +impl Hashable for Scalar { + fn chain(&self, digest: C) -> C { + digest.chain_constant_sized_bytes(&self.to_bytes().as_slice()) + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Point(BackendPoint); @@ -214,7 +220,7 @@ impl Hashable for Point { fn chain(&self, digest: C) -> C { let arr = self.to_compressed_array(); let arr_ref: &[u8] = arr.as_ref(); - digest.chain(&arr_ref) + digest.chain_constant_sized_bytes(&arr_ref) } } @@ -243,7 +249,7 @@ impl Neg for Scalar { } } -impl<'a> Neg for &'a Scalar { +impl Neg for &Scalar { type Output = Scalar; fn neg(self) -> Self::Output { Scalar(-self.0) @@ -254,7 +260,7 @@ impl Add for Scalar { type Output = Scalar; fn add(self, other: Scalar) -> Scalar { - Scalar(self.0.add(other.0)) + Scalar(self.0.add(&other.0)) } } @@ -270,7 +276,7 @@ impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { - Point(self.0.add(other.0)) + Point(self.0.add(&(other.0))) } } @@ -298,6 +304,14 @@ impl Sub<&Scalar> for &Scalar { } } +impl Mul for Point { + type Output = Point; + + fn mul(self, other: Scalar) -> Point { + Point(self.0.mul(&(other.0))) + } +} + impl Mul<&Scalar> for &Point { type Output = Point; diff --git a/synedrion/src/paillier/keys.rs b/synedrion/src/paillier/keys.rs index 45fb0f1a..48a222bf 100644 --- a/synedrion/src/paillier/keys.rs +++ b/synedrion/src/paillier/keys.rs @@ -276,6 +276,12 @@ impl Hashable for PublicKeyPaillier

{ } } +impl Hashable for PublicKeyPaillierPrecomputed

{ + fn chain(&self, digest: C) -> C { + digest.chain(&self.pk) + } +} + #[cfg(test)] mod tests { use rand_core::OsRng; diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index 6ed96739..42f6f733 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::uint::{FromScalar, HasWide, UintLike, UintModLike}; +use crate::uint::{HasWide, UintLike, UintModLike}; #[cfg(test)] use crate::uint::{U1024Mod, U2048Mod, U512Mod, U1024, U2048, U4096, U512}; @@ -16,7 +16,6 @@ pub trait PaillierParams: PartialEq + Eq + Clone + core::fmt::Debug + Send { type HalfUintMod: UintModLike; /// An integer that fits the RSA modulus. type Uint: UintLike - + FromScalar + HasWide + Serialize + for<'de> Deserialize<'de>; diff --git a/synedrion/src/paillier/ring_pedersen.rs b/synedrion/src/paillier/ring_pedersen.rs index 680c42f7..c0e9a208 100644 --- a/synedrion/src/paillier/ring_pedersen.rs +++ b/synedrion/src/paillier/ring_pedersen.rs @@ -133,6 +133,12 @@ impl Hashable for RPParams

{ } } +impl Hashable for RPParamsMod

{ + fn chain(&self, digest: C) -> C { + digest.chain(&self.pk).chain(&self.base).chain(&self.power) + } +} + #[derive(PartialEq, Eq)] pub(crate) struct RPCommitmentMod(P::UintMod); diff --git a/synedrion/src/threshold.rs b/synedrion/src/threshold.rs index 2225342e..f5000d76 100644 --- a/synedrion/src/threshold.rs +++ b/synedrion/src/threshold.rs @@ -170,7 +170,7 @@ impl ThresholdKeyShare

{ let public_shares = share_idxs .iter() .map(|share_idx| { - &self.public_shares[share_idx] * &interpolation_coeff(share_idxs, share_idx) + self.public_shares[share_idx] * interpolation_coeff(share_idxs, share_idx) }) .collect(); diff --git a/synedrion/src/tools/hashing.rs b/synedrion/src/tools/hashing.rs index 50b50c3e..576d0d82 100644 --- a/synedrion/src/tools/hashing.rs +++ b/synedrion/src/tools/hashing.rs @@ -120,6 +120,12 @@ impl Hashable for u32 { } } +impl Hashable for u64 { + fn chain(&self, digest: C) -> C { + digest.chain_constant_sized_bytes(&self.to_be_bytes()) + } +} + impl Hashable for u8 { fn chain(&self, digest: C) -> C { digest.chain_constant_sized_bytes(&self.to_be_bytes()) diff --git a/synedrion/src/uint.rs b/synedrion/src/uint.rs index 8c53bfaa..385d6afc 100644 --- a/synedrion/src/uint.rs +++ b/synedrion/src/uint.rs @@ -4,8 +4,8 @@ mod signed; mod traits; pub(crate) use crypto_bigint::{ - modular::Retrieve, subtle, CheckedAdd, CheckedMul, CheckedSub, Integer, Invert, NonZero, - PowBoundedExp, RandomMod, U1024, U2048, U4096, U512, U8192, + modular::Retrieve, subtle, CheckedAdd, CheckedMul, CheckedSub, Encoding, Integer, Invert, + NonZero, PowBoundedExp, RandomMod, Zero, U1024, U2048, U4096, U512, U8192, }; pub(crate) use crypto_primes::RandomPrimeWithRng; @@ -13,5 +13,5 @@ pub(crate) use bounded::Bounded; pub(crate) use jacobi::{JacobiSymbol, JacobiSymbolTrait}; pub(crate) use signed::Signed; pub(crate) use traits::{ - upcast_uint, FromScalar, HasWide, U1024Mod, U2048Mod, U4096Mod, U512Mod, UintLike, UintModLike, + upcast_uint, HasWide, U1024Mod, U2048Mod, U4096Mod, U512Mod, UintLike, UintModLike, }; diff --git a/synedrion/src/uint/bounded.rs b/synedrion/src/uint/bounded.rs index 1f57c3cd..8ad04f31 100644 --- a/synedrion/src/uint/bounded.rs +++ b/synedrion/src/uint/bounded.rs @@ -6,9 +6,8 @@ use serde::{Deserialize, Serialize}; use super::{ subtle::{Choice, ConditionallySelectable, CtOption}, - CheckedAdd, CheckedMul, FromScalar, HasWide, NonZero, Signed, UintLike, + CheckedAdd, CheckedMul, HasWide, NonZero, Signed, UintLike, }; -use crate::curve::{Scalar, ORDER}; use crate::tools::hashing::{Chain, Hashable}; use crate::tools::serde_bytes; @@ -124,16 +123,6 @@ impl Bounded { } } -impl FromScalar for Bounded { - fn from_scalar(value: &Scalar) -> Self { - const ORDER_BITS: usize = ORDER.bits_vartime(); - Bounded::new(T::from_scalar(value), ORDER_BITS as u32).unwrap() - } - fn to_scalar(&self) -> Scalar { - self.value.to_scalar() - } -} - impl CheckedAdd for Bounded { type Output = Self; fn checked_add(&self, rhs: Self) -> CtOption { diff --git a/synedrion/src/uint/signed.rs b/synedrion/src/uint/signed.rs index d5c19ee2..ea53a049 100644 --- a/synedrion/src/uint/signed.rs +++ b/synedrion/src/uint/signed.rs @@ -8,9 +8,8 @@ use serde::{Deserialize, Serialize}; use super::{ bounded::PackedBounded, subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption}, - Bounded, CheckedAdd, CheckedMul, FromScalar, HasWide, Integer, NonZero, UintLike, UintModLike, + Bounded, HasWide, Integer, NonZero, UintLike, UintModLike, }; -use crate::curve::{Scalar, ORDER}; /// A packed representation for serializing Signed objects. /// Usually they have the bound much lower than the full size of the integer, @@ -191,6 +190,46 @@ impl Signed { let abs_mod = self.abs().to_mod(precomputed); T::ModUint::conditional_select(&abs_mod, &-abs_mod, self.is_negative()) } + + fn checked_add(&self, rhs: &Self) -> CtOption { + let bound = core::cmp::max(self.bound, rhs.bound) + 1; + 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)), + ) + } + + fn checked_mul(&self, rhs: &Self) -> CtOption { + let bound = self.bound + rhs.bound; + let lhs_neg = self.is_negative(); + let rhs_neg = rhs.is_negative(); + let lhs = T::conditional_select(&self.value, &self.value.neg(), lhs_neg); + let rhs = T::conditional_select(&rhs.value, &rhs.value.neg(), rhs_neg); + let result = lhs.checked_mul(&rhs); + let result_neg = lhs_neg ^ rhs_neg; + result.and_then(|val| { + let out_of_range = Choice::from((bound as usize >= ::BITS - 1) as u8); + let signed_val = T::conditional_select(&val, &val.neg(), result_neg); + CtOption::new( + Self { + bound, + value: signed_val, + }, + out_of_range.not(), + ) + }) + } } impl Default for Signed { @@ -299,87 +338,51 @@ where } } -impl FromScalar for Signed { - fn from_scalar(value: &Scalar) -> Self { - const ORDER_BITS: usize = ORDER.bits_vartime(); - Signed::new_positive(T::from_scalar(value), ORDER_BITS as u32).unwrap() - } - fn to_scalar(&self) -> Scalar { - let abs_value = self.abs().to_scalar(); - Scalar::conditional_select(&abs_value, &-abs_value, self.is_negative()) +impl Add> for Signed { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + self.checked_add(&rhs).unwrap() } } -impl CheckedAdd for Signed { +impl Add<&Signed> for Signed { type Output = Self; - fn checked_add(&self, rhs: Self) -> CtOption { - let bound = core::cmp::max(self.bound, rhs.bound) + 1; - 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)), - ) + fn add(self, rhs: &Self) -> Self::Output { + self.checked_add(rhs).unwrap() } } -impl CheckedMul for Signed { +impl Sub> for Signed { type Output = Self; - fn checked_mul(&self, rhs: Self) -> CtOption { - let bound = self.bound + rhs.bound; - let lhs_neg = self.is_negative(); - let rhs_neg = rhs.is_negative(); - let lhs = T::conditional_select(&self.value, &self.value.neg(), lhs_neg); - let rhs = T::conditional_select(&rhs.value, &rhs.value.neg(), rhs_neg); - let result = lhs.checked_mul(&rhs); - let result_neg = lhs_neg ^ rhs_neg; - result.and_then(|val| { - let out_of_range = Choice::from((bound as usize >= ::BITS - 1) as u8); - let signed_val = T::conditional_select(&val, &val.neg(), result_neg); - CtOption::new( - Self { - bound, - value: signed_val, - }, - out_of_range.not(), - ) - }) + fn sub(self, rhs: Self) -> Self::Output { + self.checked_add(&-rhs).unwrap() } } -impl Add> for Signed { +impl Sub<&Signed> for Signed { type Output = Self; - fn add(self, rhs: Self) -> Self::Output { - self.checked_add(rhs).unwrap() + fn sub(self, rhs: &Self) -> Self::Output { + self.checked_add(&-rhs).unwrap() } } -impl Sub> for Signed { +impl Mul> for Signed { type Output = Self; - fn sub(self, rhs: Self) -> Self::Output { - self.checked_add(-rhs).unwrap() + fn mul(self, rhs: Self) -> Self::Output { + self.checked_mul(&rhs).unwrap() } } -impl Mul> for Signed { +impl Mul<&Signed> for Signed { type Output = Self; - fn mul(self, rhs: Self) -> Self::Output { + fn mul(self, rhs: &Self) -> Self::Output { self.checked_mul(rhs).unwrap() } } impl core::iter::Sum for Signed { fn sum>(iter: I) -> Self { - iter.reduce(|x, y| x.checked_add(y).unwrap()) + iter.reduce(|x, y| x.checked_add(&y).unwrap()) .unwrap_or(Self::default()) } } diff --git a/synedrion/src/uint/traits.rs b/synedrion/src/uint/traits.rs index cd565881..105f48ed 100644 --- a/synedrion/src/uint/traits.rs +++ b/synedrion/src/uint/traits.rs @@ -14,7 +14,6 @@ use crypto_primes::RandomPrimeWithRng; use digest::XofReader; use super::{bounded::Bounded, jacobi::JacobiSymbolTrait, signed::Signed}; -use crate::curve::Scalar; use crate::tools::hashing::{Chain, Hashable}; pub(crate) const fn upcast_uint(value: Uint) -> Uint { @@ -188,6 +187,7 @@ pub trait UintModLike: + for<'a> Mul<&'a Self, Output = Self> + subtle::ConditionallyNegatable + subtle::ConditionallySelectable + + Hashable { /// The corresponding regular integer type. type RawUint: UintLike; @@ -285,6 +285,19 @@ pub trait UintModLike: fn square(&self) -> Self; } +impl Hashable for DynResidue { + fn chain(&self, digest: C) -> C { + let mut digest = digest; + // Montgomery form is a bijection, so we can just hash it directly + // without converting back. + let montgomery_form = self.as_montgomery(); + for word in montgomery_form.as_words() { + digest = digest.chain(word); + } + digest + } +} + impl UintModLike for DynResidue where Uint: Encoding, @@ -370,38 +383,6 @@ impl HasWide for U4096 { } } -// TODO (#63): this should be moved out of Uint layer. -pub trait FromScalar { - fn from_scalar(value: &Scalar) -> Self; - fn to_scalar(&self) -> Scalar; -} - -impl FromScalar for T { - fn from_scalar(value: &Scalar) -> Self { - let scalar_bytes = value.to_bytes(); - let mut repr = Self::ZERO.to_be_bytes(); - - let uint_len = repr.as_ref().len(); - let scalar_len = scalar_bytes.len(); - - debug_assert!(uint_len >= scalar_len); - repr.as_mut()[uint_len - scalar_len..].copy_from_slice(&scalar_bytes); - Self::from_be_bytes(repr) - } - - fn to_scalar(&self) -> Scalar { - let p = NonZero::new(Self::from_scalar(&-Scalar::ONE).wrapping_add(&Self::ONE)).unwrap(); - let r = self.rem(p); - - 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_bytes(&repr.as_ref()[uint_len - scalar_len..]).unwrap() - } -} - pub type U512Mod = DynResidue<{ nlimbs!(512) }>; pub type U1024Mod = DynResidue<{ nlimbs!(1024) }>; pub type U2048Mod = DynResidue<{ nlimbs!(2048) }>; diff --git a/synedrion/src/www02.rs b/synedrion/src/www02.rs index 3de7dfd7..3b6bb7fb 100644 --- a/synedrion/src/www02.rs +++ b/synedrion/src/www02.rs @@ -337,8 +337,8 @@ impl FinalizableToResult for Round1 { let vkey = bc_payloads .values() .map(|payload| { - &payload.public_polynomial.coeff0() - * &interpolation_coeff(&old_share_idxs, &payload.old_share_idx) + payload.public_polynomial.coeff0() + * interpolation_coeff(&old_share_idxs, &payload.old_share_idx) }) .sum(); if new_holder.context.verifying_key != vkey {