From 786fe6649543c642d2c274557f92c1045840b7bb Mon Sep 17 00:00:00 2001 From: Nicolas Sarlin Date: Tue, 13 May 2025 12:39:20 +0200 Subject: [PATCH] chore(zk): check that crs group element at index n is 0 --- tfhe-zk-pok/src/curve_api.rs | 1 + tfhe-zk-pok/src/proofs/mod.rs | 9 +++++- tfhe-zk-pok/src/proofs/pke.rs | 2 +- tfhe-zk-pok/src/proofs/pke_v2.rs | 47 ++++++++++++++++++++++++++++++-- tfhe-zk-pok/src/serialization.rs | 5 ++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/tfhe-zk-pok/src/curve_api.rs b/tfhe-zk-pok/src/curve_api.rs index 972f3c53c..f2cf197d4 100644 --- a/tfhe-zk-pok/src/curve_api.rs +++ b/tfhe-zk-pok/src/curve_api.rs @@ -97,6 +97,7 @@ pub trait CurveGroupOps: + core::ops::Sub + core::ops::Neg + core::iter::Sum + + PartialEq { const ZERO: Self; const GENERATOR: Self; diff --git a/tfhe-zk-pok/src/proofs/mod.rs b/tfhe-zk-pok/src/proofs/mod.rs index 3f212d981..c1fe01e60 100644 --- a/tfhe-zk-pok/src/proofs/mod.rs +++ b/tfhe-zk-pok/src/proofs/mod.rs @@ -124,7 +124,14 @@ impl GroupElements { } /// Check if the elements are valid for their respective groups - pub fn is_valid(&self) -> bool { + pub fn is_valid(&self, n: usize) -> bool { + if self.g_list.0.len() != n * 2 + || self.g_hat_list.0.len() != n + || G::G1::projective(self.g_list[n + 1]) != G::G1::ZERO + { + return false; + } + let (g_list_valid, g_hat_list_valid) = rayon::join( || self.g_list.0.par_iter().all(G::G1::validate_affine), || self.g_hat_list.0.par_iter().all(G::G2::validate_affine), diff --git a/tfhe-zk-pok/src/proofs/pke.rs b/tfhe-zk-pok/src/proofs/pke.rs index 6bc91087b..acb950e27 100644 --- a/tfhe-zk-pok/src/proofs/pke.rs +++ b/tfhe-zk-pok/src/proofs/pke.rs @@ -189,7 +189,7 @@ impl PublicParams { /// - valid points of the curve /// - in the correct subgroup pub fn is_usable(&self) -> bool { - self.g_lists.is_valid() + self.g_lists.is_valid(self.n) } } diff --git a/tfhe-zk-pok/src/proofs/pke_v2.rs b/tfhe-zk-pok/src/proofs/pke_v2.rs index 1372ae2e3..d14001cc6 100644 --- a/tfhe-zk-pok/src/proofs/pke_v2.rs +++ b/tfhe-zk-pok/src/proofs/pke_v2.rs @@ -139,8 +139,16 @@ where hash_z, hash_chi, } = compressed; + + let uncompressed_g_lists = GroupElements::uncompress(g_lists)?; + if G::G1::projective(uncompressed_g_lists.g_list[n + 1]) != G::G1::ZERO { + return Err(InvalidSerializedPublicParamsError::InvalidGroupElements( + InvalidSerializedGroupElementsError::MissingPuncteredElement, + )); + } + Ok(Self { - g_lists: GroupElements::uncompress(g_lists)?, + g_lists: uncompressed_g_lists, D, n, d, @@ -166,6 +174,8 @@ where } impl PublicParams { + /// Builds a crs from raw elements. When the elements are received from an untrusted party, the + /// resulting crs should be validated with [`Self::is_usable`] #[allow(clippy::too_many_arguments)] pub fn from_vec( g_list: Vec>, @@ -227,8 +237,9 @@ impl PublicParams { /// This means checking that the points are: /// - valid points of the curve /// - in the correct subgroup + /// - the size of the list is correct and the element at index n is 0 pub fn is_usable(&self) -> bool { - self.g_lists.is_valid() + self.g_lists.is_valid(self.n) } } @@ -775,6 +786,7 @@ fn prove_impl( B_squared >= e_sqr_norm, "squared norm of error ({e_sqr_norm}) exceeds threshold ({B_squared})", ); + assert_eq!(G::G1::projective(g_list[n]), G::G1::ZERO); } // FIXME: div_round @@ -3290,6 +3302,37 @@ mod tests { } } + /// Test the `is_usable` method, that checks the correctness of the the crs + #[test] + fn test_crs_usable() { + let PkeTestParameters { + d, + k, + B, + q, + t, + msbs_zero_padding_bit_count, + } = PKEV2_TEST_PARAMS; + + let rng = &mut StdRng::seed_from_u64(0); + + 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); + + assert!(public_param.is_usable()); + + let public_param_that_was_compressed = + serialize_then_deserialize(&public_param, Compress::Yes).unwrap(); + + assert!(public_param_that_was_compressed.is_usable()); + + let mut bad_crs = public_param.clone(); + bad_crs.g_lists.g_list[public_param.n + 1] = bad_crs.g_lists.g_list[public_param.n]; + + assert!(!bad_crs.is_usable()); + } + /// Test the `is_usable` method, that checks the correctness of the EC points in the proof #[test] fn test_proof_usable() { diff --git a/tfhe-zk-pok/src/serialization.rs b/tfhe-zk-pok/src/serialization.rs index eb09ea1f1..8322e4cda 100644 --- a/tfhe-zk-pok/src/serialization.rs +++ b/tfhe-zk-pok/src/serialization.rs @@ -256,6 +256,7 @@ pub(crate) type SerializableFp12 = SerializableQuadExtField; pub enum InvalidSerializedGroupElementsError { InvalidAffine(InvalidSerializedAffineError), InvalidGlistDimension(InvalidArraySizeError), + MissingPuncteredElement, } impl Display for InvalidSerializedGroupElementsError { @@ -267,6 +268,9 @@ impl Display for InvalidSerializedGroupElementsError { InvalidSerializedGroupElementsError::InvalidGlistDimension(arr_error) => { write!(f, "invalid number of elements in g_list: {arr_error}") } + InvalidSerializedGroupElementsError::MissingPuncteredElement => { + write!(f, "Element at index n in g_list should be 0") + } } } } @@ -278,6 +282,7 @@ impl Error for InvalidSerializedGroupElementsError { InvalidSerializedGroupElementsError::InvalidGlistDimension(arr_error) => { Some(arr_error) } + InvalidSerializedGroupElementsError::MissingPuncteredElement => None, } } }