Skip to content

Commit

Permalink
Add aff-g*
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Dec 20, 2024
1 parent f9a039f commit f97d797
Show file tree
Hide file tree
Showing 2 changed files with 318 additions and 0 deletions.
2 changes: 2 additions & 0 deletions synedrion/src/cggmp21/sigma.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Sigma-protocols
mod aff_g;
mod aff_g_star;
mod dec;
mod dec_new;
mod elog;
Expand All @@ -15,6 +16,7 @@ mod prm;
mod sch;

pub(crate) use aff_g::{AffGProof, AffGPublicInputs, AffGSecretInputs};
pub(crate) use aff_g_star::{AffGStarProof, AffGStarPublicInputs, AffGStarSecretInputs};
pub(crate) use dec::{DecProof, DecPublicInputs, DecSecretInputs};
pub(crate) use elog::{ElogProof, ElogPublicInputs, ElogSecretInputs};
pub(crate) use enc::{EncProof, EncPublicInputs, EncSecretInputs};
Expand Down
316 changes: 316 additions & 0 deletions synedrion/src/cggmp21/sigma/aff_g_star.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
//! Setup-less Affine Operation w/ Group Commitment ($\Pi^{aff-g*}$, Fig. 27)
#![allow(dead_code)]

use alloc::{boxed::Box, vec::Vec};

use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize};

use super::super::{
conversion::{scalar_from_signed, secret_scalar_from_signed},
SchemeParams,
};
use crate::{
curve::Point,
paillier::{Ciphertext, CiphertextWire, MaskedRandomizer, PaillierParams, PublicKeyPaillier, Randomizer},
tools::{
bitvec::BitVec,
hashing::{Chain, Hashable, XofHasher},
},
uint::{PublicSigned, SecretSigned},
};

const HASH_TAG: &[u8] = b"P_aff_g*";

pub(crate) struct AffGStarSecretInputs<'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>,
/// $\mu$, a Paillier randomizer for the public key $N_1$.
pub mu: &'a Randomizer<P::Paillier>,
}

pub(crate) struct AffGStarPublicInputs<'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)$.
pub cap_d: &'a Ciphertext<P::Paillier>,
/// Paillier ciphertext $Y = enc_1(y, \mu)$.
pub cap_y: &'a Ciphertext<P::Paillier>,
/// Point $X = g * x$, where $g$ is the curve generator.
pub cap_x: &'a Point,
}

