diff --git a/Cargo.toml b/Cargo.toml index 48d7ae2b..199365c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,13 @@ thiserror = "^2.0.17" zeroize = "^1.8.2" zeroize_derive = "^1.4.2" once_cell = "^1.19.0" + +[workspace.lints.clippy] +indexing_slicing = "deny" +fallible_impl_from = "deny" +wildcard_enum_match_arm = "deny" +# Note: unneeded_field_pattern is intentionally not enabled because it conflicts +# with the defensive programming pattern of explicitly listing all struct fields +# in destructuring to ensure compiler catches when new fields are added. +fn_params_excessive_bools = "deny" +must_use_candidate = "warn" diff --git a/crates/fhe-math/Cargo.toml b/crates/fhe-math/Cargo.toml index fd098fe1..f7792390 100644 --- a/crates/fhe-math/Cargo.toml +++ b/crates/fhe-math/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true rust-version.workspace = true version = "0.1.1" +[lints] +workspace = true + [lib] bench = false # Disable default bench (we use criterion) diff --git a/crates/fhe-math/benches/ntt.rs b/crates/fhe-math/benches/ntt.rs index c93e196b..dc2dd579 100644 --- a/crates/fhe-math/benches/ntt.rs +++ b/crates/fhe-math/benches/ntt.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe_math::{ntt::NttOperator, zq::Modulus}; use rand::rng; diff --git a/crates/fhe-math/benches/rns.rs b/crates/fhe-math/benches/rns.rs index 50b352e8..d4dffbb2 100644 --- a/crates/fhe-math/benches/rns.rs +++ b/crates/fhe-math/benches/rns.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe_math::rns::{RnsContext, RnsScaler, ScalingFactor}; use num_bigint::BigUint; diff --git a/crates/fhe-math/benches/rq.rs b/crates/fhe-math/benches/rq.rs index 310aec33..dc857ce2 100644 --- a/crates/fhe-math/benches/rq.rs +++ b/crates/fhe-math/benches/rq.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::measurement::WallTime; use criterion::{criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion}; use fhe_math::rq::*; diff --git a/crates/fhe-math/benches/zq.rs b/crates/fhe-math/benches/zq.rs index 100a8e60..9abdeb59 100644 --- a/crates/fhe-math/benches/zq.rs +++ b/crates/fhe-math/benches/zq.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe_math::zq::Modulus; use rand::rng; diff --git a/crates/fhe-math/src/ntt/native.rs b/crates/fhe-math/src/ntt/native.rs index 19ed68ed..d8cbedb0 100644 --- a/crates/fhe-math/src/ntt/native.rs +++ b/crates/fhe-math/src/ntt/native.rs @@ -1,3 +1,7 @@ +// Allow indexing in this performance-critical NTT implementation. +// The unsafe blocks already indicate this is performance-sensitive code. +#![allow(clippy::indexing_slicing)] + use crate::zq::Modulus; use itertools::Itertools; use rand::{Rng, SeedableRng}; @@ -24,6 +28,7 @@ impl NttOperator { /// Aborts if the size is not a power of 2 that is >= 8 in debug mode. /// Returns None if the modulus does not support the NTT for this specific /// size. + #[must_use] pub fn new(p: &Modulus, size: usize) -> Option { if !super::supports_ntt(p.p, size) { None diff --git a/crates/fhe-math/src/ntt/tfhe.rs b/crates/fhe-math/src/ntt/tfhe.rs index 226a824d..c9e34348 100644 --- a/crates/fhe-math/src/ntt/tfhe.rs +++ b/crates/fhe-math/src/ntt/tfhe.rs @@ -13,7 +13,19 @@ pub struct NttOperator { impl PartialEq for NttOperator { fn eq(&self, other: &Self) -> bool { - self.native_operator == other.native_operator + let Self { + tfhe_operator: _, + native_operator, + } = self; + let Self { + tfhe_operator: _, + native_operator: other_native_operator, + } = other; + + // We only compare native_operator because tfhe_operator (when present) is + // just an optimized implementation that computes the same result. + // Both operators should agree if native_operators match. + native_operator == other_native_operator } } diff --git a/crates/fhe-math/src/rns/mod.rs b/crates/fhe-math/src/rns/mod.rs index 11368ba5..6e732e06 100644 --- a/crates/fhe-math/src/rns/mod.rs +++ b/crates/fhe-math/src/rns/mod.rs @@ -1,4 +1,6 @@ #![warn(missing_docs, unused_imports)] +// Allow indexing in this performance-critical RNS implementation +#![allow(clippy::indexing_slicing)] //! Residue-Number System operations. @@ -105,11 +107,13 @@ impl RnsContext { } /// Returns the product of the moduli used when creating the RNS context. + #[must_use] pub const fn modulus(&self) -> &BigUint { &self.product } /// Project a BigUint into its rests. + #[must_use] pub fn project(&self, a: &BigUint) -> Vec { self.moduli_u64 .iter() @@ -121,6 +125,7 @@ impl RnsContext { /// /// Aborts if the number of rests is different than the number of moduli in /// debug mode. + #[must_use] pub fn lift(&self, rests: ArrayView1) -> BigUint { let mut result = BigUint::zero(); izip!(rests.iter(), self.garner.iter()) @@ -129,6 +134,7 @@ impl RnsContext { } /// Getter for the i-th garner coefficient. + #[must_use] pub fn get_garner(&self, i: usize) -> Option<&BigUint> { self.garner.get(i) } diff --git a/crates/fhe-math/src/rns/scaler.rs b/crates/fhe-math/src/rns/scaler.rs index 560cca63..37019b74 100644 --- a/crates/fhe-math/src/rns/scaler.rs +++ b/crates/fhe-math/src/rns/scaler.rs @@ -1,4 +1,6 @@ #![warn(missing_docs, unused_imports)] +// Allow indexing in this performance-critical RNS scaler implementation +#![allow(clippy::indexing_slicing)] //! RNS scaler inspired from Remark 3.2 of . @@ -20,6 +22,7 @@ pub struct ScalingFactor { impl ScalingFactor { /// Create a new scaling factor. Aborts if the denominator is 0. + #[must_use] pub fn new(numerator: &BigUint, denominator: &BigUint) -> Self { assert_ne!(denominator, &BigUint::zero()); Self { @@ -30,6 +33,7 @@ impl ScalingFactor { } /// Returns the identity element of `Self`. + #[must_use] pub fn one() -> Self { Self { numerator: BigUint::one(), @@ -68,6 +72,7 @@ impl RnsScaler { /// Create a RNS scaler by numerator / denominator. /// /// Aborts if denominator is equal to 0. + #[must_use] pub fn new( from: &Arc, to: &Arc, @@ -225,6 +230,7 @@ impl RnsScaler { /// /// Aborts if the number of rests is different than the number of moduli in /// debug mode, or if the size is not in [1, ..., rests.len()]. + #[must_use] pub fn scale_new(&self, rests: ArrayView1, size: usize) -> Vec { let mut out = vec![0; size]; self.scale(rests, (&mut out).into(), 0); diff --git a/crates/fhe-math/src/rq/context.rs b/crates/fhe-math/src/rq/context.rs index 849ef468..9bb94763 100644 --- a/crates/fhe-math/src/rq/context.rs +++ b/crates/fhe-math/src/rq/context.rs @@ -98,16 +98,19 @@ impl Context { } /// Returns the modulus as a BigUint. + #[must_use] pub fn modulus(&self) -> &BigUint { self.rns.modulus() } /// Returns a reference to the moduli in this context. + #[must_use] pub fn moduli(&self) -> &[u64] { &self.moduli } /// Returns a reference to the moduli as Modulus in this context. + #[must_use] pub fn moduli_operators(&self) -> &[Modulus] { &self.q } diff --git a/crates/fhe-math/src/rq/convert.rs b/crates/fhe-math/src/rq/convert.rs index 2fdc5972..c0799060 100644 --- a/crates/fhe-math/src/rq/convert.rs +++ b/crates/fhe-math/src/rq/convert.rs @@ -160,7 +160,9 @@ impl TryConvertFrom<&Rq> for Poly { RepresentationProto::Powerbasis => Representation::PowerBasis, RepresentationProto::Ntt => Representation::Ntt, RepresentationProto::Nttshoup => Representation::NttShoup, - _ => return Err(Error::Default("Unknown representation".to_string())), + RepresentationProto::Unknown => { + return Err(Error::Default("Unknown representation".to_string())) + } }; let variable_time = variable_time || value.allow_variable_time; @@ -409,9 +411,16 @@ impl<'a, const N: usize> TryConvertFrom<&'a [i64; N]> for Poly { } } -impl From<&Poly> for Vec { - fn from(p: &Poly) -> Self { - p.coefficients.as_slice().unwrap().to_vec() +impl TryFrom<&Poly> for Vec { + type Error = Error; + + fn try_from(p: &Poly) -> Result { + p.coefficients + .as_slice() + .ok_or_else(|| { + Error::Default("Polynomial coefficients are not contiguous in memory".to_string()) + }) + .map(|slice| slice.to_vec()) } } diff --git a/crates/fhe-math/src/rq/mod.rs b/crates/fhe-math/src/rq/mod.rs index 1c7be69f..6fa27edf 100644 --- a/crates/fhe-math/src/rq/mod.rs +++ b/crates/fhe-math/src/rq/mod.rs @@ -1,4 +1,7 @@ #![warn(missing_docs, unused_imports)] +// Allow indexing/slicing in this performance-critical polynomial arithmetic module. +// This code heavily uses RNS representation and requires fast array access. +#![allow(clippy::indexing_slicing)] //! Polynomials in R_q\[x\] = (ZZ_q1 x ... x ZZ_qn)\[x\] where the qi's are //! prime moduli in zq. @@ -111,6 +114,7 @@ impl AsMut for Poly { impl Poly { /// Creates a polynomial holding the constant 0. + #[must_use] pub fn zero(ctx: &Arc, representation: Representation) -> Self { Self { ctx: ctx.clone(), @@ -142,6 +146,7 @@ impl Poly { } /// Current representation of the polynomial. + #[must_use] pub const fn representation(&self) -> &Representation { &self.representation } @@ -242,6 +247,7 @@ impl Poly { } /// Generate a random polynomial deterministically from a seed. + #[must_use] pub fn random_from_seed( ctx: &Arc, representation: Representation, @@ -296,6 +302,7 @@ impl Poly { } /// Access the polynomial coefficients in RNS representation. + #[must_use] pub fn coefficients(&self) -> ArrayView2<'_, u64> { self.coefficients.view() } @@ -384,6 +391,7 @@ impl Poly { /// # Safety /// This operation also creates a polynomial that allows variable time /// operations. + #[must_use] pub unsafe fn create_constant_ntt_polynomial_with_lazy_coefficients_and_variable_time( power_basis_coefficients: &[u64], ctx: &Arc, @@ -508,6 +516,7 @@ impl Poly { } /// Returns the context of the underlying polynomial + #[must_use] pub fn ctx(&self) -> &Arc { &self.ctx } @@ -591,16 +600,16 @@ mod tests { let p = Poly::zero(&ctx, Representation::PowerBasis); let q = Poly::zero(&ctx, Representation::Ntt); assert_ne!(p, q); - assert_eq!(Vec::::from(&p), &[0; 16]); - assert_eq!(Vec::::from(&q), &[0; 16]); + assert_eq!(Vec::::try_from(&p).unwrap(), &[0; 16]); + assert_eq!(Vec::::try_from(&q).unwrap(), &[0; 16]); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::zero(&ctx, Representation::PowerBasis); let q = Poly::zero(&ctx, Representation::Ntt); assert_ne!(p, q); - assert_eq!(Vec::::from(&p), [0; 16 * MODULI.len()]); - assert_eq!(Vec::::from(&q), [0; 16 * MODULI.len()]); + assert_eq!(Vec::::try_from(&p).unwrap(), [0; 16 * MODULI.len()]); + assert_eq!(Vec::::try_from(&q).unwrap(), [0; 16 * MODULI.len()]); assert_eq!(Vec::::from(&p), reference); assert_eq!(Vec::::from(&q), reference); @@ -659,13 +668,13 @@ mod tests { for modulus in MODULI { let ctx = Arc::new(Context::new(&[*modulus], 16)?); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); - let p_coefficients = Vec::::from(&p); + let p_coefficients = Vec::::try_from(&p).unwrap(); assert_eq!(p_coefficients, p.coefficients().as_slice().unwrap()) } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); - let p_coefficients = Vec::::from(&p); + let p_coefficients = Vec::::try_from(&p).unwrap(); assert_eq!(p_coefficients, p.coefficients().as_slice().unwrap()) } Ok(()) @@ -867,7 +876,7 @@ mod tests { p_ntt.change_representation(Representation::Ntt); let mut p_ntt_shoup = p.clone(); p_ntt_shoup.change_representation(Representation::NttShoup); - let p_coeffs = Vec::::from(&p); + let p_coeffs = Vec::::try_from(&p).unwrap(); // Substitution by a multiple of 2 * degree, or even numbers, should fail assert!(SubstitutionExponent::new(&ctx, 0).is_err()); @@ -895,7 +904,7 @@ mod tests { p_coeffs[i] }; } - assert_eq!(&Vec::::from(&q), &v); + assert_eq!(&Vec::::try_from(&q).unwrap(), &v); let q_ntt = p_ntt.substitute(&SubstitutionExponent::new(&ctx, 3)?)?; q.change_representation(Representation::Ntt); diff --git a/crates/fhe-math/src/rq/ops.rs b/crates/fhe-math/src/rq/ops.rs index 77bed1d3..2269646f 100644 --- a/crates/fhe-math/src/rq/ops.rs +++ b/crates/fhe-math/src/rq/ops.rs @@ -188,7 +188,7 @@ impl MulAssign<&Poly> for Poly { } self.has_lazy_coefficients = false } - _ => { + Representation::PowerBasis => { panic!("Multiplication requires a multipliand in Ntt or NttShoup representation.") } } @@ -249,7 +249,7 @@ impl Mul<&Poly> for &Poly { q *= self; q } - _ => { + Representation::PowerBasis | Representation::Ntt => { let mut q = self.clone(); q *= p; q @@ -483,31 +483,31 @@ mod tests { let q = Poly::random(&ctx, Representation::PowerBasis, &mut rng); let r = &p + &q; assert_eq!(r.representation, Representation::PowerBasis); - let mut a = Vec::::from(&p); - m.add_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.add_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let q = Poly::random(&ctx, Representation::Ntt, &mut rng); let r = &p + &q; assert_eq!(r.representation, Representation::Ntt); - let mut a = Vec::::from(&p); - m.add_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.add_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::PowerBasis, &mut rng); let q = Poly::random(&ctx, Representation::PowerBasis, &mut rng); - let mut a = Vec::::from(&p); - let b = Vec::::from(&q); + let mut a = Vec::::try_from(&p).unwrap(); + let b = Vec::::try_from(&q).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.add_vec(&mut a[i * 16..(i + 1) * 16], &b[i * 16..(i + 1) * 16]) } let r = &p + &q; assert_eq!(r.representation, Representation::PowerBasis); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } Ok(()) } @@ -524,31 +524,31 @@ mod tests { let q = Poly::random(&ctx, Representation::PowerBasis, &mut rng); let r = &p - &q; assert_eq!(r.representation, Representation::PowerBasis); - let mut a = Vec::::from(&p); - m.sub_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.sub_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let q = Poly::random(&ctx, Representation::Ntt, &mut rng); let r = &p - &q; assert_eq!(r.representation, Representation::Ntt); - let mut a = Vec::::from(&p); - m.sub_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.sub_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::PowerBasis, &mut rng); let q = Poly::random(&ctx, Representation::PowerBasis, &mut rng); - let mut a = Vec::::from(&p); - let b = Vec::::from(&q); + let mut a = Vec::::try_from(&p).unwrap(); + let b = Vec::::try_from(&q).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.sub_vec(&mut a[i * 16..(i + 1) * 16], &b[i * 16..(i + 1) * 16]) } let r = &p - &q; assert_eq!(r.representation, Representation::PowerBasis); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } Ok(()) } @@ -565,23 +565,23 @@ mod tests { let q = Poly::random(&ctx, Representation::Ntt, &mut rng); let r = &p * &q; assert_eq!(r.representation, Representation::Ntt); - let mut a = Vec::::from(&p); - m.mul_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.mul_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let q = Poly::random(&ctx, Representation::Ntt, &mut rng); - let mut a = Vec::::from(&p); - let b = Vec::::from(&q); + let mut a = Vec::::try_from(&p).unwrap(); + let b = Vec::::try_from(&q).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.mul_vec(&mut a[i * 16..(i + 1) * 16], &b[i * 16..(i + 1) * 16]) } let r = &p * &q; assert_eq!(r.representation, Representation::Ntt); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } Ok(()) } @@ -598,23 +598,23 @@ mod tests { let q = Poly::random(&ctx, Representation::NttShoup, &mut rng); let r = &p * &q; assert_eq!(r.representation, Representation::Ntt); - let mut a = Vec::::from(&p); - m.mul_vec(&mut a, &Vec::::from(&q)); - assert_eq!(Vec::::from(&r), a); + let mut a = Vec::::try_from(&p).unwrap(); + m.mul_vec(&mut a, &Vec::::try_from(&q).unwrap()); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let q = Poly::random(&ctx, Representation::NttShoup, &mut rng); - let mut a = Vec::::from(&p); - let b = Vec::::from(&q); + let mut a = Vec::::try_from(&p).unwrap(); + let b = Vec::::try_from(&q).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.mul_vec(&mut a[i * 16..(i + 1) * 16], &b[i * 16..(i + 1) * 16]) } let r = &p * &q; assert_eq!(r.representation, Representation::Ntt); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } Ok(()) } @@ -630,32 +630,32 @@ mod tests { let p = Poly::random(&ctx, Representation::PowerBasis, &mut rng); let r = -&p; assert_eq!(r.representation, Representation::PowerBasis); - let mut a = Vec::::from(&p); + let mut a = Vec::::try_from(&p).unwrap(); m.neg_vec(&mut a); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let r = -&p; assert_eq!(r.representation, Representation::Ntt); - let mut a = Vec::::from(&p); + let mut a = Vec::::try_from(&p).unwrap(); m.neg_vec(&mut a); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } let ctx = Arc::new(Context::new(MODULI, 16)?); let p = Poly::random(&ctx, Representation::PowerBasis, &mut rng); - let mut a = Vec::::from(&p); + let mut a = Vec::::try_from(&p).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.neg_vec(&mut a[i * 16..(i + 1) * 16]) } let r = -&p; assert_eq!(r.representation, Representation::PowerBasis); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); let r = -p; assert_eq!(r.representation, Representation::PowerBasis); - assert_eq!(Vec::::from(&r), a); + assert_eq!(Vec::::try_from(&r).unwrap(), a); } Ok(()) } @@ -713,18 +713,18 @@ mod tests { let scalar = BigUint::from(42u64); let r = &p * &scalar; assert_eq!(r.representation, Representation::PowerBasis); - let mut expected = Vec::::from(&p); + let mut expected = Vec::::try_from(&p).unwrap(); m.scalar_mul_vec(&mut expected, 42u64); - assert_eq!(Vec::::from(&r), expected); + assert_eq!(Vec::::try_from(&r).unwrap(), expected); // Test with NTT representation let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let scalar = BigUint::from(123u64); let r = &p * &scalar; assert_eq!(r.representation, Representation::Ntt); - let mut expected = Vec::::from(&p); + let mut expected = Vec::::try_from(&p).unwrap(); m.scalar_mul_vec(&mut expected, 123u64); - assert_eq!(Vec::::from(&r), expected); + assert_eq!(Vec::::try_from(&r).unwrap(), expected); } let ctx = Arc::new(Context::new(MODULI, 16)?); @@ -734,24 +734,24 @@ mod tests { let scalar = BigUint::from(99u64); let r = &p * &scalar; assert_eq!(r.representation, Representation::PowerBasis); - let mut expected = Vec::::from(&p); + let mut expected = Vec::::try_from(&p).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.scalar_mul_vec(&mut expected[i * 16..(i + 1) * 16], 99u64) } - assert_eq!(Vec::::from(&r), expected); + assert_eq!(Vec::::try_from(&r).unwrap(), expected); // Test with NTT representation let p = Poly::random(&ctx, Representation::Ntt, &mut rng); let scalar = BigUint::from(77u64); let r = &p * &scalar; assert_eq!(r.representation, Representation::Ntt); - let mut expected = Vec::::from(&p); + let mut expected = Vec::::try_from(&p).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); m.scalar_mul_vec(&mut expected[i * 16..(i + 1) * 16], 77u64) } - assert_eq!(Vec::::from(&r), expected); + assert_eq!(Vec::::try_from(&r).unwrap(), expected); } Ok(()) } @@ -769,14 +769,14 @@ mod tests { assert_eq!(r.representation, Representation::Ntt); // Verify by computing the expected result manually for each modulus - let mut expected = Vec::::from(&p); + let mut expected = Vec::::try_from(&p).unwrap(); for i in 0..MODULI.len() { let m = Modulus::new(MODULI[i]).unwrap(); // Reduce the large scalar modulo this prime let scalar_mod_qi = (&large_scalar % MODULI[i]).to_u64_digits()[0]; m.scalar_mul_vec(&mut expected[i * 16..(i + 1) * 16], scalar_mod_qi) } - assert_eq!(Vec::::from(&r), expected); + assert_eq!(Vec::::try_from(&r).unwrap(), expected); Ok(()) } diff --git a/crates/fhe-math/src/zq/mod.rs b/crates/fhe-math/src/zq/mod.rs index 4762eb5e..3329af1b 100644 --- a/crates/fhe-math/src/zq/mod.rs +++ b/crates/fhe-math/src/zq/mod.rs @@ -38,7 +38,28 @@ impl Eq for Modulus {} impl PartialEq for Modulus { fn eq(&self, other: &Self) -> bool { - self.p == other.p + let Self { + p, + barrett_hi: _, + barrett_lo: _, + leading_zeros: _, + supports_opt: _, + distribution: _, + arch: _, + } = self; + let Self { + p: other_p, + barrett_hi: _, + barrett_lo: _, + leading_zeros: _, + supports_opt: _, + distribution: _, + arch: _, + } = other; + + // All other fields are deterministically derived from p, so we only compare p. + // The destructuring ensures the compiler will warn us if new fields are added. + p == other_p } } @@ -72,6 +93,7 @@ impl Modulus { /// Performs the modular addition of a and b in constant time. /// Aborts if a >= p or b >= p in debug mode. + #[must_use] pub const fn add(&self, a: u64, b: u64) -> u64 { debug_assert!(a < self.p && b < self.p); Self::reduce1(a + b, self.p) @@ -83,6 +105,7 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the values being added. + #[must_use] pub const unsafe fn add_vt(&self, a: u64, b: u64) -> u64 { debug_assert!(a < self.p && b < self.p); Self::reduce1_vt(a + b, self.p) @@ -90,6 +113,7 @@ impl Modulus { /// Performs the modular subtraction of a and b in constant time. /// Aborts if a >= p or b >= p in debug mode. + #[must_use] pub const fn sub(&self, a: u64, b: u64) -> u64 { debug_assert!(a < self.p && b < self.p); Self::reduce1(a + self.p - b, self.p) @@ -108,6 +132,7 @@ impl Modulus { /// Performs the modular multiplication of a and b in constant time. /// Aborts if a >= p or b >= p in debug mode. + #[must_use] pub const fn mul(&self, a: u64, b: u64) -> u64 { debug_assert!(a < self.p && b < self.p); self.reduce_u128((a as u128) * (b as u128)) @@ -127,6 +152,7 @@ impl Modulus { /// Optimized modular multiplication of a and b in constant time. /// /// Aborts if a >= p or b >= p in debug mode. + #[must_use] pub const fn mul_opt(&self, a: u64, b: u64) -> u64 { debug_assert!(self.supports_opt); debug_assert!(a < self.p && b < self.p); @@ -150,6 +176,7 @@ impl Modulus { /// Modular negation in constant time. /// /// Aborts if a >= p in debug mode. + #[must_use] pub const fn neg(&self, a: u64) -> u64 { debug_assert!(a < self.p); Self::reduce1(self.p - a, self.p) @@ -169,6 +196,7 @@ impl Modulus { /// Compute the Shoup representation of a. /// /// Aborts if a >= p in debug mode. + #[must_use] pub const fn shoup(&self, a: u64) -> u64 { debug_assert!(a < self.p); @@ -178,6 +206,7 @@ impl Modulus { /// Shoup multiplication of a and b in constant time. /// /// Aborts if b >= p or b_shoup != shoup(b) in debug mode. + #[must_use] pub const fn mul_shoup(&self, a: u64, b: u64, b_shoup: u64) -> u64 { Self::reduce1(self.lazy_mul_shoup(a, b, b_shoup), self.p) } @@ -196,6 +225,7 @@ impl Modulus { /// The output is in the interval [0, 2 * p). /// /// Aborts if b >= p or b_shoup != shoup(b) in debug mode. + #[must_use] pub const fn lazy_mul_shoup(&self, a: u64, b: u64, b_shoup: u64) -> u64 { debug_assert!(b < self.p); debug_assert!(b_shoup == self.shoup(b)); @@ -391,6 +421,7 @@ impl Modulus { /// Compute the Shoup representation of a vector. /// /// Aborts if any of the values of the vector is >= p in debug mode. + #[must_use] pub fn shoup_vec(&self, a: &[u64]) -> Vec { self.arch .dispatch(|| a.iter().map(|ai| self.shoup(*ai)).collect_vec()) @@ -456,6 +487,7 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the values being centered. + #[must_use] pub unsafe fn center_vec_vt(&self, a: &[u64]) -> Vec { self.arch .dispatch(|| a.iter().map(|ai| self.center_vt(*ai)).collect_vec()) @@ -486,6 +518,7 @@ impl Modulus { } /// Reduce a vector in place in constant time. + #[must_use] pub fn reduce_vec_i64(&self, a: &[i64]) -> Vec { self.arch .dispatch(|| a.iter().map(|ai| self.reduce_i64(*ai)).collect_vec()) @@ -496,12 +529,14 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the values being reduced. + #[must_use] pub unsafe fn reduce_vec_i64_vt(&self, a: &[i64]) -> Vec { self.arch .dispatch(|| a.iter().map(|ai| self.reduce_i64_vt(*ai)).collect()) } /// Reduce a vector in constant time. + #[must_use] pub fn reduce_vec_new(&self, a: &[u64]) -> Vec { self.arch .dispatch(|| a.iter().map(|ai| self.reduce(*ai)).collect()) @@ -512,6 +547,7 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the values being reduced. + #[must_use] pub unsafe fn reduce_vec_new_vt(&self, a: &[u64]) -> Vec { self.arch .dispatch(|| a.iter().map(|bi| self.reduce_vt(*bi)).collect()) @@ -539,6 +575,7 @@ impl Modulus { /// Modular exponentiation in variable time. /// /// Aborts if a >= p or n >= p in debug mode. + #[must_use] pub fn pow(&self, a: u64, n: u64) -> u64 { debug_assert!(a < self.p && n < self.p); @@ -564,6 +601,7 @@ impl Modulus { /// /// Returns None if p is not prime or a = 0. /// Aborts if a >= p in debug mode. + #[must_use] pub fn inv(&self, a: u64) -> std::option::Option { if !is_prime(self.p) || a == 0 { None @@ -575,6 +613,7 @@ impl Modulus { } /// Modular reduction of a u128 in constant time. + #[must_use] pub const fn reduce_u128(&self, a: u128) -> u64 { Self::reduce1(self.lazy_reduce_u128(a), self.p) } @@ -584,11 +623,13 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the value being reduced. + #[must_use] pub const unsafe fn reduce_u128_vt(&self, a: u128) -> u64 { Self::reduce1_vt(self.lazy_reduce_u128(a), self.p) } /// Modular reduction of a u64 in constant time. + #[must_use] pub const fn reduce(&self, a: u64) -> u64 { Self::reduce1(self.lazy_reduce(a), self.p) } @@ -598,11 +639,13 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the value being reduced. + #[must_use] pub const unsafe fn reduce_vt(&self, a: u64) -> u64 { Self::reduce1_vt(self.lazy_reduce(a), self.p) } /// Optimized modular reduction of a u128 in constant time. + #[must_use] pub const fn reduce_opt_u128(&self, a: u128) -> u64 { debug_assert!(self.supports_opt); Self::reduce1(self.lazy_reduce_opt_u128(a), self.p) @@ -619,6 +662,7 @@ impl Modulus { } /// Optimized modular reduction of a u64 in constant time. + #[must_use] pub const fn reduce_opt(&self, a: u64) -> u64 { Self::reduce1(self.lazy_reduce_opt(a), self.p) } @@ -628,6 +672,7 @@ impl Modulus { /// # Safety /// This function is not constant time and its timing may reveal information /// about the value being reduced. + #[must_use] pub const unsafe fn reduce_opt_vt(&self, a: u64) -> u64 { Self::reduce1_vt(self.lazy_reduce_opt(a), self.p) } @@ -671,6 +716,7 @@ impl Modulus { /// Lazy modular reduction of a in constant time. /// The output is in the interval [0, 2 * p). + #[must_use] pub const fn lazy_reduce_u128(&self, a: u128) -> u64 { let a_lo = a as u64; let a_hi = (a >> 64) as u64; @@ -689,6 +735,7 @@ impl Modulus { /// Lazy modular reduction of a in constant time. /// The output is in the interval [0, 2 * p). + #[must_use] pub const fn lazy_reduce(&self, a: u64) -> u64 { let p_lo_lo = ((a as u128) * (self.barrett_lo as u128)) >> 64; let p_lo_hi = (a as u128) * (self.barrett_hi as u128); @@ -706,6 +753,7 @@ impl Modulus { /// The output is in the interval [0, 2 * p). /// /// Aborts if the input is >= p ^ 2 in debug mode. + #[must_use] pub const fn lazy_reduce_opt_u128(&self, a: u128) -> u64 { debug_assert!(a < (self.p as u128) * (self.p as u128)); @@ -748,6 +796,7 @@ impl Modulus { /// Length of the serialization of a vector of size `size`. /// /// Panics if the size is not a multiple of 8. + #[must_use] pub const fn serialization_length(&self, size: usize) -> usize { assert!(size % 8 == 0); let p_nbits = 64 - (self.p - 1).leading_zeros() as usize; @@ -757,12 +806,14 @@ impl Modulus { /// Serialize a vector of elements of length a multiple of 8. /// /// Panics if the length of the vector is not a multiple of 8. + #[must_use] pub fn serialize_vec(&self, a: &[u64]) -> Vec { let p_nbits = 64 - (self.p - 1).leading_zeros() as usize; transcode_to_bytes(a, p_nbits) } /// Deserialize a vector of bytes into a vector of elements mod p. + #[must_use] pub fn deserialize_vec(&self, b: &[u8]) -> Vec { let p_nbits = 64 - (self.p - 1).leading_zeros() as usize; transcode_from_bytes(b, p_nbits) diff --git a/crates/fhe-math/src/zq/primes.rs b/crates/fhe-math/src/zq/primes.rs index ad8978c0..75c90116 100644 --- a/crates/fhe-math/src/zq/primes.rs +++ b/crates/fhe-math/src/zq/primes.rs @@ -6,6 +6,7 @@ use num_bigint::BigUint; /// Returns whether the modulus supports optimized multiplication and reduction. /// These optimized operations are possible when the modulus verifies /// Equation (1) of . +#[must_use] pub fn supports_opt(p: u64) -> bool { if p.leading_zeros() < 1 { return false; @@ -25,6 +26,7 @@ pub fn supports_opt(p: u64) -> bool { /// Generate a `num_bits`-bit prime, congruent to 1 mod `modulo`, strictly /// smaller than `upper_bound`. Note that `num_bits` must belong to (10..=62), /// and upper_bound must be <= 1 << num_bits. +#[must_use] pub fn generate_prime(num_bits: usize, modulo: u64, upper_bound: u64) -> Option { if !(10..=62).contains(&num_bits) { None diff --git a/crates/fhe-traits/Cargo.toml b/crates/fhe-traits/Cargo.toml index 56e242a7..fcae2d95 100644 --- a/crates/fhe-traits/Cargo.toml +++ b/crates/fhe-traits/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true rust-version.workspace = true version = "0.1.1" +[lints] +workspace = true + [lib] bench = false # Disable default bench (we use criterion) diff --git a/crates/fhe-util/Cargo.toml b/crates/fhe-util/Cargo.toml index 26085865..701ee271 100644 --- a/crates/fhe-util/Cargo.toml +++ b/crates/fhe-util/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true rust-version.workspace = true version = "0.1.1" +[lints] +workspace = true + [dependencies] num-bigint-dig = { workspace = true, features = ["prime"] } num-traits.workspace = true diff --git a/crates/fhe-util/src/lib.rs b/crates/fhe-util/src/lib.rs index 047289cc..5d797d23 100644 --- a/crates/fhe-util/src/lib.rs +++ b/crates/fhe-util/src/lib.rs @@ -13,6 +13,7 @@ use num_bigint_dig::{prime::probably_prime, BigUint, ModInverse}; use num_traits::{cast::ToPrimitive, PrimInt}; /// Returns whether the modulus p is prime; this function is 100% accurate. +#[must_use] pub fn is_prime(p: u64) -> bool { probably_prime(&BigUint::from(p), 0) } @@ -55,6 +56,7 @@ pub fn sample_vec_cbd( } /// Transcodes a vector of u64 of `nbits`-bit numbers into a vector of bytes. +#[must_use] pub fn transcode_to_bytes(a: &[u64], nbits: usize) -> Vec { assert!(0 < nbits && nbits <= 64); @@ -67,8 +69,11 @@ pub fn transcode_to_bytes(a: &[u64], nbits: usize) -> Vec { let mut current_value_nbits = 0; while current_index < a.len() { if current_value_nbits < 8 { - debug_assert!(64 - a[current_index].leading_zeros() <= nbits as u32); - current_value |= ((a[current_index] as u128) & mask) << current_value_nbits; + let value = a + .get(current_index) + .expect("current_index is guaranteed to be within bounds"); + debug_assert!(64 - value.leading_zeros() <= nbits as u32); + current_value |= ((*value as u128) & mask) << current_value_nbits; current_value_nbits += nbits; current_index += 1; } @@ -90,6 +95,7 @@ pub fn transcode_to_bytes(a: &[u64], nbits: usize) -> Vec { } /// Transcodes a vector of u8 into a vector of u64 of `nbits`-bit numbers. +#[must_use] pub fn transcode_from_bytes(b: &[u8], nbits: usize) -> Vec { assert!(0 < nbits && nbits <= 64); let mask = (u64::MAX >> (64 - nbits)) as u128; @@ -102,7 +108,10 @@ pub fn transcode_from_bytes(b: &[u8], nbits: usize) -> Vec { let mut current_index = 0; while current_index < b.len() { if current_value_nbits < nbits { - current_value |= (b[current_index] as u128) << current_value_nbits; + let value = b + .get(current_index) + .expect("current_index is guaranteed to be within bounds"); + current_value |= (*value as u128) << current_value_nbits; current_value_nbits += 8; current_index += 1; } @@ -124,6 +133,7 @@ pub fn transcode_from_bytes(b: &[u8], nbits: usize) -> Vec { /// Transcodes a vector of u64 of `input_nbits`-bit numbers into a vector of u64 /// of `output_nbits`-bit numbers. +#[must_use] pub fn transcode_bidirectional(a: &[u64], input_nbits: usize, output_nbits: usize) -> Vec { assert!(0 < input_nbits && input_nbits <= 64); assert!(0 < output_nbits && output_nbits <= 64); @@ -138,8 +148,11 @@ pub fn transcode_bidirectional(a: &[u64], input_nbits: usize, output_nbits: usiz let mut current_value_nbits = 0; while current_index < a.len() { if current_value_nbits < output_nbits { - debug_assert!(64 - a[current_index].leading_zeros() <= input_nbits as u32); - current_value |= ((a[current_index] as u128) & input_mask) << current_value_nbits; + let value = a + .get(current_index) + .expect("current_index is guaranteed to be within bounds"); + debug_assert!(64 - value.leading_zeros() <= input_nbits as u32); + current_value |= ((*value as u128) & input_mask) << current_value_nbits; current_value_nbits += input_nbits; current_index += 1; } @@ -162,6 +175,7 @@ pub fn transcode_bidirectional(a: &[u64], input_nbits: usize, output_nbits: usiz /// Computes the modular multiplicative inverse of `a` modulo `p`. Returns /// `None` if `a` is not invertible modulo `p`. +#[must_use] pub fn inverse(a: u64, p: u64) -> Option { let p = BigUint::from(p); let a = BigUint::from(a); @@ -180,6 +194,9 @@ pub fn variance(values: &[T]) -> f64 { #[cfg(test)] mod tests { + // Allow indexing in tests for convenience + #![allow(clippy::indexing_slicing)] + use itertools::Itertools; use rand::RngCore; diff --git a/crates/fhe/Cargo.toml b/crates/fhe/Cargo.toml index 5c7c9f50..a45be816 100644 --- a/crates/fhe/Cargo.toml +++ b/crates/fhe/Cargo.toml @@ -8,6 +8,9 @@ repository.workspace = true rust-version.workspace = true version = "0.1.1" +[lints] +workspace = true + [lib] bench = false # Disable default bench (we use criterion) diff --git a/crates/fhe/benches/bfv.rs b/crates/fhe/benches/bfv.rs index 19ebc252..74f5d97b 100644 --- a/crates/fhe/benches/bfv.rs +++ b/crates/fhe/benches/bfv.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe::bfv::{ BfvParameters, Ciphertext, Encoding, EvaluationKeyBuilder, Multiplicator, Plaintext, PublicKey, diff --git a/crates/fhe/benches/bfv_optimized_ops.rs b/crates/fhe/benches/bfv_optimized_ops.rs index 296002ef..55827375 100644 --- a/crates/fhe/benches/bfv_optimized_ops.rs +++ b/crates/fhe/benches/bfv_optimized_ops.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe::bfv::{dot_product_scalar, BfvParameters, Ciphertext, Encoding, Plaintext, SecretKey}; use fhe_traits::{FheEncoder, FheEncrypter}; diff --git a/crates/fhe/benches/bfv_rgsw.rs b/crates/fhe/benches/bfv_rgsw.rs index eac89887..1829e15d 100644 --- a/crates/fhe/benches/bfv_rgsw.rs +++ b/crates/fhe/benches/bfv_rgsw.rs @@ -1,3 +1,6 @@ +// Allow indexing in benchmarks for convenience +#![allow(clippy::indexing_slicing)] + use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use fhe::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, RGSWCiphertext, SecretKey}; use fhe_traits::{FheEncoder, FheEncrypter}; diff --git a/crates/fhe/examples/bfv_basic.rs b/crates/fhe/examples/bfv_basic.rs index 31427b4e..745fa600 100644 --- a/crates/fhe/examples/bfv_basic.rs +++ b/crates/fhe/examples/bfv_basic.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + use std::error::Error; use fhe::bfv::{BfvParameters, Encoding, Plaintext, PublicKey, SecretKey}; diff --git a/crates/fhe/examples/bfv_ops.rs b/crates/fhe/examples/bfv_ops.rs index ee15b6cb..56e198f5 100644 --- a/crates/fhe/examples/bfv_ops.rs +++ b/crates/fhe/examples/bfv_ops.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + mod util; use std::error::Error; diff --git a/crates/fhe/examples/mulpir.rs b/crates/fhe/examples/mulpir.rs index d6022d12..99c99e0b 100644 --- a/crates/fhe/examples/mulpir.rs +++ b/crates/fhe/examples/mulpir.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + // Implementation of MulPIR using the `fhe` crate. // // SealPIR is a Private Information Retrieval scheme that enables a client to diff --git a/crates/fhe/examples/pir.rs b/crates/fhe/examples/pir.rs index 1fa18211..b0822f27 100644 --- a/crates/fhe/examples/pir.rs +++ b/crates/fhe/examples/pir.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + use clap::Parser; #[derive(Parser)] diff --git a/crates/fhe/examples/rgsw.rs b/crates/fhe/examples/rgsw.rs index fa30427a..b6cc0730 100644 --- a/crates/fhe/examples/rgsw.rs +++ b/crates/fhe/examples/rgsw.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + use std::error::Error; use fhe::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext, RGSWCiphertext, SecretKey}; diff --git a/crates/fhe/examples/sealpir.rs b/crates/fhe/examples/sealpir.rs index 6d98c2bc..8ad40d0f 100644 --- a/crates/fhe/examples/sealpir.rs +++ b/crates/fhe/examples/sealpir.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + // Implementation of SealPIR using the `fhe` crate. // // SealPIR is a Private Information Retrieval scheme that enables a client to diff --git a/crates/fhe/examples/util.rs b/crates/fhe/examples/util.rs index eea8148a..e3e26e89 100644 --- a/crates/fhe/examples/util.rs +++ b/crates/fhe/examples/util.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + //! Utility functions for the examples use fhe::bfv; @@ -66,6 +69,7 @@ impl fmt::Display for DisplayDuration { /// little endian encoding of the index. When the element size is less than 4B, /// the encoding is truncated. #[allow(dead_code)] +#[must_use] pub fn generate_database(database_size: usize, elements_size: usize) -> Vec> { assert!(database_size > 0 && elements_size > 0); let mut database = vec![vec![0u8; elements_size]; database_size]; @@ -77,6 +81,7 @@ pub fn generate_database(database_size: usize, elements_size: usize) -> Vec], par: Arc, diff --git a/crates/fhe/examples/voting.rs b/crates/fhe/examples/voting.rs index d2de55c8..1afad190 100644 --- a/crates/fhe/examples/voting.rs +++ b/crates/fhe/examples/voting.rs @@ -1,3 +1,6 @@ +// Allow indexing in examples for simplicity +#![allow(clippy::indexing_slicing)] + // Implementation of multiparty voting using the `fhe` crate. mod util; diff --git a/crates/fhe/src/bfv/ciphertext.rs b/crates/fhe/src/bfv/ciphertext.rs index 53e65a46..f80660b2 100644 --- a/crates/fhe/src/bfv/ciphertext.rs +++ b/crates/fhe/src/bfv/ciphertext.rs @@ -55,7 +55,10 @@ impl Ciphertext { }); } - let ctx = c[0].ctx(); + let ctx = c + .first() + .expect("c has at least 2 elements due to length check above") + .ctx(); let level = par.level_of_context(ctx)?; // Check that all polynomials have the expected representation and context. @@ -121,6 +124,7 @@ impl Ciphertext { } /// Get the deepest level this ciphertext can reach + #[must_use] pub fn max_switchable_level(&self) -> usize { self.par.max_level() } @@ -153,6 +157,7 @@ impl DeserializeParametrized for Ciphertext { impl Ciphertext { /// Generate the zero ciphertext. + #[must_use] pub fn zero(par: &Arc) -> Self { Self { par: par.clone(), @@ -167,14 +172,28 @@ impl Ciphertext { impl From<&Ciphertext> for CiphertextProto { fn from(ct: &Ciphertext) -> Self { let mut proto = CiphertextProto::default(); - for i in 0..ct.len() - 1 { - proto.c.push(ct[i].to_bytes()) - } - if let Some(seed) = ct.seed { - proto.seed = seed.to_vec() - } else { - proto.c.push(ct[ct.len() - 1].to_bytes()) + + // Split the ciphertext polynomials into all-but-last and last + match ct.c.split_last() { + None => { + // Empty ciphertext - this should not happen as new() requires + // at least 2 polys but we handle it gracefully + } + Some((last, rest)) => { + // Serialize all but the last polynomial + for poly in rest { + proto.c.push(poly.to_bytes()); + } + + // Handle the last polynomial based on whether we have a seed + if let Some(seed) = ct.seed { + proto.seed = seed.to_vec(); + } else { + proto.c.push(last.to_bytes()); + } + } } + proto.level = ct.level as u32; proto } diff --git a/crates/fhe/src/bfv/context/chain.rs b/crates/fhe/src/bfv/context/chain.rs index b2fa841a..f54b6e75 100644 --- a/crates/fhe/src/bfv/context/chain.rs +++ b/crates/fhe/src/bfv/context/chain.rs @@ -34,9 +34,34 @@ pub struct ContextLevel { impl PartialEq for ContextLevel { fn eq(&self, other: &Self) -> bool { - self.level == other.level - && self.num_moduli == other.num_moduli - && self.cipher_plain_context == other.cipher_plain_context + let Self { + poly_context, + cipher_plain_context, + level, + num_moduli, + next: _, + prev: _, + down_scaler: _, + up_scaler: _, + mul_params: _, + } = self; + let Self { + poly_context: other_poly_context, + cipher_plain_context: other_cipher_plain_context, + level: other_level, + num_moduli: other_num_moduli, + next: _, + prev: _, + down_scaler: _, + up_scaler: _, + mul_params: _, + } = other; + + // OnceCell fields are lazily computed caching fields, not part of equality. + level == other_level + && num_moduli == other_num_moduli + && poly_context == other_poly_context + && cipher_plain_context == other_cipher_plain_context } } @@ -44,6 +69,7 @@ impl Eq for ContextLevel {} impl ContextLevel { /// Create a new context level + #[must_use] pub fn new( poly_context: Arc, cipher_plain_context: Arc, diff --git a/crates/fhe/src/bfv/encoding.rs b/crates/fhe/src/bfv/encoding.rs index bafd3384..87d06d2b 100644 --- a/crates/fhe/src/bfv/encoding.rs +++ b/crates/fhe/src/bfv/encoding.rs @@ -26,6 +26,7 @@ pub struct Encoding { impl Encoding { /// A Poly encoding encodes a vector as coefficients of a polynomial; /// homomorphic operations are therefore polynomial operations. + #[must_use] pub fn poly() -> Self { Self { encoding: EncodingEnum::Poly, @@ -37,6 +38,7 @@ impl Encoding { /// component-wise operations on the coefficients of the underlying vectors. /// The Simd encoding require that the plaintext modulus is congruent to 1 /// modulo the degree of the underlying polynomial. + #[must_use] pub fn simd() -> Self { Self { encoding: EncodingEnum::Simd, @@ -45,6 +47,7 @@ impl Encoding { } /// A poly encoding at a given level. + #[must_use] pub fn poly_at_level(level: usize) -> Self { Self { encoding: EncodingEnum::Poly, @@ -53,6 +56,7 @@ impl Encoding { } /// A simd encoding at a given level. + #[must_use] pub fn simd_at_level(level: usize) -> Self { Self { encoding: EncodingEnum::Simd, diff --git a/crates/fhe/src/bfv/keys/evaluation_key.rs b/crates/fhe/src/bfv/keys/evaluation_key.rs index 553a3e90..1fd2d99d 100644 --- a/crates/fhe/src/bfv/keys/evaluation_key.rs +++ b/crates/fhe/src/bfv/keys/evaluation_key.rs @@ -39,6 +39,7 @@ pub struct EvaluationKey { impl EvaluationKey { /// Reports whether the evaluation key enables to compute an homomorphic /// inner sums. + #[must_use] pub fn supports_inner_sum(&self) -> bool { let mut ret = self.gk.contains_key(&(self.par.degree() * 2 - 1)); let mut i = 1; @@ -82,6 +83,7 @@ impl EvaluationKey { /// Reports whether the evaluation key enables to rotate the rows of the /// plaintext. + #[must_use] pub fn supports_row_rotation(&self) -> bool { self.gk.contains_key(&(self.par.degree() * 2 - 1)) } @@ -102,6 +104,7 @@ impl EvaluationKey { /// Reports whether the evaluation key enables to rotate the columns of the /// plaintext. + #[must_use] pub fn supports_column_rotation_by(&self, i: usize) -> bool { if let Some(exp) = self.rot_to_gk_exponent.get(&i) { self.gk.contains_key(exp) @@ -128,6 +131,7 @@ impl EvaluationKey { } /// Reports whether the evaluation key supports oblivious expansion. + #[must_use] pub fn supports_expansion(&self, level: usize) -> bool { if level == 0 { true diff --git a/crates/fhe/src/bfv/keys/secret_key.rs b/crates/fhe/src/bfv/keys/secret_key.rs index 141f76f7..f701cea4 100644 --- a/crates/fhe/src/bfv/keys/secret_key.rs +++ b/crates/fhe/src/bfv/keys/secret_key.rs @@ -239,7 +239,7 @@ impl FheDecrypter for SecretKey { // TODO: Can we handle plaintext moduli that are BigUint? let v = Zeroizing::new( - Vec::::from(d.as_ref()) + Vec::::try_from(d.as_ref())? .iter_mut() .map(|vi| *vi + *self.par.plaintext) .collect_vec(), diff --git a/crates/fhe/src/bfv/mod.rs b/crates/fhe/src/bfv/mod.rs index c0735d7a..4829187b 100644 --- a/crates/fhe/src/bfv/mod.rs +++ b/crates/fhe/src/bfv/mod.rs @@ -1,4 +1,6 @@ #![warn(missing_docs, unused_imports)] +// Allow indexing in BFV cryptographic operations for performance +#![allow(clippy::indexing_slicing)] //! The Brakerski-Fan-Vercauteren homomorphic encryption scheme diff --git a/crates/fhe/src/bfv/parameters.rs b/crates/fhe/src/bfv/parameters.rs index a5947d0b..cde4d8db 100644 --- a/crates/fhe/src/bfv/parameters.rs +++ b/crates/fhe/src/bfv/parameters.rs @@ -74,26 +74,31 @@ unsafe impl Send for BfvParameters {} impl BfvParameters { /// Returns the underlying polynomial degree + #[must_use] pub const fn degree(&self) -> usize { self.polynomial_degree } /// Returns a reference to the ciphertext moduli + #[must_use] pub fn moduli(&self) -> &[u64] { &self.moduli } /// Returns a reference to the ciphertext moduli + #[must_use] pub fn moduli_sizes(&self) -> &[usize] { &self.moduli_sizes } /// Returns the plaintext modulus + #[must_use] pub const fn plaintext(&self) -> u64 { self.plaintext_modulus } /// Returns the maximum level allowed by these parameters. + #[must_use] pub fn max_level(&self) -> usize { self.moduli.len() - 1 } @@ -132,6 +137,7 @@ impl BfvParameters { } /// Return head of context chain + #[must_use] pub fn context_chain(&self) -> Arc { self.context_chain.clone() } @@ -239,6 +245,7 @@ impl BfvParameters { #[cfg(test)] /// Returns default parameters for tests. + #[must_use] pub fn default_arc(num_moduli: usize, degree: usize) -> Arc { if !degree.is_power_of_two() || degree < 8 { panic!("Invalid degree"); @@ -265,6 +272,7 @@ pub struct BfvParametersBuilder { impl BfvParametersBuilder { /// Creates a new instance of the builder #[allow(clippy::new_without_default)] + #[must_use] pub fn new() -> Self { Self { degree: Default::default(), diff --git a/crates/fhe/src/bfv/plaintext.rs b/crates/fhe/src/bfv/plaintext.rs index c33b8a64..0a466195 100644 --- a/crates/fhe/src/bfv/plaintext.rs +++ b/crates/fhe/src/bfv/plaintext.rs @@ -78,6 +78,7 @@ impl Plaintext { } /// Returns the level of this plaintext. + #[must_use] pub fn level(&self) -> usize { self.par.level_of_context(self.poly_ntt.ctx()).unwrap() } @@ -89,10 +90,29 @@ unsafe impl Send for Plaintext {} // even if one of them doesn't store its encoding information. impl PartialEq for Plaintext { fn eq(&self, other: &Self) -> bool { - let mut eq = self.par == other.par; - eq &= self.value == other.value; - if self.encoding.is_some() && other.encoding.is_some() { - eq &= self.encoding.as_ref().unwrap() == other.encoding.as_ref().unwrap() + let Self { + par, + value, + encoding, + poly_ntt, + level, + } = self; + let Self { + par: other_par, + value: other_value, + encoding: other_encoding, + poly_ntt: other_poly_ntt, + level: other_level, + } = other; + + let mut eq = par == other_par; + eq &= value == other_value; + eq &= poly_ntt == other_poly_ntt; + eq &= level == other_level; + // Compare encoding only if both plaintexts have encoding information. + // This allows comparing plaintexts even when one doesn't store encoding. + if encoding.is_some() && other_encoding.is_some() { + eq &= encoding == other_encoding; } eq } diff --git a/crates/fhe/src/errors.rs b/crates/fhe/src/errors.rs index 6027a6c2..e398b556 100644 --- a/crates/fhe/src/errors.rs +++ b/crates/fhe/src/errors.rs @@ -320,6 +320,7 @@ pub enum ParametersError { } impl ParametersError { + #[must_use] pub fn invalid_degree_with_bounds(degree: usize) -> Self { Self::InvalidDegree { degree, @@ -328,6 +329,7 @@ impl ParametersError { } } + #[must_use] pub fn insufficient_security(actual: u32) -> Self { Self::InsufficientSecurity { actual, diff --git a/crates/fhe/src/mbfv/mod.rs b/crates/fhe/src/mbfv/mod.rs index fadfb0ea..b2fd12bf 100644 --- a/crates/fhe/src/mbfv/mod.rs +++ b/crates/fhe/src/mbfv/mod.rs @@ -1,3 +1,6 @@ +// Allow indexing in multiparty BFV cryptographic operations for performance +#![allow(clippy::indexing_slicing)] + //! The Multiparty BFV scheme, as described by Christian Mouchet et. al. //! in [Multiparty Homomorphic Encryption from Ring-Learning-with-Errors](https://eprint.iacr.org/2020/304.pdf). diff --git a/crates/fhe/src/mbfv/secret_key_switch.rs b/crates/fhe/src/mbfv/secret_key_switch.rs index ce03fce9..d57ef702 100644 --- a/crates/fhe/src/mbfv/secret_key_switch.rs +++ b/crates/fhe/src/mbfv/secret_key_switch.rs @@ -158,7 +158,7 @@ impl Aggregate for Plaintext { let ctx_lvl = ct.par.context_level_at(ct.level)?; let d = Zeroizing::new(c.scale(&ctx_lvl.cipher_plain_context.scaler)?); let v = Zeroizing::new( - Vec::::from(d.as_ref()) + Vec::::try_from(d.as_ref())? .iter_mut() .map(|vi| *vi + *ct.par.plaintext) .collect_vec(),