From d216865d6dfd8d8f0c59039b26ad1048a991b5f5 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Thu, 12 Sep 2024 11:08:56 +0200 Subject: [PATCH 1/3] chore(integer): fix an error message string referring to shortint --- tfhe/src/integer/ciphertext/compact_list.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tfhe/src/integer/ciphertext/compact_list.rs b/tfhe/src/integer/ciphertext/compact_list.rs index d4d7efa917..93c9394cab 100644 --- a/tfhe/src/integer/ciphertext/compact_list.rs +++ b/tfhe/src/integer/ciphertext/compact_list.rs @@ -323,8 +323,8 @@ impl ParameterSetConformant for CompactCiphertextList { pub const WRONG_UNPACKING_MODE_ERR_MSG: &str = "Cannot expand a CompactCiphertextList that requires unpacking without \ - a server key, please provide a shortint::ServerKey passing it with the \ - enum variant CompactCiphertextListUnpackingMode::UnpackIfNecessary \ + a server key, please provide a integer::ServerKey passing it with the \ + enum variant IntegerCompactCiphertextListUnpackingMode::UnpackIfNecessary \ as unpacking_mode."; impl CompactCiphertextList { From 62f6b3419a86d6a12ea37aecfd829fb315a963b6 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Thu, 12 Sep 2024 10:51:54 +0200 Subject: [PATCH 2/3] chore(wasm): add missing (?) wasm_bindgen annotation --- tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs index ed370acea2..298d8cce5c 100644 --- a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs @@ -700,6 +700,7 @@ impl CompactCiphertextList { self.0.get_kind_of(index).map(Into::into) } + #[wasm_bindgen] pub fn expand(&self) -> Result { catch_panic_result(|| { self.0 @@ -775,6 +776,7 @@ impl ProvenCompactCiphertextList { self.0.get_kind_of(index).map(Into::into) } + #[wasm_bindgen] pub fn verify_and_expand( &self, public_params: &CompactPkePublicParams, From 5c506cbcce91de629349efb4b56d930473ebce96 Mon Sep 17 00:00:00 2001 From: Arthur Meyre Date: Wed, 11 Sep 2024 17:26:03 +0200 Subject: [PATCH 3/3] feat(tfhe): add possibility to expand a ciphertext without verifying it --- tfhe/src/c_api/high_level_api/compact_list.rs | 15 +++ tfhe/src/high_level_api/compact_list.rs | 94 +++++++++++++++++++ .../high_level_api/tests/tags_on_entities.rs | 32 +++++++ tfhe/src/integer/ciphertext/compact_list.rs | 72 ++++++++++++++ .../js_high_level_api/integers.rs | 11 +++ tfhe/src/shortint/ciphertext/zk.rs | 23 +++++ tfhe/web_wasm_parallel_tests/worker.js | 14 +++ 7 files changed, 261 insertions(+) diff --git a/tfhe/src/c_api/high_level_api/compact_list.rs b/tfhe/src/c_api/high_level_api/compact_list.rs index 5e0e045593..4473e4e996 100644 --- a/tfhe/src/c_api/high_level_api/compact_list.rs +++ b/tfhe/src/c_api/high_level_api/compact_list.rs @@ -208,6 +208,21 @@ pub unsafe extern "C" fn proven_compact_ciphertext_list_verify_and_expand( }) } +#[cfg(feature = "zk-pok")] +#[no_mangle] +pub unsafe extern "C" fn proven_compact_ciphertext_list_expand_without_verification( + compact_list: *const ProvenCompactCiphertextList, + expander: *mut *mut CompactCiphertextListExpander, +) -> c_int { + catch_panic(|| { + let list = get_ref_checked(compact_list).unwrap(); + + let inner = list.0.expand_without_verification().unwrap(); + + *expander = Box::into_raw(Box::new(CompactCiphertextListExpander(inner))); + }) +} + #[no_mangle] pub unsafe extern "C" fn compact_ciphertext_list_expander_len( expander: *mut CompactCiphertextListExpander, diff --git a/tfhe/src/high_level_api/compact_list.rs b/tfhe/src/high_level_api/compact_list.rs index 4c83105918..6e9d9a01cd 100644 --- a/tfhe/src/high_level_api/compact_list.rs +++ b/tfhe/src/high_level_api/compact_list.rs @@ -292,6 +292,60 @@ mod zk { Some(_) => Err(crate::Error::new("Expected a CPU server key".to_string())), }) } + + #[doc(hidden)] + /// This function allows to expand a ciphertext without verifying the associated proof. + /// + /// If you are here you were probably looking for it: use at your own risks. + pub fn expand_without_verification(&self) -> crate::Result { + // For WASM + if !self.inner.is_packed() && !self.inner.needs_casting() { + // No ServerKey required, short circuit to avoid the global state call + return Ok(CompactCiphertextListExpander { + inner: self.inner.expand_without_verification( + IntegerCompactCiphertextListUnpackingMode::NoUnpacking, + IntegerCompactCiphertextListCastingMode::NoCasting, + )?, + tag: self.tag.clone(), + }); + } + + global_state::try_with_internal_keys(|maybe_keys| match maybe_keys { + None => Err(crate::high_level_api::errors::UninitializedServerKey.into()), + Some(InternalServerKey::Cpu(cpu_key)) => { + let unpacking_mode = if self.inner.is_packed() { + IntegerCompactCiphertextListUnpackingMode::UnpackIfNecessary( + cpu_key.pbs_key(), + ) + } else { + IntegerCompactCiphertextListUnpackingMode::NoUnpacking + }; + + let casting_mode = if self.inner.needs_casting() { + IntegerCompactCiphertextListCastingMode::CastIfNecessary( + cpu_key.cpk_casting_key().ok_or_else(|| { + crate::Error::new( + "No casting key found in ServerKey, \ + required to expand this CompactCiphertextList" + .to_string(), + ) + })?, + ) + } else { + IntegerCompactCiphertextListCastingMode::NoCasting + }; + + self.inner + .expand_without_verification(unpacking_mode, casting_mode) + .map(|expander| CompactCiphertextListExpander { + inner: expander, + tag: self.tag.clone(), + }) + } + #[cfg(feature = "gpu")] + Some(_) => Err(crate::Error::new("Expected a CPU server key".to_string())), + }) + } } } @@ -547,6 +601,26 @@ mod tests { // Correct type but wrong number of bits assert!(expander.get::(0).unwrap().is_err()); } + + let unverified_expander = compact_list.expand_without_verification().unwrap(); + + { + let a: FheUint32 = unverified_expander.get(0).unwrap().unwrap(); + let b: FheInt64 = unverified_expander.get(1).unwrap().unwrap(); + let c: FheBool = unverified_expander.get(2).unwrap().unwrap(); + let d: FheUint2 = unverified_expander.get(3).unwrap().unwrap(); + + let a: u32 = a.decrypt(&ck); + assert_eq!(a, 17); + let b: i64 = b.decrypt(&ck); + assert_eq!(b, -1); + let c = c.decrypt(&ck); + assert!(!c); + let d: u8 = d.decrypt(&ck); + assert_eq!(d, 3); + + assert!(unverified_expander.get::(4).is_none()); + } } #[cfg(feature = "zk-pok")] @@ -616,5 +690,25 @@ mod tests { // Correct type but wrong number of bits assert!(expander.get::(0).unwrap().is_err()); } + + let unverified_expander = compact_list.expand_without_verification().unwrap(); + + { + let a: FheUint32 = unverified_expander.get(0).unwrap().unwrap(); + let b: FheInt64 = unverified_expander.get(1).unwrap().unwrap(); + let c: FheBool = unverified_expander.get(2).unwrap().unwrap(); + let d: FheUint2 = unverified_expander.get(3).unwrap().unwrap(); + + let a: u32 = a.decrypt(&ck); + assert_eq!(a, 17); + let b: i64 = b.decrypt(&ck); + assert_eq!(b, -1); + let c = c.decrypt(&ck); + assert!(!c); + let d: u8 = d.decrypt(&ck); + assert_eq!(d, 3); + + assert!(unverified_expander.get::(4).is_none()); + } } } diff --git a/tfhe/src/high_level_api/tests/tags_on_entities.rs b/tfhe/src/high_level_api/tests/tags_on_entities.rs index 43c3d08d89..4c09154c50 100644 --- a/tfhe/src/high_level_api/tests/tags_on_entities.rs +++ b/tfhe/src/high_level_api/tests/tags_on_entities.rs @@ -100,6 +100,38 @@ fn test_tag_propagation_zk_pok() { let cbool = abool & bbool; assert_eq!(cbool.tag(), cks.tag()); } + + let unverified_expander = list_packed.expand_without_verification().unwrap(); + + { + let au32: FheUint32 = unverified_expander.get(0).unwrap().unwrap(); + let bu32: FheUint32 = unverified_expander.get(1).unwrap().unwrap(); + assert_eq!(au32.tag(), cks.tag()); + assert_eq!(bu32.tag(), cks.tag()); + + let cu32 = au32 + bu32; + assert_eq!(cu32.tag(), cks.tag()); + } + + { + let ai64: FheInt64 = unverified_expander.get(2).unwrap().unwrap(); + let bi64: FheInt64 = unverified_expander.get(3).unwrap().unwrap(); + assert_eq!(ai64.tag(), cks.tag()); + assert_eq!(bi64.tag(), cks.tag()); + + let ci64 = ai64 + bi64; + assert_eq!(ci64.tag(), cks.tag()); + } + + { + let abool: FheBool = unverified_expander.get(4).unwrap().unwrap(); + let bbool: FheBool = unverified_expander.get(5).unwrap().unwrap(); + assert_eq!(abool.tag(), cks.tag()); + assert_eq!(bbool.tag(), cks.tag()); + + let cbool = abool & bbool; + assert_eq!(cbool.tag(), cks.tag()); + } } #[test] diff --git a/tfhe/src/integer/ciphertext/compact_list.rs b/tfhe/src/integer/ciphertext/compact_list.rs index 93c9394cab..f286b6a547 100644 --- a/tfhe/src/integer/ciphertext/compact_list.rs +++ b/tfhe/src/integer/ciphertext/compact_list.rs @@ -636,6 +636,62 @@ impl ProvenCompactCiphertextList { )) } + #[doc(hidden)] + /// This function allows to expand a ciphertext without verifying the associated proof. + /// + /// If you are here you were probably looking for it: use at your own risks. + pub fn expand_without_verification( + &self, + unpacking_mode: IntegerCompactCiphertextListUnpackingMode<'_>, + casting_mode: IntegerCompactCiphertextListCastingMode<'_>, + ) -> crate::Result { + let is_packed = self.is_packed(); + + if is_packed + && matches!( + unpacking_mode, + IntegerCompactCiphertextListUnpackingMode::NoUnpacking + ) + { + return Err(crate::Error::new(String::from( + WRONG_UNPACKING_MODE_ERR_MSG, + ))); + } + + let expanded_blocks = self + .ct_list + .expand_without_verification(casting_mode.into())?; + + let expanded_blocks = if is_packed { + match unpacking_mode { + IntegerCompactCiphertextListUnpackingMode::UnpackIfNecessary(sks) => { + let degree = self.ct_list.proved_lists[0].0.degree; + let mut conformance_params = sks.key.conformance_params(); + conformance_params.degree = degree; + + for ct in expanded_blocks.iter() { + if !ct.is_conformant(&conformance_params) { + return Err(crate::Error::new( + "This compact list is not conformant with the given server key" + .to_string(), + )); + } + } + + extract_message_and_carries(expanded_blocks, sks) + } + IntegerCompactCiphertextListUnpackingMode::NoUnpacking => unreachable!(), + } + } else { + expanded_blocks + }; + + Ok(CompactCiphertextListExpander::new( + expanded_blocks, + self.info.clone(), + )) + } + pub fn is_packed(&self) -> bool { self.ct_list.proved_lists[0].0.degree.get() > self.ct_list.proved_lists[0] @@ -731,5 +787,21 @@ mod tests { let decrypted = cks.decrypt_radix::(&expanded); assert_eq!(msg, decrypted); } + + let unverified_expander = proven_ct + .expand_without_verification( + IntegerCompactCiphertextListUnpackingMode::UnpackIfNecessary(&sk), + IntegerCompactCiphertextListCastingMode::CastIfNecessary(ksk.as_view()), + ) + .unwrap(); + + for (idx, msg) in msgs.iter().copied().enumerate() { + let expanded = unverified_expander + .get::(idx) + .unwrap() + .unwrap(); + let decrypted = cks.decrypt_radix::(&expanded); + assert_eq!(msg, decrypted); + } } } diff --git a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs index 298d8cce5c..bfec4879a2 100644 --- a/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs +++ b/tfhe/src/js_on_wasm_api/js_high_level_api/integers.rs @@ -792,6 +792,17 @@ impl ProvenCompactCiphertextList { }) } + #[wasm_bindgen] + pub fn expand_without_verification(&self) -> Result { + catch_panic_result(|| { + let inner = self + .0 + .expand_without_verification() + .map_err(into_js_error)?; + Ok(CompactCiphertextListExpander(inner)) + }) + } + #[wasm_bindgen] pub fn serialize(&self) -> Result, JsError> { catch_panic_result(|| bincode::serialize(&self.0).map_err(into_js_error)) diff --git a/tfhe/src/shortint/ciphertext/zk.rs b/tfhe/src/shortint/ciphertext/zk.rs index 4e96746513..15b9b77d77 100644 --- a/tfhe/src/shortint/ciphertext/zk.rs +++ b/tfhe/src/shortint/ciphertext/zk.rs @@ -78,6 +78,18 @@ impl ProvenCompactCiphertextList { return Err(crate::ErrorKind::InvalidZkProof.into()); } + // We can call the function as we have verified the proofs + self.expand_without_verification(casting_mode) + } + + #[doc(hidden)] + /// This function allows to expand a ciphertext without verifying the associated proof. + /// + /// If you are here you were probably looking for it: use at your own risks. + pub fn expand_without_verification( + &self, + casting_mode: ShortintCompactCiphertextListCastingMode<'_>, + ) -> crate::Result> { let expanded = self .proved_lists .iter() @@ -155,6 +167,17 @@ mod tests { encryption_modulus, ) .unwrap(); + + { + let unproven_ct = proven_ct + .expand_without_verification(ShortintCompactCiphertextListCastingMode::NoCasting); + assert!(unproven_ct.is_ok()); + let unproven_ct = unproven_ct.unwrap(); + + let decrypted = cks.decrypt(&unproven_ct[0]); + assert_eq!(msg, decrypted); + } + let proven_ct = proven_ct.verify_and_expand( crs.public_params(), &pk, diff --git a/tfhe/web_wasm_parallel_tests/worker.js b/tfhe/web_wasm_parallel_tests/worker.js index 3d3b21596f..e254fa1e99 100644 --- a/tfhe/web_wasm_parallel_tests/worker.js +++ b/tfhe/web_wasm_parallel_tests/worker.js @@ -411,6 +411,10 @@ async function compactPublicKeyZeroKnowledge() { ); assert_eq(expander.get_uint64(0).decrypt(clientKey), input); + + let unverified_expander = deserialized.expand_without_verification(); + + assert_eq(unverified_expander.get_uint64(0).decrypt(clientKey), input); } { @@ -450,6 +454,16 @@ async function compactPublicKeyZeroKnowledge() { assert_eq(expander.get_uint64(2).decrypt(clientKey), inputs[2]); assert_eq(expander.get_uint64(3).decrypt(clientKey), inputs[3]); + + let unverified_expander = encrypted.expand_without_verification(); + + assert_eq(unverified_expander.get_uint64(0).decrypt(clientKey), inputs[0]); + + assert_eq(unverified_expander.get_uint64(1).decrypt(clientKey), inputs[1]); + + assert_eq(unverified_expander.get_uint64(2).decrypt(clientKey), inputs[2]); + + assert_eq(unverified_expander.get_uint64(3).decrypt(clientKey), inputs[3]); } }