Skip to content

Commit

Permalink
Update more of the Kyber spec to FIPS 203 standard. (#82)
Browse files Browse the repository at this point in the history
* Updated serialization functions and CCA KEM functions

* Removed almost all of the BitVector helper code and replaced it by 2 type aliases
  • Loading branch information
xvzcf authored Sep 21, 2023
1 parent e381b92 commit 97fe462
Show file tree
Hide file tree
Showing 11 changed files with 897 additions and 854 deletions.
152 changes: 2 additions & 150 deletions specs/hacspec-lib/src/bit_vector.rs
Original file line number Diff line number Diff line change
@@ -1,150 +1,2 @@
pub struct BitVector {
bits: Vec<u8>,
}

pub struct BitVectorChunks<'a> {
chunk_iterator: std::slice::Chunks<'a, u8>,
}

impl BitVectorChunks<'_> {
pub fn next(&mut self) -> Option<BitVector> {
self.chunk_iterator.next().map(|bits| BitVector {
bits: bits.to_vec(),
})
}
}

impl IntoIterator for BitVector {
type Item = u8;
type IntoIter = <Vec<u8> as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.bits.into_iter()
}
}

impl From<&[u8]> for BitVector {
fn from(bytes: &[u8]) -> Self {
let mut out = Vec::with_capacity(bytes.len() * 8);

for byte in bytes {
for j in 0..u8::BITS {
out.push((byte >> j) & 1);
}
}

Self { bits: out }
}
}

impl BitVector {
pub fn new(bits: Vec<u8>) -> Self {
for bit in &bits {
assert!(*bit == 0 || *bit == 1);
}

Self { bits }
}
pub fn chunks(&self, chunk_size: usize) -> BitVectorChunks {
BitVectorChunks {
chunk_iterator: self.bits.chunks(chunk_size),
}
}
}

pub trait LittleEndianBitStream {
fn nth_bit(&self, n: usize) -> u8;
fn iter(&self) -> LittleEndianBitStreamIter<'_>;
}

pub struct LittleEndianBitStreamIter<'a> {
bytes: &'a [u8],
bit: usize,
}

impl Iterator for LittleEndianBitStreamIter<'_> {
type Item = u8;

fn next(&mut self) -> Option<Self::Item> {
let byte_index = self.bit / 8;
if byte_index >= self.bytes.len() {
return None;
}

let out = self.bytes.nth_bit(self.bit);
self.bit += 1;

Some(out)
}
}

impl LittleEndianBitStream for &[u8] {
fn nth_bit(&self, n: usize) -> u8 {
let byte = n / 8;
let byte_bit = n % 8;
(self[byte] >> byte_bit) & 1
}

fn iter(&self) -> LittleEndianBitStreamIter<'_> {
LittleEndianBitStreamIter {
bytes: self,
bit: 0,
}
}
}

impl LittleEndianBitStream for Vec<u8> {
fn nth_bit(&self, n: usize) -> u8 {
self.as_slice().nth_bit(n)
}

fn iter(&self) -> LittleEndianBitStreamIter<'_> {
LittleEndianBitStreamIter {
bytes: self,
bit: 0,
}
}
}

#[cfg(test)]
mod tests {
use crate::bit_vector::LittleEndianBitStream;

#[test]
fn bits() {
// 00000001 00000010 00000011 00000100 00000101 00000110 ...
// 1 2 3 4 5 6
let v = vec![1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

let mut n = 0;

assert_eq!(v.nth_bit(n), 1);
n += 1;

for n_ in n..8 {
assert_eq!(v.nth_bit(n_), 0);
}
n = 8;

assert_eq!(v.nth_bit(n), 0);
n += 1;

assert_eq!(v.nth_bit(n), 1);
n += 1;

for n_ in n..16 {
assert_eq!(v.nth_bit(n_), 0);
}
n = 16;

assert_eq!(v.nth_bit(n), 1);
n += 1;

assert_eq!(v.nth_bit(n), 1);

for n in v.iter() {
eprint!("{n}");
}
eprintln!();
}
}
pub type BitVector = Vec<u8>;
pub type BitSlice<'a> = &'a [u8];
20 changes: 10 additions & 10 deletions specs/kyber/src/compress.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::parameters::{self, KyberFieldElement, KyberPolynomialRingElement};

/// According to the NIST FIPS 203 standard, compressing a polynomial
/// ring element is accomplished by `compress()`ing its constituent field
/// coefficients.
/// According to the NIST FIPS 203 standard (Page 10, Lines 536 - 539),
/// compressing a polynomial ring element is accomplished by `compress()`ing its
/// constituent field coefficients.
///
/// The NIST FIPS 203 standard can be found at
/// <https://csrc.nist.gov/pubs/fips/203/ipd>.
Expand All @@ -16,9 +16,9 @@ pub fn compress(
)
}

