diff --git a/synedrion/Cargo.toml b/synedrion/Cargo.toml index aa03a1a8..82b9a5da 100644 --- a/synedrion/Cargo.toml +++ b/synedrion/Cargo.toml @@ -62,7 +62,13 @@ private_benches = ["criterion"] bench = true name = "bench" harness = false +path = "benches/bench.rs" +[[bench]] +bench = true +name = "pow" +harness = false +path = "benches/pow.rs" [[bench]] bench = true diff --git a/synedrion/benches/pow.rs b/synedrion/benches/pow.rs new file mode 100644 index 00000000..607a08d0 --- /dev/null +++ b/synedrion/benches/pow.rs @@ -0,0 +1,125 @@ +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use crypto_bigint::{ + modular::{MontyForm, MontyParams}, + NonZero, Odd, Random, Uint, U1024, U2048, U256, U4096, U512, +}; +use crypto_primes::RandomPrimeWithRng; +use rand::SeedableRng; + +fn bench_pow_known_totient_512(c: &mut Criterion) { + let mut group = c.benchmark_group("modpow, 512^1024"); + + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + let p: U512 = (U256::generate_prime_with_rng(&mut rng, U256::BITS), U256::ZERO).into(); + let q: U512 = (U256::generate_prime_with_rng(&mut rng, U256::BITS), U256::ZERO).into(); + let m: U512 = p * q; + let totient = (p - U512::ONE) * (q - U512::ONE); + let prms = MontyParams::new_vartime(Odd::new(m).unwrap()); + + group.bench_function("vanilla", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U512::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U1024::random(&mut rng); + (x, exponent) + }, + |(x, exponent)| black_box(x.pow(&exponent)), + BatchSize::SmallInput, + ); + }); + + group.bench_function("known totient", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U512::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U1024::random(&mut rng); + let exponent = Uint::rem_wide_vartime(exponent.split(), &NonZero::new(totient).unwrap()); + (x, exponent) + }, + |(x, exponent)| black_box(x.pow(&exponent)), + BatchSize::SmallInput, + ); + }); + + group.bench_function("known totient (not ammortized)", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U512::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U1024::random(&mut rng); + (x, exponent) + }, + |(x, exponent)| { + let exponent = Uint::rem_wide_vartime(exponent.split(), &NonZero::new(totient).unwrap()); + black_box(x.pow(&exponent)) + }, + BatchSize::SmallInput, + ); + }); +} + +// Our production parameters use 1024-bit primes resulting in 2048-bit moduli +fn bench_pow_known_totient_2048(c: &mut Criterion) { + let mut group = c.benchmark_group("modpow, 2048^4096"); + + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + let p: U2048 = (U1024::generate_prime_with_rng(&mut rng, U1024::BITS), U1024::ZERO).into(); + let q: U2048 = (U1024::generate_prime_with_rng(&mut rng, U1024::BITS), U1024::ZERO).into(); + let m: U2048 = p * q; + let totient = (p - U2048::ONE) * (q - U2048::ONE); + let prms = MontyParams::new_vartime(Odd::new(m).unwrap()); + + group.bench_function("vanilla", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U2048::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U4096::random(&mut rng); + (x, exponent) + }, + |(x, exponent)| black_box(x.pow(&exponent)), + BatchSize::SmallInput, + ); + }); + + group.bench_function("known totient", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U2048::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U4096::random(&mut rng); + let exponent = Uint::rem_wide_vartime(exponent.split(), &NonZero::new(totient).unwrap()); + (x, exponent) + }, + |(x, exponent)| black_box(x.pow(&exponent)), + BatchSize::SmallInput, + ); + }); + + group.bench_function("known totient (not ammortized)", |b| { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1234567890); + b.iter_batched( + || { + let x = U2048::random(&mut rng); + let x = MontyForm::new(&x, prms); + let exponent = U4096::random(&mut rng); + (x, exponent) + }, + |(x, exponent)| { + let exponent = Uint::rem_wide_vartime(exponent.split(), &NonZero::new(totient).unwrap()); + black_box(x.pow(&exponent)) + }, + BatchSize::SmallInput, + ); + }); +} +criterion_group!(benches, bench_pow_known_totient_512, bench_pow_known_totient_2048); + +criterion_main!(benches); diff --git a/synedrion/src/cggmp21/sigma/aff_g.rs b/synedrion/src/cggmp21/sigma/aff_g.rs index cd0d9a7e..4037b287 100644 --- a/synedrion/src/cggmp21/sigma/aff_g.rs +++ b/synedrion/src/cggmp21/sigma/aff_g.rs @@ -102,14 +102,14 @@ impl AffGProof

