diff --git a/src/kem/kyber768.rs b/src/kem/kyber768.rs index e1db45add..fbe0c1869 100644 --- a/src/kem/kyber768.rs +++ b/src/kem/kyber768.rs @@ -5,6 +5,7 @@ mod parameters; mod sampling; mod serialize; mod utils; +mod field_element; use utils::{ArrayConversion, UpdatingArray2}; diff --git a/src/kem/kyber768/compress.rs b/src/kem/kyber768/compress.rs index 0f06fe19b..e4ac3dcbe 100644 --- a/src/kem/kyber768/compress.rs +++ b/src/kem/kyber768/compress.rs @@ -1,4 +1,4 @@ -use crate::kem::kyber768::parameters::{self, KyberFieldElement, KyberPolynomialRingElement}; +use crate::kem::kyber768::{parameters::{self, KyberPolynomialRingElement}, field_element::KyberFieldElement}; pub fn compress( re: KyberPolynomialRingElement, @@ -21,28 +21,27 @@ pub fn decompress( } fn compress_q(fe: KyberFieldElement, to_bit_size: usize) -> KyberFieldElement { - assert!(to_bit_size <= parameters::BITS_PER_COEFFICIENT); + debug_assert!(to_bit_size <= parameters::BITS_PER_COEFFICIENT); - let two_pow_bit_size = 2u32.pow(to_bit_size.try_into().unwrap_or_else(|_| { - panic!( - "Conversion should work since to_bit_size is never greater than {}.", - parameters::BITS_PER_COEFFICIENT - ) - })); + let two_pow_bit_size = 1u32 << to_bit_size; - let compressed = ((u32::from(fe.value) * 2 * two_pow_bit_size) - + u32::from(KyberFieldElement::MODULUS)) - / u32::from(2 * KyberFieldElement::MODULUS); + let mut compressed = u32::from(fe.value) * (two_pow_bit_size << 1); + compressed += u32::from(KyberFieldElement::MODULUS); + compressed /= u32::from(KyberFieldElement::MODULUS << 1); - (compressed % two_pow_bit_size).into() + KyberFieldElement { + value: (compressed & (two_pow_bit_size - 1)) as u16 + } } fn decompress_q(fe: KyberFieldElement, to_bit_size: usize) -> KyberFieldElement { - assert!(to_bit_size <= parameters::BITS_PER_COEFFICIENT); + debug_assert!(to_bit_size <= parameters::BITS_PER_COEFFICIENT); - let decompressed = (2 * u32::from(fe.value) * u32::from(KyberFieldElement::MODULUS) - + (1 << to_bit_size)) - >> (to_bit_size + 1); + let mut decompressed = u32::from(fe.value) * u32::from(KyberFieldElement::MODULUS); + decompressed = (decompressed << 1) + (1 << to_bit_size); + decompressed >>= to_bit_size + 1; - decompressed.into() + KyberFieldElement { + value: decompressed as u16 + } } diff --git a/src/kem/kyber768/field_element.rs b/src/kem/kyber768/field_element.rs new file mode 100644 index 000000000..b18319764 --- /dev/null +++ b/src/kem/kyber768/field_element.rs @@ -0,0 +1,111 @@ +use std::ops; + +use crate::kem::kyber768::{parameters::FIELD_MODULUS, utils::field::FieldElement}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct KyberFieldElement { + pub value: u16, +} + +impl KyberFieldElement { + pub const MODULUS: u16 = FIELD_MODULUS; + + const BARRETT_SHIFT : u32 = 24; // 2 * ceil(log_2(FIELD_MODULUS)) + const BARRETT_MULTIPLIER : u32 = (1u32 << Self::BARRETT_SHIFT) / (Self::MODULUS as u32); + + pub fn barrett_reduce(value : u32) -> Self { + let product : u64 = u64::from(value) * u64::from(Self::BARRETT_MULTIPLIER); + let quotient : u32 = (product >> Self::BARRETT_SHIFT) as u32; + + let remainder = value - (quotient * u32::from(Self::MODULUS)); + let remainder : u16 = remainder as u16; + + let remainder_minus_modulus = remainder.wrapping_sub(Self::MODULUS); + + // TODO: Check if LLVM detects this and optimizes it away into a + // conditional. + let selector = 0u16.wrapping_sub((remainder_minus_modulus >> 15) & 1); + + Self { + value: (selector & remainder) | (!selector & remainder_minus_modulus), + } + } +} + +impl FieldElement for KyberFieldElement { + const ZERO: Self = Self { value: 0 }; + + fn new(number: u16) -> Self { + Self::barrett_reduce(u32::from(number)) + } + + fn nth_bit_little_endian(&self, n: usize) -> u8 { + ((self.value >> n) & 1) as u8 + } +} + +impl From for KyberFieldElement { + fn from(number: u8) -> Self { + Self { + value: u16::from(number) + } + } +} + +impl From for u16 { + fn from(fe: KyberFieldElement) -> Self { + fe.value + } +} + +impl ops::Add for KyberFieldElement { + type Output = Self; + + fn add(self, other: Self) -> Self { + let sum: u16 = self.value + other.value; + let difference: u16 = sum.wrapping_sub(Self::MODULUS); + + let mask = 0u16.wrapping_sub((difference >> 15) & 1); + + Self { + value: (mask & sum) | (!mask & difference), + } + } +} +impl ops::Sub for KyberFieldElement { + type Output = Self; + + fn sub(self, other: Self) -> Self { + let lhs = self.value; + let rhs = Self::MODULUS - other.value; + + let sum: u16 = lhs + rhs; + let difference: u16 = sum.wrapping_sub(Self::MODULUS); + + let mask = 0u16.wrapping_sub((difference >> 15) & 1); + + Self { + value: (mask & sum) | (!mask & difference), + } + } +} + +impl ops::Mul for KyberFieldElement { + type Output = Self; + + fn mul(self, other: Self) -> Self { + let product: u32 = u32::from(self.value) * u32::from(other.value); + + Self::barrett_reduce(product) + } +} + +impl ops::Mul for KyberFieldElement { + type Output = Self; + + fn mul(self, other: u16) -> Self { + let product: u32 = u32::from(self.value) * u32::from(other); + + Self::barrett_reduce(product) + } +} diff --git a/src/kem/kyber768/ntt.rs b/src/kem/kyber768/ntt.rs index 26151705d..4b799adfa 100644 --- a/src/kem/kyber768/ntt.rs +++ b/src/kem/kyber768/ntt.rs @@ -3,11 +3,10 @@ use crate::kem::kyber768::parameters::{KyberPolynomialRingElement, RANK}; use self::kyber_polynomial_ring_element_mod::ntt_multiply; pub(crate) mod kyber_polynomial_ring_element_mod { - use crate::kem::kyber768::utils::field::FieldElement; - use crate::kem::kyber768::parameters::{ - self, KyberFieldElement, KyberPolynomialRingElement, COEFFICIENTS_IN_RING_ELEMENT, + self, KyberPolynomialRingElement, COEFFICIENTS_IN_RING_ELEMENT, }; + use crate::kem::kyber768::field_element::KyberFieldElement; const ZETAS: [u16; 128] = [ 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, 2786, 3260, 569, 1746, @@ -40,10 +39,9 @@ pub(crate) mod kyber_polynomial_ring_element_mod { for layer in NTT_LAYERS.iter().rev() { for offset in (0..(COEFFICIENTS_IN_RING_ELEMENT - layer)).step_by(2 * layer) { zeta_i += 1; - let zeta: KyberFieldElement = ZETAS[zeta_i].into(); for j in offset..offset + layer { - let t = zeta * re[j + layer]; + let t = re[j + layer] * ZETAS[zeta_i]; re[j + layer] = re[j] - t; re[j] = re[j] + t; } @@ -53,8 +51,7 @@ pub(crate) mod kyber_polynomial_ring_element_mod { } pub fn invert_ntt(re: KyberPolynomialRingElement) -> KyberPolynomialRingElement { - let inverse_of_2: KyberFieldElement = - KyberFieldElement::new((parameters::FIELD_MODULUS + 1) / 2); + let inverse_of_2: u16 = (parameters::FIELD_MODULUS + 1) >> 1; let mut out = KyberPolynomialRingElement::ZERO; for i in 0..re.len() { @@ -66,12 +63,11 @@ pub(crate) mod kyber_polynomial_ring_element_mod { for layer in NTT_LAYERS { for offset in (0..(COEFFICIENTS_IN_RING_ELEMENT - layer)).step_by(2 * layer) { zeta_i -= 1; - let zeta: KyberFieldElement = ZETAS[zeta_i].into(); for j in offset..offset + layer { let a_minus_b = out[j + layer] - out[j]; - out[j] = inverse_of_2 * (out[j] + out[j + layer]); - out[j + layer] = inverse_of_2 * zeta * a_minus_b; + out[j] = (out[j] + out[j + layer]) * inverse_of_2; + out[j + layer] = (a_minus_b * ZETAS[zeta_i]) * inverse_of_2; } } } @@ -79,23 +75,27 @@ pub(crate) mod kyber_polynomial_ring_element_mod { out } + fn ntt_multiply_binomials((a0, a1): (KyberFieldElement, KyberFieldElement), + (b0, b1): (KyberFieldElement, KyberFieldElement), + zeta: u16) -> (KyberFieldElement, KyberFieldElement) { + ((a0 * b0) + ((a1 * b1) * zeta), + (a0 * b1) + (a1 * b0)) + } + pub fn ntt_multiply( left: &KyberPolynomialRingElement, - other: &KyberPolynomialRingElement, + right: &KyberPolynomialRingElement, ) -> KyberPolynomialRingElement { let mut out = KyberPolynomialRingElement::ZERO; - for i in (0..COEFFICIENTS_IN_RING_ELEMENT).step_by(2) { - let mod_root: KyberFieldElement = MOD_ROOTS[i / 2].into(); - - let a0_times_b0 = left[i] * other[i]; - let a1_times_b1 = left[i + 1] * other[i + 1]; - - let a0_times_b1 = left[i + 1] * other[i]; - let a1_times_b0 = left[i] * other[i + 1]; + for i in (0..out.coefficients.len()).step_by(4) { + let product = ntt_multiply_binomials((left[i], left[i+1]), (right[i], right[i + 1]), MOD_ROOTS[i / 2]); + out[i] = product.0; + out[i + 1] = product.1; - out[i] = a0_times_b0 + (a1_times_b1 * mod_root); - out[i + 1] = a0_times_b1 + a1_times_b0; + let product = ntt_multiply_binomials((left[i + 2], left[i + 3]), (right[i + 2], right[i + 3]), MOD_ROOTS[(i + 2) / 2]); + out[i + 2] = product.0; + out[i + 3] = product.1; } out } diff --git a/src/kem/kyber768/parameters.rs b/src/kem/kyber768/parameters.rs index 69c7af614..290650b75 100644 --- a/src/kem/kyber768/parameters.rs +++ b/src/kem/kyber768/parameters.rs @@ -1,4 +1,5 @@ -use crate::kem::kyber768::utils::{field::PrimeFieldElement, ring::PolynomialRingElement}; +use crate::kem::kyber768::field_element::KyberFieldElement; +use crate::kem::kyber768::utils::ring::PolynomialRingElement; /// Field modulus: 3329 pub(crate) const FIELD_MODULUS: u16 = 3329; @@ -79,9 +80,6 @@ pub(crate) mod hash_functions { } } -/// A Kyber field element. -pub(crate) type KyberFieldElement = PrimeFieldElement; - /// A Kyber ring element pub(crate) type KyberPolynomialRingElement = PolynomialRingElement; diff --git a/src/kem/kyber768/sampling.rs b/src/kem/kyber768/sampling.rs index 38e945003..b00c326d4 100644 --- a/src/kem/kyber768/sampling.rs +++ b/src/kem/kyber768/sampling.rs @@ -1,7 +1,8 @@ use crate::kem::kyber768::{ - parameters::{self, KyberFieldElement, KyberPolynomialRingElement}, + parameters::{self, KyberPolynomialRingElement}, BadRejectionSamplingRandomnessError, }; +use crate::kem::kyber768::field_element::KyberFieldElement; pub fn sample_from_uniform_distribution( randomness: [u8; parameters::REJECTION_SAMPLING_SEED_SIZE], @@ -20,11 +21,11 @@ pub fn sample_from_uniform_distribution( let d2 = (b1 / 16) + (16 * b2); if d1 < parameters::FIELD_MODULUS && sampled_coefficients < out.len() { - out[sampled_coefficients] = d1.into(); + out[sampled_coefficients] = KyberFieldElement { value : d1 }; sampled_coefficients += 1 } if d2 < parameters::FIELD_MODULUS && sampled_coefficients < out.len() { - out[sampled_coefficients] = d2.into(); + out[sampled_coefficients] = KyberFieldElement { value : d2 }; sampled_coefficients += 1; } @@ -53,13 +54,13 @@ pub fn sample_from_binomial_distribution_with_2_coins( let coin_toss_outcomes = even_bits + odd_bits; for outcome_set in (0..u32::BITS).step_by(4) { - let outcome_1: u16 = ((coin_toss_outcomes >> outcome_set) & 0x3) as u16; + let outcome_1: u8 = ((coin_toss_outcomes >> outcome_set) & 0x3) as u8; let outcome_1: KyberFieldElement = outcome_1.into(); - let outcome_2: u16 = ((coin_toss_outcomes >> (outcome_set + 2)) & 0x3) as u16; + let outcome_2: u8 = ((coin_toss_outcomes >> (outcome_set + 2)) & 0x3) as u8; let outcome_2: KyberFieldElement = outcome_2.into(); - let offset = usize::try_from(outcome_set >> 2).unwrap(); + let offset = (outcome_set >> 2) as usize; sampled[8 * chunk_number + offset] = outcome_1 - outcome_2; } } diff --git a/src/kem/kyber768/serialize.rs b/src/kem/kyber768/serialize.rs index db660edaf..0a15fbe3d 100644 --- a/src/kem/kyber768/serialize.rs +++ b/src/kem/kyber768/serialize.rs @@ -1,9 +1,11 @@ use crate::kem::kyber768::utils::{bit_vector::BitVector, ring::LittleEndianBitStream}; use crate::kem::kyber768::parameters::{ - KyberFieldElement, KyberPolynomialRingElement, BITS_PER_COEFFICIENT, BYTES_PER_RING_ELEMENT, + KyberPolynomialRingElement, BITS_PER_COEFFICIENT, BYTES_PER_RING_ELEMENT, }; +use crate::kem::kyber768::field_element::KyberFieldElement; + pub fn serialize_little_endian( re: KyberPolynomialRingElement, bits_per_coefficient: usize, @@ -48,7 +50,9 @@ fn field_element_from_little_endian_bit_vector(bit_vector: BitVector) -> KyberFi value |= u16::from(bit) << i; } - value.into() + KyberFieldElement { + value + } } pub fn deserialize_little_endian( diff --git a/src/kem/kyber768/utils/field.rs b/src/kem/kyber768/utils/field.rs index 2df442206..525118255 100644 --- a/src/kem/kyber768/utils/field.rs +++ b/src/kem/kyber768/utils/field.rs @@ -5,8 +5,6 @@ pub trait FieldElement: + Clone + PartialEq + From - + From - + From + Into + ops::Add + ops::Sub @@ -17,91 +15,3 @@ pub trait FieldElement: fn new(number: u16) -> Self; fn nth_bit_little_endian(&self, n: usize) -> u8; } - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PrimeFieldElement { - pub value: u16, -} - -impl FieldElement for PrimeFieldElement { - const ZERO: Self = Self { value: 0 }; - - fn new(number: u16) -> Self { - Self { - value: number % MODULUS, - } - } - - fn nth_bit_little_endian(&self, n: usize) -> u8 { - ((self.value >> n) & 1) as u8 - } -} - -impl PrimeFieldElement { - pub const MODULUS: u16 = MODULUS; -} - -impl From for PrimeFieldElement { - fn from(number: u8) -> Self { - Self::new(u16::from(number)) - } -} -impl From for PrimeFieldElement { - fn from(number: u16) -> Self { - Self::new(number) - } -} -impl From> for u16 { - fn from(value: PrimeFieldElement) -> Self { - value.value - } -} -impl From for PrimeFieldElement { - fn from(number: u32) -> Self { - let remainder_as_u32 = number % u32::from(MODULUS); - - Self::new(remainder_as_u32.try_into().unwrap()) - } -} - -impl ops::Add for PrimeFieldElement { - type Output = Self; - - fn add(self, other: Self) -> Self { - let sum: u16 = self.value + other.value; - let difference: u16 = sum.wrapping_sub(MODULUS); - - let mask = 0u16.wrapping_sub((difference >> 15) & 1); - - Self { - value: (mask & sum) | (!mask & difference), - } - } -} -impl ops::Sub for PrimeFieldElement { - type Output = Self; - - fn sub(self, other: Self) -> Self { - let lhs = self.value; - let rhs = MODULUS - other.value; - - let sum: u16 = lhs + rhs; - let difference: u16 = sum.wrapping_sub(MODULUS); - - let mask = 0u16.wrapping_sub((difference >> 15) & 1); - - Self { - value: (mask & sum) | (!mask & difference), - } - } -} - -impl ops::Mul for PrimeFieldElement { - type Output = Self; - - fn mul(self, other: Self) -> Self { - let product: u32 = u32::from(self.value) * u32::from(other.value); - - product.into() - } -}