/// According to the NIST FIPS 203 standard, compressing a polynomial
/// ring element is accomplished by `decompress()`ing its constituent field
/// coefficients.
/// According to the NIST FIPS 203 standard (Page 10, Lines 536 - 539),
/// compressing a polynomial ring element is accomplished by `decompress()`ing
/// its constituent field coefficients.
///
/// The NIST FIPS 203 standard can be found at
/// <https://csrc.nist.gov/pubs/fips/203/ipd>.
Expand All @@ -32,8 +32,8 @@ pub fn decompress(
)
}

/// This function implements the `Compress` function defined on Page 18 of the
/// NIST FIPS 203 standard, which is defined as:
/// This function implements the `Compress` function specified in the NIST FIPS
/// 203 standard (Page 18, Expression 4.5), which is defined as:
///
/// ```plaintext
/// Compress_d: ℤq -> ℤ_{2ᵈ}
Expand Down Expand Up @@ -69,8 +69,8 @@ fn compress_d(fe: KyberFieldElement, to_bit_size: usize) -> KyberFieldElement {
(compressed % two_pow_bit_size).into()
}

/// This function implements the `Decompress` function defined on Page 18 of the
/// NIST FIPS 203 standard, which is defined as:
/// This function implements the `Decompress` function specified in the NIST FIPS
/// 203 standard (Page 18, Expression 4.6), which is defined as:
///
/// ```plaintext
/// Decompress_d: ℤ_{2ᵈ} -> ℤq
Expand Down
49 changes: 21 additions & 28 deletions specs/kyber/src/ind_cpa.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use hacspec_lib::{
ArrayConversion, ArrayPadding, PanickingIntegerCasts, UpdatableArray, UpdatingArray, VecUpdate,
ArrayConversion, ArrayPadding, PanickingIntegerCasts, UpdatableArray, UpdatingArray,
};

use crate::{
compress::{compress, decompress},
matrix::{multiply_matrix_transpose_by_column, multiply_column_by_row, transpose},
matrix::{encode_vector_12, multiply_column_by_row, multiply_matrix_by_column, transpose},
ntt::{ntt, ntt_inverse},
parameters::{
hash_functions::{G, H, PRF, XOF},
Expand All @@ -15,7 +15,7 @@ use crate::{
VECTOR_U_ENCODED_SIZE, VECTOR_V_COMPRESSION_FACTOR,
},
sampling::{sample_ntt, sample_poly_cbd},
serialize::{deserialize_little_endian, serialize_little_endian},
serialize::{byte_decode, byte_encode},
BadRejectionSamplingRandomnessError,
};

Expand Down Expand Up @@ -50,15 +50,6 @@ impl KeyPair {
}
}

fn encode_12(input: [KyberPolynomialRingElement; RANK]) -> Vec<u8> {
let mut out = Vec::new();
for re in input.into_iter() {
out.extend_from_slice(&serialize_little_endian(re, 12));
}

out
}

