Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test FacProof with bad Paillier keys #160

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 278 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

13 changes: 10 additions & 3 deletions synedrion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ k256 = { version = "0.13", default-features = false, features = ["ecdsa", "arith
rand_core = { version = "0.6.4", default-features = false }
sha2 = { version = "0.10", default-features = false }
sha3 = { version = "0.10", default-features = false }
digest = { version = "0.10", default-features = false, features = ["alloc"]}
digest = { version = "0.10", default-features = false, features = ["alloc"] }
hashing-serializer = { version = "0.1", default-features = false }
secrecy = { version = "0.10", default-features = false, features = ["serde"] }
zeroize = { version = "1.8", default-features = false, features = ["alloc", "zeroize_derive"] }
bip32 = { version = "0.5", default-features = false, features = ["alloc", "secp256k1", "k256"] }
tracing = { version = "0.1", default-features = false }

# Note: `alloc` is needed for `crytpto-bigint`'s dependency `serdect` to be able
# to serialize Uints in human-readable formats.
crypto-bigint = { version = "0.6.0-rc.6", default-features = false, features = ["serde", "alloc", "rand_core", "zeroize"] }
crypto-bigint = { version = "0.6.0-rc.6", default-features = false, features = [
"serde",
"alloc",
"rand_core",
"zeroize",
] }
crypto-primes = { version = "0.6.0-pre.2", default-features = false }

serde = { version = "1", default-features = false, features = ["derive"] }
Expand All @@ -40,9 +46,10 @@ serde_assert = "0.8"
tokio = { version = "1", features = ["rt", "sync", "time", "macros"] }
rand = "0.8"
criterion = "0.5"
k256 = {version = "0.13", default-features = false, features = ["ecdsa", "arithmetic", "pem", "serde"]}
k256 = { version = "0.13", default-features = false, features = ["ecdsa", "arithmetic", "pem", "serde"] }
impls = "1"
hex = { version = "0.4", default-features = false, features = ["alloc"] }
test-log = { version = "0.2.16", default-features = false, features = ["trace", "color"] }

[[bench]]
bench = true
Expand Down
2 changes: 2 additions & 0 deletions synedrion/src/cggmp21.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ pub use entities::{AuxInfo, KeyShare, KeyShareChange};
pub use interactive_signing::{InteractiveSigning, InteractiveSigningProtocol, PrehashedMessage};
pub use key_init::{KeyInit, KeyInitProtocol};
pub use key_refresh::{KeyRefresh, KeyRefreshProtocol};
#[cfg(test)]
pub use params::{PaillierProduction, PaillierTest2};
pub use params::{ProductionParams, SchemeParams, TestParams};
14 changes: 7 additions & 7 deletions synedrion/src/cggmp21/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
};

#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PaillierTest;
pub struct PaillierTest2;

const fn upcast_uint<const N1: usize, const N2: usize>(value: K256Uint<N1>) -> K256Uint<N2> {
assert!(N2 >= N1, "Upcast target must be bigger than the upcast candidate");
Expand All @@ -35,7 +35,7 @@ const fn convert_uint<const N: usize>(value: K256Uint<N>) -> Uint<N> {
Uint::from_words(value.to_words())
}

impl PaillierParams for PaillierTest {
impl PaillierParams for PaillierTest2 {
/*
The prime size is chosen to be minimal for which the `TestSchemeParams` still work.
In the presigning, we are effectively constructing a ciphertext of
Expand Down Expand Up @@ -110,13 +110,13 @@ pub trait SchemeParams: Debug + Clone + Send + PartialEq + Eq + Send + Sync + 's
/// The order of the curve as a wide integer.
const CURVE_ORDER_WIDE: NonZero<<Self::Paillier as PaillierParams>::WideUint>;
/// The scheme's statistical security parameter.
const SECURITY_PARAMETER: usize; // $\kappa$
const SECURITY_PARAMETER: usize; // $\kappa$ (κ)
/// The bound for secret values.
const L_BOUND: usize; // $\ell$, paper sets it to $\log2(q)$ (see Table 2)
const L_BOUND: usize; // $\ell$ (ℓ), paper sets it to $\log2(q)$ (see Table 2, p. 69, Appendix D)
/// The error bound for secret masks.
const LP_BOUND: usize; // $\ell^\prime$, in paper $= 5 \ell$ (see Table 2)
const LP_BOUND: usize; // $\ell^\prime$ (ℓ′), in paper $= 5 \ell$ (see Table 2, p. 69, Appendix D)
/// The error bound for range checks (referred to in the paper as the slackness parameter).
const EPS_BOUND: usize; // $\eps$, in paper $= 2 \ell$ (see Table 2)
const EPS_BOUND: usize; // $\eps$ (ε), in paper $= 2 \ell$ (see Table 2, p. 69, Appendix D)
/// The parameters of the Paillier encryption.
///
/// Note: `PaillierParams::Uint` must be able to contain the full range of `Scalar` values
Expand Down Expand Up @@ -216,7 +216,7 @@ impl SchemeParams for TestParams {
const L_BOUND: usize = 256;
const LP_BOUND: usize = 256;
const EPS_BOUND: usize = 320;
type Paillier = PaillierTest;
type Paillier = PaillierTest2;
const CURVE_ORDER: NonZero<<Self::Paillier as PaillierParams>::Uint> = convert_uint(upcast_uint(ORDER))
.to_nz()
.expect("Correct by construction");
Expand Down
107 changes: 97 additions & 10 deletions synedrion/src/cggmp21/sigma/fac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use rand_core::CryptoRngCore;
use secrecy::ExposeSecret;
use serde::{Deserialize, Serialize};
use tracing::{debug, error, trace};

use super::super::SchemeParams;
use crate::{
Expand Down Expand Up @@ -43,29 +44,46 @@ impl<P: SchemeParams> FacProof<P> {
pub fn new(
rng: &mut impl CryptoRngCore,
sk0: &SecretKeyPaillierPrecomputed<P::Paillier>,
// Auxiliary safe bi-prime N_hat and Ring-Pedersen parameters s,t ∈ Z∗N_hat
setup: &RPParamsMod<P::Paillier>,
aux: &impl Hashable,
) -> Self {
trace!(
"[FacProof, new] SchemeParams: CURVE_ORDER={:?}, PRIME_BITS={:?}, L_BOUND={:?}, EPS_BOUND={:?}",
P::CURVE_ORDER,
<P::Paillier as PaillierParams>::PRIME_BITS - 2,
P::L_BOUND,
P::EPS_BOUND
);
let pk0 = sk0.public_key();

let hat_cap_n = &setup.public_key().modulus_bounded(); // $\hat{N}$

// NOTE: using `2^(Paillier::PRIME_BITS - 2)` as $\sqrt{N_0}$ (which is its lower bound)
// According to the authors of the paper, it is acceptable.
// In the end of the day, we're proving that `p, q < sqrt{N_0} 2^\ell`,
// At the end of the day, we're proving that `p, q < sqrt{N_0} 2^\ell`,
// and really they should be `~ sqrt{N_0}`.
// Note that it has to be matched when we check the range of
// Note that the same approximation must be used when we check the range of
// `z1` and `z2` during verification.
let sqrt_cap_n = Bounded::new(
<P::Paillier as PaillierParams>::Uint::one() << (<P::Paillier as PaillierParams>::PRIME_BITS - 2),
<P::Paillier as PaillierParams>::PRIME_BITS as u32,
)
.expect("the value is bounded by `2^PRIME_BITS` by construction");
// let mut zero = <P::Paillier as PaillierParams>::Uint::zero();
// zero.set_bit((<P::Paillier as PaillierParams>::PRIME_BITS - 2) as u32, 1.into());
// error!("zero={zero:?}");
// let sqrt_cap_n = Bounded::new(zero, <P::Paillier as PaillierParams>::PRIME_BITS as u32).unwrap();
// assert!(
// sqrt_cap_n.value() != <<P::Paillier as PaillierParams>::Uint as Zero>::zero(),
// "sqrt(N_0) should not be zero"
// );

let alpha = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n);
let beta = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, &sqrt_cap_n);
let mu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n);
let nu = Signed::random_bounded_bits_scaled(rng, P::L_BOUND, hat_cap_n);
trace!("[FacProof, new] hat_cap_n={hat_cap_n:?}, sqrt_cap_n={sqrt_cap_n:?}, alpha={alpha:?}, beta={beta:?}, mu={mu:?}, nu={nu:?}");

// N_0 \hat{N}
let scale = pk0.modulus_bounded().mul_wide(hat_cap_n);
Expand All @@ -79,6 +97,7 @@ impl<P: SchemeParams> FacProof<P> {
);
let x = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n);
let y = Signed::random_bounded_bits_scaled(rng, P::L_BOUND + P::EPS_BOUND, hat_cap_n);
trace!("[FacProof, new] hat_cap_n={hat_cap_n:?}, scale={scale:?}, sigma={sigma:?}, r={r:?}, x={x:?}, y={y:?}");

let (p, q) = sk0.primes();

Expand Down Expand Up @@ -113,7 +132,7 @@ impl<P: SchemeParams> FacProof<P> {
let omega1 = x + e_wide * mu;
let omega2 = y + e_wide * nu;
let v = r + (e_wide.into_wide() * hat_sigma);

trace!("[FacProof, new] e={e:?}, z1={z1:?}, z2={z2:?}, omega1={omega1:?}, omega2={omega2:?}, v={v:?}");
Self {
e,
cap_p,
Expand Down Expand Up @@ -152,8 +171,14 @@ impl<P: SchemeParams> FacProof<P> {

// Non-interactive challenge
let e = Signed::from_xof_reader_bounded(&mut reader, &P::CURVE_ORDER);
trace!("[FacProof, verify] CURVE_ORDER={:?} (aka 'q')", P::CURVE_ORDER);
debug!("[FacProof, verify] challenge={e:?} (aka 'e')");

if e != self.e {
error!(
"Challenge mismatch, e={e:?} is NOT equal to proof's challenge={:?}",
self.e
);
return false;
}

Expand All @@ -162,60 +187,86 @@ impl<P: SchemeParams> FacProof<P> {
// R = s^{N_0} t^\sigma
let cap_r = &setup.commit_xwide(&pk0.modulus_bounded().into(), &self.sigma);

// s^{z_1} t^{\omega_1} == A * P^e \mod \hat{N}
// Check 1/3: s^{z_1} t^{\omega_1} == A * P^e \mod \hat{N}
let cap_a_mod = self.cap_a.to_mod(aux_pk);
let cap_p_mod = self.cap_p.to_mod(aux_pk);
if setup.commit_wide(&self.z1, &self.omega1) != &cap_a_mod * &cap_p_mod.pow_signed_vartime(&e) {
error!("[FacProof, verify] Check 1 failed.");
return false;
}
trace!("[FacProof, verify] Check 1 Ok.");

// s^{z_2} t^{\omega_2} == B * Q^e \mod \hat{N}
// Check 2/3: s^{z_2} t^{\omega_2} == B * Q^e \mod \hat{N}
let cap_b_mod = self.cap_b.to_mod(aux_pk);
let cap_q_mod = self.cap_q.to_mod(aux_pk);
if setup.commit_wide(&self.z2, &self.omega2) != &cap_b_mod * &cap_q_mod.pow_signed_vartime(&e) {
error!("[FacProof, verify] Check 2 failed.");
return false;
}
trace!("[FacProof, verify] Check 2 Ok.");

// Q^{z_1} * t^v == T * R^e \mod \hat{N}
// Check 3/3: Q^{z_1} * t^v == T * R^e \mod \hat{N}
let cap_t_mod = self.cap_t.to_mod(aux_pk);
if &cap_q_mod.pow_signed_wide(&self.z1) * &setup.commit_base_xwide(&self.v)
!= &cap_t_mod * &cap_r.pow_signed_vartime(&e)
{
error!("[FacProof, verify] Check 3 failed.");
return false;
}

trace!("[FacProof, verify] Check 3 Ok.");
// NOTE: since when creating this proof we generated `alpha` and `beta`
// using the approximation `sqrt(N_0) ~ 2^(PRIME_BITS - 2)`,
// this is the bound we are using here as well.

let range_bits = P::L_BOUND + P::EPS_BOUND + <P::Paillier as PaillierParams>::PRIME_BITS - 2;
debug!(
"[FacProof, verify] range bound={range_bits:?}, L_BOUND={:?}, EPS_BOUND={:?}, PRIME_BITS-2={:?}",
P::L_BOUND,
P::EPS_BOUND,
<P::Paillier as PaillierParams>::PRIME_BITS - 2
);
// z1 \in \pm \sqrt{N_0} 2^{\ell + \eps}
if !self
.z1
.in_range_bits(P::L_BOUND + P::EPS_BOUND + <P::Paillier as PaillierParams>::PRIME_BITS - 2)
{
error!(
"[FacProof, verify] z1 is NOT in range. z1={:?}, bound={:?}",
self.z1,
P::L_BOUND + P::EPS_BOUND + <P::Paillier as PaillierParams>::PRIME_BITS - 2
);
return false;
}
trace!("[FacProof, verify] z1 is in range. z1={:?}", self.z1);

// z2 \in \pm \sqrt{N_0} 2^{\ell + \eps}
if !self
.z2
.in_range_bits(P::L_BOUND + P::EPS_BOUND + <P::Paillier as PaillierParams>::PRIME_BITS - 2)
{
error!(
"[FacProof, verify] z2 is NOT in range. z2={:?}, bound={:?}",
self.z2,
P::L_BOUND + P::EPS_BOUND + <P::Paillier as PaillierParams>::PRIME_BITS - 2
);
return false;
}

trace!("[FacProof, verify] z2 is in range. z2={:?}", self.z2);
true
}
}

#[cfg(test)]
mod tests {
use rand::SeedableRng;
use rand_core::OsRng;
use tracing::trace;

use super::FacProof;
use crate::{
cggmp21::{SchemeParams, TestParams},
paillier::{RPParamsMod, SecretKeyPaillier},
cggmp21::{params::PaillierTest2, PaillierProduction, SchemeParams, TestParams},
paillier::{make_broken_paillier_key, RPParamsMod, SecretKeyPaillier},
ProductionParams,
};

#[test]
Expand All @@ -234,4 +285,40 @@ mod tests {
let proof = FacProof::<Params>::new(&mut OsRng, &sk, &setup, &aux);
assert!(proof.verify(pk, &setup, &aux));
}

#[test_log::test]
fn malicious_prover_with_production_params() {
type Skp = SecretKeyPaillier<PaillierProduction>;
let mut rng = rand_chacha::ChaCha8Rng::from_seed(*b"01234567890123456789012345678901");
let sk = make_broken_paillier_key(&mut rng, 23);
// trace!("[test] Have sk={sk:?}");
let sk_precomp = sk.to_precomputed();
// trace!("[test] Have sk_precomp={sk_precomp:?}");

let aux_sk = Skp::random(&mut OsRng).to_precomputed();
// trace!("[test] Have aux_sk={aux_sk:?}");
let setup = RPParamsMod::random(&mut rng, &aux_sk);
// trace!("[test] Have setup={setup:?}");
let aux: &[u8] = b"dontforgetthemilk";

let proof = FacProof::<ProductionParams>::new(&mut rng, &sk_precomp, &setup, &aux);
trace!("[test] Have proof={proof:?}");

assert!(proof.verify(sk_precomp.public_key(), &setup, &aux));
trace!("[test] Done");
}
#[test_log::test]
fn malicious_prover_with_test_params() {
type Skp = SecretKeyPaillier<PaillierTest2>;
let sk = make_broken_paillier_key(&mut OsRng, 7);
let sk_precomp = sk.to_precomputed();

let aux_sk = Skp::random(&mut OsRng).to_precomputed();
let setup = RPParamsMod::random(&mut OsRng, &aux_sk);
let aux: &[u8] = b"dontforgetthemilk";

let proof = FacProof::<TestParams>::new(&mut OsRng, &sk_precomp, &setup, &aux);

assert!(!proof.verify(sk_precomp.public_key(), &setup, &aux));
}
}
3 changes: 3 additions & 0 deletions synedrion/src/paillier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ pub(crate) use keys::{
};
pub(crate) use params::PaillierParams;
pub(crate) use ring_pedersen::{RPCommitment, RPParams, RPParamsMod, RPSecret};

#[cfg(test)]
pub use keys::make_broken_paillier_key;
Loading