diff --git a/synedrion/src/cggmp21/sigma.rs b/synedrion/src/cggmp21/sigma.rs index 083b59b..a3d8460 100644 --- a/synedrion/src/cggmp21/sigma.rs +++ b/synedrion/src/cggmp21/sigma.rs @@ -5,6 +5,7 @@ mod dec; mod dec_new; mod elog; mod enc; +mod enc_elg; mod fac; mod log_star; mod mod_; @@ -17,6 +18,7 @@ pub(crate) use aff_g::{AffGProof, AffGPublicInputs, AffGSecretInputs}; pub(crate) use dec::{DecProof, DecPublicInputs, DecSecretInputs}; pub(crate) use elog::{ElogProof, ElogPublicInputs, ElogSecretInputs}; pub(crate) use enc::{EncProof, EncPublicInputs, EncSecretInputs}; +pub(crate) use enc_elg::{EncElgProof, EncElgPublicInputs, EncElgSecretInputs}; pub(crate) use fac::FacProof; pub(crate) use log_star::{LogStarProof, LogStarPublicInputs, LogStarSecretInputs}; pub(crate) use mod_::ModProof; diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index d16b137..43a78cb 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -220,7 +220,7 @@ impl AffGProof

{ // g^{z_1} = B_x X^e if scalar_from_signed::

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

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

(&e) { return false; } diff --git a/synedrion/src/cggmp21/sigma/enc_elg.rs b/synedrion/src/cggmp21/sigma/enc_elg.rs new file mode 100644 index 0000000..d3fe572 --- /dev/null +++ b/synedrion/src/cggmp21/sigma/enc_elg.rs @@ -0,0 +1,264 @@ +//! Range Proof w/ EL-Gamal Commitment ($\Pi^{enc-elg}$, Section A.2, Fig. 24) + +#![allow(dead_code)] + +use rand_core::CryptoRngCore; +use serde::{Deserialize, Serialize}; + +use super::super::{ + conversion::{public_signed_from_scalar, scalar_from_signed, secret_scalar_from_signed}, + SchemeParams, +}; +use crate::{ + curve::{Point, Scalar}, + paillier::{ + Ciphertext, CiphertextWire, MaskedRandomizer, PaillierParams, PublicKeyPaillier, RPCommitmentWire, RPParams, + Randomizer, + }, + tools::{ + hashing::{Chain, Hashable, XofHasher}, + Secret, + }, + uint::{PublicSigned, SecretSigned}, +}; + +const HASH_TAG: &[u8] = b"P_enc_elg"; + +pub struct EncElgSecretInputs<'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, + /// Scalar $a$. + pub a: &'a Secret, + /// Scalar $b$. + pub b: &'a Secret, +} + +pub struct EncElgPublicInputs<'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 $A = g * a$, where $g$ is the curve generator. + pub cap_a: &'a Point, + /// Point $B = g * b$, where $g$ is the curve generator. + pub cap_b: &'a Point, + /// Point $X = g * (a b + x)$, where $g$ is the curve generator. + pub cap_x: &'a Point, +} + +/// ZK proof: Paillier encryption in range. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct EncElgProof { + e: Scalar, + cap_s: RPCommitmentWire, + cap_d: CiphertextWire, + cap_y: Point, + cap_z: Point, + cap_t: RPCommitmentWire, + z1: PublicSigned<::Uint>, + w: Scalar, + z2: MaskedRandomizer, + z3: PublicSigned<::WideUint>, +} + +impl EncElgProof

{ + pub fn new( + rng: &mut impl CryptoRngCore, + secret: EncElgSecretInputs<'_, P>, + public: EncElgPublicInputs<'_, P>, + setup: &RPParams, + aux: &impl Hashable, + ) -> Self { + 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_exponent_range(rng, P::L_BOUND + P::EPS_BOUND); + let mu = SecretSigned::random_in_exponent_range_scaled(rng, P::L_BOUND, hat_cap_n); + let r = Randomizer::random(rng, public.pk0); + let beta = Secret::init_with(|| Scalar::random(rng)); + let gamma = SecretSigned::random_in_exponent_range_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n); + + let cap_s = setup.commit(secret.x, &mu).to_wire(); + let cap_d = Ciphertext::new_with_randomizer(public.pk0, &alpha, &r).to_wire(); + let cap_y = public.cap_a * &beta + secret_scalar_from_signed::

(&alpha).mul_by_generator(); + let cap_z = beta.mul_by_generator(); + let cap_t = setup.commit(&alpha, &gamma).to_wire(); + + let mut reader = XofHasher::new_with_dst(HASH_TAG) + // commitments + .chain(&cap_s) + .chain(&cap_d) + .chain(&cap_y) + .chain(&cap_z) + .chain(&cap_t) + // public parameters + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_a) + .chain(&public.cap_b) + .chain(&public.cap_x) + .chain(&setup.to_wire()) + .chain(aux) + .finalize_to_reader(); + + // Non-interactive challenge + let e = Scalar::from_xof_reader(&mut reader); + let e_signed = public_signed_from_scalar::

(&e); + + let z1 = (alpha + secret.x * e_signed).to_public(); + let w = *(beta + secret.b * e).expose_secret(); + let z2 = secret.rho.to_masked(&r, &e_signed); + let z3 = (gamma + mu * e_signed.to_wide()).to_public(); + + Self { + e, + cap_s, + cap_d, + cap_y, + cap_z, + cap_t, + z1, + w, + z2, + z3, + } + } + + pub fn verify( + &self, + public: EncElgPublicInputs<'_, 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 + .chain(&self.cap_s) + .chain(&self.cap_d) + .chain(&self.cap_y) + .chain(&self.cap_z) + .chain(&self.cap_t) + // public parameters + .chain(public.pk0.as_wire()) + .chain(&public.cap_c.to_wire()) + .chain(&public.cap_a) + .chain(&public.cap_b) + .chain(&public.cap_x) + .chain(&setup.to_wire()) + .chain(aux) + .finalize_to_reader(); + + // Non-interactive challenge + let e = Scalar::from_xof_reader(&mut reader); + + if e != self.e { + return false; + } + + let e_signed = public_signed_from_scalar::

(&e); + + // z_1 \in \pm 2^{\ell + \eps} + if !self.z1.in_range_bits(P::L_BOUND + P::EPS_BOUND) { + return false; + } + + // enc_0(z1, z2) == D (+) C (*) e + let c = Ciphertext::new_public_with_randomizer(public.pk0, &self.z1, &self.z2); + let cap_d = self.cap_d.to_precomputed(public.pk0); + if c != cap_d + public.cap_c * &e_signed { + return false; + } + + // A * w + g * z1 == Y + X * e + if public.cap_a * self.w + scalar_from_signed::

(&self.z1).mul_by_generator() != self.cap_y + public.cap_x * e + { + return false; + } + + // g * w == Z + B * e + if self.w.mul_by_generator() != self.cap_z + public.cap_b * e { + return false; + } + + // s^{z_1} t^{z_3} == T S^e \mod \hat{N} + let cap_t = self.cap_t.to_precomputed(setup); + let cap_s = self.cap_s.to_precomputed(setup); + if setup.commit(&self.z1, &self.z3) != &cap_t * &cap_s.pow(&e_signed) { + return false; + } + + true + } +} + +#[cfg(test)] +mod tests { + use rand_core::OsRng; + + use super::{EncElgProof, EncElgPublicInputs, EncElgSecretInputs}; + use crate::{ + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, + curve::Scalar, + paillier::{Ciphertext, RPParams, Randomizer, SecretKeyPaillierWire}, + tools::Secret, + uint::SecretSigned, + }; + + #[test] + fn prove_and_verify() { + type Params = TestParams; + type Paillier = ::Paillier; + + let sk = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); + let pk = sk.public_key(); + + let setup = RPParams::random(&mut OsRng); + + let aux: &[u8] = b"abcde"; + + let x = SecretSigned::random_in_exponent_range(&mut OsRng, Params::L_BOUND); + let rho = Randomizer::random(&mut OsRng, pk); + let a = Secret::init_with(|| Scalar::random(&mut OsRng)); + let b = Secret::init_with(|| Scalar::random(&mut OsRng)); + + let cap_c = Ciphertext::new_with_randomizer(pk, &x, &rho); + let cap_a = a.mul_by_generator(); + let cap_b = b.mul_by_generator(); + let cap_x = (&a * &b + secret_scalar_from_signed::(&x)).mul_by_generator(); + + let proof = EncElgProof::::new( + &mut OsRng, + EncElgSecretInputs { + x: &x, + rho: &rho, + a: &a, + b: &b, + }, + EncElgPublicInputs { + pk0: pk, + cap_c: &cap_c, + cap_a: &cap_a, + cap_b: &cap_b, + cap_x: &cap_x, + }, + &setup, + &aux, + ); + assert!(proof.verify( + EncElgPublicInputs { + pk0: pk, + cap_c: &cap_c, + cap_a: &cap_a, + cap_b: &cap_b, + cap_x: &cap_x, + }, + &setup, + &aux + )); + } +} diff --git a/synedrion/src/cggmp21/sigma/log_star.rs b/synedrion/src/cggmp21/sigma/log_star.rs index b32500c..50084dd 100644 --- a/synedrion/src/cggmp21/sigma/log_star.rs +++ b/synedrion/src/cggmp21/sigma/log_star.rs @@ -150,7 +150,7 @@ impl LogStarProof

{ } // g^{z_1} == Y X^e - if public.g * &scalar_from_signed::

(&self.z1) != self.cap_y + public.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; } diff --git a/synedrion/src/cggmp21/sigma/mul_star.rs b/synedrion/src/cggmp21/sigma/mul_star.rs index cd17e34..18fa8f3 100644 --- a/synedrion/src/cggmp21/sigma/mul_star.rs +++ b/synedrion/src/cggmp21/sigma/mul_star.rs @@ -163,7 +163,7 @@ impl MulStarProof

{ // g^{z_1} == B_x X^e if scalar_from_signed::

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

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

(&e) { return false; } diff --git a/synedrion/src/cggmp21/sigma/sch.rs b/synedrion/src/cggmp21/sigma/sch.rs index 08e3bfc..101f3bc 100644 --- a/synedrion/src/cggmp21/sigma/sch.rs +++ b/synedrion/src/cggmp21/sigma/sch.rs @@ -84,7 +84,7 @@ impl SchProof { 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 + cap_x * &challenge.0 + challenge == self.challenge && self.proof.mul_by_generator() == commitment.0 + cap_x * challenge.0 } } diff --git a/synedrion/src/tools/secret.rs b/synedrion/src/tools/secret.rs index 860a755..ddf91ef 100644 --- a/synedrion/src/tools/secret.rs +++ b/synedrion/src/tools/secret.rs @@ -233,6 +233,13 @@ impl<'a, T: Zeroize + Clone + Mul<&'a T, Output = T>> Mul<&'a Secret> for Sec } } +impl<'a, T: Zeroize + Clone + Mul<&'a T, Output = T>> Mul<&'a Secret> for &Secret { + type Output = Secret; + fn mul(self, rhs: &'a Secret) -> Self::Output { + self * rhs.expose_secret() + } +} + // Division impl<'a, T: Zeroize + DivAssign<&'a NonZero>> DivAssign<&'a NonZero> for Secret { diff --git a/synedrion/src/tools/sss.rs b/synedrion/src/tools/sss.rs index 442aac7..a3e12fa 100644 --- a/synedrion/src/tools/sss.rs +++ b/synedrion/src/tools/sss.rs @@ -134,7 +134,7 @@ pub(crate) fn shamir_join_points(pairs: &BTreeMap) -> Point { let share_ids = pairs.keys().cloned().collect::>(); pairs .iter() - .map(|(share_id, val)| val * &interpolation_coeff(&share_ids, share_id)) + .map(|(share_id, val)| val * interpolation_coeff(&share_ids, share_id)) .sum() } diff --git a/synedrion/src/www02/entities.rs b/synedrion/src/www02/entities.rs index 80e0acd..7e48d63 100644 --- a/synedrion/src/www02/entities.rs +++ b/synedrion/src/www02/entities.rs @@ -168,7 +168,7 @@ impl ThresholdKeyShare>()?; @@ -208,7 +208,7 @@ impl ThresholdKeyShare Round for Round1 { payload .public_polynomial .coeff0() - .map(|coeff0| coeff0 * &interpolation_coeff(&old_share_ids, &payload.old_share_id)) + .map(|coeff0| coeff0 * interpolation_coeff(&old_share_ids, &payload.old_share_id)) }) .sum::>()?; if Point::from_verifying_key(&new_holder.inputs.verifying_key) != vkey {