diff --git a/synedrion/src/cggmp21/sigma.rs b/synedrion/src/cggmp21/sigma.rs index a3d8460..7ed2576 100644 --- a/synedrion/src/cggmp21/sigma.rs +++ b/synedrion/src/cggmp21/sigma.rs @@ -1,6 +1,7 @@ //! Sigma-protocols mod aff_g; +mod aff_g_star; mod dec; mod dec_new; mod elog; @@ -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}; diff --git a/synedrion/src/cggmp21/sigma/aff_g_star.rs b/synedrion/src/cggmp21/sigma/aff_g_star.rs new file mode 100644 index 0000000..4b26b01 --- /dev/null +++ b/synedrion/src/cggmp21/sigma/aff_g_star.rs @@ -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<::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, + /// $\mu$, a Paillier randomizer for the public key $N_1$. + pub mu: &'a Randomizer, +} + +pub(crate) struct AffGStarPublicInputs<'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)$. + pub cap_d: &'a Ciphertext, + /// Paillier ciphertext $Y = enc_1(y, \mu)$. + pub cap_y: &'a Ciphertext, + /// Point $X = g * x$, where $g$ is the curve generator. + pub cap_x: &'a Point, +} + +struct AffGStarProofEphemeral { + alpha: SecretSigned<::Uint>, + beta: SecretSigned<::Uint>, + r: Randomizer, + s: Randomizer, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct AffGStarProofCommitment { + cap_a: CiphertextWire, + cap_r: Point, + cap_b: CiphertextWire, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct AffGStarProofElement { + z: PublicSigned<::Uint>, + z_prime: PublicSigned<::Uint>, + w: MaskedRandomizer, + lambda: MaskedRandomizer, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct AffGStarProof { + e: BitVec, + commitments: Box<[AffGStarProofCommitment

]>, + elements: Box<[AffGStarProofElement

]>, +} + +impl AffGStarProof

{ + 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::

(&alpha).mul_by_generator(); + let cap_b = Ciphertext::new_with_randomizer(public.pk1, &beta, &s).to_wire(); + + let ephemeral = AffGStarProofEphemeral::

{ 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::>(); + + 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::

(&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 = ::Paillier; + + let sk0 = SecretKeyPaillierWire::::random(&mut OsRng).into_precomputed(); + let pk0 = sk0.public_key(); + + let sk1 = SecretKeyPaillierWire::::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::(&x).mul_by_generator(); + + let proof = AffGStarProof::::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 + )); + } +}