Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines +40 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge New clippy lint breaks workspace build

Enabling fallible_impl_from = "deny" at workspace level will cause cargo clippy --all-targets -- -D warnings to fail immediately because there are existing From implementations that call unwrap (for example impl From<&Poly> for Rq in crates/fhe-math/src/rq/convert.rs:15-47 uses v.as_slice().unwrap()). That lint forbids fallible From impls, so adding it without converting those impls to TryFrom or adding an allow will make clippy/CI fail.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed changed in this PR.

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"
3 changes: 3 additions & 0 deletions crates/fhe-math/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions crates/fhe-math/benches/ntt.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions crates/fhe-math/benches/rns.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions crates/fhe-math/benches/rq.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down
3 changes: 3 additions & 0 deletions crates/fhe-math/benches/zq.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
5 changes: 5 additions & 0 deletions crates/fhe-math/src/ntt/native.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<Self> {
if !super::supports_ntt(p.p, size) {
None
Expand Down
14 changes: 13 additions & 1 deletion crates/fhe-math/src/ntt/tfhe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/fhe-math/src/rns/mod.rs
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -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<u64> {
self.moduli_u64
.iter()
Expand All @@ -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<u64>) -> BigUint {
let mut result = BigUint::zero();
izip!(rests.iter(), self.garner.iter())
Expand All @@ -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)
}
Expand Down
6 changes: 6 additions & 0 deletions crates/fhe-math/src/rns/scaler.rs
Original file line number Diff line number Diff line change
@@ -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 <https://eprint.iacr.org/2021/204.pdf>.

Expand All @@ -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 {
Expand All @@ -30,6 +33,7 @@ impl ScalingFactor {
}

/// Returns the identity element of `Self`.
#[must_use]
pub fn one() -> Self {
Self {
numerator: BigUint::one(),
Expand Down Expand Up @@ -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<RnsContext>,
to: &Arc<RnsContext>,
Expand Down Expand Up @@ -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<u64>, size: usize) -> Vec<u64> {
let mut out = vec![0; size];
self.scale(rests, (&mut out).into(), 0);
Expand Down
3 changes: 3 additions & 0 deletions crates/fhe-math/src/rq/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
17 changes: 13 additions & 4 deletions crates/fhe-math/src/rq/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -409,9 +411,16 @@ impl<'a, const N: usize> TryConvertFrom<&'a [i64; N]> for Poly {
}
}

impl From<&Poly> for Vec<u64> {
fn from(p: &Poly) -> Self {
p.coefficients.as_slice().unwrap().to_vec()
impl TryFrom<&Poly> for Vec<u64> {
type Error = Error;

fn try_from(p: &Poly) -> Result<Self> {
p.coefficients
.as_slice()
.ok_or_else(|| {
Error::Default("Polynomial coefficients are not contiguous in memory".to_string())
})
.map(|slice| slice.to_vec())
}
}

Expand Down
25 changes: 17 additions & 8 deletions crates/fhe-math/src/rq/mod.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -111,6 +114,7 @@ impl AsMut<Poly> for Poly {

impl Poly {
/// Creates a polynomial holding the constant 0.
#[must_use]
pub fn zero(ctx: &Arc<Context>, representation: Representation) -> Self {
Self {
ctx: ctx.clone(),
Expand Down Expand Up @@ -142,6 +146,7 @@ impl Poly {
}

/// Current representation of the polynomial.
#[must_use]
pub const fn representation(&self) -> &Representation {
&self.representation
}
Expand Down Expand Up @@ -242,6 +247,7 @@ impl Poly {
}

/// Generate a random polynomial deterministically from a seed.
#[must_use]
pub fn random_from_seed(
ctx: &Arc<Context>,
representation: Representation,
Expand Down Expand Up @@ -296,6 +302,7 @@ impl Poly {
}

/// Access the polynomial coefficients in RNS representation.
#[must_use]
pub fn coefficients(&self) -> ArrayView2<'_, u64> {
self.coefficients.view()
}
Expand Down Expand Up @@ -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<Context>,
Expand Down Expand Up @@ -508,6 +516,7 @@ impl Poly {
}

/// Returns the context of the underlying polynomial
#[must_use]
pub fn ctx(&self) -> &Arc<Context> {
&self.ctx
}
Expand Down Expand Up @@ -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::<u64>::from(&p), &[0; 16]);
assert_eq!(Vec::<u64>::from(&q), &[0; 16]);
assert_eq!(Vec::<u64>::try_from(&p).unwrap(), &[0; 16]);
assert_eq!(Vec::<u64>::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::<u64>::from(&p), [0; 16 * MODULI.len()]);
assert_eq!(Vec::<u64>::from(&q), [0; 16 * MODULI.len()]);
assert_eq!(Vec::<u64>::try_from(&p).unwrap(), [0; 16 * MODULI.len()]);
assert_eq!(Vec::<u64>::try_from(&q).unwrap(), [0; 16 * MODULI.len()]);
assert_eq!(Vec::<BigUint>::from(&p), reference);
assert_eq!(Vec::<BigUint>::from(&q), reference);

Expand Down Expand Up @@ -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::<u64>::from(&p);
let p_coefficients = Vec::<u64>::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::<u64>::from(&p);
let p_coefficients = Vec::<u64>::try_from(&p).unwrap();
assert_eq!(p_coefficients, p.coefficients().as_slice().unwrap())
}
Ok(())
Expand Down Expand Up @@ -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::<u64>::from(&p);
let p_coeffs = Vec::<u64>::try_from(&p).unwrap();

// Substitution by a multiple of 2 * degree, or even numbers, should fail
assert!(SubstitutionExponent::new(&ctx, 0).is_err());
Expand Down Expand Up @@ -895,7 +904,7 @@ mod tests {
p_coeffs[i]
};
}
assert_eq!(&Vec::<u64>::from(&q), &v);
assert_eq!(&Vec::<u64>::try_from(&q).unwrap(), &v);

let q_ntt = p_ntt.substitute(&SubstitutionExponent::new(&ctx, 3)?)?;
q.change_representation(Representation::Ntt);
Expand Down
Loading
Loading