Skip to content

Commit

Permalink
Argument grouping for ZK proofs (#169)
Browse files Browse the repository at this point in the history
* Group aff-g inputs

* Group dec inputs

* Group enc inputs

* Group log* inputs

* Group mul inputs

* Group mul* inputs
  • Loading branch information
fjarri authored Dec 19, 2024
1 parent a7dc794 commit 423f1b5
Show file tree
Hide file tree
Showing 8 changed files with 673 additions and 481 deletions.
387 changes: 239 additions & 148 deletions synedrion/src/cggmp21/interactive_signing.rs

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions synedrion/src/cggmp21/sigma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
191 changes: 105 additions & 86 deletions synedrion/src/cggmp21/sigma/aff_g.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<<P::Paillier as PaillierParams>::Uint>,
/// $y \in \pm 2^{\ell^\prime}$.
pub y: &'a SecretSigned<<P::Paillier as PaillierParams>::Uint>,
/// $\rho$, a Paillier randomizer for the public key $N_0$.
pub rho: &'a Randomizer<P::Paillier>,
/// $\rho_y$, a Paillier randomizer for the public key $N_1$.
pub rho_y: &'a Randomizer<P::Paillier>,
}

pub(crate) struct AffGPublicInputs<'a, P: SchemeParams> {
/// Paillier public keys $N_0$.
pub pk0: &'a PublicKeyPaillier<P::Paillier>,
/// Paillier public keys $N_1$.
pub pk1: &'a PublicKeyPaillier<P::Paillier>,
/// Paillier ciphertext $C$ encrypted with $N_0$.
pub cap_c: &'a Ciphertext<P::Paillier>,
/// 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<P::Paillier>,
/// Paillier ciphertext $Y = enc_1(y, \rho_y)$.
pub cap_y: &'a Ciphertext<P::Paillier>,
/// 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<P: SchemeParams> {
e: PublicSigned<<P::Paillier as PaillierParams>::Uint>,
Expand All @@ -60,52 +69,43 @@ pub(crate) struct AffGProof<P: SchemeParams> {
}

impl<P: SchemeParams> AffGProof<P> {
#[allow(clippy::too_many_arguments)]
pub fn new(
rng: &mut impl CryptoRngCore,
x: &SecretSigned<<P::Paillier as PaillierParams>::Uint>,
y: &SecretSigned<<P::Paillier as PaillierParams>::Uint>,
rho: &Randomizer<P::Paillier>,
rho_y: &Randomizer<P::Paillier>,
pk0: &PublicKeyPaillier<P::Paillier>,
pk1: &PublicKeyPaillier<P::Paillier>,
cap_c: &Ciphertext<P::Paillier>,
cap_d: &Ciphertext<P::Paillier>,
cap_y: &Ciphertext<P::Paillier>,
cap_x: &Point,
secret: AffGSecretInputs<'_, P>,
public: AffGPublicInputs<'_, P>,
setup: &RPParams<P::Paillier>,
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::<P>(&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
Expand All @@ -117,12 +117,12 @@ impl<P: SchemeParams> AffGProof<P> {
.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();
Expand All @@ -131,23 +131,23 @@ impl<P: SchemeParams> AffGProof<P> {
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,
Expand All @@ -168,20 +168,10 @@ impl<P: SchemeParams> AffGProof<P> {
}

#[allow(clippy::too_many_arguments)]
pub fn verify(
&self,
pk0: &PublicKeyPaillier<P::Paillier>,
pk1: &PublicKeyPaillier<P::Paillier>,
cap_c: &Ciphertext<P::Paillier>,
cap_d: &Ciphertext<P::Paillier>,
cap_y: &Ciphertext<P::Paillier>,
cap_x: &Point,
setup: &RPParams<P::Paillier>,
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<P::Paillier>, 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
Expand All @@ -193,12 +183,12 @@ impl<P: SchemeParams> AffGProof<P> {
.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();
Expand All @@ -222,24 +212,26 @@ impl<P: SchemeParams> AffGProof<P> {

// 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::<P>(&self.z1).mul_by_generator() != self.cap_b_x + cap_x * &scalar_from_signed::<P>(&e) {
if scalar_from_signed::<P>(&self.z1).mul_by_generator()
!= self.cap_b_x + public.cap_x * &scalar_from_signed::<P>(&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;
}
Expand All @@ -266,7 +258,7 @@ impl<P: SchemeParams> AffGProof<P> {
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},
Expand Down Expand Up @@ -301,8 +293,35 @@ mod tests {
let cap_x = secret_scalar_from_signed::<TestParams>(&x).mul_by_generator();

let proof = AffGProof::<Params>::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
));
}
}
Loading

0 comments on commit 423f1b5

Please sign in to comment.