From 3ddc3e32360f664c0bcc7756fc05df4196e97e04 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Fri, 22 Nov 2024 17:36:42 +0100 Subject: [PATCH 1/2] chore(zk): small refactor of tests to use assert_prove_and_verify --- tfhe-zk-pok/src/proofs/mod.rs | 2 +- tfhe-zk-pok/src/proofs/pke.rs | 15 +++--- tfhe-zk-pok/src/proofs/pke_v2.rs | 81 +++++++++++++------------------- 3 files changed, 40 insertions(+), 58 deletions(-) diff --git a/tfhe-zk-pok/src/proofs/mod.rs b/tfhe-zk-pok/src/proofs/mod.rs index 78a4e198a3..7a10cf92aa 100644 --- a/tfhe-zk-pok/src/proofs/mod.rs +++ b/tfhe-zk-pok/src/proofs/mod.rs @@ -133,7 +133,7 @@ impl GroupElements { } /// Allows to compute proof with bad inputs for tests -#[derive(PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq)] enum ProofSanityCheckMode { Panic, #[cfg(test)] diff --git a/tfhe-zk-pok/src/proofs/pke.rs b/tfhe-zk-pok/src/proofs/pke.rs index ec766f5ddb..4382142cb0 100644 --- a/tfhe-zk-pok/src/proofs/pke.rs +++ b/tfhe-zk-pok/src/proofs/pke.rs @@ -1262,6 +1262,8 @@ mod tests { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; + type Curve = curve_api::Bls12_446; + /// Compact key params used with pkev1 pub(super) const PKEV1_TEST_PARAMS: PkeTestParameters = PkeTestParameters { d: 1024, @@ -1310,8 +1312,6 @@ mod tests { let mut fake_metadata = [255u8; METADATA_LEN]; fake_metadata.fill_with(|| rng.gen::()); - type Curve = curve_api::Bls12_446; - // To check management of bigger k_max from CRS during test let crs_k = k + 1 + (rng.gen::() % (d - k)); @@ -1397,9 +1397,9 @@ mod tests { } } - fn prove_and_verify( + fn prove_and_verify( testcase: &PkeTestcase, - crs: &PublicParams, + crs: &PublicParams, load: ComputeLoad, rng: &mut StdRng, ) -> VerificationResult { @@ -1434,10 +1434,10 @@ mod tests { } } - fn assert_prove_and_verify( + fn assert_prove_and_verify( testcase: &PkeTestcase, testcase_name: &str, - crs: &PublicParams, + crs: &PublicParams, rng: &mut StdRng, expected_result: VerificationResult, ) { @@ -1466,8 +1466,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV1_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - // A CRS where the number of slots = the number of messages to encrypt let crs = crs_gen::(d, k, B, q, t, msbs_zero_padding_bit_count, rng); @@ -1630,7 +1628,6 @@ mod tests { }; let ct = testcase.encrypt(PKEV1_TEST_PARAMS); - type Curve = curve_api::Bls12_446; // To check management of bigger k_max from CRS during test let crs_k = k + 1 + (rng.gen::() % (d - k)); diff --git a/tfhe-zk-pok/src/proofs/pke_v2.rs b/tfhe-zk-pok/src/proofs/pke_v2.rs index 9db0ee29fa..72150adec8 100644 --- a/tfhe-zk-pok/src/proofs/pke_v2.rs +++ b/tfhe-zk-pok/src/proofs/pke_v2.rs @@ -2412,6 +2412,8 @@ mod tests { use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; + type Curve = curve_api::Bls12_446; + /// Compact key params used with pkev2 pub(super) const PKEV2_TEST_PARAMS: PkeTestParameters = PkeTestParameters { d: 2048, @@ -2459,8 +2461,6 @@ mod tests { let mut fake_metadata = [255u8; METADATA_LEN]; fake_metadata.fill_with(|| rng.gen::()); - type Curve = curve_api::Bls12_446; - // To check management of bigger k_max from CRS during test let crs_k = k + 1 + (rng.gen::() % (d - k)); @@ -2546,14 +2546,14 @@ mod tests { } } - fn prove_and_verify( + fn prove_and_verify( testcase: &PkeTestcase, - crs: &PublicParams, + ct: &PkeTestCiphertext, + crs: &PublicParams, load: ComputeLoad, + sanity_check_mode: ProofSanityCheckMode, rng: &mut StdRng, ) -> VerificationResult { - let ct = testcase.encrypt_unchecked(PKEV2_TEST_PARAMS); - let (public_commit, private_commit) = commit( testcase.a.clone(), testcase.b.clone(), @@ -2573,7 +2573,7 @@ mod tests { &testcase.metadata, load, rng, - ProofSanityCheckMode::Ignore, + sanity_check_mode, ); if verify(&proof, (crs, &public_commit), &testcase.metadata).is_ok() { @@ -2583,16 +2583,18 @@ mod tests { } } - fn assert_prove_and_verify( + fn assert_prove_and_verify( testcase: &PkeTestcase, + ct: &PkeTestCiphertext, testcase_name: &str, - crs: &PublicParams, - rng: &mut StdRng, + crs: &PublicParams, + sanity_check_mode: ProofSanityCheckMode, expected_result: VerificationResult, + rng: &mut StdRng, ) { for load in [ComputeLoad::Proof, ComputeLoad::Verify] { assert_eq!( - prove_and_verify(testcase, crs, load, rng), + prove_and_verify(testcase, ct, crs, load, sanity_check_mode, rng), expected_result, "Testcase {testcase_name} failed" ) @@ -2785,8 +2787,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV2_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - let crs = crs_gen::(d, k, B, q, t, msbs_zero_padding_bit_count, rng); let crs_max_k = crs_gen::(d, d, B, q, t, msbs_zero_padding_bit_count, rng); @@ -2848,19 +2848,24 @@ mod tests { expected_result, } in testcases { + let ct = testcase.encrypt_unchecked(PKEV2_TEST_PARAMS); assert_prove_and_verify( &testcase, + &ct, &format!("{name}_crs"), &crs, - rng, + ProofSanityCheckMode::Ignore, expected_result, + rng, ); assert_prove_and_verify( &testcase, + &ct, &format!("{name}_crs_max_k"), &crs_max_k, - rng, + ProofSanityCheckMode::Ignore, expected_result, + rng, ); } } @@ -2926,8 +2931,6 @@ mod tests { let ct = testcase.encrypt(PKEV2_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - // To check management of bigger k_max from CRS during test let crs_k = k + 1 + (rng.gen::() % (d - k)); @@ -2938,37 +2941,23 @@ mod tests { let public_param_that_was_not_compressed = serialize_then_deserialize(&original_public_param, Compress::No).unwrap(); - for public_param in [ - original_public_param, - public_param_that_was_compressed, - public_param_that_was_not_compressed, + for (public_param, test_name) in [ + (original_public_param, "original_params"), + ( + public_param_that_was_compressed, + "serialized_compressed_params", + ), + (public_param_that_was_not_compressed, "serialize_params"), ] { - let (public_commit, private_commit) = commit( - testcase.a.clone(), - testcase.b.clone(), - ct.c1.clone(), - ct.c2.clone(), - testcase.r.clone(), - testcase.e1.clone(), - testcase.m.clone(), - testcase.e2.clone(), + assert_prove_and_verify( + &testcase, + &ct, + test_name, &public_param, + ProofSanityCheckMode::Panic, + VerificationResult::Reject, rng, ); - - for load in [ComputeLoad::Proof, ComputeLoad::Verify] { - let proof = prove( - (&public_param, &public_commit), - &private_commit, - &testcase.metadata, - load, - rng, - ); - - assert!( - verify(&proof, (&public_param, &public_commit), &testcase.metadata).is_err() - ); - } } } @@ -2989,8 +2978,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV2_TEST_PARAMS); let ct = testcase.encrypt(PKEV2_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - let crs_k = k + 1 + (rng.gen::() % (d - k)); let public_param = crs_gen::(d, crs_k, B, q, t, msbs_zero_padding_bit_count, rng); @@ -3042,8 +3029,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV2_TEST_PARAMS); let ct = testcase.encrypt(PKEV2_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - let crs_k = k + 1 + (rng.gen::() % (d - k)); let public_param = crs_gen::(d, crs_k, B, q, t, msbs_zero_padding_bit_count, rng); From 26020a82dda857c4a822ad36275ef0be45fb1421 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Fri, 22 Nov 2024 18:07:56 +0100 Subject: [PATCH 2/2] chore(zk): add tests of a proof/verify with different ct --- tfhe-zk-pok/src/proofs/mod.rs | 55 +++++++++++-- tfhe-zk-pok/src/proofs/pke.rs | 136 ++++++++++++++++++++++++++++++- tfhe-zk-pok/src/proofs/pke_v2.rs | 132 ++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+), 13 deletions(-) diff --git a/tfhe-zk-pok/src/proofs/mod.rs b/tfhe-zk-pok/src/proofs/mod.rs index 7a10cf92aa..f3cd1b5305 100644 --- a/tfhe-zk-pok/src/proofs/mod.rs +++ b/tfhe-zk-pok/src/proofs/mod.rs @@ -306,6 +306,7 @@ mod test { use serde::{Deserialize, Serialize}; use crate::curve_api::Compressible; + use crate::proofs::decode_q; // One of our usecases uses 320 bits of additional metadata pub(super) const METADATA_LEN: usize = (320 / u8::BITS) as usize; @@ -438,8 +439,36 @@ mod test { } } - /// Encrypt using compact pke, the encryption is validated by doing a decryption - pub(super) fn encrypt(&self, params: PkeTestParameters) -> PkeTestCiphertext { + pub(super) fn sk_encrypt_zero( + &self, + params: PkeTestParameters, + rng: &mut StdRng, + ) -> Vec { + let PkeTestParameters { + d, + k: _, + B, + q: _, + t: _, + msbs_zero_padding_bit_count: _msbs_zero_padding_bit_count, + } = params; + + let e = (rng.gen::() % (2 * B)) as i64 - B as i64; + + let mut a = (0..d).map(|_| rng.gen::()).collect::>(); + + let b = a.iter().zip(&self.s).map(|(ai, si)| ai * si).sum::() + e; + + a.push(b); + a + } + + /// Decrypt a ciphertext list + pub(super) fn decrypt( + &self, + ct: &PkeTestCiphertext, + params: PkeTestParameters, + ) -> Vec { let PkeTestParameters { d, k, @@ -449,8 +478,6 @@ mod test { msbs_zero_padding_bit_count: _msbs_zero_padding_bit_count, } = params; - let ct = self.encrypt_unchecked(params); - // Check decryption let mut m_decrypted = vec![0i64; k]; for (i, decrypted) in m_decrypted.iter_mut().enumerate() { @@ -465,15 +492,25 @@ mod test { dot += self.s[d - j - 1] as i128 * c as i128; } - let q = if q == 0 { 1i128 << 64 } else { q as i128 }; + let decoded_q = decode_q(q) as i128; let val = ((ct.c2[i] as i128).wrapping_sub(dot)) * t as i128; - let div = val.div_euclid(q); - let rem = val.rem_euclid(q); - let result = div as i64 + (rem > (q / 2)) as i64; + let div = val.div_euclid(decoded_q); + let rem = val.rem_euclid(decoded_q); + let result = div as i64 + (rem > (decoded_q / 2)) as i64; let result = result.rem_euclid(params.t as i64); *decrypted = result; } + m_decrypted + } + + /// Encrypt using compact pke, the encryption is validated by doing a decryption + pub(super) fn encrypt(&self, params: PkeTestParameters) -> PkeTestCiphertext { + let ct = self.encrypt_unchecked(params); + + // Check decryption + let m_decrypted = self.decrypt(&ct, params); + assert_eq!(self.m, m_decrypted); ct @@ -491,7 +528,7 @@ mod test { } = params; let delta = { - let q = if q == 0 { 1i128 << 64 } else { q as i128 }; + let q = decode_q(q) as i128; // delta takes the encoding with the padding bit (q / t as i128) as u64 }; diff --git a/tfhe-zk-pok/src/proofs/pke.rs b/tfhe-zk-pok/src/proofs/pke.rs index 4382142cb0..b2c72dd196 100644 --- a/tfhe-zk-pok/src/proofs/pke.rs +++ b/tfhe-zk-pok/src/proofs/pke.rs @@ -1274,6 +1274,16 @@ mod tests { msbs_zero_padding_bit_count: 1, }; + /// Compact key params used with pkve1 to encrypt a single message + pub(super) const PKEV1_TEST_PARAMS_SINGLE: PkeTestParameters = PkeTestParameters { + d: 1024, + k: 1, + B: 4398046511104, // 2**42 + q: 0, + t: 32, // 2b msg, 2b carry, 1b padding + msbs_zero_padding_bit_count: 1, + }; + /// Test that the proof is rejected if we use a different value between encryption and proof #[test] fn test_pke() { @@ -1673,6 +1683,128 @@ mod tests { } } + /// Test verification with modified ciphertexts + #[test] + fn test_bad_ct() { + let PkeTestParameters { + d, + k, + B, + q, + t, + msbs_zero_padding_bit_count, + } = PKEV1_TEST_PARAMS; + + let effective_cleartext_t = t >> msbs_zero_padding_bit_count; + + let rng = &mut StdRng::seed_from_u64(0); + + let testcase = PkeTestcase::gen(rng, PKEV1_TEST_PARAMS_SINGLE); + let ct = testcase.encrypt(PKEV1_TEST_PARAMS_SINGLE); + + let ct_zero = testcase.sk_encrypt_zero(PKEV1_TEST_PARAMS_SINGLE, rng); + + let c1_plus_zero = ct + .c1 + .iter() + .zip(ct_zero.iter()) + .map(|(a1, az)| a1.wrapping_add(*az)) + .collect(); + let c2_plus_zero = vec![ct.c2[0].wrapping_add(*ct_zero.last().unwrap())]; + + let ct_plus_zero = PkeTestCiphertext { + c1: c1_plus_zero, + c2: c2_plus_zero, + }; + + let m_plus_zero = testcase.decrypt(&ct_plus_zero, PKEV1_TEST_PARAMS_SINGLE); + assert_eq!(testcase.m, m_plus_zero); + + let delta = { + let q = decode_q(q) as i128; + // delta takes the encoding with the padding bit + (q / t as i128) as u64 + }; + + let trivial = rng.gen::() % effective_cleartext_t; + let trivial_pt = trivial * delta; + let c2_plus_trivial = vec![ct.c2[0].wrapping_add(trivial_pt as i64)]; + + let ct_plus_trivial = PkeTestCiphertext { + c1: ct.c1.clone(), + c2: c2_plus_trivial, + }; + + let m_plus_trivial = testcase.decrypt(&ct_plus_trivial, PKEV1_TEST_PARAMS_SINGLE); + assert_eq!(testcase.m[0] + trivial as i64, m_plus_trivial[0]); + + let crs = crs_gen::(d, k, B, q, t, msbs_zero_padding_bit_count, rng); + + // Test proving with one ct and verifying another + let (public_commit_proof, private_commit) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct.c1.clone(), + ct.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + let (public_commit_verify_zero, _) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct_plus_zero.c1.clone(), + ct_plus_zero.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + let (public_commit_verify_trivial, _) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct_plus_trivial.c1.clone(), + ct_plus_trivial.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + for load in [ComputeLoad::Proof, ComputeLoad::Verify] { + let proof = prove( + (&crs, &public_commit_proof), + &private_commit, + &testcase.metadata, + load, + rng, + ); + + assert!(verify( + &proof, + (&crs, &public_commit_verify_zero), + &testcase.metadata + ) + .is_err()); + + assert!(verify( + &proof, + (&crs, &public_commit_verify_trivial), + &testcase.metadata + ) + .is_err()); + } + } + /// Test compression of proofs #[test] fn test_proof_compression() { @@ -1690,8 +1822,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV1_TEST_PARAMS); let ct = testcase.encrypt(PKEV1_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - let crs_k = k + 1 + (rng.gen::() % (d - k)); let public_param = crs_gen::(d, crs_k, B, q, t, msbs_zero_padding_bit_count, rng); @@ -1743,8 +1873,6 @@ mod tests { let testcase = PkeTestcase::gen(rng, PKEV1_TEST_PARAMS); let ct = testcase.encrypt(PKEV1_TEST_PARAMS); - type Curve = curve_api::Bls12_446; - let crs_k = k + 1 + (rng.gen::() % (d - k)); let public_param = crs_gen::(d, crs_k, B, q, t, msbs_zero_padding_bit_count, rng); diff --git a/tfhe-zk-pok/src/proofs/pke_v2.rs b/tfhe-zk-pok/src/proofs/pke_v2.rs index 72150adec8..2f7ba7ff82 100644 --- a/tfhe-zk-pok/src/proofs/pke_v2.rs +++ b/tfhe-zk-pok/src/proofs/pke_v2.rs @@ -2424,6 +2424,16 @@ mod tests { msbs_zero_padding_bit_count: 1, }; + /// Compact key params used with pkve2 to encrypt a single message + pub(super) const PKEV2_TEST_PARAMS_SINGLE: PkeTestParameters = PkeTestParameters { + d: 2048, + k: 1, + B: 131072, // 2**17 + q: 0, + t: 32, // 2b msg, 2b carry, 1b padding + msbs_zero_padding_bit_count: 1, + }; + /// Test that the proof is rejected if we use a different value between encryption and proof #[test] fn test_pke() { @@ -2961,6 +2971,128 @@ mod tests { } } + /// Test verification with modified ciphertexts + #[test] + fn test_bad_ct() { + let PkeTestParameters { + d, + k, + B, + q, + t, + msbs_zero_padding_bit_count, + } = PKEV2_TEST_PARAMS; + + let effective_cleartext_t = t >> msbs_zero_padding_bit_count; + + let rng = &mut StdRng::seed_from_u64(0); + + let testcase = PkeTestcase::gen(rng, PKEV2_TEST_PARAMS_SINGLE); + let ct = testcase.encrypt(PKEV2_TEST_PARAMS_SINGLE); + + let ct_zero = testcase.sk_encrypt_zero(PKEV2_TEST_PARAMS_SINGLE, rng); + + let c1_plus_zero = ct + .c1 + .iter() + .zip(ct_zero.iter()) + .map(|(a1, az)| a1.wrapping_add(*az)) + .collect(); + let c2_plus_zero = vec![ct.c2[0].wrapping_add(*ct_zero.last().unwrap())]; + + let ct_plus_zero = PkeTestCiphertext { + c1: c1_plus_zero, + c2: c2_plus_zero, + }; + + let m_plus_zero = testcase.decrypt(&ct_plus_zero, PKEV2_TEST_PARAMS_SINGLE); + assert_eq!(testcase.m, m_plus_zero); + + let delta = { + let q = decode_q(q) as i128; + // delta takes the encoding with the padding bit + (q / t as i128) as u64 + }; + + let trivial = rng.gen::() % effective_cleartext_t; + let trivial_pt = trivial * delta; + let c2_plus_trivial = vec![ct.c2[0].wrapping_add(trivial_pt as i64)]; + + let ct_plus_trivial = PkeTestCiphertext { + c1: ct.c1.clone(), + c2: c2_plus_trivial, + }; + + let m_plus_trivial = testcase.decrypt(&ct_plus_trivial, PKEV2_TEST_PARAMS_SINGLE); + assert_eq!(testcase.m[0] + trivial as i64, m_plus_trivial[0]); + + let crs = crs_gen::(d, k, B, q, t, msbs_zero_padding_bit_count, rng); + + // Test proving with one ct and verifying another + let (public_commit_proof, private_commit) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct.c1.clone(), + ct.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + let (public_commit_verify_zero, _) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct_plus_zero.c1.clone(), + ct_plus_zero.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + let (public_commit_verify_trivial, _) = commit( + testcase.a.clone(), + testcase.b.clone(), + ct_plus_trivial.c1.clone(), + ct_plus_trivial.c2.clone(), + testcase.r.clone(), + testcase.e1.clone(), + testcase.m.clone(), + testcase.e2.clone(), + &crs, + rng, + ); + + for load in [ComputeLoad::Proof, ComputeLoad::Verify] { + let proof = prove( + (&crs, &public_commit_proof), + &private_commit, + &testcase.metadata, + load, + rng, + ); + + assert!(verify( + &proof, + (&crs, &public_commit_verify_zero), + &testcase.metadata + ) + .is_err()); + + assert!(verify( + &proof, + (&crs, &public_commit_verify_trivial), + &testcase.metadata + ) + .is_err()); + } + } + /// Test compression of proofs #[test] fn test_proof_compression() {