Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kyber spec. #15

Merged
merged 22 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ documentation = "https://docs.rs/libcrux/"
description = "Formally Verified Cryptography"
readme = "Readme.md"
repository = "https://github.com/cryspen/libcrux"
exclude = ["/tests"]
exclude = ["/tests", "specs/kyber"]

[lib]
crate-type = ["staticlib", "cdylib", "lib"]
Expand Down
7 changes: 7 additions & 0 deletions specs/kyber/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "hacspec-kyber"
version = "0.1.0"
edition = "2021"

[dependencies]
getrandom = "0.2.9"
xvzcf marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions specs/kyber/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2023-06-02"
xvzcf marked this conversation as resolved.
Show resolved Hide resolved
42 changes: 42 additions & 0 deletions specs/kyber/src/bit_vector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::parameters;
use crate::ring::RingElement;

pub(crate) fn bytes_to_bits(bytes : &[u8]) -> Vec<u8> {
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
let mut out = Vec::with_capacity(bytes.len() * usize::try_from(u8::BITS).unwrap());

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

pub(crate) fn ring_element_to_bits(r : &RingElement) -> Vec<u8> {
xvzcf marked this conversation as resolved.
Show resolved Hide resolved
let mut out = Vec::with_capacity(r.coefficients.len() * parameters::L);

for coeff in &r.coefficients {
for j in 0..parameters::L {
out.push(((coeff.value >> j) & 1).try_into().unwrap());
}
}
out
}

pub(crate) fn bits_to_u16(bit_vector : &[u8]) -> u16 {
assert_eq!(bit_vector.len(), parameters::L);
let mut result : u16 = 0;
for i in 0..parameters::L {
result += u16::from(bit_vector[i]) * 2u16.pow(i.try_into().unwrap());
}
result
}

pub(crate) fn bits_to_u8(bit_vector : &[u8]) -> u8 {
assert_eq!(bit_vector.len(), usize::try_from(u8::BITS).unwrap());
let mut result : u8 = 0;
for i in 0..8 {
result += bit_vector[i] * 2u8.pow(i.try_into().unwrap());
}
result
}
56 changes: 56 additions & 0 deletions specs/kyber/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use crate::ring::{FieldElement, RingElement};
use crate::bit_vector::*;
use crate::parameters;

pub(crate) fn encode(r : &RingElement) -> [u8; parameters::L * 32] {
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
let bit_vector = ring_element_to_bits(&r);
assert_eq!(bit_vector.len(), r.coefficients.len() * parameters::L);

let mut out = [0u8; parameters::L * 32];

for i in 0..out.len() {
out[i] = bits_to_u8(&bit_vector[i*8..(i+1)*8]);
}
out
}
//generate_array<const LEN: usize>(&[u8]) -> Result<[u8; LEN], Error>

pub(crate) fn decode(bytes : [u8; parameters::L * 32]) -> RingElement {
let bit_vector = bytes_to_bits(&bytes[..]);
assert_eq!(bit_vector.len(), parameters::L * 32 * (u8::BITS as usize));

let mut out : RingElement = RingElement::ZERO;

for i in 0..out.coefficients.len() {
let coefficient = bits_to_u16(&bit_vector[i*parameters::L..(i + 1)*parameters::L]);
out.coefficients[i] = FieldElement::from_u16(coefficient);
}
out
}

#[cfg(test)]
mod tests {
use super::*;

extern crate getrandom;
use getrandom::getrandom;

#[test]
fn apply_encode_then_decode() {
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
let mut randombytes : [u8; 2] = [0; 2];

let mut r = RingElement::ZERO;
for i in 0..r.coefficients.len() {
getrandom(&mut randombytes[..]).unwrap();
let random_element = ((randombytes[1] as u16) << 8) | randombytes[0] as u16;
r.coefficients[i] = FieldElement::from_u16(random_element);
}

let r_actual = decode(encode(&r));

for i in 0..r.coefficients.len() {
assert_eq!(r.coefficients[i].value, r_actual.coefficients[i].value);
}
}

}
5 changes: 5 additions & 0 deletions specs/kyber/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod parameters;
mod ring;
mod bit_vector;
franziskuskiefer marked this conversation as resolved.
Show resolved Hide resolved
mod sampling;
mod encoding;
5 changes: 5 additions & 0 deletions specs/kyber/src/parameters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub(crate) const Q : u16 = 3329;
pub(crate) const N : usize = 256;
pub(crate) const REJECTION_SAMPLING_BYTES : usize = N * 3 * 3;
pub(crate) const L : usize = 12;
pub(crate) const ETA : usize = 2;
30 changes: 30 additions & 0 deletions specs/kyber/src/ring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use crate::parameters;

#[derive(Clone)]
pub struct FieldElement {
pub value : u16
}

impl FieldElement {
const MODULUS : u16 = parameters::Q;
pub const ZERO: Self = Self { value: 0 };

pub fn from_u8(inp : u8) -> Self {
Self { value : u16::from(inp) % Self::MODULUS }
}

pub fn from_u16(inp : u16) -> Self {
Self { value : inp % Self::MODULUS }
}
}

pub struct RingElement {
pub coefficients : [FieldElement; parameters::N]
}

impl RingElement {
// We use `256` instead of `parameters::N` due to
// https://github.com/hacspec/hacspec-v2/issues/27
pub const ZERO : Self = Self { coefficients : [FieldElement::ZERO; 256] };
}

77 changes: 77 additions & 0 deletions specs/kyber/src/sampling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::ring::{FieldElement, RingElement};
use crate::bit_vector::bytes_to_bits;
use crate::parameters;

pub(crate) fn parse(random_bytes : [u8; parameters::REJECTION_SAMPLING_BYTES]) -> Result<RingElement, &'static str> {
let mut j : usize = 0;
let mut out : RingElement = RingElement::ZERO;

for i in (0..random_bytes.len()).step_by(3) {
let byte = u16::from(random_bytes[i]);
let byte1 = u16::from(random_bytes[i + 1]);
let byte2 = u16::from(random_bytes[i + 2]);

let d1 = byte + (256 * (byte1 % 16));
let d2 = (byte1 / 16) + (16 * byte2); // N.B.: Integer division is flooring in Rust.

// N.B.: In an efficient implementation, we'd break when j == parameters::N
if d1 < parameters::Q && j < parameters::N {
out.coefficients[j] = FieldElement::from_u16(d1);
j += 1
}
if d2 < parameters::Q && j < parameters::N {
out.coefficients[j] = FieldElement::from_u16(d2);
j += 1;
}
}

if j == out.coefficients.len() {
Ok(out)
} else {
Err("Could not sample RingElement.")
}
}

