From c07fb7cbb43be11c23c2b0a0c220be31aa607cb4 Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Fri, 22 Nov 2024 18:07:56 +0100 Subject: [PATCH] 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 7a10cf92a..f3cd1b530 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 4382142cb..b2c72dd19 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 72150adec..2f7ba7ff8 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() {