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

+-x into 2x #6

Merged
merged 14 commits into from
Jan 27, 2023
2 changes: 1 addition & 1 deletion src/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ impl generic_ec_core::One for Scalar {

impl generic_ec_core::Samplable for Scalar {
fn random<R: rand_core::RngCore>(rng: &mut R) -> Self {
Scalar(rng.next_u64())
rng.next_u64().into()
}
}

Expand Down
176 changes: 97 additions & 79 deletions src/group_element_vs_paillier_encryption_in_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
//! ## Description
//!
//! A party P has a number `X = g ^ x`, with g being a generator of
//! multiplicative group G. P wants to prove to party V that the logarithm of X,
//! i.e. x, is at most L bits.
//! multiplicative group G. P has encrypted x as C. P shares X and C with V and
//! wants to prove that the logarithm of X is the plaintext of C, and that the
//! plaintext (i.e. x) is at most L+1 bits.
//!
//! Given:
//! - `key0`, `pkey0` - pair of public and private keys in paillier cryptosystem
Expand Down Expand Up @@ -56,12 +57,14 @@
//!
//! // 3. Prover computes a non-interactive proof that plaintext is at most 1024 bits:
//!
//! let mut rng = rand_core::OsRng::default();
//!
//! let security = p::SecurityParams {
//! l: 1024,
//! epsilon: 128,
//! q: BigNumber::prime_from_rng(128, &mut rng),
//! };
//!
//! let rng = rand_core::OsRng::default();
//! let data = p::Data { key0, c: ciphertext, x: power, g };
//! let pdata = p::PrivateData { x: plaintext, nonce };
//! let (commitment, proof) =
Expand Down Expand Up @@ -93,25 +96,32 @@ use crate::unknown_order::BigNumber;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SecurityParams {
/// l in paper, bit size of plaintext
/// l in paper, bit size of +-plaintext
pub l: usize,
/// Epsilon in paper, range extension and security parameter for x
/// Epsilon in paper, slackness parameter
pub epsilon: usize,
/// q in paper. Security parameter for challenge
pub q: BigNumber,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))]
pub struct Data<C: Curve> {
/// N0 in paper, public key that C was encrypted on
pub key0: EncryptionKey,
/// C in paper, logarithm of X encrypted on N0
pub c: Ciphertext,
/// X in paper, exponent of plaintext of C
pub x: Point<C>,
/// A generator in group
pub g: Point<C>,
}

#[derive(Clone)]
pub struct PrivateData {
/// x in paper, logarithm of X and plaintext of C
pub x: BigNumber,
/// rho in paper, nonce in encryption x -> C
pub nonce: Nonce,
}