/// This function implements most of <strong>Algorithm 12</strong> of the
/// NIST FIPS 203 specification; this is the Kyber CPA-PKE key generation algorithm.
///
Expand All @@ -72,7 +63,7 @@ fn encode_12(input: [KyberPolynomialRingElement; RANK]) -> Vec<u8> {
/// Output: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}.
/// Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}.
///
/// d $← B
/// d ←$ B
/// (ρ,σ) ← G(d)
/// N ← 0
/// for (i ← 0; i < k; i++)
Expand Down Expand Up @@ -174,16 +165,19 @@ pub(crate) fn generate_keypair(
}

// t̂ ← Â◦ŝ + ê
let mut t_as_ntt = multiply_matrix_transpose_by_column(&A_as_ntt, &secret_as_ntt);
let mut t_as_ntt = multiply_matrix_by_column(&A_as_ntt, &secret_as_ntt);
for i in 0..t_as_ntt.len() {
t_as_ntt[i] = t_as_ntt[i] + error_as_ntt[i];
}

// ekₚₖₑ ← ByteEncode₁₂(t̂) ‖ ρ
let public_key_serialized = encode_12(t_as_ntt).concat(seed_for_A);
let public_key_serialized = UpdatableArray::new([0u8; CPA_PKE_PUBLIC_KEY_SIZE])
.push(&encode_vector_12(t_as_ntt))
.push(seed_for_A)
.array();

// dkₚₖₑ ← ByteEncode₁₂(ŝ)
let secret_key_serialized = encode_12(secret_as_ntt);
let secret_key_serialized = encode_vector_12(secret_as_ntt);

Ok(KeyPair::new(
secret_key_serialized.into_array(),
Expand All @@ -194,9 +188,9 @@ pub(crate) fn generate_keypair(
fn encode_and_compress_u(input: [KyberPolynomialRingElement; RANK]) -> Vec<u8> {
let mut out = Vec::new();
for re in input.into_iter() {
out.extend_from_slice(&serialize_little_endian(
compress(re, VECTOR_U_COMPRESSION_FACTOR),
out.extend_from_slice(&byte_encode(
VECTOR_U_COMPRESSION_FACTOR,
compress(re, VECTOR_U_COMPRESSION_FACTOR),
));
}

Expand Down Expand Up @@ -257,7 +251,7 @@ pub(crate) fn encrypt(
.chunks(BYTES_PER_RING_ELEMENT)
.enumerate()
{
t_as_ntt[i] = deserialize_little_endian(12, t_as_ntt_bytes);
t_as_ntt[i] = byte_decode(12, t_as_ntt_bytes);
}

// ρ ← ekₚₖₑ[384k: 384k + 32]
Expand Down Expand Up @@ -328,14 +322,13 @@ pub(crate) fn encrypt(

// u ← NTT-¹(Âᵀ ◦ r̂) + e₁
let A_as_ntt_transpose = transpose(&A_as_ntt);
let mut u = multiply_matrix_transpose_by_column(&A_as_ntt_transpose, &r_as_ntt)
.map(|re| ntt_inverse(re));
let mut u = multiply_matrix_by_column(&A_as_ntt_transpose, &r_as_ntt).map(|re| ntt_inverse(re));
for i in 0..u.len() {
u[i] = u[i] + error_1[i];
}

// μ ← Decompress₁(ByteDecode₁(m)))
let message_as_ring_element = decompress(deserialize_little_endian(1, &message), 1);
let message_as_ring_element = decompress(byte_decode(1, &message), 1);

// v ← NTT-¹(t̂ᵀ ◦ r̂) + e₂ + μ
let v = ntt_inverse(multiply_column_by_row(&t_as_ntt, &r_as_ntt))
Expand All @@ -346,9 +339,9 @@ pub(crate) fn encrypt(
let c1 = encode_and_compress_u(u);

// c₂ ← ByteEncode_{dᵥ}(Compress_{dᵥ}(v))
let c2 = serialize_little_endian(
compress(v, VECTOR_V_COMPRESSION_FACTOR),
let c2 = byte_encode(
VECTOR_V_COMPRESSION_FACTOR,
compress(v, VECTOR_V_COMPRESSION_FACTOR),
);

// return c ← (c₁ ‖ c₂)
Expand Down Expand Up @@ -392,14 +385,14 @@ pub(crate) fn decrypt(
.enumerate()
{
u[i] = decompress(
deserialize_little_endian(VECTOR_U_COMPRESSION_FACTOR, u_bytes),
byte_decode(VECTOR_U_COMPRESSION_FACTOR, u_bytes),
VECTOR_U_COMPRESSION_FACTOR,
);
}

// v ← Decompress_{dᵥ}(ByteDecode_{dᵥ}(c₂))
let v = decompress(
deserialize_little_endian(
byte_decode(
VECTOR_V_COMPRESSION_FACTOR,
&ciphertext[VECTOR_U_ENCODED_SIZE..],
),
Expand All @@ -409,7 +402,7 @@ pub(crate) fn decrypt(
// ŝ ← ByteDecode₁₂(dkₚₖₑ)
let mut secret_as_ntt = [KyberPolynomialRingElement::ZERO; RANK];
for (i, secret_bytes) in secret_key.chunks_exact(BYTES_PER_RING_ELEMENT).enumerate() {
secret_as_ntt[i] = deserialize_little_endian(12, secret_bytes);
secret_as_ntt[i] = byte_decode(12, secret_bytes);
}

// w ← v - NTT-¹(ŝᵀ ◦ NTT(u))
Expand All @@ -422,5 +415,5 @@ pub(crate) fn decrypt(
// m ← ByteEncode₁(Compress₁(w))
// return m
// FIXME: remove conversion
serialize_little_endian(compress(message, 1), 1).as_array()
byte_encode(1, compress(message, 1)).as_array()
}
Loading

0 comments on commit 97fe462

Please sign in to comment.