#![allow(missing_docs)]
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error, PartialEq, Eq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Error {
#[error("Math library error: {0}")]
MathError(fhe_math::Error),
#[error("Context mismatch: found {found}, expected {expected}")]
ContextMismatch { found: String, expected: String },
#[error("Polynomial format mismatch: found {found:?}, expected {expected:?}")]
PolyFormatMismatch {
found: fhe_math::rq::Representation,
expected: fhe_math::rq::Representation,
},
#[error("Encoding mismatch: found {found}, expected {expected}")]
EncodingMismatch { found: String, expected: String },
#[error("Encoding '{encoding}' not supported for parameters: {reason}")]
EncodingNotSupported { encoding: String, reason: String },
#[error("Data value {value} exceeds modulus {modulus}")]
DataExceedsModulus { value: u64, modulus: u64 },
#[error("Encoding data size {actual} exceeds limit {limit} for degree {degree}")]
EncodingDataExceedsLimit {
actual: usize,
limit: usize,
degree: usize,
},
#[error("Too many values provided: {actual} exceeds limit {limit}")]
TooManyValues { actual: usize, limit: usize },
#[error("Too few values provided: {actual} is below minimum {minimum}")]
TooFewValues { actual: usize, minimum: usize },
#[error("Level {level} out of bounds: valid range is [{min_level}, {max_level}]")]
InvalidLevel {
level: usize,
min_level: usize,
max_level: usize,
},
#[error("Invalid ciphertext: {reason}")]
InvalidCiphertext { reason: String },
#[error("Invalid plaintext: {reason}")]
InvalidPlaintext { reason: String },
#[error("Invalid secret key: {reason}")]
InvalidSecretKey { reason: String },
#[error("Secret key incompatible with context: {reason}")]
IncompatibleSecretKey { reason: String },
#[error("Invalid Galois element {element}: {reason}")]
InvalidGaloisElement { element: u64, reason: String },
#[error("Invalid rotation step {step}: must be in range [{min}, {max}]")]
InvalidRotationStep { step: i64, min: i64, max: i64 },
#[error("SIMD operations not supported: {reason}")]
SimdNotSupported { reason: String },
#[error("No decryptor available for operation")]
NoDecryptor,
#[error("Parameters error: {0}")]
ParametersError(ParametersError),
#[error("Serialization error: {0}")]
SerializationError(SerializationError),
#[error("Dimension mismatch: {operation} requires dimensions {expected}, got {actual}")]
DimensionMismatch {
operation: String,
expected: String,
actual: String,
},
#[error("Security validation failed: {reason}")]
SecurityValidationError { reason: String },
#[error("Unexpected error: {message}")]
UnexpectedError { message: String },
#[error("{0}")]
DefaultError(String),
}
impl From<fhe_math::Error> for Error {
fn from(e: fhe_math::Error) -> Self {
Error::MathError(e)
}
}
impl Error {
pub fn context_mismatch<T, U>(found: &T, expected: &U) -> Self
where
T: std::fmt::Debug,
U: std::fmt::Debug,
{
Self::ContextMismatch {
found: format!("{found:?}"),
expected: format!("{expected:?}"),
}
}
pub fn invalid_ciphertext<S: Into<String>>(reason: S) -> Self {
Self::InvalidCiphertext {
reason: reason.into(),
}
}
pub fn encoding_not_supported<S1, S2>(encoding: S1, reason: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Self::EncodingNotSupported {
encoding: encoding.into(),
reason: reason.into(),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum SerializationError {
#[error("Polynomial context not found: {context_id}")]
PolynomialContextNotFound { context_id: String },
#[error("{structure_type} has wrong number of polynomials: expected {expected}, got {actual}")]
WrongPolynomialCount {
structure_type: String,
expected: usize,
actual: usize,
},
#[error("Invalid serialized format: {reason}")]
InvalidFormat { reason: String },
#[error("Version mismatch: serialized with {serialized_version}, current version is {current_version}")]
VersionMismatch {
serialized_version: String,
current_version: String,
},
#[error("Corrupted data detected: {details}")]
CorruptedData { details: String },
#[error("Missing required field: {field_name}")]
MissingField { field_name: String },
#[error("IO error: {error}")]
IOError { error: String },
#[error("Protobuf error: {message}")]
ProtobufError { message: String },
}
impl From<std::io::Error> for SerializationError {
fn from(error: std::io::Error) -> Self {
SerializationError::IOError {
error: error.to_string(),
}
}
}
#[derive(Debug, Error, PartialEq, Eq)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum ParametersError {
#[error("Invalid polynomial degree {degree}: must be a power of 2 between {min} and {max}")]
InvalidDegree {
degree: usize,
min: usize,
max: usize,
},
#[error("Invalid plaintext modulus {modulus}: {reason}")]
InvalidPlaintextModulus { modulus: u64, reason: String },
#[error("Invalid ciphertext modulus at index {index}: {modulus} ({reason})")]
InvalidCiphertextModulus {
index: usize,
modulus: u64,
reason: String,
},
#[error("Invalid modulus size at index {index}: {size}, expected between {min} and {max}")]
InvalidModulusSize {
index: usize,
size: usize,
min: usize,
max: usize,
},
#[error(
"Not enough primes of size {size} for degree {degree}: need {needed}, found {available}"
)]
NotEnoughPrimes {
size: usize,
degree: usize,
needed: usize,
available: usize,
},
#[error("Duplicate moduli detected: {modulus} appears at indices {indices:?}")]
DuplicateModuli { modulus: u64, indices: Vec<usize> },
#[error("Moduli {modulus1} and {modulus2} are not coprime (gcd = {gcd})")]
ModuliNotCoprime {
modulus1: u64,
modulus2: u64,
gcd: u64,
},
#[error("Plaintext modulus {modulus} is not NTT-friendly for degree {degree}")]
PlaintextNotNttFriendly { modulus: u64, degree: usize },
#[error(
"Ciphertext modulus {modulus} at index {index} is not NTT-friendly for degree {degree}"
)]
CiphertextModulusNotNttFriendly {
index: usize,
modulus: u64,
degree: usize,
},
#[error("Plaintext modulus {plaintext_modulus} exceeds ciphertext modulus {ciphertext_modulus} at index {index}")]
PlaintextModulusTooLarge {
plaintext_modulus: u64,
ciphertext_modulus: u64,
index: usize,
},
#[error("Parameters provide insufficient security: estimated security level {actual} bits, minimum required {minimum} bits")]
InsufficientSecurity { actual: u32, minimum: u32 },
#[error("Invalid variance {variance}: must be between {min} and {max}")]
InvalidVariance {
variance: usize,
min: usize,
max: usize,
},
#[error("Conflicting parameters: {conflict}")]
ConflictingParameters { conflict: String },
#[error("Missing required parameter: {parameter}")]
MissingParameter { parameter: String },
#[error("No parameters available: {reason}")]
NoParametersAvailable { reason: String },
}
impl ParametersError {
pub fn invalid_degree_with_bounds(degree: usize) -> Self {
Self::InvalidDegree {
degree,
min: 8,
max: 65536,
}
}
pub fn insufficient_security(actual: u32) -> Self {
Self::InsufficientSecurity {
actual,
minimum: 128,
}
}
}
#[cfg(test)]
mod tests {
use super::{Error, ParametersError, SerializationError};
#[test]
fn error_strings() {
assert_eq!(
Error::MathError(fhe_math::Error::InvalidContext).to_string(),
"Math library error: Invalid context provided."
);
assert_eq!(
Error::ContextMismatch {
found: "a".into(),
expected: "b".into()
}
.to_string(),
"Context mismatch: found a, expected b"
);
assert_eq!(
Error::TooManyValues {
actual: 20,
limit: 17
}
.to_string(),
"Too many values provided: 20 exceeds limit 17"
);
assert_eq!(
Error::TooFewValues {
actual: 10,
minimum: 17
}
.to_string(),
"Too few values provided: 10 is below minimum 17"
);
assert_eq!(
Error::EncodingMismatch {
found: "enc1".into(),
expected: "enc2".into()
}
.to_string(),
"Encoding mismatch: found enc1, expected enc2"
);
assert_eq!(
Error::EncodingNotSupported {
encoding: "test".into(),
reason: "oops".into()
}
.to_string(),
"Encoding 'test' not supported for parameters: oops"
);
assert_eq!(
Error::SerializationError(SerializationError::InvalidFormat {
reason: "bad".into()
})
.to_string(),
"Serialization error: Invalid serialized format: bad"
);
assert_eq!(
Error::ParametersError(ParametersError::invalid_degree_with_bounds(10)).to_string(),
"Parameters error: Invalid polynomial degree 10: must be a power of 2 between 8 and 65536"
);
}
}