Expand Down Expand Up @@ -145,7 +155,7 @@ pub struct Proof {
pub use crate::common::Aux;

pub mod interactive {
use generic_ec::{Curve, Scalar};
use generic_ec::Curve;
use libpaillier::unknown_order::BigNumber;
use rand_core::RngCore;

Expand All @@ -163,8 +173,8 @@ pub mod interactive {
security: &SecurityParams,
mut rng: R,
) -> Result<(Commitment<C>, PrivateCommitment), ProtocolError> {
let two_to_l = BigNumber::one() << security.l;
let two_to_l_e = BigNumber::one() << (security.l + security.epsilon);
let two_to_l = BigNumber::one() << (security.l + 1);
let two_to_l_e = BigNumber::one() << (security.l + security.epsilon + 1);
let modulo_l = two_to_l * &aux.rsa_modulo;
let modulo_l_e = &two_to_l_e * &aux.rsa_modulo;

Expand Down Expand Up @@ -256,21 +266,24 @@ pub mod interactive {
fail_if(lhs == rhs, InvalidProof::EqualityCheckFailed(3))?;
}
fail_if(
proof.z1 <= one << (security.l + security.epsilon),
proof.z1 <= one << (security.l + security.epsilon + 1),
InvalidProof::RangeCheckFailed(4),
)?;

Ok(())
}

/// Generate random challenge
pub fn challenge<C, R>(rng: &mut R) -> BigNumber
///
/// `data` parameter is used to generate challenge in correct range
pub fn challenge<C, R>(rng: &mut R, security: &SecurityParams) -> BigNumber
maurges marked this conversation as resolved.
Show resolved Hide resolved
where
C: Curve,
R: RngCore,
{
let x = Scalar::<C>::random(rng);
BigNumber::from_slice(x.to_be_bytes().as_bytes())
// double the range to account for +-
let m = BigNumber::from(2) * &security.q;
BigNumber::from_rng(&m, rng)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Openssl (and maybe some other) backends of unknown_order ignore provided source of randomness. I just can imagine someone choosing openssl backend and, as a result, getting unintuitive errors and unexpected behavior.

Can we detect somehow which backend is chosen and produce compiler_error! (if it's possible to detect on compile-time) or panic! (if we can determine backend only in runtime)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's easier to forbid openssl as a feature, since a backend is explicitly selected with this crate. I'll disable it in the next commit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do that, but one can bypass that by setting pailler-zk.deafult-features = false, and turning openssl feature on directly on unknown-order. If we can't check which backend is used, we should at least add a note, that this backend is not supported

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have any root-level docs or readme where we could mention that, I'm fine if you open an issue to remember mention it in the docs in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. Let's make this and the discussion below into a separate documentation MR

}
}

Expand Down Expand Up @@ -353,8 +366,8 @@ pub mod non_interactive {
.finalize();

let mut rng = rand_chacha::ChaCha20Rng::from_seed(seed.into());
let scalar = Scalar::<C>::random(&mut rng);
BigNumber::from_slice(scalar.to_be_bytes().as_bytes())
let m = BigNumber::from(2) * &security.q;
BigNumber::from_rng(&m, &mut rng)
}
}

Expand All @@ -365,19 +378,29 @@ mod test {

use crate::common::{convert_scalar, InvalidProof};

fn passing_test<C: Curve>()
fn random_key<R: rand_core::RngCore>(rng: &mut R) -> Option<libpaillier::DecryptionKey> {
let p = BigNumber::prime_from_rng(1024, rng);
survived marked this conversation as resolved.
Show resolved Hide resolved
let q = BigNumber::prime_from_rng(1024, rng);
libpaillier::DecryptionKey::with_primes_unchecked(&p, &q)
}

fn nonce<R: rand_core::RngCore>(rng: &mut R, n: &BigNumber) -> Option<BigNumber> {
Some(BigNumber::from_rng(n, rng))
}

fn run<R: rand_core::RngCore, C: Curve>(
mut rng: R,
security: super::SecurityParams,
plaintext: BigNumber,
) -> Result<(), crate::common::InvalidProof>
where
Scalar<C>: FromHash,
{
let security = super::SecurityParams {
l: 1024,
epsilon: 128,
};
let private_key0 = libpaillier::DecryptionKey::random().unwrap();
let private_key0 = random_key(&mut rng).unwrap();
let key0 = libpaillier::EncryptionKey::from(&private_key0);

let plaintext = BigNumber::from(228);
let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), None).unwrap();
let nonce = nonce(&mut rng, key0.n());
let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), nonce).unwrap();
let g = generic_ec::Point::<C>::generator() * generic_ec::Scalar::<C>::from(1337);
let x = g * convert_scalar(&plaintext);

Expand All @@ -392,8 +415,8 @@ mod test {
nonce,
};

let p = BigNumber::prime(1024);
let q = BigNumber::prime(1024);
let p = BigNumber::prime_from_rng(1024, &mut rng);
let q = BigNumber::prime_from_rng(1024, &mut rng);
let rsa_modulo = p * q;
let s: BigNumber = 123.into();
let t: BigNumber = 321.into();
Expand All @@ -409,18 +432,25 @@ mod test {
&data,
&pdata,
&security,
rand_core::OsRng::default(),
rng,
)
.unwrap();

