diff --git a/src/algebra/field/prime/mod.rs b/src/algebra/field/prime/mod.rs index 8794f80f..131e76fa 100644 --- a/src/algebra/field/prime/mod.rs +++ b/src/algebra/field/prime/mod.rs @@ -36,7 +36,7 @@ pub type AESField = PrimeField<2>; /// The [`PrimeField`] struct represents elements of a field with prime order. The field is defined /// by a prime number `P`, and the elements are integers modulo `P`. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct PrimeField { pub(crate) value: usize, } diff --git a/src/encryption/asymmetric/lwe/mod.rs b/src/encryption/asymmetric/lwe/mod.rs new file mode 100644 index 00000000..fc783c98 --- /dev/null +++ b/src/encryption/asymmetric/lwe/mod.rs @@ -0,0 +1,186 @@ +//! This module implements the Learning With Errors (LWE) public-key encryption scheme. +//! +//! LWE is a lattice-based cryptosystem whose security is based on the hardness of the +//! Learning With Errors problem, first introduced by Regev in 2005. The scheme encrypts +//! single bits and provides security against quantum computers. +//! +//! The implementation uses three type parameters: +//! - Q: The modulus (must be prime) +//! - N: The dimension of the lattice +//! - K: The parameter for the binomial distribution used for error sampling + +use rand::Rng; + +use super::AsymmetricEncryption; +use crate::algebra::field::{prime::PrimeField, Field}; + +/// An implementation of Learning With Errors (LWE) encryption +pub struct LWE { + private_key: PrivateKey, + public_key: PublicKey, +} + +/// The public key consisting of a random matrix A and vector b = As + e +#[derive(Debug, Clone)] +pub struct PublicKey { + /// Random matrix in Z_q^{n×n} + a: [[PrimeField; N]; N], + /// Vector b = As + e where s is secret and e is error + b: [PrimeField; N], +} + +/// The private key consisting of the secret vector s +#[derive(Debug, Clone)] +pub struct PrivateKey { + /// Secret vector with small coefficients + s: [PrimeField; N], +} + +/// A ciphertext consisting of vector u and scalar v +#[derive(Debug, Clone)] +pub struct Ciphertext { + /// First component u = A^T r + u: [PrimeField; N], + /// Second component v = b^T r + ⌊q/2⌋m + v: PrimeField, +} + +/// Sample from a centered binomial distribution with parameter K +/// +/// Returns a value in the range [-K, K] following a discrete approximation +/// of a Gaussian distribution. +pub fn sample_binomial() -> PrimeField { + let mut rng = rand::thread_rng(); + let mut sum = PrimeField::::ZERO; + + for _ in 0..K { + if rng.gen::() { + sum += PrimeField::::ONE; + } + if rng.gen::() { + sum -= PrimeField::::ONE; + } + } + sum +} + +impl LWE { + pub fn new() -> LWE { + let mut rng = rand::thread_rng(); + + // Generate random matrix A + let mut a = [[PrimeField::::ZERO; N]; N]; + for i in 0..N { + for j in 0..N { + a[i][j] = PrimeField::from(rng.gen_range(0..Q)); + } + } + + // Sample secret vector s with small coefficients + let mut s = [PrimeField::::ZERO; N]; + for i in 0..N { + s[i] = sample_binomial::(); + } + + // Generate error vector e + let mut e = [PrimeField::::ZERO; N]; + for i in 0..N { + e[i] = sample_binomial::(); + } + + // Compute b = As + e + let mut b = [PrimeField::::ZERO; N]; + for i in 0..N { + let mut sum = PrimeField::::ZERO; + for j in 0..N { + sum += a[i][j] * s[j]; + } + sum += e[i]; + b[i] = sum; + } + + Self { public_key: PublicKey { a, b }, private_key: PrivateKey { s } } + } +} + +impl AsymmetricEncryption for LWE { + type Ciphertext = Ciphertext; + type Plaintext = bool; + type PrivateKey = PrivateKey; + type PublicKey = PublicKey; + + fn encrypt(&self, plaintext: &Self::Plaintext) -> Self::Ciphertext { + let mut rng = rand::thread_rng(); + + // Sample random vector r (binary) + let mut r = [PrimeField::::ZERO; N]; + for i in 0..N { + r[i] = PrimeField::from(rng.gen_range(0..2)); + } + + // Compute u = A^T r + let mut u = [PrimeField::::ZERO; N]; + for i in 0..N { + for j in 0..N { + u[i] += self.public_key.a[j][i] * r[j]; + } + } + + // Compute v = b^T r + ⌊q/2⌋m + let mut v = PrimeField::::ZERO; + for i in 0..N { + v += self.public_key.b[i] * r[i]; + } + + if *plaintext { + v += PrimeField::from(Q / 2); + } + + Ciphertext { u, v } + } + + fn decrypt(&self, ct: &Self::Ciphertext) -> Self::Plaintext { + // Compute v - s^T u + let mut result = ct.v; + for i in 0..N { + result -= self.private_key.s[i] * ct.u[i]; + } + + // Get q/2 as a field element + let q_half = PrimeField::::from(Q / 2); + + // For distance to zero, we need min(x, q-x) + let dist_to_zero = result.min(-result); + + // For distance to q/2, we need min(|x - q/2|, |q/2 - x|) + let dist_to_q_half = if result >= q_half { + // If result ≥ q/2, distance is min(result - q/2, q - result + q/2) + (result - q_half).min(-result + q_half) + } else { + // If result < q/2, distance is min(q/2 - result, result + q/2) + (q_half - result).min(result + q_half) + }; + + dist_to_q_half < dist_to_zero + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encryption_decryption() { + for _ in 0..100 { + let lwe = LWE::<97, 4, 2>::new(); + + // Test encryption and decryption of 0 + let ct_zero = lwe.encrypt(&false); + assert_eq!(lwe.decrypt(&ct_zero), false, "Failed decrypting 0"); + + // Test encryption and decryption of 1 + let ct_one = lwe.encrypt(&true); + assert_eq!(lwe.decrypt(&ct_one), true, "Failed decrypting 1"); + } + } +} diff --git a/src/encryption/asymmetric/mod.rs b/src/encryption/asymmetric/mod.rs index 62939b9a..1b9e310f 100644 --- a/src/encryption/asymmetric/mod.rs +++ b/src/encryption/asymmetric/mod.rs @@ -1,2 +1,16 @@ //! Contains implementation of asymmetric cryptographic primitives like RSA encryption. +pub mod lwe; pub mod rsa; + +pub trait AsymmetricEncryption { + type PublicKey; + type PrivateKey; + type Plaintext; + type Ciphertext; + + /// Encrypts plaintext using key and returns ciphertext + fn encrypt(&self, plaintext: &Self::Plaintext) -> Self::Ciphertext; + + /// Decrypts ciphertext using key and returns plaintext + fn decrypt(&self, ciphertext: &Self::Ciphertext) -> Self::Plaintext; +} diff --git a/src/encryption/symmetric/mod.rs b/src/encryption/symmetric/mod.rs index 9c4be844..bee82c41 100644 --- a/src/encryption/symmetric/mod.rs +++ b/src/encryption/symmetric/mod.rs @@ -6,6 +6,8 @@ pub mod counter; pub mod des; pub mod modes; +// TODO (autoparallel): All of these could be simplified and combined given the +// `AsymmetricEncryption` trait added. /// Trait for symmetric encryption primitive pub trait SymmetricEncryption { /// Key represents the secret key or subkeys used during the encryption algorithm diff --git a/src/lib.rs b/src/lib.rs index 7f31982d..82b3ec27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ pub mod encryption; pub mod hashes; pub mod hmac; pub mod kzg; -pub mod lwe; pub mod multi_var_poly; pub mod polynomial; pub mod sumcheck; diff --git a/src/lwe/mod.rs b/src/lwe/mod.rs deleted file mode 100644 index 58ac9573..00000000 --- a/src/lwe/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -use rand::Rng; - -use crate::algebra::field::{extension::GaloisField, prime::PrimeField, Field, FiniteField}; - -// Small parameters for testing -// const N: usize = 4; // Dimension (use larger like 256 in practice) -// const Q: u32 = 97; // Modulus (prime, use larger in practice) -const K: usize = 1; // Parameter for binomial sampling - -#[derive(Debug, Clone)] -pub struct PublicKey { - a: [[PrimeField; N]; N], // Matrix A in Z_q^{n×n} - b: [PrimeField; N], // Vector b = As + e -} - -#[derive(Debug, Clone)] -pub struct SecretKey { - s: [PrimeField; N], // Secret vector in Z_q^n -} - -#[derive(Debug, Clone)] -pub struct Ciphertext { - u: Vec>, // First component - v: PrimeField, // Second component -} - -fn sample_binomial() -> PrimeField { - let mut rng = rand::thread_rng(); - let mut sum = PrimeField::::ZERO; - - for _ in 0..K { - // Add 1 or 0 for first term - if rng.gen::() { - sum = sum + PrimeField::::ONE; - } - // Subtract 1 or 0 for second term - if rng.gen::() { - sum = sum - PrimeField::::ONE; - } - } - sum -} - -pub fn keygen() -> (PublicKey, SecretKey) { - let mut rng = rand::thread_rng(); - - // Generate random matrix A - let mut a = [[PrimeField::::ZERO; N]; N]; - for i in 0..N { - for j in 0..N { - a[i][j] = PrimeField::from(rng.gen_range(0..Q)); - } - } - - // Sample secret vector s with small coefficients - let mut s = [PrimeField::::ZERO; N]; - for i in 0..N { - s[i] = sample_binomial(); - } - - // Generate error vector e - let mut e = [PrimeField::::ZERO; N]; - for i in 0..N { - e[i] = sample_binomial(); - } - - // Compute b = As + e - let mut b = [PrimeField::::ZERO; N]; - for i in 0..N { - let mut sum = PrimeField::::ZERO; - for j in 0..N { - sum += a[i][j] * s[j]; - } - sum += e[i]; - b[i] = sum; - } - - (PublicKey { a, b }, SecretKey { s }) -} - -// TODO: Should impl the `SymmetricEncryption` trait -pub fn encrypt(pk: &PublicKey, message: bool) -> Ciphertext { - let mut rng = rand::thread_rng(); - - // Sample random vector r - let mut r = vec![0; N]; - for i in 0..N { - r[i] = rng.gen_range(0..2) as u32; // Binary vector - } - - // Compute u = A^T r - let mut u = vec![0; N]; - for i in 0..N { - let mut sum = 0i32; - for j in 0..N { - sum += (pk.a[j][i] * r[j]) as i32; - } - u[i] = mod_q(sum); - } - - // Compute v = b^T r + ⌊q/2⌋m - let mut v = 0i32; - for i in 0..N { - v += (pk.b[i] * r[i]) as i32; - } - if message { - v += (Q / 2) as i32; - } - - Ciphertext { u, v: mod_q(v) } -} - -pub fn decrypt(sk: &SecretKey, ct: &Ciphertext) -> bool { - // Compute v - s^T u - let mut sum = ct.v as i32; - for i in 0..N { - sum -= (sk.s[i] * ct.u[i]) as i32; - } - sum = mod_q(sum) as i32; - - // Check if closer to 0 or ⌊q/2⌋ - let q_half = (Q / 2) as i32; - let mut dist_to_zero = sum.min(Q as i32 - sum); - let mut dist_to_q_half = ((sum - q_half).abs()).min((sum - q_half + Q as i32).abs()); - - dist_to_q_half < dist_to_zero -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_encryption_decryption() { - // Test multiple random instances - for _ in 0..100 { - let (pk, sk) = keygen(); - - // Test encryption and decryption of 0 - let ct_zero = encrypt(&pk, false); - assert_eq!(decrypt(&sk, &ct_zero), false); - - // Test encryption and decryption of 1 - let ct_one = encrypt(&pk, true); - assert_eq!(decrypt(&sk, &ct_one), true); - } - } - - #[test] - fn test_with_fixed_randomness() { - // This test would need a deterministic RNG to be meaningful - // In practice, you'd want to test with known test vectors - } - - #[test] - fn test_binomial_distribution() { - let mut counts = vec![0; 3]; // -1, 0, 1 for k=1 - for _ in 0..1000 { - let sample = sample_binomial(); - counts[(sample + 1) as usize] += 1; - } - // Check rough distribution - should be approximately 1/4, 1/2, 1/4 - for count in counts.iter() { - assert!(*count > 150); // Should be roughly 250 but allow some variance - } - } -}