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 C extraction #153

Merged
merged 36 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ab48a57
rewrites for extraction
franziskuskiefer Dec 7, 2023
b1afbf5
update kyber-crate.sh
franziskuskiefer Dec 7, 2023
ee6e7ca
More tweaks to get C extraction
msprotz Dec 8, 2023
41d224c
C code
msprotz Dec 8, 2023
b9f6b1f
big improvement
msprotz Dec 8, 2023
b2ee434
Add files via upload
franziskuskiefer Dec 9, 2023
4ec094a
Added file with remarks and some glue definitions.
xvzcf Dec 9, 2023
79fe947
Some more progress reducing the number of errors.
xvzcf Dec 10, 2023
65f56b6
thoughts
msprotz Dec 10, 2023
7f1f778
Merge branch 'dev' into franziskus/c-extraction
franziskuskiefer Dec 11, 2023
ddcdba9
Update header
msprotz Dec 11, 2023
c409b4e
update kyber-crate to dev (no errors in sampling)
franziskuskiefer Dec 11, 2023
1095965
Merge branch 'franziskus/c-extraction' of github.com:cryspen/libcrux …
franziskuskiefer Dec 11, 2023
26f3963
Refresh C code after removing unwrap and fixing something upstream in…
msprotz Dec 11, 2023
9e2f49e
Refresh with latest header
msprotz Dec 11, 2023
2d30bb9
Latest code
msprotz Dec 11, 2023
7794543
Refresh
msprotz Dec 11, 2023
1eb5bba
debuging
franziskuskiefer Dec 12, 2023
4a864c1
Merge branch 'franziskus/c-extraction' of github.com:cryspen/libcrux …
franziskuskiefer Dec 12, 2023
e55acf8
refresh
msprotz Dec 12, 2023
e54a415
With three-dimensional array fixes
msprotz Dec 12, 2023
4a753da
Merge branch 'franziskus/c-extraction' of github.com:cryspen/libcrux …
msprotz Dec 12, 2023
69ccd18
Regenerate via charon
msprotz Dec 12, 2023
033cbe9
towards no manual changes
franziskuskiefer Dec 12, 2023
b45299b
automate c extraction
franziskuskiefer Dec 13, 2023
05915a2
making it all work
franziskuskiefer Dec 13, 2023
1788b7f
fixup f*
franziskuskiefer Dec 13, 2023
bf140b6
copy internal headers
franziskuskiefer Dec 19, 2023
a9ba0c2
Python script to move Kyber C extraction into NSS.
xvzcf Dec 19, 2023
e516a12
remove unused variable in script.
xvzcf Dec 19, 2023
d7d530b
Fix bug in script.
xvzcf Dec 20, 2023
3376971
Another script update.
xvzcf Dec 20, 2023
a00a4e6
Fixed bug in script.
xvzcf Dec 20, 2023
26835c6
Pass in nss root and kyber c extraction root to script through argume…
xvzcf Dec 20, 2023
1a0d568
Merge branch 'dev' into franziskus/c-extraction2
franziskuskiefer Jan 2, 2024
c7f95db
Review comments.
xvzcf Jan 2, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ fuzz/corpus
fuzz/artifacts
proofs/fstar/extraction/.cache
__pycache__
kyber-crate/
133 changes: 133 additions & 0 deletions add-c-kyber-to-nss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#! /usr/bin/env python3

import os
import subprocess
import re
import shutil
import argparse


def shell(command, expect=0, cwd=None, env={}):
subprocess_stdout = subprocess.DEVNULL

os_env = os.environ
os_env.update(env)

result = subprocess.run(
command, cwd=cwd, env=os_env, capture_output=True, text=True
)

if result.returncode != expect:
raise Exception("Error {}. Expected {}.".format(result, expect))

return result.stdout


def add_libcrux_kyber_h(c_extraction_root, freebl_verified_root):
path_to_header = os.path.join(c_extraction_root, "libcrux_kyber.h")
destination = os.path.join(freebl_verified_root, "internal", "Libcrux_Kyber_768.h")

shell(["clang-format", "-i", "-style=Google", destination])

with open(destination, "r") as f:
original = f.read()
replaced = re.sub("extern void libcrux_digest_sha3_512.*\n", "", original)
replaced = re.sub("extern void libcrux_digest_sha3_256.*\n", "", replaced)
with open(destination, "w") as f:
f.write(replaced)

shell(["clang-format", "-i", "-style=Mozilla", destination])


def add_libcrux_kyber_c(c_extraction_root, freebl_verified_root):
path_to_c_file = os.path.join(c_extraction_root, "libcrux_kyber.c")
destination = os.path.join(freebl_verified_root, "Libcrux_Kyber_768.c")
shutil.copyfile(path_to_c_file, destination)

shell(["clang-format", "-i", "-style=Google", destination])

sed_cmd = shutil.which("gsed")
if sed_cmd is None:
sed_cmd = shutil.which("sed")

