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
103 changes: 80 additions & 23 deletions crates/fhe/src/bfv/ciphertext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::bfv::{parameters::BfvParameters, traits::TryConvertFrom};
use crate::proto::bfv::Ciphertext as CiphertextProto;
use crate::{Error, Result};
use crate::{Error, Result, SerializationError};
use fhe_math::rq::{Poly, Representation};
use fhe_traits::{
DeserializeParametrized, DeserializeWithContext, FheCiphertext, FheParametrized, Serialize,
Expand Down Expand Up @@ -49,7 +49,10 @@ impl Ciphertext {
/// must be in Ntt representation and with the same context.
pub fn new(c: Vec<Poly>, par: &Arc<BfvParameters>) -> Result<Self> {
if c.len() < 2 {
return Err(Error::TooFewValues(c.len(), 2));
return Err(Error::TooFewValues {
actual: c.len(),
minimum: 2,
});
}

let ctx = c[0].ctx();
Expand Down Expand Up @@ -98,17 +101,18 @@ impl Ciphertext {
/// Switch to a specific level (only moving down)
pub fn switch_to_level(&mut self, target_level: usize) -> Result<()> {
if target_level < self.level {
return Err(Error::DefaultError(format!(
"Cannot switch to a higher level: current {}, target {}",
self.level, target_level
)));
return Err(Error::InvalidLevel {
level: target_level,
min_level: self.level,
max_level: self.max_switchable_level(),
});
}
if target_level > self.max_switchable_level() {
return Err(Error::DefaultError(format!(
"Cannot switch to a level higher than the max: max {}, target {}",
self.max_switchable_level(),
target_level
)));
return Err(Error::InvalidLevel {
level: target_level,
min_level: self.level,
max_level: self.max_switchable_level(),
});
}
while self.level < target_level {
self.switch_down()?;
Expand Down Expand Up @@ -136,11 +140,12 @@ impl Serialize for Ciphertext {

impl DeserializeParametrized for Ciphertext {
fn from_bytes(bytes: &[u8], par: &Arc<BfvParameters>) -> Result<Self> {
if let Ok(ctp) = Message::decode(bytes) {
Ciphertext::try_convert_from(&ctp, par)
} else {
Err(Error::SerializationError)
}
let ctp = Message::decode(bytes).map_err(|_| {
Error::SerializationError(SerializationError::ProtobufError {
message: "Ciphertext decode".into(),
})
})?;
Ciphertext::try_convert_from(&ctp, par)
}

type Error = Error;
Expand Down Expand Up @@ -178,11 +183,17 @@ impl From<&Ciphertext> for CiphertextProto {
impl TryConvertFrom<&CiphertextProto> for Ciphertext {
fn try_convert_from(value: &CiphertextProto, par: &Arc<BfvParameters>) -> Result<Self> {
if value.c.is_empty() || (value.c.len() == 1 && value.seed.is_empty()) {
return Err(Error::DefaultError("Not enough polynomials".to_string()));
return Err(Error::InvalidCiphertext {
reason: "Not enough polynomials".into(),
});
}

if value.level as usize > par.max_level() {
return Err(Error::DefaultError("Invalid level".to_string()));
return Err(Error::InvalidLevel {
level: value.level as usize,
min_level: 0,
max_level: par.max_level(),
});
}

let ctx = par.context_at_level(value.level as usize)?;
Expand Down Expand Up @@ -222,13 +233,14 @@ mod tests {
traits::TryConvertFrom, BfvParameters, Ciphertext, Encoding, Plaintext, SecretKey,
};
use crate::proto::bfv::Ciphertext as CiphertextProto;
use crate::Error as FheError;
use fhe_traits::FheDecrypter;
use fhe_traits::{DeserializeParametrized, FheEncoder, FheEncrypter, Serialize};
use rand::thread_rng;
use std::error::Error;
use std::error::Error as StdError;

#[test]
fn proto_conversion() -> Result<(), Box<dyn Error>> {
fn proto_conversion() -> Result<(), Box<dyn StdError>> {
let mut rng = thread_rng();
for params in [
BfvParameters::default_arc(1, 16),
Expand All @@ -249,7 +261,7 @@ mod tests {
}

#[test]
fn serialize() -> Result<(), Box<dyn Error>> {
fn serialize() -> Result<(), Box<dyn StdError>> {
let mut rng = thread_rng();
for params in [
BfvParameters::default_arc(1, 16),
Expand All @@ -266,7 +278,7 @@ mod tests {
}

#[test]
fn new() -> Result<(), Box<dyn Error>> {
fn new() -> Result<(), Box<dyn StdError>> {
let mut rng = thread_rng();
for params in [
BfvParameters::default_arc(1, 16),
Expand Down Expand Up @@ -304,7 +316,7 @@ mod tests {
}

#[test]
fn switch_to_last_level() -> Result<(), Box<dyn Error>> {
fn switch_to_last_level() -> Result<(), Box<dyn StdError>> {
let mut rng = thread_rng();
for params in [
BfvParameters::default_arc(1, 16),
Expand All @@ -325,4 +337,49 @@ mod tests {

Ok(())
}

#[test]
fn switch_to_level_invalid() -> Result<(), Box<dyn StdError>> {
let mut rng = thread_rng();
let params = BfvParameters::default_arc(2, 16);
let sk = SecretKey::random(&params, &mut rng);
let v = params.plaintext.random_vec(params.degree(), &mut rng);
let pt = Plaintext::try_encode(&v, Encoding::simd(), &params)?;
let mut ct: Ciphertext = sk.try_encrypt(&pt, &mut rng)?;

// Move to level 1
ct.switch_down()?;
assert_eq!(ct.level, 1);

// Target level smaller than current
match ct.switch_to_level(0) {
Err(FheError::InvalidLevel {
level,
min_level,
max_level,
}) => {
assert_eq!(level, 0);
assert_eq!(min_level, 1);
assert_eq!(max_level, params.max_level());
}
_ => panic!("expected InvalidLevel error"),
}

// Target level larger than max
let too_high = params.max_level() + 1;
match ct.switch_to_level(too_high) {
Err(FheError::InvalidLevel {
level,
min_level,
max_level,
}) => {
assert_eq!(level, too_high);
assert_eq!(min_level, 1);
assert_eq!(max_level, params.max_level());
}
_ => panic!("expected InvalidLevel error"),
}

Ok(())
}
}
21 changes: 16 additions & 5 deletions crates/fhe/src/bfv/keys/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::bfv::traits::TryConvertFrom;
use crate::bfv::{BfvParameters, Ciphertext, Encoding, Plaintext};
use crate::proto::bfv::{Ciphertext as CiphertextProto, PublicKey as PublicKeyProto};
use crate::{Error, Result};
use crate::{Error, Result, SerializationError};
use fhe_math::rq::{Poly, Representation};
use fhe_traits::{DeserializeParametrized, FheEncrypter, FheParametrized, Serialize};
use prost::Message;
Expand Down Expand Up @@ -113,12 +113,19 @@ impl DeserializeParametrized for PublicKey {
type Error = Error;

fn from_bytes(bytes: &[u8], par: &Arc<Self::Parameters>) -> Result<Self> {
let proto: PublicKeyProto =
Message::decode(bytes).map_err(|_| Error::SerializationError)?;
let proto: PublicKeyProto = Message::decode(bytes).map_err(|_| {
Error::SerializationError(SerializationError::ProtobufError {
message: "PublicKey decode".into(),
})
})?;
if proto.c.is_some() {
let mut c = Ciphertext::try_convert_from(&proto.c.unwrap(), par)?;
if c.level != 0 {
Err(Error::SerializationError)
Err(Error::SerializationError(
SerializationError::InvalidFormat {
reason: "ciphertext level must be 0".into(),
},
))
} else {
// The polynomials of a public key should not allow for variable time
// computation.
Expand All @@ -130,7 +137,11 @@ impl DeserializeParametrized for PublicKey {
})
}
} else {
Err(Error::SerializationError)
Err(Error::SerializationError(
SerializationError::MissingField {
field_name: "c".into(),
},
))
}
}
}
Expand Down
80 changes: 56 additions & 24 deletions crates/fhe/src/bfv/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::bfv::{context::CipherPlainContext, context::ContextLevel};
use crate::proto::bfv::Parameters;
use crate::{Error, ParametersError, Result};
use crate::{Error, ParametersError, Result, SerializationError};
use fhe_math::{
ntt::NttOperator,
rns::{RnsContext, ScalingFactor},
Expand Down Expand Up @@ -105,13 +105,21 @@ impl BfvParameters {
current = current
.next
.get()
.ok_or_else(|| Error::DefaultError(format!("Invalid level: {level}")))?
.ok_or_else(|| Error::InvalidLevel {
level,
min_level: 0,
max_level: self.max_level(),
})?
.as_ref();
}
if current.level == level {
Ok(&current.poly_context)
} else {
Err(Error::DefaultError(format!("Invalid level: {level}")))
Err(Error::InvalidLevel {
level,
min_level: 0,
max_level: self.max_level(),
})
}
}

Expand All @@ -134,7 +142,13 @@ impl BfvParameters {
while current.level < level {
match current.next.get() {
Some(n) => current = n.clone(),
None => return Err(Error::DefaultError(format!("Invalid level: {level}"))),
None => {
return Err(Error::InvalidLevel {
level,
min_level: 0,
max_level: self.max_level(),
})
}
}
}
Ok(current)
Expand Down Expand Up @@ -275,26 +289,39 @@ impl BfvParametersBuilder {
/// Generate ciphertext moduli with the specified sizes
fn generate_moduli(moduli_sizes: &[usize], degree: usize) -> Result<Vec<u64>> {
let mut moduli = vec![];
for size in moduli_sizes {
let required_counts = moduli_sizes.iter().copied().counts();
let mut generated_counts: HashMap<usize, usize> = HashMap::new();
for (i, size) in moduli_sizes.iter().enumerate() {
if *size > 62 || *size < 10 {
return Err(Error::ParametersError(ParametersError::InvalidModulusSize(
*size, 10, 62,
)));
return Err(Error::ParametersError(
ParametersError::InvalidModulusSize {
index: i,
size: *size,
min: 10,
max: 62,
},
));
}

let mut upper_bound = 1 << size;
loop {
if let Some(prime) = generate_prime(*size, 2 * degree as u64, upper_bound) {
if !moduli.contains(&prime) {
moduli.push(prime);
*generated_counts.entry(*size).or_insert(0) += 1;
break;
} else {
upper_bound = prime;
}
} else {
return Err(Error::ParametersError(ParametersError::NotEnoughPrimes(
*size, degree,
)));
let needed = *required_counts.get(size).unwrap_or(&0);
let available = *generated_counts.get(size).unwrap_or(&0);
return Err(Error::ParametersError(ParametersError::NotEnoughPrimes {
size: *size,
degree,
needed,
available,
}));
}
}
}
Expand All @@ -311,29 +338,30 @@ impl BfvParametersBuilder {
pub fn build(&self) -> Result<BfvParameters> {
// Check that the degree is a power of 2 (and large enough).
if self.degree < 8 || !self.degree.is_power_of_two() {
return Err(Error::ParametersError(ParametersError::InvalidDegree(
self.degree,
)));
return Err(Error::ParametersError(
ParametersError::invalid_degree_with_bounds(self.degree),
));
}

// This checks that the plaintext modulus is valid.
// TODO: Check bound on the plaintext modulus.
let plaintext_modulus = Modulus::new(self.plaintext).map_err(|e| {
Error::ParametersError(ParametersError::InvalidPlaintext(e.to_string()))
Error::ParametersError(ParametersError::InvalidPlaintextModulus {
modulus: self.plaintext,
reason: e.to_string(),
})
})?;

// Check that one of `ciphertext_moduli` and `ciphertext_moduli_sizes` is
// specified.
if !self.ciphertext_moduli.is_empty() && !self.ciphertext_moduli_sizes.is_empty() {
return Err(Error::ParametersError(ParametersError::TooManySpecified(
"Only one of `ciphertext_moduli` and `ciphertext_moduli_sizes` can be specified"
.to_string(),
)));
return Err(Error::ParametersError(ParametersError::ConflictingParameters {
conflict: "Only one of `ciphertext_moduli` and `ciphertext_moduli_sizes` can be specified".into(),
}));
} else if self.ciphertext_moduli.is_empty() && self.ciphertext_moduli_sizes.is_empty() {
return Err(Error::ParametersError(ParametersError::TooFewSpecified(
"One of `ciphertext_moduli` and `ciphertext_moduli_sizes` must be specified"
.to_string(),
)));
return Err(Error::ParametersError(ParametersError::MissingParameter {
parameter: "ciphertext_moduli or ciphertext_moduli_sizes".into(),
}));
}

// Get or generate the moduli
Expand Down Expand Up @@ -501,7 +529,11 @@ impl Serialize for BfvParameters {

impl Deserialize for BfvParameters {
fn try_deserialize(bytes: &[u8]) -> Result<Self> {
let params: Parameters = Message::decode(bytes).map_err(|_| Error::SerializationError)?;
let params: Parameters = Message::decode(bytes).map_err(|_| {
Error::SerializationError(SerializationError::ProtobufError {
message: "Parameters decode".into(),
})
})?;
BfvParametersBuilder::new()
.set_degree(params.degree as usize)
.set_plaintext_modulus(params.plaintext)
Expand Down
Loading
Loading