pub(crate) fn cbd(random_bytes : [u8; parameters::ETA * 64]) -> RingElement {
let bit_vector = bytes_to_bits(&random_bytes[..]);
assert_eq!(bit_vector.len(), parameters::ETA * 64 * usize::try_from(u8::BITS).unwrap());

let mut out : RingElement = RingElement::ZERO;

for i in 0..out.coefficients.len() {
let mut a : u8 = 0;
for j in 0..parameters::ETA {
a += bit_vector[2 * i * parameters::ETA + j];
}

let mut b : u8 = 0;
for j in 0..parameters::ETA {
b += bit_vector[2 * i * parameters::ETA + parameters::ETA + j];
}

out.coefficients[i] = FieldElement::from_u8(a - b);
}

out
}

#[cfg(test)]
mod tests {
use super::*;
use parameters::REJECTION_SAMPLING_BYTES;

#[test]
fn parse_all_zeros() {
let sampled_ring_element = parse([0; REJECTION_SAMPLING_BYTES]).unwrap();

for i in 0..sampled_ring_element.coefficients.len() {
assert_eq!(sampled_ring_element.coefficients[i].value, 0);
}
}

#[test]
#[should_panic]
fn parse_all_max() {
let _ = parse([u8::MAX; REJECTION_SAMPLING_BYTES]).unwrap();
}
}