diff --git a/synedrion/src/cggmp21/interactive_signing.rs b/synedrion/src/cggmp21/interactive_signing.rs index b38a7b0..6e57cc2 100644 --- a/synedrion/src/cggmp21/interactive_signing.rs +++ b/synedrion/src/cggmp21/interactive_signing.rs @@ -23,7 +23,11 @@ use super::{ }, entities::{AuxInfo, AuxInfoPrecomputed, KeyShare, PresigningData, PresigningValues, PublicAuxInfoPrecomputed}, params::SchemeParams, - sigma::{AffGProof, DecProof, EncProof, LogStarProof, MulProof, MulStarProof}, + sigma::{ + AffGProof, AffGPublicInputs, AffGSecretInputs, DecProof, DecPublicInputs, DecSecretInputs, EncProof, + EncPublicInputs, EncSecretInputs, LogStarProof, LogStarPublicInputs, LogStarSecretInputs, MulProof, + MulPublicInputs, MulSecretInputs, MulStarProof, MulStarPublicInputs, MulStarSecretInputs, + }, }; use crate::{ curve::{Point, RecoverableSignature, Scalar}, @@ -299,10 +303,14 @@ impl Round for Round1 { let aux = (&self.context.ssid_hash, &destination); let psi0 = EncProof::new( rng, - &secret_signed_from_scalar::

(&self.context.k), - &self.context.rho, - self.context.aux_info.secret_aux.paillier_sk.public_key(), - &self.cap_k, + EncSecretInputs { + k: &secret_signed_from_scalar::

(&self.context.k), + rho: &self.context.rho, + }, + EncPublicInputs { + pk0: self.context.aux_info.secret_aux.paillier_sk.public_key(), + cap_k: &self.cap_k, + }, &self.public_aux(destination)?.rp_params, &aux, ); @@ -331,8 +339,10 @@ impl Round for Round1 { let from_pk = &self.public_aux(from)?.paillier_pk; if !direct_message.psi0.verify( - from_pk, - &echo_broadcast.cap_k.to_precomputed(from_pk), + EncPublicInputs { + pk0: from_pk, + cap_k: &echo_broadcast.cap_k.to_precomputed(from_pk), + }, &public_aux.rp_params, &aux, ) { @@ -483,71 +493,77 @@ impl Round for Round2 { let gamma = secret_signed_from_scalar::

(&self.context.gamma); let x = secret_signed_from_scalar::

(&self.context.key_share.secret_share); - let cap_f = Ciphertext::new_with_randomizer_signed(pk, &beta, &r); - let cap_d = self + let others_cap_k = self .all_cap_k .get(destination) - .ok_or(LocalError::new("Missing destination={destination:?} in all_cap_k"))? - * &gamma - + Ciphertext::new_with_randomizer_signed(target_pk, &-&beta, &s); + .ok_or(LocalError::new("destination={destination:?} is missing in all_cap_k"))?; + + let cap_f = Ciphertext::new_with_randomizer_signed(pk, &beta, &r); + let cap_d = others_cap_k * &gamma + Ciphertext::new_with_randomizer_signed(target_pk, &-&beta, &s); let hat_cap_f = Ciphertext::new_with_randomizer_signed(pk, &hat_beta, &hat_r); - let hat_cap_d = self - .all_cap_k - .get(destination) - .ok_or(LocalError::new("Missing destination={destination:?} in all_cap_k"))? - * &secret_signed_from_scalar::

(&self.context.key_share.secret_share) + let hat_cap_d = others_cap_k * &secret_signed_from_scalar::

(&self.context.key_share.secret_share) + Ciphertext::new_with_randomizer_signed(target_pk, &-&hat_beta, &hat_s); + let cap_g = self.all_cap_g.get(&self.context.my_id).ok_or(LocalError::new(format!( + "my_id={:?} is missing in all_cap_g", + &self.context.my_id + )))?; + let rp = &self.context.public_aux(destination)?.rp_params; let psi = AffGProof::new( rng, - &gamma, - &beta, - &s, - &r, - target_pk, - pk, - self.all_cap_k - .get(destination) - .ok_or(LocalError::new("destination={destination:?} is missing in all_cap_k"))?, - &cap_d, - &cap_f, - &cap_gamma, + AffGSecretInputs { + x: &gamma, + y: &beta, + rho: &s, + rho_y: &r, + }, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c: others_cap_k, + cap_d: &cap_d, + cap_y: &cap_f, + cap_x: &cap_gamma, + }, rp, &aux, ); let hat_psi = AffGProof::new( rng, - &x, - &hat_beta, - &hat_s, - &hat_r, - target_pk, - pk, - self.all_cap_k - .get(destination) - .ok_or(LocalError::new("destination={destination:?} is missing in all_cap_k"))?, - &hat_cap_d, - &hat_cap_f, - self.context.public_share(&self.context.my_id)?, + AffGSecretInputs { + x: &x, + y: &hat_beta, + rho: &hat_s, + rho_y: &hat_r, + }, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c: others_cap_k, + cap_d: &hat_cap_d, + cap_y: &hat_cap_f, + cap_x: self.context.public_share(&self.context.my_id)?, + }, rp, &aux, ); let hat_psi_prime = LogStarProof::new( rng, - &gamma, - &self.context.nu, - pk, - self.all_cap_g.get(&self.context.my_id).ok_or(LocalError::new(format!( - "my_id={:?} is missing in all_cap_g", - &self.context.my_id - )))?, - &Point::GENERATOR, - &cap_gamma, + LogStarSecretInputs { + x: &gamma, + rho: &self.context.nu, + }, + LogStarPublicInputs { + pk0: pk, + cap_c: cap_g, + g: &Point::GENERATOR, + cap_x: &cap_gamma, + }, rp, &aux, ); @@ -606,15 +622,24 @@ impl Round for Round2 { let cap_d = direct_message.cap_d.to_precomputed(pk); let hat_cap_d = direct_message.hat_cap_d.to_precomputed(pk); + let my_cap_k = self + .all_cap_k + .get(&self.context.my_id) + .ok_or(LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?; + let cap_g = self + .all_cap_g + .get(from) + .ok_or(LocalError::new("from={from:?} is missing in all_cap_g"))?; + if !direct_message.psi.verify( - pk, - from_pk, - self.all_cap_k - .get(&self.context.my_id) - .ok_or(LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?, - &cap_d, - &direct_message.cap_f.to_precomputed(from_pk), - &direct_message.cap_gamma, + AffGPublicInputs { + pk0: pk, + pk1: from_pk, + cap_c: my_cap_k, + cap_d: &cap_d, + cap_y: &direct_message.cap_f.to_precomputed(from_pk), + cap_x: &direct_message.cap_gamma, + }, rp, &aux, ) { @@ -624,14 +649,14 @@ impl Round for Round2 { } if !direct_message.hat_psi.verify( - pk, - from_pk, - self.all_cap_k - .get(&self.context.my_id) - .ok_or(LocalError::new("my_id={my_id:?} is missing in all_cap_k"))?, - &hat_cap_d, - &direct_message.hat_cap_f.to_precomputed(from_pk), - cap_x, + AffGPublicInputs { + pk0: pk, + pk1: from_pk, + cap_c: my_cap_k, + cap_d: &hat_cap_d, + cap_y: &direct_message.hat_cap_f.to_precomputed(from_pk), + cap_x, + }, rp, &aux, ) { @@ -641,12 +666,12 @@ impl Round for Round2 { } if !direct_message.hat_psi_prime.verify( - from_pk, - self.all_cap_g - .get(from) - .ok_or(LocalError::new("from={from:?} is missing in all_cap_g"))?, - &Point::GENERATOR, - &direct_message.cap_gamma, + LogStarPublicInputs { + pk0: from_pk, + cap_c: cap_g, + g: &Point::GENERATOR, + cap_x: &direct_message.cap_gamma, + }, rp, &aux, ) { @@ -791,17 +816,23 @@ impl Round for Round3 { let rp = &self.public_aux(destination)?.rp_params; + let cap_k = self.all_cap_k.get(&self.context.my_id).ok_or(LocalError::new(format!( + "my_id={:?} is missing in all_cap_k", + &self.context.my_id + )))?; + let psi_pprime = LogStarProof::new( rng, - &secret_signed_from_scalar::

(&self.context.k), - &self.context.rho, - pk, - self.all_cap_k.get(&self.context.my_id).ok_or(LocalError::new(format!( - "my_id={:?} is missing in all_cap_k", - &self.context.my_id - )))?, - &self.cap_gamma, - &self.cap_delta, + LogStarSecretInputs { + x: &secret_signed_from_scalar::

(&self.context.k), + rho: &self.context.rho, + }, + LogStarPublicInputs { + pk0: pk, + cap_c: cap_k, + g: &self.cap_gamma, + cap_x: &self.cap_delta, + }, rp, &aux, ); @@ -834,15 +865,20 @@ impl Round for Round3 { let aux = (&self.context.ssid_hash, &from); let from_pk = &self.public_aux(from)?.paillier_pk; + let others_cap_k = self + .all_cap_k + .get(from) + .ok_or(LocalError::new("from={from:?} is missing in all_cap_k"))?; + let rp = &self.public_aux(&self.context.my_id)?.rp_params; if !direct_message.psi_pprime.verify( - from_pk, - self.all_cap_k - .get(from) - .ok_or(LocalError::new("from={from:?} is missing in all_cap_k"))?, - &self.cap_gamma, - &direct_message.cap_delta, + LogStarPublicInputs { + pk0: from_pk, + cap_c: others_cap_k, + g: &self.cap_gamma, + cap_x: &direct_message.cap_delta, + }, rp, &aux, ) { @@ -959,27 +995,33 @@ impl Round for Round3 { let p_aff_g = AffGProof::

::new( rng, - &secret_signed_from_scalar::

(&self.context.gamma), - beta, - s, - r, - target_pk, - pk, - cap_c, - &r2_artifacts.cap_d, - &r2_artifacts.cap_f, - &cap_gamma, + AffGSecretInputs { + x: &secret_signed_from_scalar::

(&self.context.gamma), + y: beta, + rho: s, + rho_y: r, + }, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c, + cap_d: &r2_artifacts.cap_d, + cap_y: &r2_artifacts.cap_f, + cap_x: &cap_gamma, + }, rp, &aux, ); assert!(p_aff_g.verify( - target_pk, - pk, - cap_c, - &r2_artifacts.cap_d, - &r2_artifacts.cap_f, - &cap_gamma, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c, + cap_d: &r2_artifacts.cap_d, + cap_y: &r2_artifacts.cap_f, + cap_x: &cap_gamma + }, rp, &aux, )); @@ -1003,16 +1045,28 @@ impl Round for Round3 { let p_mul = MulProof::

::new( rng, - &secret_signed_from_scalar::

(&self.context.k), - &self.context.rho, - &rho, - pk, - cap_k, - cap_g, - &cap_h, + MulSecretInputs { + x: &secret_signed_from_scalar::

(&self.context.k), + rho_x: &self.context.rho, + rho: &rho, + }, + MulPublicInputs { + pk, + cap_x: cap_k, + cap_y: cap_g, + cap_c: &cap_h, + }, &aux, ); - assert!(p_mul.verify(pk, cap_k, cap_g, &cap_h, &aux)); + assert!(p_mul.verify( + MulPublicInputs { + pk, + cap_x: cap_k, + cap_y: cap_g, + cap_c: &cap_h + }, + &aux + )); // Dec proof @@ -1036,18 +1090,24 @@ impl Round for Round3 { for id_j in self.context.other_ids.iter() { let p_dec = DecProof::

::new( rng, - &self.delta, - &rho, - pk, - scalar_delta.expose_secret(), - &ciphertext, + DecSecretInputs { + y: &self.delta, + rho: &rho, + }, + DecPublicInputs { + pk0: pk, + x: scalar_delta.expose_secret(), + cap_c: &ciphertext, + }, &self.public_aux(id_j)?.rp_params, &aux, ); assert!(p_dec.verify( - pk, - scalar_delta.expose_secret(), - &ciphertext, + DecPublicInputs { + pk0: pk, + x: scalar_delta.expose_secret(), + cap_c: &ciphertext + }, &self.public_aux(id_j)?.rp_params, &aux )); @@ -1186,27 +1246,33 @@ impl Round for Round4 { let p_aff_g = AffGProof::

::new( rng, - &secret_signed_from_scalar::

(&self.context.key_share.secret_share), - &values.hat_beta, - &values.hat_s, - &values.hat_r, - target_pk, - pk, - &values.cap_k, - &values.hat_cap_d, - &values.hat_cap_f, - self.context.public_share(&my_id)?, + AffGSecretInputs { + x: &secret_signed_from_scalar::

(&self.context.key_share.secret_share), + y: &values.hat_beta, + rho: &values.hat_s, + rho_y: &values.hat_r, + }, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c: &values.cap_k, + cap_d: &values.hat_cap_d, + cap_y: &values.hat_cap_f, + cap_x: self.context.public_share(&my_id)?, + }, rp, &aux, ); assert!(p_aff_g.verify( - target_pk, - pk, - &values.cap_k, - &values.hat_cap_d, - &values.hat_cap_f, - self.context.public_share(&my_id)?, + AffGPublicInputs { + pk0: target_pk, + pk1: pk, + cap_c: &values.cap_k, + cap_d: &values.hat_cap_d, + cap_y: &values.hat_cap_f, + cap_x: self.context.public_share(&my_id)? + }, rp, &aux, )); @@ -1231,17 +1297,30 @@ impl Round for Round4 { let paux = self.context.public_aux(id_l)?; let p_mul = MulStarProof::

::new( rng, - &secret_signed_from_scalar::

(x), - &rho, - pk, - &self.presigning.cap_k, - &hat_cap_h, - cap_x, + MulStarSecretInputs { + x: &secret_signed_from_scalar::

(x), + rho: &rho, + }, + MulStarPublicInputs { + pk0: pk, + cap_c: &self.presigning.cap_k, + cap_d: &hat_cap_h, + cap_x, + }, &paux.rp_params, &aux, ); - assert!(p_mul.verify(pk, &self.presigning.cap_k, &hat_cap_h, cap_x, &paux.rp_params, &aux,)); + assert!(p_mul.verify( + MulStarPublicInputs { + pk0: pk, + cap_c: &self.presigning.cap_k, + cap_d: &hat_cap_h, + cap_x + }, + &paux.rp_params, + &aux, + )); mul_star_proofs.push((id_l.clone(), p_mul)); } @@ -1276,15 +1355,27 @@ impl Round for Round4 { let paux = self.context.public_aux(id_l)?; let p_dec = DecProof::

::new( rng, - &s_part_nonreduced, - &rho, - pk, - &self.sigma, - &ciphertext, + DecSecretInputs { + y: &s_part_nonreduced, + rho: &rho, + }, + DecPublicInputs { + pk0: pk, + x: &self.sigma, + cap_c: &ciphertext, + }, &paux.rp_params, &aux, ); - assert!(p_dec.verify(pk, &self.sigma, &ciphertext, &paux.rp_params, &aux,)); + assert!(p_dec.verify( + DecPublicInputs { + pk0: pk, + x: &self.sigma, + cap_c: &ciphertext + }, + &paux.rp_params, + &aux, + )); dec_proofs.push((id_l.clone(), p_dec)); } diff --git a/synedrion/src/cggmp21/sigma.rs b/synedrion/src/cggmp21/sigma.rs index 0abb686..cf89093 100644 --- a/synedrion/src/cggmp21/sigma.rs +++ b/synedrion/src/cggmp21/sigma.rs @@ -11,13 +11,13 @@ mod mul_star; mod prm; mod sch; -pub(crate) use aff_g::AffGProof; -pub(crate) use dec::DecProof; -pub(crate) use enc::EncProof; +pub(crate) use aff_g::{AffGProof, AffGPublicInputs, AffGSecretInputs}; +pub(crate) use dec::{DecProof, DecPublicInputs, DecSecretInputs}; +pub(crate) use enc::{EncProof, EncPublicInputs, EncSecretInputs}; pub(crate) use fac::FacProof; -pub(crate) use log_star::LogStarProof; +pub(crate) use log_star::{LogStarProof, LogStarPublicInputs, LogStarSecretInputs}; pub(crate) use mod_::ModProof; -pub(crate) use mul::MulProof; -pub(crate) use mul_star::MulStarProof; +pub(crate) use mul::{MulProof, MulPublicInputs, MulSecretInputs}; +pub(crate) use mul_star::{MulStarProof, MulStarPublicInputs, MulStarSecretInputs}; pub(crate) use prm::PrmProof; pub(crate) use sch::{SchCommitment, SchProof, SchSecret}; diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index f3686c4..a0e2bbc 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -19,28 +19,37 @@ use crate::{ 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$). -*/ +pub(crate) struct AffGSecretInputs<'a, P: SchemeParams> { + /// $x \in \pm 2^\ell$. + pub x: &'a SecretSigned<::Uint>, + /// $y \in \pm 2^{\ell^\prime}$. + pub y: &'a SecretSigned<::Uint>, + /// $\rho$, a Paillier randomizer for the public key $N_0$. + pub rho: &'a Randomizer, + /// $\rho_y$, a Paillier randomizer for the public key $N_1$. + pub rho_y: &'a Randomizer, +} + +pub(crate) struct AffGPublicInputs<'a, P: SchemeParams> { + /// Paillier public keys $N_0$. + pub pk0: &'a PublicKeyPaillier, + /// Paillier public keys $N_1$. + pub pk1: &'a PublicKeyPaillier, + /// Paillier ciphertext $C$ encrypted with $N_0$. + pub cap_c: &'a Ciphertext, + /// Paillier ciphertext $D = C (*) x (+) enc_0(-y, \rho)$. + // 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. + pub cap_d: &'a Ciphertext, + /// Paillier ciphertext $Y = enc_1(y, \rho_y)$. + pub cap_y: &'a Ciphertext, + /// Point $X = g * x$, where $g$ is the curve generator. + pub cap_x: &'a Point, +} + +/// ZK proof: Paillier Affine Operation with Group Commitment in Range. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct AffGProof { e: PublicSigned<::Uint>, @@ -60,52 +69,43 @@ pub(crate) struct AffGProof { } impl AffGProof

{ - #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &SecretSigned<::Uint>, - y: &SecretSigned<::Uint>, - rho: &Randomizer, - rho_y: &Randomizer, - pk0: &PublicKeyPaillier, - pk1: &PublicKeyPaillier, - cap_c: &Ciphertext, - cap_d: &Ciphertext, - cap_y: &Ciphertext, - cap_x: &Point, + secret: AffGSecretInputs<'_, P>, + public: AffGPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> Self { - x.assert_exponent_range(P::L_BOUND); - y.assert_exponent_range(P::LP_BOUND); - assert!(cap_c.public_key() == pk0); - assert!(cap_d.public_key() == pk0); - assert!(cap_y.public_key() == pk1); + secret.x.assert_exponent_range(P::L_BOUND); + secret.y.assert_exponent_range(P::LP_BOUND); + assert!(public.cap_c.public_key() == public.pk0); + assert!(public.cap_d.public_key() == public.pk0); + assert!(public.cap_y.public_key() == public.pk1); let hat_cap_n = setup.modulus(); 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 r = Randomizer::random(rng, public.pk0); + let r_y = Randomizer::random(rng, public.pk1); 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_a = (public.cap_c * &alpha + Ciphertext::new_with_randomizer_signed(public.pk0, &beta, &r)).to_wire(); let cap_b_x = secret_scalar_from_signed::

(&alpha).mul_by_generator(); - let cap_b_y = Ciphertext::new_with_randomizer_signed(pk1, &beta, &r_y).to_wire(); + let cap_b_y = Ciphertext::new_with_randomizer_signed(public.pk1, &beta, &r_y).to_wire(); let cap_e = setup.commit(&alpha, &gamma).to_wire(); - let cap_s = setup.commit(x, &m).to_wire(); + let cap_s = setup.commit(secret.x, &m).to_wire(); let cap_f = setup.commit(&beta, &delta).to_wire(); // NOTE: deviation from the paper to support a different $D$ - // (see the comment in `AffGProof`) + // (see the comment in `AffGPublicInputs`) // Original: $s^y$. Modified: $s^{-y}$ - let cap_t = setup.commit(&(-y), &mu).to_wire(); + let cap_t = setup.commit(&(-secret.y), &mu).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -117,12 +117,12 @@ impl AffGProof

{ .chain(&cap_s) .chain(&cap_t) // public parameters - .chain(pk0.as_wire()) - .chain(pk1.as_wire()) - .chain(&cap_c.to_wire()) - .chain(&cap_d.to_wire()) - .chain(&cap_y.to_wire()) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(public.pk1.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_d.to_wire()) + .chain(&public.cap_y.to_wire()) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -131,23 +131,23 @@ impl AffGProof

{ let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); let e_wide = e.to_wide(); - let z1 = (alpha + x * e).to_public(); + let z1 = (alpha + secret.x * e).to_public(); // NOTE: deviation from the paper to support a different $D$ - // (see the comment in `AffGProof`) + // (see the comment in `AffGPublicInputs`) // Original: $z_2 = \beta + e y$ // Modified: $z_2 = \beta - e y$ - let z2 = (beta + (-y) * e).to_public(); + let z2 = (beta + (-secret.y) * e).to_public(); let z3 = (gamma + m * e_wide).to_public(); let z4 = (delta + mu * e_wide).to_public(); - let omega = rho.to_masked(&r, &e); + let omega = secret.rho.to_masked(&r, &e); // NOTE: deviation from the paper to support a different $D$ - // (see the comment in `AffGProof`) + // (see the comment in `AffGPublicInputs`) // Original: $\rho_y^e$. Modified: $\rho_y^{-e}$. - let omega_y = rho_y.to_masked(&r_y, &-e); + let omega_y = secret.rho_y.to_masked(&r_y, &-e); Self { e, @@ -168,20 +168,10 @@ impl AffGProof

{ } #[allow(clippy::too_many_arguments)] - pub fn verify( - &self, - pk0: &PublicKeyPaillier, - pk1: &PublicKeyPaillier, - cap_c: &Ciphertext, - cap_d: &Ciphertext, - cap_y: &Ciphertext, - cap_x: &Point, - setup: &RPParams, - aux: &impl Hashable, - ) -> bool { - assert!(cap_c.public_key() == pk0); - assert!(cap_d.public_key() == pk0); - assert!(cap_y.public_key() == pk1); + pub fn verify(&self, public: AffGPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable) -> bool { + assert!(public.cap_c.public_key() == public.pk0); + assert!(public.cap_d.public_key() == public.pk0); + assert!(public.cap_y.public_key() == public.pk1); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -193,12 +183,12 @@ impl AffGProof

{ .chain(&self.cap_s) .chain(&self.cap_t) // public parameters - .chain(pk0.as_wire()) - .chain(pk1.as_wire()) - .chain(&cap_c.to_wire()) - .chain(&cap_d.to_wire()) - .chain(&cap_y.to_wire()) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(public.pk1.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_d.to_wire()) + .chain(&public.cap_y.to_wire()) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -222,24 +212,26 @@ 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 public.cap_c * &self.z1 + Ciphertext::new_public_with_randomizer_signed(public.pk0, &self.z2, &self.omega) + != public.cap_d * &e + self.cap_a.to_precomputed(public.pk0) { return false; } // g^{z_1} = B_x X^e - if scalar_from_signed::

(&self.z1).mul_by_generator() != self.cap_b_x + cap_x * &scalar_from_signed::

(&e) { + if scalar_from_signed::

(&self.z1).mul_by_generator() + != self.cap_b_x + public.cap_x * &scalar_from_signed::

(&e) + { return false; } // NOTE: deviation from the paper to support a different `D` - // (see the comment in `AffGProof`) + // (see the comment in `AffGPublicInputs`) // 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) - if Ciphertext::new_public_with_randomizer_signed(pk1, &self.z2, &self.omega_y) - != cap_y * &(-e) + self.cap_b_y.to_precomputed(pk1) + if Ciphertext::new_public_with_randomizer_signed(public.pk1, &self.z2, &self.omega_y) + != public.cap_y * &(-e) + self.cap_b_y.to_precomputed(public.pk1) { return false; } @@ -266,7 +258,7 @@ impl AffGProof

{ mod tests { use rand_core::OsRng; - use super::AffGProof; + use super::{AffGProof, AffGPublicInputs, AffGSecretInputs}; use crate::{ cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, @@ -301,8 +293,35 @@ mod tests { let cap_x = secret_scalar_from_signed::(&x).mul_by_generator(); let proof = AffGProof::::new( - &mut OsRng, &x, &y, &rho, &rho_y, pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &rp_params, &aux, + &mut OsRng, + AffGSecretInputs { + x: &x, + y: &y, + rho: &rho, + rho_y: &rho_y, + }, + AffGPublicInputs { + pk0, + pk1, + cap_c: &cap_c, + cap_d: &cap_d, + cap_y: &cap_y, + cap_x: &cap_x, + }, + &rp_params, + &aux, ); - assert!(proof.verify(pk0, pk1, &cap_c, &cap_d, &cap_y, &cap_x, &rp_params, &aux)); + assert!(proof.verify( + AffGPublicInputs { + pk0, + pk1, + cap_c: &cap_c, + cap_d: &cap_d, + cap_y: &cap_y, + cap_x: &cap_x, + }, + &rp_params, + &aux + )); } } diff --git a/synedrion/src/cggmp21/sigma/dec.rs b/synedrion/src/cggmp21/sigma/dec.rs index 6a2b20f..0ded5da 100644 --- a/synedrion/src/cggmp21/sigma/dec.rs +++ b/synedrion/src/cggmp21/sigma/dec.rs @@ -19,20 +19,24 @@ use crate::{ 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$). -*/ +pub(crate) struct DecSecretInputs<'a, P: SchemeParams> { + /// $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). + pub y: &'a SecretSigned<::Uint>, + /// $\rho$, a Paillier randomizer for the public key $N_0$. + pub rho: &'a Randomizer, +} + +pub(crate) struct DecPublicInputs<'a, P: SchemeParams> { + /// Paillier public key $N_0$. + pub pk0: &'a PublicKeyPaillier, + /// Scalar $x = y \mod q$, where $q$ is the curve order. + pub x: &'a Scalar, + /// Paillier ciphertext $C = enc_0(y, \rho)$. + pub cap_c: &'a Ciphertext, +} + +/// ZK proof: Paillier decryption modulo $q$. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct DecProof { e: PublicSigned<::Uint>, @@ -46,29 +50,25 @@ pub(crate) struct DecProof { } impl DecProof

{ - #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - y: &SecretSigned<::Uint>, - rho: &Randomizer, - pk0: &PublicKeyPaillier, - x: &Scalar, - cap_c: &Ciphertext, + secret: DecSecretInputs<'_, P>, + public: DecPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> Self { - assert_eq!(cap_c.public_key(), pk0); + assert_eq!(public.cap_c.public_key(), public.pk0); let hat_cap_n = setup.modulus(); // $\hat{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 r = Randomizer::random(rng, public.pk0); - let cap_s = setup.commit(y, &mu).to_wire(); + let cap_s = setup.commit(secret.y, &mu).to_wire(); let cap_t = setup.commit(&alpha, &nu).to_wire(); - let cap_a = Ciphertext::new_with_randomizer_signed(pk0, &alpha, &r).to_wire(); + let cap_a = Ciphertext::new_with_randomizer_signed(public.pk0, &alpha, &r).to_wire(); // `alpha` is secret, but `gamma` only uncovers $\ell$ bits of `alpha`'s full $\ell + \eps$ bits, // and it's transmitted to another node, so it can be considered public. @@ -84,9 +84,9 @@ impl DecProof

{ .chain(&cap_a) .chain(&gamma) // public parameters - .chain(pk0.as_wire()) - .chain(x) - .chain(&cap_c.to_wire()) + .chain(public.pk0.as_wire()) + .chain(public.x) + .chain(&public.cap_c.to_wire()) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -94,10 +94,10 @@ impl DecProof

{ // Non-interactive challenge let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = (alpha.to_wide() + y.mul_wide(&e)).to_public(); + let z1 = (alpha.to_wide() + secret.y.mul_wide(&e)).to_public(); let z2 = (nu + mu * e.to_wide()).to_public(); - let omega = rho.to_masked(&r, &e); + let omega = secret.rho.to_masked(&r, &e); Self { e, @@ -111,15 +111,8 @@ impl DecProof

{ } } - pub fn verify( - &self, - pk0: &PublicKeyPaillier, - x: &Scalar, - cap_c: &Ciphertext, - setup: &RPParams, - aux: &impl Hashable, - ) -> bool { - assert_eq!(cap_c.public_key(), pk0); + pub fn verify(&self, public: DecPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable) -> bool { + assert_eq!(public.cap_c.public_key(), public.pk0); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -128,9 +121,9 @@ impl DecProof

{ .chain(&self.cap_a) .chain(&self.gamma) // public parameters - .chain(pk0.as_wire()) - .chain(x) - .chain(&cap_c.to_wire()) + .chain(public.pk0.as_wire()) + .chain(public.x) + .chain(&public.cap_c.to_wire()) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -143,14 +136,14 @@ 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 + if Ciphertext::new_public_with_randomizer_wide(public.pk0, &self.z1, &self.omega) + != self.cap_a.to_precomputed(public.pk0) + public.cap_c * &e { return false; } // z_1 == \gamma + e x \mod q - if scalar_from_wide_signed::

(&self.z1) != self.gamma + scalar_from_signed::

(&e) * *x { + if scalar_from_wide_signed::

(&self.z1) != self.gamma + scalar_from_signed::

(&e) * *public.x { return false; } @@ -169,7 +162,7 @@ impl DecProof

{ mod tests { use rand_core::OsRng; - use super::DecProof; + use super::{DecProof, DecPublicInputs, DecSecretInputs}; use crate::{ cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, PaillierParams, RPParams, Randomizer, SecretKeyPaillierWire}, @@ -195,7 +188,25 @@ mod tests { let rho = Randomizer::random(&mut OsRng, pk); let cap_c = Ciphertext::new_with_randomizer_signed(pk, &y, &rho); - let proof = DecProof::::new(&mut OsRng, &y, &rho, pk, &x, &cap_c, &setup, &aux); - assert!(proof.verify(pk, &x, &cap_c, &setup, &aux)); + let proof = DecProof::::new( + &mut OsRng, + DecSecretInputs { y: &y, rho: &rho }, + DecPublicInputs { + pk0: pk, + x: &x, + cap_c: &cap_c, + }, + &setup, + &aux, + ); + assert!(proof.verify( + DecPublicInputs { + pk0: pk, + x: &x, + cap_c: &cap_c + }, + &setup, + &aux + )); } } diff --git a/synedrion/src/cggmp21/sigma/enc.rs b/synedrion/src/cggmp21/sigma/enc.rs index 7ae33a2..e6a963f 100644 --- a/synedrion/src/cggmp21/sigma/enc.rs +++ b/synedrion/src/cggmp21/sigma/enc.rs @@ -15,18 +15,21 @@ use crate::{ const HASH_TAG: &[u8] = b"P_enc"; -/** -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$). -*/ +pub struct EncSecretInputs<'a, P: SchemeParams> { + /// $k \in \pm 2^\ell$. + pub k: &'a SecretSigned<::Uint>, + /// $\rho$, a Paillier randomizer for the public key $N_0$. + pub rho: &'a Randomizer, +} + +pub struct EncPublicInputs<'a, P: SchemeParams> { + /// Paillier public key $N_0$. + pub pk0: &'a PublicKeyPaillier, + /// Paillier ciphertext $K = enc_0(k, \rho)$. + pub cap_k: &'a Ciphertext, +} + +/// ZK proof: Paillier encryption in range. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct EncProof { e: PublicSigned<::Uint>, @@ -41,15 +44,13 @@ pub(crate) struct EncProof { impl EncProof

{ pub fn new( rng: &mut impl CryptoRngCore, - k: &SecretSigned<::Uint>, - rho: &Randomizer, - pk0: &PublicKeyPaillier, - cap_k: &Ciphertext, + secret: EncSecretInputs<'_, P>, + public: EncPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> Self { - k.assert_exponent_range(P::L_BOUND); - assert_eq!(cap_k.public_key(), pk0); + secret.k.assert_exponent_range(P::L_BOUND); + assert_eq!(public.cap_k.public_key(), public.pk0); let hat_cap_n = setup.modulus(); // $\hat{N}$ @@ -57,11 +58,11 @@ impl EncProof

{ // This will ensure that the range check on the prover side will pass. 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 r = Randomizer::random(rng, public.pk0); 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(); + let cap_s = setup.commit(secret.k, &mu).to_wire(); + let cap_a = Ciphertext::new_with_randomizer_signed(public.pk0, &alpha, &r).to_wire(); let cap_c = setup.commit(&alpha, &gamma).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) @@ -70,8 +71,8 @@ impl EncProof

{ .chain(&cap_a) .chain(&cap_c) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_k.to_wire()) + .chain(public.pk0.as_wire()) + .chain(&public.cap_k.to_wire()) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -79,8 +80,8 @@ impl EncProof

{ // Non-interactive challenge let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = (alpha + k * e).to_public(); - let z2 = rho.to_masked(&r, &e); + let z1 = (alpha + secret.k * e).to_public(); + let z2 = secret.rho.to_masked(&r, &e); let z3 = (gamma + mu * e.to_wide()).to_public(); Self { @@ -94,14 +95,8 @@ impl EncProof

{ } } - pub fn verify( - &self, - pk0: &PublicKeyPaillier, - cap_k: &Ciphertext, - setup: &RPParams, - aux: &impl Hashable, - ) -> bool { - assert_eq!(cap_k.public_key(), pk0); + pub fn verify(&self, public: EncPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable) -> bool { + assert_eq!(public.cap_k.public_key(), public.pk0); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -109,8 +104,8 @@ impl EncProof

{ .chain(&self.cap_a) .chain(&self.cap_c) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_k.to_wire()) + .chain(public.pk0.as_wire()) + .chain(&public.cap_k.to_wire()) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -128,8 +123,8 @@ 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 { + let c = Ciphertext::new_public_with_randomizer_signed(public.pk0, &self.z1, &self.z2); + if c != self.cap_a.to_precomputed(public.pk0) + public.cap_k * &e { return false; } @@ -148,7 +143,7 @@ impl EncProof

{ mod tests { use rand_core::OsRng; - use super::EncProof; + use super::{EncProof, EncPublicInputs, EncSecretInputs}; use crate::{ cggmp21::{SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, @@ -171,7 +166,26 @@ mod tests { let randomizer = Randomizer::random(&mut OsRng, pk); let ciphertext = Ciphertext::new_with_randomizer_signed(pk, &secret, &randomizer); - let proof = EncProof::::new(&mut OsRng, &secret, &randomizer, pk, &ciphertext, &setup, &aux); - assert!(proof.verify(pk, &ciphertext, &setup, &aux)); + let proof = EncProof::::new( + &mut OsRng, + EncSecretInputs { + k: &secret, + rho: &randomizer, + }, + EncPublicInputs { + pk0: pk, + cap_k: &ciphertext, + }, + &setup, + &aux, + ); + assert!(proof.verify( + EncPublicInputs { + pk0: pk, + cap_k: &ciphertext + }, + &setup, + &aux + )); } } diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index 43ff2bd..5e790e4 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -19,20 +19,25 @@ use crate::{ const HASH_TAG: &[u8] = b"P_log*"; -/** -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$). -*/ +pub(crate) struct LogStarSecretInputs<'a, P: SchemeParams> { + /// $x \in \pm 2^\ell$. + pub x: &'a SecretSigned<::Uint>, + /// $\rho$, a Paillier randomizer for the public key $N_0$. + pub rho: &'a Randomizer, +} + +pub(crate) struct LogStarPublicInputs<'a, P: SchemeParams> { + /// Paillier public key $N_0$. + pub pk0: &'a PublicKeyPaillier, + /// Paillier ciphertext $C = enc_0(x, \rho)$. + pub cap_c: &'a Ciphertext, + /// Point $g$. + pub g: &'a Point, + /// Point $X = g * x$. + pub cap_x: &'a Point, +} + +/// ZK proof: Knowledge of Exponent vs Paillier Encryption. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct LogStarProof { e: PublicSigned<::Uint>, @@ -46,31 +51,26 @@ pub(crate) struct LogStarProof { } impl LogStarProof

{ - #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &SecretSigned<::Uint>, - rho: &Randomizer, - pk0: &PublicKeyPaillier, - cap_c: &Ciphertext, - g: &Point, - cap_x: &Point, + secret: LogStarSecretInputs<'_, P>, + public: LogStarPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> Self { - x.assert_exponent_range(P::L_BOUND); - assert_eq!(cap_c.public_key(), pk0); + secret.x.assert_exponent_range(P::L_BOUND); + assert_eq!(public.cap_c.public_key(), public.pk0); let hat_cap_n = setup.modulus(); // $\hat{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 r = Randomizer::random(rng, public.pk0); 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(); - let cap_y = g * secret_scalar_from_signed::

(&alpha); + let cap_s = setup.commit(secret.x, &mu).to_wire(); + let cap_a = Ciphertext::new_with_randomizer_signed(public.pk0, &alpha, &r).to_wire(); + let cap_y = public.g * secret_scalar_from_signed::

(&alpha); let cap_d = setup.commit(&alpha, &gamma).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) @@ -80,10 +80,10 @@ impl LogStarProof

{ .chain(&cap_y) .chain(&cap_d) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_c.to_wire()) - .chain(g) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(public.g) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -91,8 +91,8 @@ impl LogStarProof

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

{ #[allow(clippy::too_many_arguments)] pub fn verify( &self, - pk0: &PublicKeyPaillier, - cap_c: &Ciphertext, - g: &Point, - cap_x: &Point, + public: LogStarPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> bool { - assert_eq!(cap_c.public_key(), pk0); + assert_eq!(public.cap_c.public_key(), public.pk0); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -126,10 +123,10 @@ impl LogStarProof

{ .chain(&self.cap_y) .chain(&self.cap_d) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_c.to_wire()) - .chain(g) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(public.g) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -147,13 +144,13 @@ 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 { + let c = Ciphertext::new_public_with_randomizer_signed(public.pk0, &self.z1, &self.z2); + if c != self.cap_a.to_precomputed(public.pk0) + public.cap_c * &e { return false; } // g^{z_1} == Y X^e - if g * &scalar_from_signed::

(&self.z1) != self.cap_y + cap_x * &scalar_from_signed::

(&e) { + if public.g * &scalar_from_signed::

(&self.z1) != self.cap_y + public.cap_x * &scalar_from_signed::

(&e) { return false; } @@ -172,7 +169,7 @@ impl LogStarProof

{ mod tests { use rand_core::OsRng; - use super::LogStarProof; + use super::{LogStarProof, LogStarPublicInputs, LogStarSecretInputs}; use crate::{ cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, curve::{Point, Scalar}, @@ -198,7 +195,27 @@ mod tests { let cap_c = Ciphertext::new_with_randomizer_signed(pk, &x, &rho); let cap_x = g * secret_scalar_from_signed::(&x); - 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)); + let proof = LogStarProof::::new( + &mut OsRng, + LogStarSecretInputs { x: &x, rho: &rho }, + LogStarPublicInputs { + pk0: pk, + cap_c: &cap_c, + g: &g, + cap_x: &cap_x, + }, + &setup, + &aux, + ); + assert!(proof.verify( + LogStarPublicInputs { + pk0: pk, + cap_c: &cap_c, + g: &g, + cap_x: &cap_x + }, + &setup, + &aux + )); } } diff --git a/synedrion/src/cggmp21/sigma/mul.rs b/synedrion/src/cggmp21/sigma/mul.rs index be04a39..3ea66f1 100644 --- a/synedrion/src/cggmp21/sigma/mul.rs +++ b/synedrion/src/cggmp21/sigma/mul.rs @@ -15,6 +15,27 @@ use crate::{ const HASH_TAG: &[u8] = b"P_mul"; +pub(crate) struct MulSecretInputs<'a, P: SchemeParams> { + /// $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). + pub x: &'a SecretSigned<::Uint>, + /// $\rho_x$, a Paillier randomizer for the public key $N$. + pub rho_x: &'a Randomizer, + /// $\rho$, a Paillier randomizer for the public key $N$. + pub rho: &'a Randomizer, +} + +pub(crate) struct MulPublicInputs<'a, P: SchemeParams> { + /// Paillier public key $N$. + pub pk: &'a PublicKeyPaillier, + /// Paillier ciphertext $X = enc(x, \rho_x)$. + pub cap_x: &'a Ciphertext, + /// Paillier ciphertext $Y$ encrypted with $N$. + pub cap_y: &'a Ciphertext, + /// Paillier ciphertext $C = (Y (*) x) * \rho^N \mod N^2$. + pub cap_c: &'a Ciphertext, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulProof { e: PublicSigned<::Uint>, @@ -25,58 +46,38 @@ pub(crate) struct MulProof { v: MaskedRandomizer, } -/** -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$). -*/ +/// ZK proof: Paillier multiplication. impl MulProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &SecretSigned<::Uint>, - rho_x: &Randomizer, - rho: &Randomizer, - pk: &PublicKeyPaillier, - cap_x: &Ciphertext, - cap_y: &Ciphertext, - cap_c: &Ciphertext, + secret: MulSecretInputs<'_, P>, + public: MulPublicInputs<'_, P>, aux: &impl Hashable, ) -> Self { - assert_eq!(cap_x.public_key(), pk); - assert_eq!(cap_y.public_key(), pk); - assert_eq!(cap_c.public_key(), pk); + assert_eq!(public.cap_x.public_key(), public.pk); + assert_eq!(public.cap_y.public_key(), public.pk); + assert_eq!(public.cap_c.public_key(), public.pk); - let alpha_uint = Secret::init_with(|| pk.random_invertible_residue(rng)); + let alpha_uint = Secret::init_with(|| public.pk.random_invertible_residue(rng)); 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 r = Randomizer::random(rng, public.pk); + let s = Randomizer::random(rng, public.pk); - let cap_a = (cap_y * &alpha).mul_randomizer(&r).to_wire(); - let cap_b = Ciphertext::new_with_randomizer(pk, &alpha, &s).to_wire(); + let cap_a = (public.cap_y * &alpha).mul_randomizer(&r).to_wire(); + let cap_b = Ciphertext::new_with_randomizer(public.pk, &alpha, &s).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments .chain(&cap_a) .chain(&cap_b) // public parameters - .chain(pk.as_wire()) - .chain(&cap_x.to_wire()) - .chain(&cap_y.to_wire()) - .chain(&cap_c.to_wire()) + .chain(public.pk.as_wire()) + .chain(&public.cap_x.to_wire()) + .chain(&public.cap_y.to_wire()) + .chain(&public.cap_c.to_wire()) .chain(aux) .finalize_to_reader(); @@ -87,10 +88,10 @@ impl MulProof

{ .to_wide() .into_signed() .expect("conversion to `WideUint` provides enough space for a sign bit") - + x.mul_wide(&e)) + + secret.x.mul_wide(&e)) .to_public(); - let u = rho.to_masked(&r, &e); - let v = rho_x.to_masked(&s, &e); + let u = secret.rho.to_masked(&r, &e); + let v = secret.rho_x.to_masked(&s, &e); Self { e, @@ -102,27 +103,20 @@ impl MulProof

{ } } - pub fn verify( - &self, - pk: &PublicKeyPaillier, - cap_x: &Ciphertext, - cap_y: &Ciphertext, - cap_c: &Ciphertext, - aux: &impl Hashable, - ) -> bool { - assert_eq!(cap_x.public_key(), pk); - assert_eq!(cap_y.public_key(), pk); - assert_eq!(cap_c.public_key(), pk); + pub fn verify(&self, public: MulPublicInputs<'_, P>, aux: &impl Hashable) -> bool { + assert_eq!(public.cap_x.public_key(), public.pk); + assert_eq!(public.cap_y.public_key(), public.pk); + assert_eq!(public.cap_c.public_key(), public.pk); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments .chain(&self.cap_a) .chain(&self.cap_b) // public parameters - .chain(pk.as_wire()) - .chain(&cap_x.to_wire()) - .chain(&cap_y.to_wire()) - .chain(&cap_c.to_wire()) + .chain(public.pk.as_wire()) + .chain(&public.cap_x.to_wire()) + .chain(&public.cap_y.to_wire()) + .chain(&public.cap_c.to_wire()) .chain(aux) .finalize_to_reader(); @@ -134,14 +128,16 @@ impl MulProof

{ } // Y^z u^N = A * C^e \mod N^2 - if (cap_y * &self.z).mul_masked_randomizer(&self.u) != self.cap_a.to_precomputed(pk) + cap_c * &e { + if (public.cap_y * &self.z).mul_masked_randomizer(&self.u) + != self.cap_a.to_precomputed(public.pk) + public.cap_c * &e + { return false; } // 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 + if Ciphertext::new_public_with_randomizer_wide(public.pk, &self.z, &self.v) + != self.cap_b.to_precomputed(public.pk) + public.cap_x * &e { return false; } @@ -154,7 +150,7 @@ impl MulProof

{ mod tests { use rand_core::OsRng; - use super::MulProof; + use super::{MulProof, MulPublicInputs, MulSecretInputs}; use crate::{ cggmp21::{SchemeParams, TestParams}, paillier::{Ciphertext, Randomizer, SecretKeyPaillierWire}, @@ -180,7 +176,29 @@ mod tests { let cap_y = Ciphertext::new_signed(&mut OsRng, pk, &y); let cap_c = (&cap_y * &x).mul_randomizer(&rho); - 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)); + let proof = MulProof::::new( + &mut OsRng, + MulSecretInputs { + x: &x, + rho_x: &rho_x, + rho: &rho, + }, + MulPublicInputs { + pk, + cap_x: &cap_x, + cap_y: &cap_y, + cap_c: &cap_c, + }, + &aux, + ); + assert!(proof.verify( + MulPublicInputs { + pk, + cap_x: &cap_x, + cap_y: &cap_y, + cap_c: &cap_c + }, + &aux + )); } } diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index 57947c8..b6f4c56 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -19,20 +19,25 @@ use crate::{ 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$). -*/ +pub(crate) struct MulStarSecretInputs<'a, P: SchemeParams> { + /// $x \in +- 2^\ell$. + pub x: &'a SecretSigned<::Uint>, + /// $\rho$, a Paillier randomizer for the public key $N_0$. + pub rho: &'a Randomizer, +} + +pub(crate) struct MulStarPublicInputs<'a, P: SchemeParams> { + /// Paillier public key $N_0$. + pub pk0: &'a PublicKeyPaillier, + /// Paillier ciphertext $C$ encrypted with $N_0$. + pub cap_c: &'a Ciphertext, + /// Paillier ciphertext $D = (C (*) x) * \rho^{N_0} \mod N_0^2$. + pub cap_d: &'a Ciphertext, + /// Point $X = g * x$, where $g$ is the curve generator. + pub cap_x: &'a Point, +} + +/// ZK proof: Multiplication Paillier vs Group. #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct MulStarProof { e: PublicSigned<::Uint>, @@ -49,12 +54,8 @@ impl MulStarProof

{ #[allow(clippy::too_many_arguments)] pub fn new( rng: &mut impl CryptoRngCore, - x: &SecretSigned<::Uint>, - rho: &Randomizer, - pk0: &PublicKeyPaillier, - cap_c: &Ciphertext, - cap_d: &Ciphertext, - cap_x: &Point, + secret: MulStarSecretInputs<'_, P>, + public: MulStarPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> Self { @@ -65,21 +66,21 @@ impl MulStarProof

{ - $\beta$ used to create $A$ is not mentioned anywhere else - a typo, it is effectively == 0 */ - x.assert_exponent_range(P::L_BOUND); - assert_eq!(cap_c.public_key(), pk0); - assert_eq!(cap_d.public_key(), pk0); + secret.x.assert_exponent_range(P::L_BOUND); + assert_eq!(public.cap_c.public_key(), public.pk0); + assert_eq!(public.cap_d.public_key(), public.pk0); let hat_cap_n = setup.modulus(); // $\hat{N}$ - let r = Randomizer::random(rng, pk0); + let r = Randomizer::random(rng, public.pk0); 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_a = (public.cap_c * &alpha).mul_randomizer(&r).to_wire(); let cap_b_x = secret_scalar_from_signed::

(&alpha).mul_by_generator(); let cap_e = setup.commit(&alpha, &gamma).to_wire(); - let cap_s = setup.commit(x, &m).to_wire(); + let cap_s = setup.commit(secret.x, &m).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -88,10 +89,10 @@ impl MulStarProof

{ .chain(&cap_e) .chain(&cap_s) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_c.to_wire()) - .chain(&cap_d.to_wire()) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_d.to_wire()) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -99,9 +100,9 @@ impl MulStarProof

{ // Non-interactive challenge let e = PublicSigned::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER); - let z1 = (alpha + x * e).to_public(); + let z1 = (alpha + secret.x * e).to_public(); let z2 = (gamma + m * e.to_wide()).to_public(); - let omega = rho.to_masked(&r, &e); + let omega = secret.rho.to_masked(&r, &e); Self { e, @@ -119,15 +120,12 @@ impl MulStarProof

{ #[allow(clippy::too_many_arguments)] pub fn verify( &self, - pk0: &PublicKeyPaillier, - cap_c: &Ciphertext, - cap_d: &Ciphertext, - cap_x: &Point, + public: MulStarPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable, ) -> bool { - assert_eq!(cap_c.public_key(), pk0); - assert_eq!(cap_d.public_key(), pk0); + assert_eq!(public.cap_c.public_key(), public.pk0); + assert_eq!(public.cap_d.public_key(), public.pk0); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -136,10 +134,10 @@ impl MulStarProof

{ .chain(&self.cap_e) .chain(&self.cap_s) // public parameters - .chain(pk0.as_wire()) - .chain(&cap_c.to_wire()) - .chain(&cap_d.to_wire()) - .chain(cap_x) + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_d.to_wire()) + .chain(public.cap_x) .chain(&setup.to_wire()) .chain(aux) .finalize_to_reader(); @@ -157,12 +155,16 @@ 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 (public.cap_c * &self.z1).mul_masked_randomizer(&self.omega) + != self.cap_a.to_precomputed(public.pk0) + public.cap_d * &e + { return false; } // g^{z_1} == B_x X^e - if scalar_from_signed::

(&self.z1).mul_by_generator() != self.cap_b_x + cap_x * &scalar_from_signed::

(&e) { + if scalar_from_signed::

(&self.z1).mul_by_generator() + != self.cap_b_x + public.cap_x * &scalar_from_signed::

(&e) + { return false; } @@ -181,7 +183,7 @@ impl MulStarProof

{ mod tests { use rand_core::OsRng; - use super::MulStarProof; + use super::{MulStarProof, MulStarPublicInputs, MulStarSecretInputs}; use crate::{ cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, @@ -207,7 +209,27 @@ mod tests { let cap_d = (&cap_c * &x).mul_randomizer(&rho); let cap_x = secret_scalar_from_signed::(&x).mul_by_generator(); - 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)); + let proof = MulStarProof::::new( + &mut OsRng, + MulStarSecretInputs { x: &x, rho: &rho }, + MulStarPublicInputs { + pk0: pk, + cap_c: &cap_c, + cap_d: &cap_d, + cap_x: &cap_x, + }, + &setup, + &aux, + ); + assert!(proof.verify( + MulStarPublicInputs { + pk0: pk, + cap_c: &cap_c, + cap_d: &cap_d, + cap_x: &cap_x + }, + &setup, + &aux + )); } }