diff --git a/Cargo.toml b/Cargo.toml index c759592..3a947bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ eyre ="0.6.12" rand ="0.8.5" rand_core ="0.6.4" sha2 ="0.10.8" -thiserror ="1.0.63" +thiserror ="2.0.9" serde ={ version="1.0.205", features=["derive"] } serde_derive="1.0.205" serde_json ="1.0.122" diff --git a/src/homomorphic_encryption/array_2d.rs b/src/homomorphic_encryption/array_2d.rs new file mode 100644 index 0000000..ed6fe1f --- /dev/null +++ b/src/homomorphic_encryption/array_2d.rs @@ -0,0 +1,112 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{ + marker::Send, + ops::{Add, Index, IndexMut, Range}, +}; + +/// Stores values in a 2 dimensional array. +#[derive(PartialEq, Clone, Debug)] +pub struct Array2d + Send + Clone + Default> { + data: Vec, + pub row_count: usize, + pub column_count: usize, +} + +impl + Send + Clone + Default> Array2d { + /// Returns the shape of the array as a tuple (row_count, column_count). + pub fn shape(&self) -> (usize, usize) { + (self.row_count, self.column_count) + } + + /// Returns the total number of elements in the array. + pub fn count(&self) -> usize { + self.row_count * self.column_count + } + + /// Creates a new `Array2d` with the given data and dimensions. + pub fn new(data: Vec, row_count: usize, column_count: usize) -> Self { + assert_eq!(data.len(), (row_count * column_count), "Data size does not match dimensions"); + Self { data, row_count, column_count } + } + + /// Calculates the linear index for a given (row, column) pair. + pub fn index(&self, row: usize, column: usize) -> usize { + row * self.column_count + column + } + + /// Returns the range of indices for a given row. + pub fn row_indices(&self, row: usize) -> Range { + let start = self.index(row, 0); + let end = start + self.column_count; + start..end + } + + /// Returns an iterator over the columns of the array. + pub fn columns_iter(&self) -> impl Iterator> { + (0..self.column_count).map(move |col| { + (0..self.row_count).map(move |row| &self.data[self.index(row, col)]).collect() + }) + } +} + +impl + Send + Clone + Default> Index for Array2d { + type Output = [T]; + + fn index(&self, row: usize) -> &Self::Output { + let start = self.index(row, 0); + let end = start + self.column_count; + &self.data[start..end] + } +} + +impl + Send + Clone + Default> IndexMut for Array2d { + fn index_mut(&mut self, row: usize) -> &mut Self::Output { + let start = self.index(row, 0); + let end = start + self.column_count; + &mut self.data[start..end] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_array2d() { + let data = vec![1, 2, 3, 4, 5, 6]; + let mut array = Array2d::new(data, 2, 3); + + // Test shape and count + assert_eq!(array.shape(), (2, 3)); + assert_eq!(array.count(), 6); + + // Test double indexing + assert_eq!(array[0][0], 1); + assert_eq!(array[0][1], 2); + assert_eq!(array[0][2], 3); + assert_eq!(array[1][0], 4); + assert_eq!(array[1][1], 5); + assert_eq!(array[1][2], 6); + + // Test mutability + array[1][2] = 42; + assert_eq!(array[1][2], 42); + + // Test row_indices + let row_1_indices = array.row_indices(1); + assert_eq!(row_1_indices, 3..6); + } +} diff --git a/src/homomorphic_encryption/bfv/bfv.rs b/src/homomorphic_encryption/bfv/bfv.rs new file mode 100644 index 0000000..c92e1a1 --- /dev/null +++ b/src/homomorphic_encryption/bfv/bfv.rs @@ -0,0 +1,311 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::homomorphic_encryption::ciphertext::Ciphertext; +use crate::homomorphic_encryption::context::Context; +use crate::homomorphic_encryption::he_scheme::{Coeff, EncodeFormat, HeScheme}; +use crate::homomorphic_encryption::keys::EvaluationKeyConfiguration; +use crate::homomorphic_encryption::plaintext::Plaintext; +use crate::homomorphic_encryption::scalar::ScalarType; +use eyre::Result; + +/// Brakerski-Fan-Vercauteren cryptosystem. +pub struct Bfv { + _marker: std::marker::PhantomData, +} + +/// Protocol for HE schemes. +impl HeScheme for Bfv { + /// Coefficient type for each polynomial. + type Scalar = T; + /// Polynomial format for the `HeScheme::CanonicalCiphertext`. + type CanonicalCiphertextFormat = Coeff; + type CoeffPlaintext = Plaintext; + type EvalPlaintext = Plaintext; + type CoeffCiphertext = Ciphertext; + type EvalCiphertext = (); + type CanonicalCiphertext = (); + type SecretKey = (); + type EvaluationKey = (); + const FRESH_CIPHERTEXT_POLY_COUNT: usize = 0; + const MIN_NOISE_BUDGET: f64 = 0.0; + + fn generate_secret_key(context: &Context) -> Result { + todo!() + } + + fn generate_evaluation_key( + context: &Context, + configuration: &EvaluationKeyConfiguration, + secret_key: &Self::SecretKey, + ) -> Result { + todo!() + } + + fn encode( + context: &Context, + values: &[V], + format: EncodeFormat, + ) -> Result { + todo!() + } + + fn encode_eval( + context: &Context, + values: &[V], + format: EncodeFormat, + moduli_count: usize, + ) -> Result { + todo!() + } + + fn decode(plaintext: &Self::CoeffPlaintext, format: EncodeFormat) -> Result> { + todo!() + } + + fn decode_eval(plaintext: &Self::EvalPlaintext, format: EncodeFormat) -> Result> { + todo!() + } + + fn encrypt( + plaintext: &Self::CoeffPlaintext, + secret_key: &Self::SecretKey, + ) -> Result { + todo!() + } + + fn zero_ciphertext( + context: &Context, + moduli_count: usize, + ) -> Result { + todo!() + } + + fn zero_ciphertext_eval( + context: &Context, + moduli_count: usize, + ) -> Result { + todo!() + } + + fn is_transparent(ciphertext: &Self::CoeffCiphertext) -> bool { + todo!() + } + + fn is_transparent_eval(ciphertext: &Self::EvalCiphertext) -> bool { + todo!() + } + + fn decrypt( + ciphertext: &Self::CoeffCiphertext, + secret_key: &Self::SecretKey, + ) -> Result { + todo!() + } + + fn decrypt_eval( + ciphertext: &Self::EvalCiphertext, + secret_key: &Self::SecretKey, + ) -> Result { + todo!() + } + + fn rotate_columns( + ciphertext: &mut Self::CanonicalCiphertext, + step: isize, + evaluation_key: &Self::EvaluationKey, + ) -> Result<()> { + todo!() + } + + fn swap_rows( + ciphertext: &mut Self::CanonicalCiphertext, + evaluation_key: &Self::EvaluationKey, + ) -> Result<()> { + todo!() + } + + fn add_assign_plaintext( + lhs: &mut Self::CoeffPlaintext, + rhs: &Self::CoeffPlaintext, + ) -> Result<()> { + todo!() + } + + fn add_assign_plaintext_eval( + lhs: &mut Self::EvalPlaintext, + rhs: &Self::EvalPlaintext, + ) -> Result<()> { + todo!() + } + + fn add_assign(lhs: &mut Self::CoeffCiphertext, rhs: &Self::CoeffCiphertext) -> Result<()> { + todo!() + } + + fn add_assign_eval(lhs: &mut Self::EvalCiphertext, rhs: &Self::EvalCiphertext) -> Result<()> { + todo!() + } + + fn sub_assign(lhs: &mut Self::CoeffCiphertext, rhs: &Self::CoeffCiphertext) -> Result<()> { + todo!() + } + + fn sub_assign_eval(lhs: &mut Self::EvalCiphertext, rhs: &Self::EvalCiphertext) -> Result<()> { + todo!() + } + + fn add_assign_ciphertext_plaintext( + ciphertext: &mut Self::CoeffCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()> { + todo!() + } + + fn add_assign_ciphertext_plaintext_eval( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()> { + todo!() + } + + fn sub_assign_ciphertext_plaintext( + ciphertext: &mut Self::CoeffCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()> { + todo!() + } + + fn sub_assign_ciphertext_plaintext_eval( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()> { + todo!() + } + + fn mul_assign( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()> { + todo!() + } + + fn neg_assign(ciphertext: &mut Self::CoeffCiphertext) { + todo!() + } + + fn neg_assign_eval(ciphertext: &mut Self::EvalCiphertext) { + todo!() + } + + fn inner_product(lhs: I, rhs: I) -> Result + where + I: IntoIterator, + { + todo!() + } + + fn inner_product_plaintexts(ciphertexts: C, plaintexts: P) -> Result + where + C: IntoIterator, + P: IntoIterator, + { + todo!() + } + + fn inner_product_optional_plaintexts( + ciphertexts: C, + plaintexts: P, + ) -> Result + where + C: IntoIterator, + P: IntoIterator>, + { + todo!() + } + + fn mul_assign_ciphertexts( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()> { + todo!() + } + + fn add_assign_canonical( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()> { + todo!() + } + + fn sub_assign_canonical( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()> { + todo!() + } + + fn sub_assign_canonical_plaintext( + ciphertext: &mut Self::CanonicalCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()> { + todo!() + } + + fn sub_assign_canonical_plaintext_eval( + ciphertext: &mut Self::CanonicalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()> { + todo!() + } + + fn mod_switch_down(ciphertext: &mut Self::CanonicalCiphertext) -> Result<()> { + todo!() + } + + fn apply_galois( + ciphertext: &mut Self::CanonicalCiphertext, + element: usize, + key: &Self::EvaluationKey, + ) -> Result<()> { + todo!() + } + + fn relinearize( + ciphertext: &mut Self::CanonicalCiphertext, + key: &Self::EvaluationKey, + ) -> Result<()> { + todo!() + } + + fn validate_equality(lhs: &Context, rhs: &Context) -> Result<()> { + todo!() + } + + fn noise_budget( + ciphertext: &Self::CoeffCiphertext, + secret_key: &Self::SecretKey, + variable_time: bool, + ) -> Result { + todo!() + } + + fn noise_budget_eval( + ciphertext: &Self::EvalCiphertext, + secret_key: &Self::SecretKey, + variable_time: bool, + ) -> Result { + todo!() + } +} diff --git a/src/homomorphic_encryption/bfv/mod.rs b/src/homomorphic_encryption/bfv/mod.rs new file mode 100644 index 0000000..441c250 --- /dev/null +++ b/src/homomorphic_encryption/bfv/mod.rs @@ -0,0 +1 @@ +// pub(crate) mod bfv; diff --git a/src/homomorphic_encryption/ciphertext.rs b/src/homomorphic_encryption/ciphertext.rs new file mode 100644 index 0000000..6e396f9 --- /dev/null +++ b/src/homomorphic_encryption/ciphertext.rs @@ -0,0 +1,51 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::marker::PhantomData; + +use crate::homomorphic_encryption::{ + context::Context, + he_scheme::{HeScheme, PolyFormat}, + poly_rq::poly::PolyRq, +}; + +/// Ciphertext type. +pub struct Ciphertext { + context: Context, + polys: Vec>, + correction_factor: Scheme::Scalar, + seed: Vec, + _marker: PhantomData, +} + +impl Ciphertext { + /// The number of polynomials in the ciphertext. + /// + /// After a fresh encryption, the ciphertext has `HeScheme::freshCiphertextPolyCount` + /// polynomials. The count may change during the course of HE operations, e.g. increase + /// during ciphertext multiplication, or decrease during relinearization, + /// `Ciphertext::relinearize`. + pub fn poly_count(&self) -> usize { + self.polys.len() + } + + pub fn new( + _context: &Context, + _polys: &[PolyRq], + _correction_factor: &Scheme::Scalar, + _seed: &[u8], + ) -> Self { + todo!() + } +} diff --git a/src/homomorphic_encryption/context.rs b/src/homomorphic_encryption/context.rs index 1b5c2c1..eae5174 100644 --- a/src/homomorphic_encryption/context.rs +++ b/src/homomorphic_encryption/context.rs @@ -12,23 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::marker::PhantomData; - use crate::homomorphic_encryption::{ encryption_parameters::EncryptionParameters, he_scheme::HeScheme, + poly_rq::poly_context::PolyContext, }; /// Pre-computation for the HE operations. /// /// HE operations are typically only supported between objects, such as ``Ciphertext``, /// ``Plaintext``, ``EvaluationKey``, ``SecretKey``, with the same context. +#[derive(Clone)] pub struct Context { - _marker: PhantomData, + _marker: std::marker::PhantomData, } +impl Context {} + impl Context { /// Creates a new `Context`. - pub fn new(_encryption_parameters: &EncryptionParameters) -> Self { + pub fn new(_encryption_parameters: &EncryptionParameters) -> Self { + todo!() + } + + /// Top-level ciphertext context. + pub fn ciphertext_context(&self) -> PolyContext { + todo!() + } + + pub(crate) fn bytes_per_plaintext(&self) -> usize { todo!() } } diff --git a/src/homomorphic_encryption/encryption_parameters.rs b/src/homomorphic_encryption/encryption_parameters.rs index 18c0d2e..45d0bfe 100644 --- a/src/homomorphic_encryption/encryption_parameters.rs +++ b/src/homomorphic_encryption/encryption_parameters.rs @@ -12,4 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub struct EncryptionParameters {} +use crate::homomorphic_encryption::he_scheme::HeScheme; + +pub struct EncryptionParameters { + _market: std::marker::PhantomData, +} + +impl EncryptionParameters { + pub fn new() -> Self { + todo!() + } +} diff --git a/src/homomorphic_encryption/he_scheme.rs b/src/homomorphic_encryption/he_scheme.rs index e728d0f..a1ee85e 100644 --- a/src/homomorphic_encryption/he_scheme.rs +++ b/src/homomorphic_encryption/he_scheme.rs @@ -12,26 +12,250 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::homomorphic_encryption::{context::Context, keys::SecretKey}; +use eyre::Result; + +use crate::homomorphic_encryption::{ + context::Context, keys::EvaluationKeyConfiguration, scalar::ScalarType, +}; + +pub trait PolyFormat: Clone { + fn description() -> String; +} + +#[derive(Clone)] +pub struct Coeff; +impl PolyFormat for Coeff { + fn description() -> String { + "Coeff format".to_string() + } +} + +#[derive(Clone)] +pub struct Eval; +impl PolyFormat for Eval { + fn description() -> String { + "Eval format".to_string() + } +} + +#[derive(Clone, Copy)] +pub enum EncodeFormat { + Coefficient, + Simd, +} pub trait HeScheme: Sized { + type Scalar: ScalarType; + type CanonicalCiphertextFormat: PolyFormat; + + type CoeffPlaintext; + type EvalPlaintext; + type CoeffCiphertext; + type EvalCiphertext; type CanonicalCiphertext; + type SecretKey; + type EvaluationKey; + type Context; + + const FRESH_CIPHERTEXT_POLY_COUNT: usize; + const MIN_NOISE_BUDGET: f64; + + fn generate_secret_key(context: &Context) -> Result; + + fn generate_evaluation_key( + context: &Context, + configuration: &EvaluationKeyConfiguration, + secret_key: &Self::SecretKey, + ) -> Result; + + fn encode( + context: &Context, + values: &[T], + format: EncodeFormat, + ) -> Result; + + fn encode_eval( + context: &Context, + values: &[T], + format: EncodeFormat, + moduli_count: usize, + ) -> Result; + + fn decode( + plaintext: &Self::CoeffPlaintext, + format: EncodeFormat, + ) -> Result>; + + fn decode_eval( + plaintext: &Self::EvalPlaintext, + format: EncodeFormat, + ) -> Result>; + + fn encrypt( + plaintext: &Self::CoeffPlaintext, + secret_key: &Self::SecretKey, + ) -> Result; + + fn zero_ciphertext( + context: &Context, + moduli_count: usize, + ) -> Result; + + fn zero_ciphertext_eval( + context: &Context, + moduli_count: usize, + ) -> Result; + + fn is_transparent(ciphertext: &Self::CoeffCiphertext) -> bool; + fn is_transparent_eval(ciphertext: &Self::EvalCiphertext) -> bool; + + fn decrypt( + ciphertext: &Self::CoeffCiphertext, + secret_key: &Self::SecretKey, + ) -> Result; + + fn decrypt_eval( + ciphertext: &Self::EvalCiphertext, + secret_key: &Self::SecretKey, + ) -> Result; + + fn rotate_columns( + ciphertext: &mut Self::CanonicalCiphertext, + step: isize, + evaluation_key: &Self::EvaluationKey, + ) -> Result<()>; + + fn swap_rows( + ciphertext: &mut Self::CanonicalCiphertext, + evaluation_key: &Self::EvaluationKey, + ) -> Result<()>; + + fn add_assign_plaintext( + lhs: &mut Self::CoeffPlaintext, + rhs: &Self::CoeffPlaintext, + ) -> Result<()>; + fn add_assign_plaintext_eval( + lhs: &mut Self::EvalPlaintext, + rhs: &Self::EvalPlaintext, + ) -> Result<()>; + + fn add_assign(lhs: &mut Self::CoeffCiphertext, rhs: &Self::CoeffCiphertext) -> Result<()>; + fn add_assign_eval(lhs: &mut Self::EvalCiphertext, rhs: &Self::EvalCiphertext) -> Result<()>; + + fn sub_assign(lhs: &mut Self::CoeffCiphertext, rhs: &Self::CoeffCiphertext) -> Result<()>; + fn sub_assign_eval(lhs: &mut Self::EvalCiphertext, rhs: &Self::EvalCiphertext) -> Result<()>; + + fn add_assign_ciphertext_plaintext( + ciphertext: &mut Self::CoeffCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()>; + fn add_assign_ciphertext_plaintext_eval( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()>; + + fn sub_assign_ciphertext_plaintext( + ciphertext: &mut Self::CoeffCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()>; + fn sub_assign_ciphertext_plaintext_eval( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()>; + + fn mul_assign( + ciphertext: &mut Self::EvalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()>; + + fn neg_assign(ciphertext: &mut Self::CoeffCiphertext); + fn neg_assign_eval(ciphertext: &mut Self::EvalCiphertext); + + fn inner_product(lhs: I, rhs: I) -> Result + where + I: IntoIterator; + + fn inner_product_plaintexts( + ciphertexts: C, + plaintexts: P, + ) -> Result + where + C: IntoIterator, + P: IntoIterator; + + fn inner_product_optional_plaintexts( + ciphertexts: C, + plaintexts: P, + ) -> Result + where + C: IntoIterator, + P: IntoIterator>; + + fn mul_assign_ciphertexts( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()>; + + fn add_assign_canonical( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()>; + + fn sub_assign_canonical( + lhs: &mut Self::CanonicalCiphertext, + rhs: &Self::CanonicalCiphertext, + ) -> Result<()>; + + fn sub_assign_canonical_plaintext( + ciphertext: &mut Self::CanonicalCiphertext, + plaintext: &Self::CoeffPlaintext, + ) -> Result<()>; + fn sub_assign_canonical_plaintext_eval( + ciphertext: &mut Self::CanonicalCiphertext, + plaintext: &Self::EvalPlaintext, + ) -> Result<()>; + + fn mod_switch_down(ciphertext: &mut Self::CanonicalCiphertext) -> Result<()>; + + fn apply_galois( + ciphertext: &mut Self::CanonicalCiphertext, + element: usize, + key: &Self::EvaluationKey, + ) -> Result<()>; + + /// Converts polynomials from coefficient representation to evaluation representation. + fn forward_ntt(context: &Self::Context, plaintext: &Self::CoeffPlaintext) -> Result<()>; + + fn relinearize( + ciphertext: &mut Self::CanonicalCiphertext, + key: &Self::EvaluationKey, + ) -> Result<()>; + + fn validate_equality(lhs: &Context, rhs: &Context) -> Result<()>; + + fn noise_budget( + ciphertext: &Self::CoeffCiphertext, + secret_key: &Self::SecretKey, + variable_time: bool, + ) -> Result; + + fn noise_budget_eval( + ciphertext: &Self::EvalCiphertext, + secret_key: &Self::SecretKey, + variable_time: bool, + ) -> Result; +} - /// Generates a `SecretKey`. - /// - /// # Parameters - /// - /// - `context`: Context for the HE computations. - /// - /// # Returns - /// - /// A `SecretKey` for the given `Context`. - fn generate_secret_key(_context: &Context) -> SecretKey { - todo!() +impl Context { + pub fn generate_secret_key(&self) -> Result { + S::generate_secret_key(self) } - /// The minimum level of "noise budget" required to guarantee a successful decryption. - fn min_noise_budget() -> f64 { - todo!() + pub fn generate_evaluation_key( + &self, + configuration: &EvaluationKeyConfiguration, + secret_key: &S::SecretKey, + ) -> Result { + S::generate_evaluation_key(self, configuration, secret_key) } } diff --git a/src/homomorphic_encryption/mod.rs b/src/homomorphic_encryption/mod.rs index 2497694..6398346 100644 --- a/src/homomorphic_encryption/mod.rs +++ b/src/homomorphic_encryption/mod.rs @@ -15,8 +15,15 @@ //! Contains the homomorphic encryption scheme and related functionality. // TODO: Add module documentation // TODO: Update crate visibility +pub(crate) mod array_2d; +pub(crate) mod bfv; +pub(crate) mod ciphertext; pub(crate) mod context; pub(crate) mod encryption_parameters; pub(crate) mod he_scheme; pub(crate) mod keys; +pub(crate) mod modulus; +pub(crate) mod plaintext; +pub(crate) mod poly_rq; pub(crate) mod scalar; +pub(crate) mod serialized_plaintext; diff --git a/src/homomorphic_encryption/modulus.rs b/src/homomorphic_encryption/modulus.rs new file mode 100644 index 0000000..5120067 --- /dev/null +++ b/src/homomorphic_encryption/modulus.rs @@ -0,0 +1,30 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::homomorphic_encryption::scalar::ScalarType; + +/// Stores pre-computed data for efficient modular operations. +/// +/// # Warning +/// +/// - The operations may leak the modules through timing or other side channels. Use this structure +/// only for public moduli. +pub struct Modulus { + _marker: std::marker::PhantomData, +} + +/// Pre-computed factor for fast modular reduction. +pub struct ReduceModulus { + _marker: std::marker::PhantomData, +} diff --git a/src/homomorphic_encryption/plaintext.rs b/src/homomorphic_encryption/plaintext.rs new file mode 100644 index 0000000..52dab56 --- /dev/null +++ b/src/homomorphic_encryption/plaintext.rs @@ -0,0 +1,51 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use eyre::Result; + +use crate::homomorphic_encryption::{ + context::Context, + he_scheme::HeScheme, + poly_rq::{poly::PolyRq, poly_context::PolyContext}, + serialized_plaintext::SerializedPlaintext, +}; + +pub trait PlaintextType: Sized { + fn deserialize( + serialized_plaintext: &SerializedPlaintext, + context: &Context, + ) -> Result; + + fn poly_context(&self) -> PolyContext; +} + +/// Plaintext struct. +#[derive(Clone)] +pub struct Plaintext { + poly_context: PolyContext, + pub poly: PolyRq, +} + +impl PlaintextType for Plaintext { + fn deserialize( + _serialized_plaintext: &SerializedPlaintext, + _context: &Context, + ) -> Result { + todo!() + } + + fn poly_context(&self) -> PolyContext { + todo!() + } +} diff --git a/src/homomorphic_encryption/poly_rq/galois.rs b/src/homomorphic_encryption/poly_rq/galois.rs new file mode 100644 index 0000000..2a31e6f --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/galois.rs @@ -0,0 +1,410 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::ops::Index; + +use eyre::Result; +use thiserror::Error; + +use crate::homomorphic_encryption::{poly_rq::poly::PolyRq, scalar::ScalarType}; + +/// Iterates over coefficients of a polynomial, applying a Galois transformation. +pub struct GaloisCoeffIterator { + /// Degree of the RLWE polynomial. + degree: u32, + /// `log2(degree)`. + log2_degree: u32, + /// `x % degree == x & mod_degree_mask`, because `degree` is a power of two + mod_degree_mask: u32, + /// Power in transformation `f(x) -> f(x^{galois_element})`. + galois_element: u32, + /// Simple incrementing index of the iterator in `[0, degree)`. + iter_index: u32, + /// `iter_index * galois_element`. + raw_out_index: u32, + /// Raw output index mod-reduced to `[0, degree)`. + out_index: u32, +} + +impl GaloisCoeffIterator { + pub fn new(degree: u32, galois_element: u32) -> Self { + galois_element.is_valid_galois_element(degree); + Self { + degree, + log2_degree: degree.ilog2(), + mod_degree_mask: degree - 1, + galois_element, + iter_index: 0, + raw_out_index: 0, + out_index: 0, + } + } +} + +impl Iterator for GaloisCoeffIterator { + type Item = (bool, u32); + + fn next(&mut self) -> Option { + if self.iter_index < self.degree { + // Use x^degree == -1 mod (x^degree + 1) + // floor(out_raw_index / degree) odd => negate coefficient + let negate = (self.raw_out_index >> self.log2_degree) & 1 != 0; + let ret = (negate, self.out_index); + self.iter_index += 1; + self.raw_out_index += self.galois_element; + self.out_index = self.raw_out_index & self.mod_degree_mask; + Some(ret) + } else { + None + } + } +} + +/// Iterates over evaluation points of a polynomial, applying a Galois transformation. +pub struct GaloisEvalIterator { + /// Degree of the RLWE polynomial. + degree: u32, + /// `log2(degree)`. + log2_degree: u32, + /// `x % degree == x & mod_degree_mask`, because `degree` is a power of two + mod_degree_mask: u32, + /// Power in transformation `f(x) -> f(x^{galois_element})`. + galois_element: u32, + /// Simple incrementing index of the iterator in `[0, degree)`. + iter_index: u32, +} + +impl GaloisEvalIterator { + pub fn new(degree: u32, galois_element: u32) -> Self { + galois_element.is_valid_galois_element(degree); + Self { + degree, + log2_degree: degree.ilog2(), + mod_degree_mask: degree - 1, + galois_element, + iter_index: 0, + } + } +} + +impl Iterator for GaloisEvalIterator { + type Item = u32; + + fn next(&mut self) -> Option { + if self.iter_index < self.degree { + let reversed = + (self.iter_index + self.degree).reverse_bits() >> (32 - (self.log2_degree + 1)); + let mut index_raw = (self.galois_element * reversed) >> 1; + index_raw &= self.mod_degree_mask; + self.iter_index += 1; + Some(index_raw.reverse_bits() >> (32 - self.log2_degree)) + } else { + None + } + } +} + +pub trait FixedWidthInteger { + fn is_valid_galois_element(&self, degree: u32) -> bool; +} + +impl FixedWidthInteger for u32 { + fn is_valid_galois_element(&self, degree: u32) -> bool { + degree.is_power_of_two() && *self % 2 != 0 && *self < (degree << 1) && *self > 1 + } +} + +impl PolyRq +where + Type: ScalarType, +{ + /// Applies a Galois transformation, also known as a Frobenius transformation. + /// + /// The Galois transformation with Galois element `p` transforms the polynomial `f(x)` to + /// `f(x^p)`. + /// + /// # Parameters + /// - `element`: Galois element of the transformation. + /// + /// # Returns + /// The polynomial after applying the Galois transformation. + pub fn apply_galois(&self, element: u32) -> Self { + assert!(element.is_valid_galois_element(self.degree())); + let mut output = self.clone(); + for (rns_index, modulus) in self.moduli().iter().enumerate() { + let mut iterator = GaloisCoeffIterator::new(self.degree(), element); + let data_indices = self.data.row_indices(rns_index); + let output_index = |column: usize| self.data.index(rns_index, column); + for data_index in data_indices { + if let Some((negate, out_index)) = iterator.next() { + let out_idx = output_index(out_index as usize); + if negate { + let negated = self.data[data_index] + .iter() + .map(|x| x.negate_mod(modulus)) + .collect::>(); + output.data[out_idx].copy_from_slice(&negated); + } else { + output.data[out_idx].copy_from_slice(&self.data[data_index]); + } + } else { + panic!("GaloisCoeffIterator goes out of index"); + } + } + } + output + } +} + +#[derive(Error, Debug)] +pub enum GaloisElementError { + /// Invalid degree. + #[error("Invalid degree: {0}")] + InvalidDegree(u32), + /// Invalid rotation step. + #[error("Invalid rotation step: step {step}, degree {degree}")] + InvalidRotationStep { step: i32, degree: u32 }, +} + +/// Utilities for generating Galois elements. +pub struct GaloisElement {} + +impl GaloisElement { + pub const GENERATOR: u32 = 3; + + /// Returns the Galois element to swap rows. + /// + /// # Parameters + /// + /// - `degree`: Polynomial degree + /// + /// # Returns + /// + /// The galois element to swap rows. + pub fn swapping_rows(degree: u32) -> u32 { + (degree << 1) - 1 + } + + /// Returns the Galois element for column rotation by `step`. + /// + /// # Parameters + /// - `step`: Number of slots to rotate. Negative values indicate a left rotation, and positive + /// values indicate right rotation. Must have absolute value in `[1, N / 2 - 1]`. + /// - `degree`: The RLWE ring dimension `N`. + /// + /// # Returns + /// The Galois element for column rotation by `step`. + /// + /// # Errors + /// Returns an error if the degree is not a power of two or if the step is invalid. + pub fn rotating_columns(step: i32, degree: u32) -> Result { + if !degree.is_power_of_two() { + return Err(GaloisElementError::InvalidDegree(degree).into()); + } + + let mut positive_step = step.unsigned_abs(); + if positive_step >= (degree >> 1) || positive_step == 0 { + return Err(GaloisElementError::InvalidRotationStep { step, degree }.into()); + } + + let twice_degree_minus1 = (degree << 1) - 1; + positive_step &= twice_degree_minus1; + + if step > 0 { + positive_step = (degree >> 1) - positive_step; + } + + Ok(GaloisElement::GENERATOR.pow_mod(&(positive_step), &(degree << 1), true)) + } +} + +#[cfg(test)] +mod test { + use eyre::Result; + + use crate::homomorphic_encryption::{ + array_2d::Array2d, + poly_rq::{ + galois::GaloisElement, + ntt::{ForwardNtt, InverseNtt}, + poly::PolyRq, + poly_context::PolyContext, + }, + scalar::ScalarType, + }; + + fn get_test_poly_with_element3_degree4_moduli1>( + ) -> Result<(PolyRq, PolyRq)> { + let degree = 4; + // Convert moduli into the generic type T + let moduli = vec![T::from(17u32)]; + let plaintext_poly_context = PolyContext::new(degree, &moduli); + + // Convert data and expected_data into type T + let data = + Array2d::new(vec![T::from(0u32), T::from(1u32), T::from(2u32), T::from(3u32)], 1, 4); + let expected_data = + Array2d::new(vec![T::from(0u32), T::from(3u32), T::from(15u32), T::from(1u32)], 1, 4); + + // Initialize PolyRq with the converted data + let poly = PolyRq::new(plaintext_poly_context.clone(), data); + let expected_poly = PolyRq::new(plaintext_poly_context, expected_data); + Ok((poly, expected_poly)) + } + + fn get_test_poly_with_element3_degree8_moduli1>( + ) -> Result<(PolyRq, PolyRq)> { + let degree = 8; + let moduli = vec![T::from(17u32)]; + let plaintext_poly_context = PolyContext::new(degree, &moduli); + + let data = Array2d::new( + vec![ + T::from(0u32), + T::from(1u32), + T::from(2u32), + T::from(3u32), + T::from(4u32), + T::from(5u32), + T::from(6u32), + T::from(7u32), + ], + 1, + 8, + ); + let expected_data = Array2d::new( + vec![ + T::from(0u32), + T::from(14u32), + T::from(6u32), + T::from(1u32), + T::from(13u32), + T::from(7u32), + T::from(2u32), + T::from(12u32), + ], + 1, + 8, + ); + + let poly = PolyRq::new(plaintext_poly_context.clone(), data); + let expected_poly = PolyRq::new(plaintext_poly_context, expected_data); + Ok((poly, expected_poly)) + } + + fn get_test_poly_with_element3_degree8_moduli2>( + ) -> Result<(PolyRq, PolyRq)> { + let degree = 8; + let moduli = vec![T::from(17u32), T::from(97u32)]; + let plaintext_poly_context = PolyContext::new(degree, &moduli); + + let data = Array2d::new( + vec![ + T::from(0u32), + T::from(1u32), + T::from(2u32), + T::from(3u32), + T::from(4u32), + T::from(5u32), + T::from(6u32), + T::from(7u32), + T::from(7u32), + T::from(6u32), + T::from(5u32), + T::from(4u32), + T::from(3u32), + T::from(2u32), + T::from(1u32), + T::from(0u32), + ], + 2, + 8, + ); + let expected_data = Array2d::new( + vec![ + T::from(0u32), + T::from(14u32), + T::from(6u32), + T::from(1u32), + T::from(13u32), + T::from(7u32), + T::from(2u32), + T::from(12u32), + T::from(7u32), + T::from(93u32), + T::from(1u32), + T::from(6u32), + T::from(94u32), + T::from(0u32), + T::from(5u32), + T::from(95u32), + ], + 2, + 8, + ); + + let poly = PolyRq::new(plaintext_poly_context.clone(), data); + let expected_poly = PolyRq::new(plaintext_poly_context, expected_data); + Ok((poly, expected_poly)) + } + + fn apply_galois_test_helper( + get_func: fn() -> Result<(PolyRq, PolyRq)>, + ) -> Result<()> { + let (mut poly, expected_poly) = get_func()?; + assert_eq!(poly.apply_galois(3), expected_poly); + assert_eq!(poly.forward_ntt().apply_galois(3).inverse_ntt(), expected_poly); + for index in 1..poly.degree() { + let element = index * 2 + 1; + assert_eq!( + poly.apply_galois(element).forward_ntt(), + poly.forward_ntt().apply_galois(element) + ); + } + + let forward_element = GaloisElement::swapping_rows(poly.degree()); + assert_eq!(poly.apply_galois(forward_element).apply_galois(forward_element), poly); + assert_eq!( + poly.forward_ntt().apply_galois(forward_element).apply_galois(forward_element), + poly.forward_ntt() + ); + + for step in 1..(poly.degree() >> 1) { + let inverse_step = (poly.degree() >> 1) - step; + let forward_element = GaloisElement::rotating_columns(step as i32, poly.degree())?; + let backward_element = + GaloisElement::rotating_columns(inverse_step as i32, poly.degree())?; + assert_eq!(poly.apply_galois(forward_element).apply_galois(backward_element), poly); + assert_eq!( + poly.forward_ntt().apply_galois(forward_element).apply_galois(backward_element), + poly.forward_ntt() + ); + } + Ok(()) + } + + fn test_apply_galois_for_type>() -> Result<()> { + apply_galois_test_helper(get_test_poly_with_element3_degree4_moduli1::)?; + apply_galois_test_helper(get_test_poly_with_element3_degree8_moduli1::)?; + apply_galois_test_helper(get_test_poly_with_element3_degree8_moduli2::)?; + Ok(()) + } + + #[test] + pub fn test_apply_galois() -> Result<()> { + test_apply_galois_for_type::()?; + test_apply_galois_for_type::()?; + Ok(()) + } +} diff --git a/src/homomorphic_encryption/poly_rq/mod.rs b/src/homomorphic_encryption/poly_rq/mod.rs new file mode 100644 index 0000000..09f08d3 --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod galois; +mod ntt; +pub(crate) mod poly; +mod poly_collection; +pub(crate) mod poly_context; diff --git a/src/homomorphic_encryption/poly_rq/ntt.rs b/src/homomorphic_encryption/poly_rq/ntt.rs new file mode 100644 index 0000000..3908547 --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/ntt.rs @@ -0,0 +1,28 @@ +use crate::homomorphic_encryption::{poly_rq::poly::PolyRq, scalar::ScalarType}; + +// TODO: Consider this extension trait pattern for other types + +pub trait ForwardNtt { + fn forward_ntt(&mut self) -> Self; +} + +impl ForwardNtt for PolyRq { + fn forward_ntt(&mut self) -> Self { + todo!(); + // for rns_index in self.rns_indices() { + // let modulus = &self.context.moduli[rns_index]; + // let coeffs = &mut self.data[rns_index]; + // self.context.forward_ntt(coeffs, modulus); + // } + } +} + +pub trait InverseNtt { + fn inverse_ntt(&mut self) -> Self; +} + +impl InverseNtt for PolyRq { + fn inverse_ntt(&mut self) -> Self { + todo!() + } +} diff --git a/src/homomorphic_encryption/poly_rq/poly.rs b/src/homomorphic_encryption/poly_rq/poly.rs new file mode 100644 index 0000000..260f8c9 --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/poly.rs @@ -0,0 +1,169 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::ops::{Add, AddAssign, Range, Sub, SubAssign}; + +use crate::homomorphic_encryption::{ + array_2d::Array2d, poly_rq::poly_context::PolyContext, scalar::ScalarType, +}; + +/// Represents a polynomial in `R_q = Z_q[X] / (X^N + 1)` for `N` a power of +/// two and `q` a (possibly) multi-word integer. +/// +/// The number-theoretic transform is used for efficient arithmetic. +#[derive(Clone, PartialEq, Debug)] +pub struct PolyRq { + /// Context for the polynomial. + pub(crate) context: PolyContext, + /// Residue number system (RNS) decomposition of each coefficient. + /// + /// Coefficients are stored in coefficient-major order. That is, `data[rns_index, coeff_index]` + /// stores the `coeff_index`'th coefficient mod `q_{rns_index}.` + pub data: Array2d, +} + +impl PolyRq { + /// Creates a new polynomial with specified context and data. + pub fn new(context: PolyContext, data: Array2d) -> Self { + assert_eq!(context.degree, data.column_count as u32); + assert_eq!(context.moduli.len(), data.row_count); + assert!(Self::is_valid_data(&context, &data)); + Self { context, data } + } + + /// Access a single coefficient. + pub fn get(&self, row: usize, column: usize) -> &Type { + &self.data[row][column] + } + + /// Mutably access a single coefficient. + pub fn set(&mut self, row: usize, column: usize, value: Type) { + self.data[row][column] = value; + } + + /// Validate that all coefficients are within the modulus constraints. + fn is_valid_data(context: &PolyContext, data: &Array2d) -> bool { + for (rns_index, modulus) in context.moduli.iter().enumerate() { + for coeff in data[rns_index].iter() { + if *coeff >= *modulus { + return false; + } + } + } + true + } + + /// Returns the indices of polynomial coefficients. + pub fn coeff_indices(&self) -> Range { + 0..self.data.column_count + } + + /// Returns the indices of RNS moduli. + pub fn rns_indices(&self) -> Range { + 0..self.data.row_count + } + + /// Get all coefficients mod a specific RNS modulus. + pub fn poly(&self, rns_index: usize) -> &[Type] { + &self.data[rns_index] + } + + /// Get a specific coefficient across all moduli. + pub fn coefficient(&self, coeff_index: usize) -> Vec { + self.data.columns_iter().map(|row| *row[coeff_index]).collect::>() + } + + /// Initialize a polynomial with all coefficients set to zero. + pub fn zero(context: PolyContext) -> Self { + let zeroes = Array2d::new( + vec![Type::default(); (context.degree * context.moduli.len() as u32) as usize], + context.moduli.len(), + context.degree as usize, + ); + Self { context, data: zeroes } + } + + /// Validate that two polynomials have the same context and RNS dimensions. + fn validate_metadata_equality(&self, other: &Self) { + assert_eq!(self.context, other.context, "Contexts must match"); + assert_eq!(self.data.row_count, other.data.row_count, "Row counts must match"); + assert_eq!(self.data.column_count, other.data.column_count, "Column counts must match"); + } + + pub fn serialize(&self) -> Vec { + todo!() + } + + pub fn moduli(&self) -> Vec { + self.context.moduli.clone() + } + + pub fn degree(&self) -> u32 { + self.context.degree + } +} + +// TODO: What are the proper (restrictive?) bounds for these traits? + +impl + Clone> Add for PolyRq { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + let mut result = self.clone(); + result += rhs; + result + } +} + +impl + Clone> AddAssign for PolyRq { + fn add_assign(&mut self, rhs: Self) { + // Ensure metadata is equal across the two polynomials + self.validate_metadata_equality(&rhs); + + // Perform modular addition for each coefficient + for (rns_index, modulus) in self.context.moduli.iter().enumerate() { + for coeff_index in self.coeff_indices() { + let lhs_value = self.data[rns_index][coeff_index]; + let rhs_value = rhs.data[rns_index][coeff_index]; + self.data[rns_index][coeff_index] = (lhs_value + rhs_value) % *modulus; + } + } + } +} + +impl + Clone> Sub for PolyRq { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + let mut result = self.clone(); + result -= rhs; + result + } +} + +impl + Clone> SubAssign for PolyRq { + fn sub_assign(&mut self, rhs: Self) { + // Ensure metadata is equal across the two polynomials + self.validate_metadata_equality(&rhs); + + // Perform modular subtraction for each coefficient + for (rns_index, modulus) in self.context.moduli.iter().enumerate() { + for coeff_index in self.coeff_indices() { + let lhs_value = self.data[rns_index][coeff_index]; + let rhs_value = rhs.data[rns_index][coeff_index]; + self.data[rns_index][coeff_index] = (lhs_value + *modulus - rhs_value) % *modulus; + } + } + } +} diff --git a/src/homomorphic_encryption/poly_rq/poly_collection.rs b/src/homomorphic_encryption/poly_rq/poly_collection.rs new file mode 100644 index 0000000..65cf538 --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/poly_collection.rs @@ -0,0 +1,39 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::homomorphic_encryption::{poly_rq::poly_context::PolyContext, scalar::ScalarType}; + +/// Trait for collection of `PolyRq` polynomials. +pub trait PolyCollection { + /// Coefficient type + type Scalar: ScalarType; + + /// Returns the polynomial's context + fn poly_context(&self) -> PolyContext; + + /// The polynomial's degree. + fn degree(&self) -> usize { + self.poly_context().degree as usize + } + + /// The polynomial's scalar moduli. + fn moduli(&self) -> Vec { + self.poly_context().moduli + } + + /// The polynomial's moduli. + fn reduce_moduli(&self) -> Vec { + self.poly_context().reduce_moduli + } +} diff --git a/src/homomorphic_encryption/poly_rq/poly_context.rs b/src/homomorphic_encryption/poly_rq/poly_context.rs new file mode 100644 index 0000000..5de933a --- /dev/null +++ b/src/homomorphic_encryption/poly_rq/poly_context.rs @@ -0,0 +1,44 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::homomorphic_encryption::scalar::ScalarType; + +/// Polynomial context that holds all the pre-computed values for doing efficient calculations on +/// `PolyRq` polynomials. +#[derive(Clone, Debug, PartialEq)] +pub struct PolyContext { + /// Number `N` of coefficients in the polynomial, must be a power of two. + pub degree: u32, + /// CRT-representation of the modulus `Q = product_{i=0}^{L-1} q_i`. + pub moduli: Vec, + /// Precomputed roots of unity for modular arithmetic in NTT. + pub root_of_unity_powers: Vec>, + /// Precomputed inverse roots of unity for the inverse NTT. + pub inverse_root_of_unity_powers: Vec>, + /// Precomputed `degree^-1 mod q_i` for inverse NTT. + pub inverse_degree: Vec, + + // TODO: + pub reduce_moduli: Vec, +} + +impl PolyContext { + pub fn new(degree: u32, moduli: &[T]) -> Self { + todo!() + } + + pub fn serialization_byte_count(&self) -> usize { + todo!() + } +} diff --git a/src/homomorphic_encryption/scalar.rs b/src/homomorphic_encryption/scalar.rs index f6cb539..31bb4f1 100644 --- a/src/homomorphic_encryption/scalar.rs +++ b/src/homomorphic_encryption/scalar.rs @@ -15,6 +15,11 @@ //! Contains helper methods for constant-time operation on scalars. // TODO: Currently only needed by CuckooTable +use std::{ + fmt::Debug, + ops::{Add, Shr, Sub}, +}; + /// Computes `ceil(value / divisor)`. /// /// # Parameters @@ -39,3 +44,165 @@ pub fn dividing_ceil(value: i64, divisor: i64, variable_time: bool) -> i64 { } value / divisor } + +/// Scalar type for ``PolyRq`` polynomial coefficients. +pub trait ScalarType: + PartialEq + + Add + + Sub + + Send + + Sized + + Clone + + Copy + + Debug + + Default + + PartialEq + + Ord + + Shr +{ + fn subtract_if_exceeds(&self, modulus: &Self) -> Self { + // Guard against difference mask fails + assert!(*self <= (Self::max_value() >> 1) + *modulus); + let difference = *self - *modulus; + let mask = 0i32 - (difference >> (self.bit_width() as i32 - 1)).to_i32(); + difference - modulus.bitwise_and(&(Self::from_i32(mask))) + } + + /// Computes `-self mod modulus`. + /// + /// `self` must be in `[0, modulus-1]`. + /// + /// # Parameters + /// + /// - `modulus`: The modulus. + /// + /// # Returns + /// + /// `-self mod modulus`. + fn negate_mod(&self, modulus: &Self) -> Self; + + /// Computes modular exponentiation. + /// + /// Computes `self` raised to the power of `exponent` mod `modulus`, i.e., `self^exponent mod + /// modulus`. + /// + /// # Parameters + /// + /// - `exponent`: The exponent. + /// - `modulus`: The modulus. + /// - `variable_time`: Must be `true`. Setting to `true` causes `modulus` and `exponent` to be + /// leaked through timing. + /// + /// # Returns + /// + /// - `self^exponent mod modulus` + /// + /// # Warning + /// + /// - Leaks `self`, `exponent`, `modulus` through timing. + fn pow_mod(&self, _exponent: &Self, _modulus: &Self, variable_time: bool) -> Self { + assert!(variable_time); + todo!(); + // if exponent == 0 { + // return 1 + // } + // let base = self; + // let exponent = exponent; + } + + fn add_mod(&self, other: &Self, modulus: &Self) -> Self { + todo!(); + } + + fn sub_mod(&self, other: &Self, modulus: &Self) -> Self { + todo!(); + } + + fn max_value() -> Self; + fn bit_width(&self) -> u8; + fn bitwise_and(&self, other: &Self) -> Self; + + fn to_i32(&self) -> i32; + fn from_i32(value: i32) -> Self; +} + +impl ScalarType for u32 { + fn negate_mod(&self, modulus: &u32) -> Self { + assert!(self < modulus); + (modulus - self).subtract_if_exceeds(modulus) + } + + fn max_value() -> Self { + u32::MAX + } + + fn bit_width(&self) -> u8 { + 32 + } + + fn bitwise_and(&self, other: &Self) -> Self { + self & other + } + + fn to_i32(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> Self { + value as Self + } +} + +impl ScalarType for u64 { + fn negate_mod(&self, modulus: &u64) -> Self { + assert!(self < modulus); + (modulus - self).subtract_if_exceeds(modulus) + } + + fn max_value() -> Self { + u64::MAX + } + + fn bit_width(&self) -> u8 { + 64 + } + + fn bitwise_and(&self, other: &Self) -> Self { + self & other + } + + fn to_i32(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> Self { + value as Self + } +} + +impl ScalarType for usize { + fn negate_mod(&self, modulus: &usize) -> Self { + assert!(self < modulus); + (modulus - self).subtract_if_exceeds(modulus) + } + + fn max_value() -> Self { + usize::MAX + } + + fn bit_width(&self) -> u8 { + 32 + } + + fn bitwise_and(&self, other: &Self) -> Self { + self & other + } + + fn to_i32(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> Self { + value as Self + } +} diff --git a/src/homomorphic_encryption/serialized_plaintext.rs b/src/homomorphic_encryption/serialized_plaintext.rs new file mode 100644 index 0000000..4df8374 --- /dev/null +++ b/src/homomorphic_encryption/serialized_plaintext.rs @@ -0,0 +1,30 @@ +// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Serialized ``Plaintext`` type. +pub struct SerializedPlaintext { + /// The serialized polynomial. + pub poly: Vec, +} + +impl SerializedPlaintext { + /// Creates a serialized plaintext. + /// + /// # Parameters + /// + /// - `poly`: Serialized polynomial. + pub fn new(poly: &[u8]) -> Self { + Self { poly: poly.to_vec() } + } +} diff --git a/src/lib.rs b/src/lib.rs index fd882c2..2655c2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,3 +21,5 @@ pub mod private_information_retrieval; pub(crate) mod homomorphic_encryption; +#[cfg(test)] +mod test_utilities; diff --git a/src/private_information_retrieval/index_pir_protocol.rs b/src/private_information_retrieval/index_pir_protocol.rs index 19131f6..18fc6cd 100644 --- a/src/private_information_retrieval/index_pir_protocol.rs +++ b/src/private_information_retrieval/index_pir_protocol.rs @@ -12,16 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::marker::PhantomData; +use std::path::PathBuf; + +use eyre::Result; +use thiserror::Error; use crate::{ homomorphic_encryption::{ + context::Context, + encryption_parameters::EncryptionParameters, he_scheme::HeScheme, keys::{EvaluationKeyConfiguration, SecretKey}, + plaintext::{Plaintext, PlaintextType}, + serialized_plaintext::SerializedPlaintext, }, private_information_retrieval::keyword_pir_protocol::KeywordPirParameter, }; +// Which algorithm to use for PIR computation. pub enum PirAlgorithm { /// PIR using ciphertext word decomposition. /// @@ -37,25 +45,89 @@ pub enum PirAlgorithm { MulPir, } +/// Index PIR config errors. +#[derive(Debug, Clone, Error, PartialEq)] +pub enum IndexPirConfigError { + /// Invalid dimensions count. + #[error("Invalid dimensions count: {dimension_count}, expected {expected}")] + InvalidDimensionCount { dimension_count: usize, expected: String }, +} + +/// Configuration for an Index PIR database. +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct IndexPirConfig { + /// Number of entries in the database. + pub entry_count: usize, + /// Byte size of each entry in the database. + pub entry_size_in_bytes: usize, + /// Number of dimensions in the database. + pub dimension_count: usize, + /// Number of indices in a query to the database. + pub batch_size: usize, + /// Whether to enable `uneven_dimensions` optimization. + pub uneven_dimensions: bool, +} + +impl IndexPirConfig { + /// Initializes an `IndexPirConfig`. + /// + /// # Parameters + /// + /// - `entry_count`: Number of entries in the database. + /// - `entry_size_in_bytes`: Byte size of each entry in the database. + /// - `dimension_count`: Number of dimensions in the database. + /// - `batch_size`: Number of indices in a query to the database. + /// - `uneven_dimensions`: Whether to enable `uneven dimensions` optimization. + /// + /// # Errors + /// + /// Returns an error if the `dimension_count` is not valid. + pub fn new( + entry_count: usize, + entry_size_in_bytes: usize, + dimension_count: usize, + batch_size: usize, + uneven_dimensions: bool, + ) -> Result { + let valid_dimensions_count = [1, 2]; + if !valid_dimensions_count.contains(&dimension_count) { + return Err(IndexPirConfigError::InvalidDimensionCount { + dimension_count, + expected: format!("{:?}", valid_dimensions_count.to_vec()), + } + .into()); + } + Ok(Self { + entry_count, + entry_size_in_bytes, + dimension_count, + batch_size, + uneven_dimensions, + }) + } +} + /// Parameters for an index PIR lookup. /// /// Must be the same between client and server for a correct database lookup. pub struct IndexPirParameter { /// Number of entries in the database. - pub(crate) entry_count: usize, + pub entry_count: usize, /// Byte size of each entry in the database. - entry_size_in_bytes: usize, + pub entry_size_in_bytes: usize, /// Number of plaintexts in each dimension of the database. - dimensions: Vec, + pub dimensions: Vec, /// Number of indices in a query to the database. - batch_size: usize, + pub batch_size: usize, } impl IndexPirParameter { + /// The number of dimensions in the database. pub fn dimension_count(&self) -> usize { self.dimensions.len() } + /// The number of ciphertexts in each query after server-side expansion. pub fn expanded_query_count(&self) -> usize { self.dimensions.iter().sum() } @@ -78,14 +150,187 @@ impl IndexPirParameter { } } +/// Index PIR database errors. +#[derive(Debug, Clone, Error, PartialEq)] +pub enum IndexPirDatabaseError { + /// Invalid serialization plaintext tag. + #[error("Invalid plaintext tag: {tag}")] + InvalidSerializationPlaintextTag { tag: u8 }, + /// Invalid serialization version + #[error("Invalid serialization version: {serialization_version}, expected {expected}")] + InvalidDatabaseSerializationVersion { serialization_version: u8, expected: u8 }, + /// Empty database. + #[error("Empty database")] + EmptyDatabase, +} + +/// Type of serialization version. +type SerializationVersionType = u8; + /// A database after processing to prepare to PIR queries. -pub struct ProcessedDatabase { - // TODO: Implement - _marker: PhantomData, // TODO: Remove after implementing the struct. +pub struct ProcessedDatabase { + /// Plaintexts in the database, including nil plaintexts used for padding. + pub plaintexts: Vec>>, +} + +impl>> ProcessedDatabase { + /// Serialization version. + pub const SERIALIZATION_VERSION: SerializationVersionType = 1; + /// Indicates a non-zero plaintext. + pub const SERIALIZED_PLAINTEXT_TAG: u8 = 0; + /// Indicates a zero plaintext. + pub const SERIALIZED_ZERO_PLAINTEXT_TAG: u8 = 0; + + /// Number of plaintexts in the database, including padding plaintexts. + pub fn count(&self) -> usize { + self.plaintexts.len() + } + + /// Whether the database is empty. + pub fn is_empty(&self) -> bool { + self.plaintexts.is_empty() + } + + /// Creates a `ProcessedDatabase` from plaintexts. + /// + /// # Parameters + /// + /// - `plaintexts`: Plaintexts to build the database with. + pub fn new(plaintexts: Vec>>) -> Self { + Self { plaintexts } + } + + /// Creates a `ProcessedDatabase` from a filepath. + /// + /// # Parameters + /// + /// - `path`: Filepath storing serialized plaintexts. + /// - `context`: Context for the HE computation. + /// + /// # Errors + /// + /// - If we fail to load the database. + pub fn from_path(path: &PathBuf, context: &Context) -> Result { + let file = std::fs::read(path)?; + Self::from_bytes(&file, context) + } + + /// Creates a `ProcessedDatabase` from bytes. + /// + /// # Parameters + /// + /// - `bytes`: Serialized plaintexts. + /// - `context`: Context for the HE computation. + /// + /// # Errors + /// + /// - If we fail to deserialize. + pub fn from_bytes(bytes: &[u8], context: &Context) -> Result { + let mut offset = 0; + + // Read version number + let version_number = bytes[offset]; + offset += std::mem::size_of::(); + if version_number != Self::SERIALIZATION_VERSION { + return Err(IndexPirDatabaseError::InvalidDatabaseSerializationVersion { + serialization_version: version_number, + expected: Self::SERIALIZATION_VERSION, + } + .into()); + } + + // Read plaintext count + let plaintext_count = + u32::from_le_bytes(bytes[offset..offset + std::mem::size_of::()].try_into()?) + as usize; + offset += std::mem::size_of::(); + + // Determine the byte size of each serialized plaintext + let serialized_plaintext_byte_count = + context.ciphertext_context().serialization_byte_count(); + + // Deserialize plaintexts + let mut plaintexts = Vec::with_capacity(plaintext_count); + for _ in 0..plaintext_count { + let tag = bytes[offset]; + offset += 1; + match tag { + tag if tag == Self::SERIALIZED_ZERO_PLAINTEXT_TAG => { + plaintexts.push(None); + }, + tag if tag == Self::SERIALIZED_PLAINTEXT_TAG => { + let plaintext_bytes = &bytes[offset..offset + serialized_plaintext_byte_count]; + offset += serialized_plaintext_byte_count; + let serialized_plaintext = SerializedPlaintext::new(plaintext_bytes); + let plaintext = + Scheme::EvalPlaintext::deserialize(&serialized_plaintext, context)?; + plaintexts.push(Some(plaintext)); + }, + _ => { + return Err( + IndexPirDatabaseError::InvalidSerializationPlaintextTag { tag }.into() + ); + }, + } + } + + Ok(Self { plaintexts }) + } + + /// Returns the serialization size in bytes of the database. + pub fn serialization_byte_count(&self) -> Result { + let non_nil_plaintexts: Vec<&Plaintext> = + self.plaintexts.iter().filter_map(|p| p.as_ref()).collect(); + let poly_context = + non_nil_plaintexts.first().ok_or(IndexPirDatabaseError::EmptyDatabase)?.poly_context(); + let mut serialization_size = std::mem::size_of::(); + serialization_size += std::mem::size_of::(); // plaintext count + serialization_size += non_nil_plaintexts.len() * poly_context.serialization_byte_count(); // non-nil plaintexts + serialization_size += self.plaintexts.len() * std::mem::size_of::(); // "nil" or "non-nil" indicator + + Ok(serialization_size) + } + + /// Saves the database to a file path. + /// + /// # Parameters + /// + /// - `path`: File path to save the database to. + /// + /// # Errors + /// + /// - If we fail to serialize the database. + /// - If we fail to save it to a file. + pub fn save(&self, path: &PathBuf) -> Result<()> { + let serialized_data = self.serialize()?; + std::fs::write(path, serialized_data)?; + Ok(()) + } + + /// Serializes the database. + /// + /// # Errors + /// + /// - If we fail to serialize the database. + pub fn serialize(&self) -> Result> { + let mut buffer: Vec = Vec::with_capacity(self.serialization_byte_count()?); + buffer.push(Self::SERIALIZATION_VERSION); + buffer.extend_from_slice(&(self.plaintexts.len() as u32).to_le_bytes()); + + for plaintext in &self.plaintexts { + if let Some(plaintext) = plaintext { + buffer.push(Self::SERIALIZED_PLAINTEXT_TAG); + buffer.extend_from_slice(&plaintext.poly.serialize()); + } else { + buffer.push(Self::SERIALIZED_ZERO_PLAINTEXT_TAG); + } + } + Ok(buffer) + } } /// A processed database along with PIR parameters describing the database. -pub struct ProcessedDatabaseWithParameters { +pub struct ProcessedDatabaseWithParameters { /// Processed database. database: ProcessedDatabase, /// Evaluation key configuration. @@ -96,7 +341,7 @@ pub struct ProcessedDatabaseWithParameters { pub keyword_pir_parameters: Option, } -impl ProcessedDatabaseWithParameters { +impl ProcessedDatabaseWithParameters { /// Creates a new `ProcessedDatabaseWithParameters`. /// /// # Parameters @@ -161,3 +406,195 @@ impl Response { todo!() } } + +/// Trait for queries to an integer-indexed database. +pub trait IndexPirProtocol { + /// HE scheme used for PIR computation. + type Scheme: HeScheme; + + /// Generates the PIR parameters for database. + /// + /// # Parameters + /// + /// - `config`: Database configuration. + /// - `context`: Context for the HE computation. + /// + /// # Returns + /// + /// The PIR parameters for the database. + fn generate_parameter( + config: &IndexPirConfig, + context: &Context, + ) -> IndexPirParameter; + + /// Computes the evaluation key configuration. + /// + /// The client and server must agree on the evaluation key configuration. + /// + /// # Parameters + /// + /// - `parameter`: Index PIR parameters. + /// - `encryption_parameters`: Encryption parameters. + /// + /// # Returns + /// + /// The evaluation key configuration. + fn evaluation_key_configuration( + parameter: &IndexPirParameter, + encryption_parameters: &EncryptionParameters, + ) -> EvaluationKeyConfiguration; +} + +/// Trait for a server hosting index PIR databases for lookup. +/// +/// The server hosts multiple databases, which are all compatible with a single PIR parameters. +pub trait IndexPirServer { + /// Index PIR type backing the keyword PIR computation. + type IndexPir: IndexPirProtocol; + + /// HE scheme to be used by the database. + type Scheme: HeScheme; + + /// The processed databases. + fn databases(&self) -> &Vec>; + + /// The index PIR parameters, suitable for use with any of the databases. + fn parameter(&self) -> &IndexPirParameter; + + /// Evaluation key configuration. + /// + /// This tells the client what to include in the evaluation key. Must be the same between client + /// and server. + fn evaluation_key_configuration(&self) -> &EvaluationKeyConfiguration; +} + +/// Client which can perform an Index PIR lookup. +pub trait IndexPirClient {} + +#[cfg(test)] +mod test { + use rand::Rng; + + // use crate::test_utilities::get_test_context; + + fn get_database_for_testing( + number_of_entries: usize, + entry_size_in_bytes: usize, + ) -> Vec> { + let mut rng = rand::thread_rng(); + (0..number_of_entries) + .map(|_| (0..entry_size_in_bytes).map(|_| rng.gen()).collect()) + .collect() + } + + // fn test_generate_parameter() -> Result<()> { + // let context: Context> = get_test_context()?; + // // unevenDimensions: false + // { + // let config = IndexPirConfig::new(16, context.bytes_per_plaintext(), 2, 1, false)?; + // let parameter = MulPir::generate_parameter(&config, &context); + // assert_eq!(parameter.dimensions, vec![4, 4]); + // } + // { + // let config = IndexPirConfig::new(10, context.bytes_per_plaintext(), 2, 2, false)?; + // let parameter = MulPir::generate_parameter(&config, &context); + // assert_eq!(parameter.dimensions, vec![4, 3]); + // } + // // unevenDimensions: true + // { + // let config = IndexPirConfig::new(15, context.bytes_per_plaintext(), 2, 1, true)?; + // let parameter = MulPir::generate_parameter(&config, &context); + // assert_eq!(parameter.dimensions, vec![5, 3]); + // } + // { + // let config = IndexPirConfig::new(15, context.bytes_per_plaintext(), 2, 2, true)?; + // let parameter = MulPir::generate_parameter(&config, &context); + // assert_eq!(parameter.dimensions, vec![5, 3]); + // } + // { + // let config = IndexPirConfig::new(17, context.bytes_per_plaintext(), 2, 2, true)?; + // let parameter = MulPir::generate_parameter(&config, &context); + // assert_eq!(parameter.dimensions, vec![9, 2]); + // } + // Ok(()) + // } + + // fn index_pir_test_for_parameter( + // _server: &Server, + // _client: &Client, + // parameter: &IndexPirParameter, + // context: &Context, + // ) -> Result<()> + // where + // Server: IndexPirServer, + // Client: IndexPirClient, + // { + // let database = get_database_for_testing(parameter.entry_count(), + // parameter.entry_size_in_bytes()); let processed_db = Server::process(&database, + // context, parameter)?; + // + // let server = Server::new(parameter, context, &processed_db)?; + // let client = Client::new(parameter, context); + // + // let secret_key = context.generate_secret_key()?; + // let evaluation_key = client.generate_evaluation_key(&secret_key)?; + // + // for _ in 0..10 { + // let mut indices: Vec = (0..parameter.batch_size()).collect(); + // indices.shuffle(&mut rand::thread_rng()); + // let batch_size = rand::thread_rng().gen_range(1..=parameter.batch_size()); + // let query_indices: Vec = indices.iter().take(batch_size).cloned().collect(); + // let query = client.generate_query(&query_indices, &secret_key)?; + // let response = server.compute_response(&query, &evaluation_key)?; + // if !Server::Scheme::is_no_op() { + // assert!(!response.is_transparent()); + // } + // let decrypted_response = client.decrypt(&response, &query_indices, &secret_key)?; + // for (i, &index) in query_indices.iter().enumerate() { + // assert_eq!(decrypted_response[i], database[index]); + // } + // } + // Ok(()) + // } + // + // fn index_pir_test() -> Result<()> + // where + // Server: IndexPirServer, + // Client: IndexPirClient, + // { + // let config1 = IndexPirConfig::new(100, 1, 2, 2, false)?; + // let config2 = IndexPirConfig::new(100, 8, 2, 2, false)?; + // let config3 = IndexPirConfig::new(100, 24, 2, 2, true)?; + // let config4 = IndexPirConfig::new(100, 24, 1, 2, true)?; + // + // let context: Context = TestUtils::get_test_context()?; + // let parameter1 = Server::generate_parameter(&config1, &context); + // index_pir_test_for_parameter::(&Server, &Client, ¶meter1, &context)?; + // + // let parameter2 = Server::generate_parameter(&config2, &context); + // index_pir_test_for_parameter::(&Server, &Client, ¶meter2, &context)?; + // + // let parameter3 = Server::generate_parameter(&config3, &context); + // index_pir_test_for_parameter::(&Server, &Client, ¶meter3, &context)?; + // + // let parameter4 = Server::generate_parameter(&config4, &context); + // index_pir_test_for_parameter::(&Server, &Client, ¶meter4, &context)?; + // + // Ok(()) + // } + // + // fn mul_index_pir_test() -> Result<()> + // where + // Scheme: HeScheme, + // { + // index_pir_test::, MulPirClient>() + // } + // + // #[test] + // fn test_index_pir() -> Result<(), Box> { + // mul_index_pir_test::()?; + // mul_index_pir_test::>()?; + // mul_index_pir_test::>()?; + // Ok(()) + // } +} diff --git a/src/private_information_retrieval/keyword_database.rs b/src/private_information_retrieval/keyword_database.rs index 7ffd206..9be2669 100644 --- a/src/private_information_retrieval/keyword_database.rs +++ b/src/private_information_retrieval/keyword_database.rs @@ -369,11 +369,11 @@ impl KeywordDatabase { } /// Arguments for processing a keyword database. -pub struct Arguments { +pub struct Arguments { /// Configuration for the keyword database. database_config: KeywordDatabaseConfig, /// Encryption parameters - encryption_parameters: EncryptionParameters, + encryption_parameters: EncryptionParameters, /// PIR algorithm to process with. algorithm: PirAlgorithm, /// Number of test queries per shard. @@ -393,7 +393,7 @@ impl Arguments { /// - `trials_per_shard`: Number of test queries per shard. pub fn new( database_config: KeywordDatabaseConfig, - encryption_parameters: EncryptionParameters, + encryption_parameters: EncryptionParameters, algorithm: PirAlgorithm, trials_per_shard: usize, ) -> Self { @@ -443,14 +443,14 @@ impl ShardValidationResult { } /// A processed keyword database. -pub struct Processed { +pub struct Processed { /// Evaluation key configuration. evaluation_key_config: EvaluationKeyConfiguration, /// Maps each shard_id to the associated database shard and PIR parameters. shards: HashMap>, } -impl Processed { +impl Processed { /// Creates a new `Processed` keyword database. /// /// # Parameters @@ -541,7 +541,7 @@ impl ProcessKeywordDatabase { let compute_times: Vec = (0..trials) .map(|trial| { - let secret_key = Scheme::generate_secret_key(context); + let secret_key = Scheme::generate_secret_key(context)?; let trial_evaluation_key = client.generate_evaluation_key(&secret_key); let trial_query = client.generate_query(&row.keyword, &secret_key)?; @@ -555,7 +555,7 @@ impl ProcessKeywordDatabase { let decrypted_response = client.decrypt(&response, &row.keyword, &secret_key)?; if decrypted_response != row.value { let noise_budget = response.noise_budget(&secret_key, true); - if noise_budget < Scheme::min_noise_budget() { + if noise_budget < Scheme::MIN_NOISE_BUDGET { return Err( KeywordDatabaseError::InsufficientNoiseBudget(noise_budget).into() ); diff --git a/src/private_information_retrieval/mul_pir.rs b/src/private_information_retrieval/mul_pir.rs index 6dd68b5..e34729e 100644 --- a/src/private_information_retrieval/mul_pir.rs +++ b/src/private_information_retrieval/mul_pir.rs @@ -29,7 +29,7 @@ pub enum MulPir { impl MulPir { pub fn evaluation_key_configuration( _parameter: &IndexPirParameter, - _encryption_parameters: &EncryptionParameters, + _encryption_parameters: &EncryptionParameters, ) -> EvaluationKeyConfiguration { todo!() } diff --git a/src/test_utilities.rs b/src/test_utilities.rs new file mode 100644 index 0000000..bedcd29 --- /dev/null +++ b/src/test_utilities.rs @@ -0,0 +1,39 @@ +const TEST_POLY_DEGREE: u32 = 16; +const TEST_PLAINTEXT_MODULUS: u32 = 1153; + +// pub fn test_coefficient_moduli() -> Result> { +// if std::any::TypeId::of::() == std::any::TypeId::of::() { +// return T::generate_primes( +// &[28, 28, 28, 28], +// false, +// TEST_POLY_DEGREE +// ); +// } +// if std::any::TypeId::of::() == std::any::TypeId::of::() { +// return T::generate_primes( +// &[55, 55, 55, 55], +// false, +// TEST_POLY_DEGREE +// ); +// } +// eyre::bail!("Unsupported scalar type"); +// } + +// pub fn get_test_encryption_parameters() -> Result> +// { let coefficient_moduli = test_coefficient_moduli::()? +// .into_iter() +// .map(Scheme::Scalar::from) +// .collect(); +// Ok(EncryptionParameters::new( +// // TEST_POLY_DEGREE, +// // Scheme::Scalar::from(TEST_PLAINTEXT_MODULUS), +// // coefficient_moduli, +// // ErrorStdDev::StdDev32, +// // SecurityLevel::Unchecked, +// )) +// } + +// pub fn get_test_context() -> Result> { +// let encryption_parameters = get_test_encryption_parameters::()?; +// Ok(Context::new(&encryption_parameters)) +// }