diff --git a/PAPER.md b/PAPER.md index 124eee54..f48bc2b4 100644 --- a/PAPER.md +++ b/PAPER.md @@ -14,6 +14,8 @@ Replies from Nikos Makriyannis are marked as (NM) # Typos +In `П_dec` (Fig. 28), Inputs, the condition `(1 + N)^z` should read `(1 + N)^y`. Also note that at the point where it is used, the first secret argument corresponds to `y`, and the second to `x`. + In `П^{aff-g}` (Fig. 25) I had to modify the proof to account for how `D` is actually constructed in the Presigning protocol. In the proof it is assumed that `D = C (*) x (+) enc(y)` (where `(*)` and `(+)` are homomorphic operations on Paillier ciphertexts), but in the Presigning it's actually `D = C (*) x (+) enc(-y)` (see Fig. 8, Round 2, step 2). So I had to modify the following: - (prover) `T = s^{-y} t^\mu \mod \hat{N}` - (prover) `z_2 = \beta - e y` diff --git a/synedrion/src/cggmp21/sigma.rs b/synedrion/src/cggmp21/sigma.rs index cf89093c..21c58ba3 100644 --- a/synedrion/src/cggmp21/sigma.rs +++ b/synedrion/src/cggmp21/sigma.rs @@ -2,6 +2,7 @@ mod aff_g; mod dec; +mod dec_new; mod enc; mod fac; mod log_star; diff --git a/synedrion/src/cggmp21/sigma/dec_new.rs b/synedrion/src/cggmp21/sigma/dec_new.rs new file mode 100644 index 00000000..e9e87128 --- /dev/null +++ b/synedrion/src/cggmp21/sigma/dec_new.rs @@ -0,0 +1,294 @@ +//! Paillier Special Decryption in the Exponent ($\Pi^{dec}$, Section A.6, Fig. 28) + +#![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, RPParams, Randomizer}, + tools::{ + bitvec::BitVec, + hashing::{Chain, Hashable, XofHasher}, + }, + uint::{PublicSigned, SecretSigned}, +}; + +const HASH_TAG: &[u8] = b"P_dec"; + +pub(crate) struct DecSecretInputs<'a, P: SchemeParams> { + /// $x ∈ \mathbb{I}$, that is $x ∈ ±2^\ell$ (see N.B. just before Section 4.1) + pub x: &'a SecretSigned<::Uint>, + /// $y ∈ \mathbb{I}$, that is $x ∈ ±2^{\ell^\prime}$ (see N.B. just before Section 4.1) + 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, + /// Paillier ciphertext $K$ such that $enc_0(y, \rho) = K (*) x + D$. + // NOTE: paper says `enc_0(z, \rho)` which is a typo. + pub cap_k: &'a Ciphertext, + /// Point $X = g^x$, where $g$ is the curve generator. + pub cap_x: &'a Point, + /// Paillier ciphertext $D$, see the doc for `cap_k` above. + pub cap_d: &'a Ciphertext, + /// Point $S = g^y$, where $g$ is the curve generator. + pub cap_s: &'a Point, +} + +/// ZK proof: Paillier decryption modulo $q$. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct DecProof { + e: BitVec, + commitments: Box<[DecProofCommitment

]>, + elements: Box<[DecProofElement

]>, +} + +struct DecProofEphemeral { + alpha: SecretSigned<::Uint>, + beta: SecretSigned<::Uint>, + r: Randomizer, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct DecProofCommitment { + cap_a: CiphertextWire, + cap_b: Point, + cap_c: Point, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub(crate) struct DecProofElement { + z: PublicSigned<::Uint>, + w: PublicSigned<::Uint>, + nu: MaskedRandomizer, +} + +impl DecProof

{ + pub fn new( + rng: &mut impl CryptoRngCore, + secret: DecSecretInputs<'_, P>, + public: DecPublicInputs<'_, P>, + setup: &RPParams, + aux: &impl Hashable, + ) -> Self { + secret.x.assert_exponent_range(P::L_BOUND); + secret.y.assert_exponent_range(P::LP_BOUND); + assert_eq!(public.cap_k.public_key(), public.pk0); + assert_eq!(public.cap_d.public_key(), public.pk0); + + 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 cap_a = + (public.cap_k * &-&alpha + Ciphertext::new_with_randomizer(public.pk0, &beta, &r)).to_wire(); + let cap_b = secret_scalar_from_signed::

(&beta).mul_by_generator(); + let cap_c = secret_scalar_from_signed::

(&alpha).mul_by_generator(); + + let ephemeral = DecProofEphemeral::

{ alpha, beta, r }; + let commitment = DecProofCommitment { cap_a, cap_b, cap_c }; + + (ephemeral, commitment) + }) + .unzip(); + + let mut reader = XofHasher::new_with_dst(HASH_TAG) + // commitments + .chain(&commitments) + // public parameters + .chain(public.pk0.as_wire()) + .chain(&public.cap_k.to_wire()) + .chain(&public.cap_x) + .chain(&public.cap_d.to_wire()) + .chain(&public.cap_s) + .chain(&setup.to_wire()) + .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 w = ephemeral.beta; + if *e_bit { + w = w + secret.y; + } + + let nu = if *e_bit { + secret.rho.to_masked(&ephemeral.r, &PublicSigned::one()) + } else { + secret.rho.to_masked(&ephemeral.r, &PublicSigned::zero()) + }; + + DecProofElement { + z: z.to_public(), + w: w.to_public(), + nu, + } + }) + .collect::>(); + + Self { + e, + elements: elements.into(), + commitments: commitments.into(), + } + } + + pub fn verify(&self, public: DecPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable) -> bool { + let mut reader = XofHasher::new_with_dst(HASH_TAG) + // commitments + .chain(&self.commitments) + // public parameters + .chain(public.pk0.as_wire()) + .chain(&public.cap_k.to_wire()) + .chain(&public.cap_x) + .chain(&public.cap_d.to_wire()) + .chain(&public.cap_s) + .chain(&setup.to_wire()) + .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()) + { + // enc(w_j, \nu_j) (+) K (*) (-z_j) == A_j (+) D (*) e_j + let cap_a = commitment.cap_a.to_precomputed(public.pk0); + let lhs = Ciphertext::new_public_with_randomizer(public.pk0, &element.w, &element.nu) + + public.cap_k * &-element.z; + let rhs = if e_bit { cap_a + public.cap_d } else { cap_a }; + if lhs != rhs { + return false; + } + + // g^z_j == C_j X^{e_j} + let lhs = scalar_from_signed::

(&element.z).mul_by_generator(); + let rhs = if e_bit { + commitment.cap_c + *public.cap_x + } else { + commitment.cap_c + }; + if lhs != rhs { + return false; + } + + // g^{w_j} == B_j S^{e_j} + let lhs = scalar_from_signed::

(&element.w).mul_by_generator(); + let rhs = if e_bit { + commitment.cap_b + *public.cap_s + } else { + commitment.cap_b + }; + if lhs != rhs { + return false; + } + } + + true + } +} + +#[cfg(test)] +mod tests { + use rand_core::OsRng; + + use super::{DecProof, DecPublicInputs, DecSecretInputs}; + use crate::{ + cggmp21::{conversion::secret_scalar_from_signed, SchemeParams, TestParams}, + paillier::{Ciphertext, PaillierParams, RPParams, Randomizer, SecretKeyPaillierWire}, + 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 y = SecretSigned::random_in_exponent_range(&mut OsRng, Params::LP_BOUND); + let rho = Randomizer::random(&mut OsRng, pk); + + // We need $enc_0(y, \rho) = K (*) x + D$, + // so we can choose the plaintext of `K` at random, and derive the plaintext of `D` + // (not deriving `y` since we want it to be in a specific range). + + let k = SecretSigned::random_in_exponent_range(&mut OsRng, Paillier::PRIME_BITS * 2 - 1); + let cap_k = Ciphertext::new(&mut OsRng, pk, &k); + let cap_d = Ciphertext::new_with_randomizer(pk, &y, &rho) + &cap_k * &-&x; + + let cap_x = secret_scalar_from_signed::(&x).mul_by_generator(); + let cap_s = secret_scalar_from_signed::(&y).mul_by_generator(); + + let proof = DecProof::::new( + &mut OsRng, + DecSecretInputs { + x: &x, + y: &y, + rho: &rho, + }, + DecPublicInputs { + pk0: pk, + cap_k: &cap_k, + cap_x: &cap_x, + cap_d: &cap_d, + cap_s: &cap_s, + }, + &setup, + &aux, + ); + assert!(proof.verify( + DecPublicInputs { + pk0: pk, + cap_k: &cap_k, + cap_x: &cap_x, + cap_d: &cap_d, + cap_s: &cap_s, + }, + &setup, + &aux + )); + } +} diff --git a/synedrion/src/uint/public_signed.rs b/synedrion/src/uint/public_signed.rs index 8acb5438..7dff2f11 100644 --- a/synedrion/src/uint/public_signed.rs +++ b/synedrion/src/uint/public_signed.rs @@ -115,6 +115,20 @@ where Some(result) } + pub fn one() -> Self { + Self { + value: T::one(), + bound: 1, + } + } + + pub fn zero() -> Self { + Self { + value: T::zero(), + bound: 0, + } + } + pub fn is_negative(&self) -> bool { self.value.bit_vartime(T::BITS - 1) }