{ let cap_a = (public.cap_c * &alpha + Ciphertext::new_with_randomizer(public.pk0, &beta, &r)).to_wire(); let cap_b_x = secret_scalar_from_signed::

(&alpha).mul_by_generator(); let cap_b_y = Ciphertext::new_with_randomizer(public.pk1, &beta, &r_y).to_wire(); - let cap_e = setup.commit(&alpha, &gamma).to_wire(); - let cap_s = setup.commit(secret.x, &m).to_wire(); - let cap_f = setup.commit(&beta, &delta).to_wire(); + let cap_e = setup.commit_secret_mixed(&alpha, &gamma).to_wire(); + let cap_s = setup.commit_secret_mixed(secret.x, &m).to_wire(); + let cap_f = setup.commit_secret_mixed(&beta, &delta).to_wire(); // DEVIATION FROM THE PAPER. // See the comment in `AffGPublicInputs`. // Original: $s^y$. Modified: $s^{-y}$ - let cap_t = setup.commit(&(-secret.y), &mu).to_wire(); + let cap_t = setup.commit_secret_mixed(&(-secret.y), &mu).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -172,7 +172,6 @@ impl AffGProof

{ } } - #[allow(clippy::too_many_arguments)] pub fn verify(&self, public: AffGPublicInputs<'_, P>, setup: &RPParams, aux: &impl Hashable) -> bool { assert!(public.cap_c.public_key() == public.pk0); assert!(public.cap_d.public_key() == public.pk0); @@ -243,14 +242,14 @@ impl AffGProof