ctags = shell(["ctags", "--fields=+ne", "-o", "-", destination])
sed_input = ""
for line in ctags.splitlines():
if (
"libcrux_kyber_serialize_compress_then_serialize_11___320size_t" in line
or "libcrux_kyber_serialize_compress_then_serialize_5___128size_t" in line
):
line_start = re.findall(r"line:(\d+)", line)[0]
line_end = re.findall(r"end:(\d+)", line)[0]
sed_input = "{},{}d;{}".format(line_start, line_end, sed_input)

shell([sed_cmd, "-i", sed_input, destination])

with open(destination, "r") as f:
original = f.read()
replaced = re.sub(
'#include "libcrux_kyber.h"',
'#include "internal/Libcrux_Kyber_768.h"',
original,
)
replaced = re.sub(
'#include "libcrux_hacl_glue.h"',
'#include "Libcrux_Kyber_Hash_Functions.h"',
replaced,
)
replaced = re.sub("uu____0 = !false", "uu____0 = false", replaced)
with open(destination, "w") as f:
f.write(replaced)

shell(["clang-format", "-i", "-style=Mozilla", destination])


def add_internal_core_h(c_extraction_root, freebl_verified_root):
src_file = os.path.join(c_extraction_root, "internal", "core.h")
destination = os.path.join(freebl_verified_root, "internal", "core.h")

shutil.copyfile(src_file, destination)
shell(["clang-format", "-i", "-style=Mozilla", destination])


def add_Eurydice_h(c_extraction_root, freebl_verified_root):
src_file = os.path.join(c_extraction_root, "Eurydice.h")
destination = os.path.join(freebl_verified_root, "eurydice", "Eurydice.h")

shutil.copyfile(src_file, destination)
shell(["clang-format", "-i", "-style=Mozilla", destination])


def add_eurydice_glue_h(c_extraction_root, freebl_verified_root):
src_file = os.path.join(c_extraction_root, "eurydice_glue.h")
destination = os.path.join(freebl_verified_root, "eurydice", "eurydice_glue.h")

shutil.copyfile(src_file, destination)
shell(["clang-format", "-i", "-style=Mozilla", destination])


parser = argparse.ArgumentParser()
parser.add_argument(
"--nss-root",
required=True,
help="Absolute or relative path to the root directory containing the NSS source code.",
type=os.path.abspath,
)
parser.add_argument(
"--kyber-c-root",
required=True,
help="Absolute or relative path to the root directory containing the extracted Kyber C code.",
type=os.path.abspath,
)
args = parser.parse_args()

nss_root = args.nss_root
freebl_verified_root = os.path.join(nss_root, "lib", "freebl", "verified")

c_extraction_root = args.kyber_c_root

add_libcrux_kyber_h(c_extraction_root, freebl_verified_root)
add_libcrux_kyber_c(c_extraction_root, freebl_verified_root)
add_internal_core_h(c_extraction_root, freebl_verified_root)
add_Eurydice_h(c_extraction_root, freebl_verified_root)
add_eurydice_glue_h(c_extraction_root, freebl_verified_root)
200 changes: 200 additions & 0 deletions kyber-crate-tests/kyber.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use libcrux::{
digest::shake256,
kem::{self, Algorithm, Ct, PrivateKey},
};

use rand::{CryptoRng, Rng};

use rand::rngs::OsRng;

const SHARED_SECRET_SIZE: usize = 32;

macro_rules! impl_consistency {
($name:ident, $alg:expr) => {
#[test]
fn $name() {
let mut rng = OsRng;

if let Ok((secret_key, public_key)) = kem::key_gen($alg, &mut rng) {
if let Ok((shared_secret, ciphertext)) = kem::encapsulate(&public_key, &mut rng) {
let shared_secret_decapsulated =
kem::decapsulate(&ciphertext, &secret_key).unwrap();
assert_eq!(shared_secret.encode(), shared_secret_decapsulated.encode());
}
}

// If the randomness was not enough for the rejection sampling step
// in key-generation and encapsulation, simply return without
// failing.
}
};
}

fn modify_ciphertext(alg: Algorithm, rng: &mut (impl CryptoRng + Rng), ciphertext: Ct) -> Ct {
let mut raw_ciphertext = ciphertext.encode();

let mut random_u32: usize = rng.next_u32().try_into().unwrap();

let mut random_byte: u8 = (random_u32 & 0xFF) as u8;
if random_byte == 0 {
random_byte += 1;
}
random_u32 >>= 8;

let position = random_u32 % raw_ciphertext.len();
raw_ciphertext[position] ^= random_byte;

Ct::decode(alg, &raw_ciphertext).unwrap()
}

macro_rules! impl_modified_ciphertext {
($name:ident, $alg:expr) => {
#[test]
fn $name() {
let mut rng = OsRng;

if let Ok((secret_key, public_key)) = kem::key_gen($alg, &mut rng) {
if let Ok((shared_secret, ciphertext)) = kem::encapsulate(&public_key, &mut rng) {
let ciphertext = modify_ciphertext($alg, &mut rng, ciphertext);
let shared_secret_decapsulated =
kem::decapsulate(&ciphertext, &secret_key).unwrap();

assert_ne!(shared_secret.encode(), shared_secret_decapsulated.encode());
}
}
// if the randomness was not enough for the rejection sampling step
// in key-generation and encapsulation, simply return without
// failing.
}
};
}