let r = super::non_interactive::verify(
shared_state,
&aux,
&data,
&commitment,
&security,
&proof,
);
super::non_interactive::verify(shared_state, &aux, &data, &commitment, &security, &proof)
}

fn passing_test<C: Curve>()
where
Scalar<C>: FromHash,
{
let mut rng = rand_core::OsRng::default();
let security = super::SecurityParams {
l: 1024,
epsilon: 256,
q: BigNumber::prime_from_rng(128, &mut rng),
};
let plaintext = BigNumber::from(228);
let r = run(rng, security, plaintext);
match r {
Ok(()) => (),
Err(e) => panic!("{:?}", e),
Expand All @@ -431,57 +461,14 @@ mod test {
where
Scalar<C>: FromHash,
{
let mut rng = rand_core::OsRng::default();
let security = super::SecurityParams {
l: 1024,
epsilon: 128,
q: BigNumber::prime_from_rng(128, &mut rng),
};
let private_key0 = libpaillier::DecryptionKey::random().unwrap();
let key0 = libpaillier::EncryptionKey::from(&private_key0);

let plaintext = BigNumber::from(1) << (security.l + security.epsilon + 1);
let (ciphertext, nonce) = key0.encrypt(plaintext.to_bytes(), None).unwrap();
let g = generic_ec::Point::<C>::generator() * generic_ec::Scalar::<C>::from(1337);
let x = g * convert_scalar(&plaintext);

let data = super::Data {
key0,
c: ciphertext,
x,
g,
};
let pdata = super::PrivateData {
x: plaintext,
nonce,
};

let p = BigNumber::prime(1024);
let q = BigNumber::prime(1024);
let rsa_modulo = p * q;
let s: BigNumber = 123.into();
let t: BigNumber = 321.into();
assert_eq!(s.gcd(&rsa_modulo), 1.into());
assert_eq!(t.gcd(&rsa_modulo), 1.into());
let aux = super::Aux { s, t, rsa_modulo };

let shared_state = sha2::Sha256::default();

let (commitment, proof) = super::non_interactive::prove(
shared_state.clone(),
&aux,
&data,
&pdata,
&security,
rand_core::OsRng::default(),
)
.unwrap();
let r = super::non_interactive::verify(
shared_state,
&aux,
&data,
&commitment,
&security,
&proof,
);
let r = run(rng, security, plaintext);
match r {
Ok(()) => panic!("proof should not pass"),
Err(InvalidProof::RangeCheckFailed(_)) => (),
Expand All @@ -506,4 +493,35 @@ mod test {
fn failing_million() {
failing_test::<crate::curve::C>()
}

// see notes in
// [crate::paillier_encryption_in_range::test::rejected_with_probability_1_over_2]
// for motivation and design of the following test.
// Altough no security estimate was given in the paper, my own calculations
// show that the parameters here achieve the probability about as good as in
// other tests

#[test]
fn mul_rejected_with_probability_1_over_2() {
use rand_core::SeedableRng;
fn maybe_rejected(mut rng: rand_chacha::ChaCha20Rng) -> bool {
let security = super::SecurityParams {
l: 1024,
epsilon: 130,
q: BigNumber::prime_from_rng(128, &mut rng),
};
let plaintext = (BigNumber::from(1) << (security.l + 1)) - 1;
let r = run::<_, generic_ec_curves::rust_crypto::Secp256r1>(rng, security, plaintext);
match r {
Ok(()) => true,
Err(crate::common::InvalidProof::RangeCheckFailed(4)) => false,
Err(e) => panic!("proof should not fail with: {:?}", e),
}
}

let rng = rand_chacha::ChaCha20Rng::seed_from_u64(2);
assert!(!maybe_rejected(rng), "should fail");
let rng = rand_chacha::ChaCha20Rng::seed_from_u64(3);
assert!(maybe_rejected(rng), "should pass");
}
}
Loading