{ // s^{z_1} t^{z_3} == E S^e \mod \hat{N} let cap_e = self.cap_e.to_precomputed(setup); let cap_s = self.cap_s.to_precomputed(setup); - if setup.commit(&self.z1, &self.z3) != &cap_e * &cap_s.pow(&e) { + if setup.commit_pub_mixed(&self.z1, &self.z3) != &cap_e * &cap_s.pow(&e) { return false; } // s^{z_2} t^{z_4} == F T^e \mod \hat{N} let cap_f = self.cap_f.to_precomputed(setup); let cap_t = self.cap_t.to_precomputed(setup); - if setup.commit(&self.z2, &self.z4) != &cap_f * &cap_t.pow(&e) { + if setup.commit_pub_mixed(&self.z2, &self.z4) != &cap_f * &cap_t.pow(&e) { return false; } @@ -270,7 +269,7 @@ mod tests { uint::SecretSigned, }; - #[test] + #[test_log::test] fn prove_and_verify() { type Params = TestParams; type Paillier = ::Paillier; @@ -295,7 +294,7 @@ mod tests { let cap_d = &cap_c * &x + Ciphertext::new_with_randomizer(pk0, &-&y, &rho); let cap_y = Ciphertext::new_with_randomizer(pk1, &y, &rho_y); - let cap_x = secret_scalar_from_signed::(&x).mul_by_generator(); + let cap_x = secret_scalar_from_signed::(&x).mul_by_generator(); let proof = AffGProof::::new( &mut OsRng, diff --git a/synedrion/src/cggmp21/sigma/enc_elg.rs b/synedrion/src/cggmp21/sigma/enc_elg.rs index 72a00b8c..07f0e14b 100644 --- a/synedrion/src/cggmp21/sigma/enc_elg.rs +++ b/synedrion/src/cggmp21/sigma/enc_elg.rs @@ -81,12 +81,11 @@ impl EncElgProof

{ 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_s = setup.commit_secret_mixed(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 cap_t = setup.commit_secret_mixed(&alpha, &gamma).to_wire(); let mut reader = XofHasher::new_with_dst(HASH_TAG) // commitments @@ -188,7 +187,7 @@ impl EncElgProof

{ // 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) { + if setup.commit_pub_mixed(&self.z1, &self.z3) != &cap_t * &cap_s.pow(&e_signed) { return false; } diff --git a/synedrion/src/cggmp21/sigma/fac.rs b/synedrion/src/cggmp21/sigma/fac.rs index 07cc8844..e636966f 100644 --- a/synedrion/src/cggmp21/sigma/fac.rs +++ b/synedrion/src/cggmp21/sigma/fac.rs @@ -80,10 +80,10 @@ impl FacProof

{ let p = sk0.p_signed(); let q = sk0.q_signed(); - let cap_p = setup.commit(&p, &mu).to_wire(); - let cap_q = setup.commit(&q, &nu); - let cap_a = setup.commit(&alpha, &x).to_wire(); - let cap_b = setup.commit(&beta, &y).to_wire(); + let cap_p = setup.commit_secret_mixed(&p, &mu).to_wire(); + let cap_q = setup.commit_secret_mixed(&q, &nu); + let cap_a = setup.commit_secret(&alpha, &x).to_wire(); + let cap_b = setup.commit_secret(&beta, &y).to_wire(); let cap_t = (&cap_q.pow(&alpha) * &setup.commit_zero_value(&r)).to_wire(); let cap_q = cap_q.to_wire(); @@ -177,14 +177,14 @@ impl FacProof

{ // s^{z_1} t^{w_1} == A P^e \mod \hat{N} let cap_a = self.cap_a.to_precomputed(setup); let cap_p = self.cap_p.to_precomputed(setup); - if setup.commit(&self.z1, &self.w1) != &cap_a * &cap_p.pow(&e) { + if setup.commit_pub(&self.z1, &self.w1) != &cap_a * &cap_p.pow(&e) { return false; } // s^{z_2} t^{w_2} == B Q^e \mod \hat{N} let cap_b = self.cap_b.to_precomputed(setup); let cap_q = self.cap_q.to_precomputed(setup); - if setup.commit(&self.z2, &self.w2) != &cap_b * &cap_q.pow(&e) { + if setup.commit_pub(&self.z2, &self.w2) != &cap_b * &cap_q.pow(&e) { return false; } diff --git a/synedrion/src/paillier/params.rs b/synedrion/src/paillier/params.rs index c6da3114..a24aca5c 100644 --- a/synedrion/src/paillier/params.rs +++ b/synedrion/src/paillier/params.rs @@ -1,7 +1,8 @@ use crypto_bigint::{ modular::Retrieve, subtle::{ConditionallyNegatable, ConditionallySelectable, ConstantTimeGreater, CtOption}, - Bounded, Encoding, Gcd, Integer, InvMod, Invert, Monty, PowBoundedExp, RandomBits, RandomMod, + Bounded, Encoding, Gcd, Integer, InvMod, Invert, Monty, MultiExponentiateBoundedExp, PowBoundedExp, RandomBits, + RandomMod, }; use crypto_primes::RandomPrimeWithRng; use serde::{Deserialize, Serialize}; @@ -65,6 +66,8 @@ pub trait PaillierParams: core::fmt::Debug + PartialEq + Eq + Clone + Send + Syn + PowBoundedExp + PowBoundedExp + PowBoundedExp + + MultiExponentiateBoundedExp + + MultiExponentiateBoundedExp + Monty + Retrieve + Invert> diff --git a/synedrion/src/paillier/ring_pedersen.rs b/synedrion/src/paillier/ring_pedersen.rs index c8409fcc..f24c79b5 100644 --- a/synedrion/src/paillier/ring_pedersen.rs +++ b/synedrion/src/paillier/ring_pedersen.rs @@ -1,9 +1,13 @@ /// Implements the Definition 3.3 from the CGGMP'21 paper and related operations. use core::ops::Mul; -use crypto_bigint::{modular::Retrieve, Monty, NonZero, RandomMod, ShrVartime}; +use crypto_bigint::{ + modular::Retrieve, subtle::ConditionallySelectable, Bounded, Integer, Invert, Monty, MultiExponentiateBoundedExp, + NonZero, RandomMod, ShrVartime, +}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use zeroize::Zeroize; use super::{ rsa::{PublicModulus, PublicModulusWire, SecretPrimes, SecretPrimesWire}, @@ -11,13 +15,14 @@ use super::{ }; use crate::{ tools::Secret, - uint::{Exponentiable, SecretUnsigned, ToMontgomery}, + uint::{Exponentiable, HasWide, PublicSigned, SecretSigned, SecretUnsigned, ToMontgomery}, }; /// Ring-Pedersen secret. #[derive(Debug, Clone)] pub(crate) struct RPSecret { primes: SecretPrimes

, + /// λ is a random number mod ϕ(N)/4 lambda: SecretUnsigned, } @@ -124,12 +129,71 @@ impl RPParams

{ self.modulus.monty_params_mod_n() } - /// Creates a commitment for a secret `value` with a secret `randomizer`. - pub fn commit(&self, value: &V, randomizer: &R) -> RPCommitment

+ /// Creates a commitment for a public `value` with a `randomizer`. + pub(crate) fn commit_pub(&self, value: &PublicSigned, randomizer: &PublicSigned) -> RPCommitment

+ where + E: Integer + Bounded, + P::UintMod: MultiExponentiateBoundedExp, + { + let signs = (value.is_negative(), randomizer.is_negative()); + + let r = randomizer.abs(); + let v = value.abs(); + + self.commit(signs, v, r, randomizer.bound()) + } + + /// Creates a commitment for a public `value` with a `randomizer` where the `randomizer` is twice the size of the `value`. + pub(crate) fn commit_pub_mixed( + &self, + value: &PublicSigned, + randomizer: &PublicSigned, + ) -> RPCommitment

where - P::UintMod: Exponentiable + Exponentiable, + E: HasWide + Bounded, + ::Wide: Integer + Bounded + Zeroize + ConditionallySelectable, + P::UintMod: MultiExponentiateBoundedExp, { - RPCommitment(self.base_value.pow(value) * self.base_randomizer.pow(randomizer)) + let signs = (value.is_negative(), randomizer.is_negative()); + + let r = randomizer.abs(); + let v = value.abs().to_wide(); + + self.commit(signs, v, r, randomizer.bound()) + } + + /// Creates a commitment for a secret `value` with a `randomizer`. + pub(crate) fn commit_secret(&self, value: &SecretSigned, randomizer: &SecretSigned) -> RPCommitment

+ where + E: Integer + Bounded + Zeroize + ConditionallySelectable, + P::UintMod: MultiExponentiateBoundedExp, + { + let signs = (bool::from(value.is_negative()), bool::from(randomizer.is_negative())); + + let r = *randomizer.abs_value().expose_secret(); + let v = *value.abs_value().expose_secret(); + + self.commit(signs, v, r, randomizer.bound()) + } + + /// Creates a commitment for a secret `value` with a `randomizer` where the `randomizer` is twice the size of the `value`. + pub(crate) fn commit_secret_mixed( + &self, + value: &SecretSigned, + randomizer: &SecretSigned, + ) -> RPCommitment

+ where + E: Integer + Bounded + Zeroize + ConditionallySelectable, + E: HasWide, + ::Wide: Integer + Bounded + Zeroize + ConditionallySelectable, + P::UintMod: MultiExponentiateBoundedExp, + { + let signs = (bool::from(value.is_negative()), bool::from(randomizer.is_negative())); + + let r = *randomizer.abs_value().expose_secret(); + let v = value.abs_value().expose_secret().to_wide(); + + self.commit(signs, v, r, randomizer.bound()) } /// Creates a commitment for a secret `randomizer` and the value 0. @@ -155,6 +219,51 @@ impl RPParams

{ base_value: self.base_value.retrieve(), } } + + #[inline] + fn bases_and_exponents(&self, signs: (bool, bool), v: I, r: I) -> [(P::UintMod, I); 2] { + match signs { + (true, true) => { + // both negative => multi-exp and then invert + [(self.base_value, v), (self.base_randomizer, r)] + } + (true, false) => { + // v neg, r pos => invert self.base_value, then multi-exp + [ + (self.base_value.invert_vartime().expect("base_value is constructed by raising an invertible number to a power; exponentiation preserves invertibility."), v), + (self.base_randomizer, r), + ] + } + (false, false) => { + // both positive => multi-exp + [(self.base_value, v), (self.base_randomizer, r)] + } + (false, true) => { + // v pos, r neg => invert self.base_randomizer, then multi-exp + [ + (self.base_value, v), + ( + self.base_randomizer.invert_vartime().expect("base_randomizer is a square of an invertible number; exponentiation preserves invertibility."), + r, + ), + ] + } + } + } + + #[inline] + fn commit(&self, signs: (bool, bool), v: E, r: E, bound: u32) -> RPCommitment

+ where + P::UintMod: MultiExponentiateBoundedExp, + { + let bases_and_exponents = self.bases_and_exponents(signs, v, r); + let mut commitment = P::UintMod::multi_exponentiate_bounded_exp(&bases_and_exponents, bound); + // If both exponents are negative, we can do the exponentiation with the absolute values and then invert in the end. + if signs == (true, true) { + commitment = commitment.invert_vartime().expect("Invertible numbers forms a group; groups are closed under multiplication, so this product is also invertible") + } + RPCommitment(commitment) + } } /// Minimal public ring-Pedersen parameters suitable for serialization and transmission. diff --git a/synedrion/src/paillier/rsa.rs b/synedrion/src/paillier/rsa.rs index 7f2f28cc..a41d7c31 100644 --- a/synedrion/src/paillier/rsa.rs +++ b/synedrion/src/paillier/rsa.rs @@ -267,24 +267,29 @@ impl PublicModulus

{ } } + /// Convert this [`PublicModulus`] to its wire-format equivalent. pub fn to_wire(&self) -> PublicModulusWire

{ self.modulus.clone() } + /// The base RSA modulus $N$. pub fn modulus(&self) -> &P::Uint { &self.modulus.0 } + /// The base RSA modulus $N$ wrapped in a [`NonZero`]. pub fn modulus_nonzero(&self) -> NonZero { NonZero::new(self.modulus.0).expect("the modulus is non-zero") } + /// The base RSA modulus $N$ wrapped in a [`PublicSigned`] (and therefore widended to accomodate the sign bit). pub fn modulus_signed(&self) -> PublicSigned { // Have to return WideUint, since Uint::BITS == P::MODULUS_BITS, so it won't fit in a Signed. PublicSigned::new_positive(self.modulus.0.to_wide(), P::MODULUS_BITS) .expect("the modulus can be bounded by 2^MODULUS_BITS") } + /// Montgomery representation parameters for modulo $N$. pub fn monty_params_mod_n(&self) -> &::Params { &self.monty_params_mod_n } diff --git a/synedrion/src/private_benches.rs b/synedrion/src/private_benches.rs index c261300c..df06f3cf 100644 --- a/synedrion/src/private_benches.rs +++ b/synedrion/src/private_benches.rs @@ -23,7 +23,6 @@ type PUint = <::Paillier as PaillierParams>::Uint; pub mod fac_proof { use super::*; - /// Benchmark Fac-proof construction pub fn fac_proof_prove(mut rng: R) -> impl FnMut(&mut Bencher<'_>) { let mut rng2 = rng.clone();