struct AffGStarProofEphemeral<P: SchemeParams> {
alpha: SecretSigned<<P::Paillier as PaillierParams>::Uint>,
beta: SecretSigned<<P::Paillier as PaillierParams>::Uint>,
r: Randomizer<P::Paillier>,
s: Randomizer<P::Paillier>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct AffGStarProofCommitment<P: SchemeParams> {
cap_a: CiphertextWire<P::Paillier>,
cap_r: Point,
cap_b: CiphertextWire<P::Paillier>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct AffGStarProofElement<P: SchemeParams> {
z: PublicSigned<<P::Paillier as PaillierParams>::Uint>,
z_prime: PublicSigned<<P::Paillier as PaillierParams>::Uint>,
w: MaskedRandomizer<P::Paillier>,
lambda: MaskedRandomizer<P::Paillier>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct AffGStarProof<P: SchemeParams> {
e: BitVec,
commitments: Box<[AffGStarProofCommitment<P>]>,
elements: Box<[AffGStarProofElement<P>]>,
}

impl<P: SchemeParams> AffGStarProof<P> {
pub fn new(
rng: &mut impl CryptoRngCore,
secret: AffGStarSecretInputs<'_, P>,
public: AffGStarPublicInputs<'_, P>,
aux: &impl Hashable,
) -> Self {
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 (ephemerals, commitments): (Vec<_>, Vec<_>) = (0..P::SECURITY_PARAMETER)
.map(|_| {
let alpha = SecretSigned::random_in_exponent_range(rng, P::L_BOUND + P::EPS_BOUND);
let beta = SecretSigned::random_in_exponent_range(rng, P::LP_BOUND + P::EPS_BOUND);
let r = Randomizer::random(rng, public.pk0);
let s = Randomizer::random(rng, public.pk1);

let cap_a = (public.cap_c * &alpha + Ciphertext::new_with_randomizer(public.pk0, &beta, &r)).to_wire();
let cap_r = secret_scalar_from_signed::<P>(&alpha).mul_by_generator();
let cap_b = Ciphertext::new_with_randomizer(public.pk1, &beta, &s).to_wire();

let ephemeral = AffGStarProofEphemeral::<P> { alpha, beta, r, s };
let commitment = AffGStarProofCommitment { cap_a, cap_r, cap_b };

(ephemeral, commitment)
})
.unzip();

let mut reader = XofHasher::new_with_dst(HASH_TAG)
// commitments
.chain(&commitments)
// public parameters
.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(aux)
.finalize_to_reader();

// Non-interactive challenge
let e = BitVec::from_xof_reader(&mut reader, P::SECURITY_PARAMETER);

let elements = ephemerals
.into_iter()
.zip(e.bits())
.map(|(ephemeral, e_bit)| {
let mut z = ephemeral.alpha;
if *e_bit {
z = z + secret.x;
}

let mut z_prime = ephemeral.beta;
if *e_bit {
z_prime = z_prime + secret.y;
}

let w = if *e_bit {
secret.rho.to_masked(&ephemeral.r, &PublicSigned::one())
} else {
secret.rho.to_masked(&ephemeral.r, &PublicSigned::zero())
};

let lambda = if *e_bit {
secret.mu.to_masked(&ephemeral.s, &PublicSigned::one())
} else {
secret.mu.to_masked(&ephemeral.s, &PublicSigned::zero())
};

AffGStarProofElement {
z: z.to_public(),
z_prime: z_prime.to_public(),
w,
lambda,
}
})
.collect::<Vec<_>>();

Self {
e,
elements: elements.into(),
commitments: commitments.into(),
}
}

#[allow(clippy::too_many_arguments)]
pub fn verify(&self, public: AffGStarPublicInputs<'_, P>, 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
.chain(&self.commitments)
// public parameters
.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(aux)
.finalize_to_reader();

// Non-interactive challenge
let e = BitVec::from_xof_reader(&mut reader, P::SECURITY_PARAMETER);

if e != self.e {
return false;
}

if e.bits().len() != self.commitments.len() || e.bits().len() != self.elements.len() {
return false;
}

for ((e_bit, commitment), element) in e
.bits()
.iter()
.cloned()
.zip(self.commitments.iter())
.zip(self.elements.iter())
{
// z_j \in \pm 2^{\ell + \eps}
if !element.z.in_range_bits(P::L_BOUND + P::EPS_BOUND) {
return false;
}

// z^\prime_j \in \pm 2^{\ell^\prime + \eps}
if !element.z_prime.in_range_bits(P::LP_BOUND + P::EPS_BOUND) {
return false;
}

// C (*) z_j (+) enc_0(z^\prime_j, w_j) == A_j (+) D_j (*) e_j
let cap_a = commitment.cap_a.to_precomputed(public.pk0);
let lhs = public.cap_c * &element.z
+ Ciphertext::new_public_with_randomizer(public.pk0, &element.z_prime, &element.w);
let rhs = if e_bit { cap_a + public.cap_d } else { cap_a };
if lhs != rhs {
return false;
}

// g^{z_j} == R_j X^{e_j}
let lhs = scalar_from_signed::<P>(&element.z).mul_by_generator();
let rhs = if e_bit {
commitment.cap_r + *public.cap_x
} else {
commitment.cap_r
};
if lhs != rhs {
return false;
}

// enc_1(z^\prime_j, \lambda_j) == B_j (+) Y^{e_j}
let cap_b = commitment.cap_b.to_precomputed(public.pk1);
let lhs = Ciphertext::new_public_with_randomizer(public.pk1, &element.z_prime, &element.lambda);
let rhs = if e_bit { cap_b + public.cap_y } else { cap_b };
if lhs != rhs {
return false;
}
}

true
}
}

#[cfg(test)]
mod tests {
use rand_core::OsRng;

use super::{AffGStarProof, AffGStarPublicInputs, AffGStarSecretInputs};
use crate::{
cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams},
paillier::{Ciphertext, Randomizer, SecretKeyPaillierWire},
uint::SecretSigned,
};

#[test]
fn prove_and_verify() {
type Params = TestParams;
type Paillier = <Params as SchemeParams>::Paillier;

let sk0 = SecretKeyPaillierWire::<Paillier>::random(&mut OsRng).into_precomputed();
let pk0 = sk0.public_key();

let sk1 = SecretKeyPaillierWire::<Paillier>::random(&mut OsRng).into_precomputed();
let pk1 = sk1.public_key();

let aux: &[u8] = b"abcde";

let x = SecretSigned::random_in_exponent_range(&mut OsRng, Params::L_BOUND);
let y = SecretSigned::random_in_exponent_range(&mut OsRng, Params::LP_BOUND);
let rho = Randomizer::random(&mut OsRng, pk0);
let mu = Randomizer::random(&mut OsRng, pk1);

let secret = SecretSigned::random_in_exponent_range(&mut OsRng, Params::L_BOUND);
let cap_c = Ciphertext::new(&mut OsRng, pk0, &secret);

let cap_d = &cap_c * &x + Ciphertext::new_with_randomizer(pk0, &y, &rho);
let cap_y = Ciphertext::new_with_randomizer(pk1, &y, &mu);
let cap_x = secret_scalar_from_signed::<Params>(&x).mul_by_generator();

let proof = AffGStarProof::<Params>::new(
&mut OsRng,
AffGStarSecretInputs {
x: &x,
y: &y,
rho: &rho,
mu: &mu,
},
AffGStarPublicInputs {
pk0,
pk1,
cap_c: &cap_c,
cap_d: &cap_d,
cap_y: &cap_y,
cap_x: &cap_x,
},
&aux,
);
assert!(proof.verify(
AffGStarPublicInputs {
pk0,
pk1,
cap_c: &cap_c,
cap_d: &cap_d,
cap_y: &cap_y,
cap_x: &cap_x,
},
&aux
));
}
}

0 comments on commit f97d797

Please sign in to comment.