fn modify_secret_key(
alg: Algorithm,
rng: &mut (impl CryptoRng + Rng),
secret_key: PrivateKey,
modify_implicit_rejection_value: bool,
) -> PrivateKey {
let mut raw_secret_key = secret_key.encode();

let mut random_u32: usize = rng.next_u32().try_into().unwrap();

let mut random_byte: u8 = (random_u32 & 0xFF) as u8;
if random_byte == 0 {
random_byte += 1;
}
random_u32 >>= 8;

let position = if modify_implicit_rejection_value {
(raw_secret_key.len() - SHARED_SECRET_SIZE) + (random_u32 % SHARED_SECRET_SIZE)
} else {
random_u32 % (raw_secret_key.len() - SHARED_SECRET_SIZE)
};

raw_secret_key[position] ^= random_byte;

PrivateKey::decode(alg, &raw_secret_key).unwrap()
}

fn compute_implicit_rejection_shared_secret(
ciphertext: Ct,
secret_key: PrivateKey,
) -> [u8; SHARED_SECRET_SIZE] {
let raw_secret_key = secret_key.encode();

let mut to_hash = raw_secret_key[raw_secret_key.len() - SHARED_SECRET_SIZE..].to_vec();
to_hash.extend_from_slice(&ciphertext.encode());

shake256(&to_hash)
}

macro_rules! impl_modified_secret_key {
($name:ident, $alg:expr) => {
#[test]
fn $name() {
let mut rng = OsRng;

if let Ok((secret_key, public_key)) = kem::key_gen($alg, &mut rng) {
if let Ok((shared_secret, ciphertext)) = kem::encapsulate(&public_key, &mut rng) {
let secret_key = modify_secret_key($alg, &mut rng, secret_key, false);
let shared_secret_decapsulated =
kem::decapsulate(&ciphertext, &secret_key).unwrap();
assert_ne!(shared_secret.encode(), shared_secret_decapsulated.encode());

let secret_key = modify_secret_key($alg, &mut rng, secret_key, true);
let shared_secret_decapsulated =
kem::decapsulate(&ciphertext, &secret_key).unwrap();

assert_eq!(
shared_secret_decapsulated.encode(),
compute_implicit_rejection_shared_secret(ciphertext, secret_key)
);
}
}

// if the randomness was not enough for the rejection sampling step
// in key-generation and encapsulation, simply return without
// failing.
}
};
}

macro_rules! impl_modified_ciphertext_and_implicit_rejection_value {
($name:ident, $alg:expr) => {
#[test]
fn $name() {
let mut rng = OsRng;

if let Ok((secret_key, public_key)) = kem::key_gen($alg, &mut rng) {
if let Ok((_, ciphertext)) = kem::encapsulate(&public_key, &mut rng) {
let ciphertext = modify_ciphertext($alg, &mut rng, ciphertext);
let shared_secret_decapsulated =
kem::decapsulate(&ciphertext, &secret_key).unwrap();

let secret_key = modify_secret_key($alg, &mut rng, secret_key, true);
let shared_secret_decapsulated_1 =
kem::decapsulate(&ciphertext, &secret_key).unwrap();

assert_ne!(
shared_secret_decapsulated.encode(),
shared_secret_decapsulated_1.encode()
);

assert_eq!(
shared_secret_decapsulated_1.encode(),
compute_implicit_rejection_shared_secret(ciphertext, secret_key)
);
}
}

// if the randomness was not enough for the rejection sampling step
// in key-generation and encapsulation, simply return without
// failing.
}
};
}

impl_consistency!(consistency_512, Algorithm::Kyber512);
impl_consistency!(consistency_768, Algorithm::Kyber768);
impl_consistency!(consistency_1024, Algorithm::Kyber1024);

impl_modified_ciphertext!(modified_ciphertext_512, Algorithm::Kyber512);
impl_modified_ciphertext!(modified_ciphertext_768, Algorithm::Kyber768);
impl_modified_ciphertext!(modified_ciphertext_1024, Algorithm::Kyber1024);

impl_modified_secret_key!(modified_secret_key_512, Algorithm::Kyber512);
impl_modified_secret_key!(modified_secret_key_768, Algorithm::Kyber768);
impl_modified_secret_key!(modified_secret_key_1024, Algorithm::Kyber1024);

impl_modified_ciphertext_and_implicit_rejection_value!(
modified_ciphertext_and_implicit_rejection_value_512,
Algorithm::Kyber512
);
impl_modified_ciphertext_and_implicit_rejection_value!(
modified_ciphertext_and_implicit_rejection_value_768,
Algorithm::Kyber768
);
impl_modified_ciphertext_and_implicit_rejection_value!(
modified_ciphertext_and_implicit_rejection_value_1024,
Algorithm::Kyber1024
);
1 change: 1 addition & 0 deletions kyber-crate-tests/kyber_kats/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In order to regenerate the JSON KAT files for all parameter sets, simply run `./generate_kats.py`.
Loading