Skip to content

Commit

Permalink
fuzz up
Browse files Browse the repository at this point in the history
  • Loading branch information
eschorn1 committed Feb 16, 2024
1 parent de86193 commit 4e850ef
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ rand = "0.8.5"
regex = "1.10.2"
hex = "0.4.3"
rand_chacha = "0.3.1"
criterion = "0.4.0" #"0.5.1"
criterion = "0.4.0" # Needed to keep MSRV back at 1.72, newer: "0.5.1"
flate2 = "1.0.27"


Expand Down
5 changes: 5 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ cargo-fuzz = true

[dependencies]
libfuzzer-sys = "0.4"
rand_core = "0.6.4"

[dependencies.fips203]
path = ".."

[dev-dependencies]
rand = "0.8.5"


# Prevent this from interfering with workspaces
[workspace]
members = ["."]
Expand Down
2 changes: 1 addition & 1 deletion fuzz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ Fuzzing
https://rust-fuzz.github.io/book/cargo-fuzz.html
cd fuzz
rustup default nightly
head -c 3200 </dev/urandom > corpus/seed1
head -c 3328 </dev/urandom > corpus/fuzz_all/seed1
cargo fuzz run fuzz_all -j 4
~~~
86 changes: 76 additions & 10 deletions fuzz/fuzz_targets/fuzz_all.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,84 @@
#![no_main]
// rustup default nightly
// head -c 3200 </dev/urandom > seed1
// cargo fuzz run fuzz_all -j 4

use fips203::ml_kem_512;
use fips203::traits::{Decaps, Encaps, SerDes};
use fips203::traits::{Decaps, Encaps, KeyGen, SerDes};
use libfuzzer_sys::fuzz_target;
use rand_core::{CryptoRng, RngCore};

fuzz_target!(|data: [u8; 1632+800+768]| { // dk_len + ek+len + ct_len = 3200
const RND_SIZE: usize = 32;


struct TestRng {
data: Vec<Vec<u8>>,
}

impl RngCore for TestRng {
fn next_u32(&mut self) -> u32 { unimplemented!() }

fn next_u64(&mut self) -> u64 { unimplemented!() }

fn fill_bytes(&mut self, out: &mut [u8]) {
let x = self.data.pop().expect("TestRng problem");
out.copy_from_slice(&x)
}

fn try_fill_bytes(&mut self, out: &mut [u8]) -> Result<(), rand_core::Error> {
self.fill_bytes(out);
Ok(()) // panic on probs is OK
}
}

impl CryptoRng for TestRng {}

impl TestRng {
fn new() -> Self { TestRng { data: Vec::new() } }

fn push(&mut self, new_data: &[u8]) {
let x = new_data.to_vec();
self.data.push(x);
}
}

fuzz_target!(|data: [u8; 3328]| {

let mut rng = TestRng::new();
let mut start = 0;
rng.push(&data[start..start+RND_SIZE]);
start += RND_SIZE;
rng.push(&data[start..start+RND_SIZE]);
start += RND_SIZE;
let keypair = ml_kem_512::KG::try_keygen_with_rng_vt(&mut rng); // consumes 2 rng values
let (ek1, dk1) = keypair.unwrap(); // only rng can fail, which it won't

let ek2_bytes = &data[start..start+ml_kem_512::EK_LEN];
start += ml_kem_512::EK_LEN;
let ek2 = ml_kem_512::EncapsKey::try_from_bytes(ek2_bytes.try_into().unwrap());

rng.push(&data[start..start+RND_SIZE]);
start += RND_SIZE;
rng.push(&data[start..start+RND_SIZE]);
start += RND_SIZE;

if ek2.is_ok() {
let _res = ek2.unwrap().try_encaps_with_rng_vt(&mut rng);
}
let _res = ek1.try_encaps_with_rng_vt(&mut rng);


let dk2_bytes = &data[start..start+ml_kem_512::DK_LEN];
start += ml_kem_512::DK_LEN;
let dk2 = ml_kem_512::DecapsKey::try_from_bytes(dk2_bytes.try_into().unwrap());

let ct_bytes = &data[start..start+ml_kem_512::CT_LEN];
start += ml_kem_512::CT_LEN;
let ct = ml_kem_512::CipherText::try_from_bytes(ct_bytes.try_into().unwrap()).unwrap(); // always good

if dk2.is_ok() {
let _res = dk2.unwrap().try_decaps_vt(&ct);
}
let _res = dk1.try_decaps_vt(&ct);

assert_eq!(start, data.len()); // this doesn't appear to trigger (even when wrong)

let ek = ml_kem_512::EncapsKey::try_from_bytes(data[0..800].try_into().unwrap()).unwrap();
let dk = ml_kem_512::DecapsKey::try_from_bytes(data[800..800+1632].try_into().unwrap()).unwrap();
let ct = ml_kem_512::CipherText::try_from_bytes(data[800+1632..800+1632+768].try_into().unwrap()).unwrap();

let _result = ek.try_encaps_vt();
let _result = dk.try_decaps_vt(&ct);
});
22 changes: 11 additions & 11 deletions src/byte_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,31 @@ use crate::types::Z;
// /// Output: bit array b ∈ {0,1}^{8·ℓ}


/// Algorithm 4 `ByteEncode<d>(F)` on page 19.
/// Algorithm 4 `ByteEncode_d(F)` on page 19.
/// Encodes an array of `d`-bit integers into a byte array, for `1 ≤ d ≤ 12`.
/// This is an optimized variant (which does not use individual bit functions)
/// This is an optimized variant (which does not use individual bit functions).
///
/// Input: integer array `F ∈ Z^256_m`, where `m = 2^d if d < 12` and `m = q if d = 12` <br>
/// Output: byte array B ∈ B^{32d}
/// Input: integer array `F ∈ Z^{256}_m`, where `m = 2^d if d < 12` and `m = q if d = 12` <br>
/// Output: byte array B ∈ B^{32·d}
pub(crate) fn byte_encode(
d: u32, integers_f: &[Z; 256], bytes_b: &mut [u8],
) -> Result<(), &'static str> {
//
// Our "working" register, from which to drop bytes out of
let mut temp = 0u64;
let mut temp = 0u32;
// Bit index of current temp contents, and byte index of current output
let mut bit_index = 0;
let mut byte_index = 0;
// Choose m per spec
let m = if d < 12 { 2u64.pow(d) } else { u64::from(Q) };
let m = if d < 12 { 2u32.pow(d) } else { Q };

// Work through each of the input integers
for coeff in integers_f {
//
// Get coeff as u64, check magnitude, and clean off top bits
let coeff = u64::from(coeff.get_u16());
let coeff = coeff.get_u32();
ensure!(coeff <= m, "Alg4: Coeff out of range");
let coeff = coeff & (2u64.pow(d) - 1);
let coeff = coeff & (2u32.pow(d) - 1);

// Drop coeff into the upper unused bit positions of coeff; adjust bit index
temp |= coeff << bit_index;
Expand All @@ -67,11 +67,11 @@ pub(crate) fn byte_encode(
}


/// Algorithm 5 `ByteDecode<d>(B)` on page 19.
/// Algorithm 5 `ByteDecode_d(B)` on page 19.
/// Decodes a byte array into an array of d-bit integers, for 1 ≤ d ≤ 12.
/// This is an optimized variant (which does not use individual bit functions)
/// This is an optimized variant (which does not use individual bit functions).
///
/// Input: byte array B ∈ B^{32d} <br>
/// Input: byte array B ∈ B^{32·d} <br>
/// Output: integer array `F ∈ Z^256_m`, where `m = 2^d if d < 12` and `m = q if d = 12`
pub(crate) fn byte_decode(
d: u32, bytes_b: &[u8], integers_f: &mut [Z; 256],
Expand Down
4 changes: 2 additions & 2 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,11 @@ pub(crate) fn j(bytes: &[&[u8]]) -> [u8; 32] {
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn compress(d: u32, inout: &mut [Z]) {
for x_ref in &mut *inout {
// Barrett constants should be sorted out at compile time
// Barrett constants should be resolved at compile time
let q64 = u64::from(Q);
let k = 32;
let m = 2u64.pow(k) / q64;
// Barrett division/reduction, quotient could be too small by one
// Barrett division, quotient could be too small by one
let top = u64::from(x_ref.get_u32()) << d;
let quot = (top * m) >> k;
let bump = (top - quot * q64) > u64::from(Q);
Expand Down
20 changes: 11 additions & 9 deletions src/k_pke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::types::Z;
/// Algorithm 12 `K-PKE.KeyGen()` on page 26.
/// Generates an encryption key and a corresponding decryption key.
///
/// Output: encryption key `ekPKE ∈ B^{384*k+32}` <br>
/// Output: decryption key `dkPKE ∈ B^{384*k}`
/// Output: encryption key `ekPKE ∈ B^{384·k+32}` <br>
/// Output: decryption key `dkPKE ∈ B^{384·k}`
#[allow(clippy::similar_names, clippy::module_name_repetitions)]
pub fn k_pke_key_gen<const K: usize, const ETA1_64: usize>(
rng: &mut impl CryptoRngCore, eta1: u32, ek_pke: &mut [u8], dk_pke: &mut [u8],
Expand Down Expand Up @@ -115,15 +115,16 @@ pub fn k_pke_key_gen<const K: usize, const ETA1_64: usize>(

/// Algorithm 13 `K-PKE.Encrypt(ekPKE , m, r)` on page 27.
/// Uses the encryption key to encrypt a plaintext message using the randomness r.
///
/// Input: encryption key `ekPKE` ∈ `B^{384·k+32}` <br>
/// Input: message `m` ∈ `B^{32}` <br>
/// Input: encryption randomness `r` ∈ `B^{32}` <br>
/// Output: ciphertext `c` ∈ `B^{32(du·k+dv)}` <br>
#[allow(clippy::many_single_char_names, clippy::too_many_arguments)]
pub(crate) fn k_pke_encrypt<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
du: u32, dv: u32, eta1: u32, eta2: u32, ek: &[u8], m: &[u8], randomness: &[u8; 32],
ct: &mut [u8],
) -> Result<(), &'static str> {
// Input: encryption key ekPKE ∈ B^{384k+32}
// Input: message m ∈ B^{32}
// Input: encryption randomness r ∈ B^{32}
// Output: ciphertext c ∈ B^{32(du k+dv )}
ensure!(ek.len() == 384 * K + 32, "Alg13: ek len not 384 * K + 32");
ensure!(m.len() == 32, "Alg13: m len not 32");
ensure!(eta1 as usize * 64 == ETA1_64, "Alg13: const probs 1");
Expand Down Expand Up @@ -233,12 +234,13 @@ pub(crate) fn k_pke_encrypt<const K: usize, const ETA1_64: usize, const ETA2_64:

/// Algorithm 14 `K-PKE.Decrypt(dkPKE, c)` on page 28.
/// Uses the decryption key to decrypt a ciphertext.
///
/// Input: decryption key `dk_{PKE}` ∈ `B^{384·k}`
/// Input: ciphertext `c` ∈ `B^{32(du·k+dv)}`
/// Output: message `m` ∈ `B^{32}`
pub(crate) fn k_pke_decrypt<const K: usize>(
du: u32, dv: u32, dk: &[u8], ct: &[u8],
) -> Result<[u8; 32], &'static str> {
// Input: decryption key dk_{PKE} ∈ B^{384*k}
// Input: ciphertext c ∈ B^{32(du*k+dv)}
// Output: message m ∈ B^{32}
ensure!(dk.len() == 384 * K, "Alg14: dk len not 384 * K");
ensure!(ct.len() == 32 * (du as usize * K + dv as usize), "Alg14: 32 * (DU * K + DV)");

Expand Down
21 changes: 12 additions & 9 deletions src/ml_kem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ use crate::types::Z;

/// Algorithm 15 `ML-KEM.KeyGen()` on page 29.
/// Generates an encapsulation key and a corresponding decapsulation key.
///
/// Output: Encapsulation key `ek` ∈ `B^{384·k+32}` <br>
/// Output: Decapsulation key `dk` ∈ `B^{768·k+96}`
pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(
rng: &mut impl CryptoRngCore, eta1: u32, ek: &mut [u8], dk: &mut [u8],
) -> Result<(), &'static str> {
// Output: Encapsulation key ek ∈ B^{384k+32}
// Output: Decapsulation key dk ∈ B^{768k+96}
ensure!(ek.len() == 384 * K + 32, "Alg15: ek len not 384 * K + 32");
ensure!(dk.len() == 768 * K + 96, "Alg15: dk len not 768 * K + 96");

Expand Down Expand Up @@ -40,12 +41,13 @@ pub(crate) fn ml_kem_key_gen<const K: usize, const ETA1_64: usize>(

/// Algorithm 16 `ML-KEM.Encaps(ek)` on page 30.
/// Uses the encapsulation key to generate a shared key and an associated ciphertext.
///
/// Validated input: encapsulation key `ek` ∈ `B^{384·k+32}` <br>
/// Output: shared key `K` ∈ `B^{32}` <br>
/// Output: ciphertext `c` ∈ `B^{32(du·k+dv)}` <br>
pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64: usize>(
rng: &mut impl CryptoRngCore, du: u32, dv: u32, eta1: u32, eta2: u32, ek: &[u8], ct: &mut [u8],
) -> Result<SharedSecretKey, &'static str> {
// Validated input: encapsulation key ek ∈ B^{384k+32}
// Output: shared key K ∈ B^{32}
// Output: ciphertext c ∈ B^{32(du k+dv)}
ensure!(ek.len() == 384 * K + 32, "Alg16: ek len not 384 * K + 32"); // type check: array of length 384k + 32

// modulus check: perform the computation ek ← ByteEncode12 (ByteDecode12(ek_tidle)
Expand All @@ -60,7 +62,7 @@ pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64:

// 1: m ←− B32 ▷ m is 32 random bytes (see Section 3.3)
let mut m = [0u8; 32];
rng.fill_bytes(&mut m); //random::<[u8; 32]>();
rng.fill_bytes(&mut m);

// 2: (K, r) ← G(m∥H(ek)) ▷ derive shared secret key K and randomness r
let h_ek = h(ek);
Expand All @@ -76,6 +78,10 @@ pub(crate) fn ml_kem_encaps<const K: usize, const ETA1_64: usize, const ETA2_64:

/// Algorithm 17 `ML-KEM.Decaps(c, dk)` on page 32.
/// Uses the decapsulation key to produce a shared key from a ciphertext.
///
/// Validated input: ciphertext `c` ∈ `B^{32(du·k+dv)}` <br>
/// Validated input: decapsulation key `dk` ∈ `B^{768·k+96}` <br>
/// Output: shared key `K` ∈ `B^{32}`
#[allow(clippy::similar_names)]
pub(crate) fn ml_kem_decaps<
const K: usize,
Expand All @@ -86,9 +92,6 @@ pub(crate) fn ml_kem_decaps<
>(
du: u32, dv: u32, eta1: u32, eta2: u32, dk: &[u8], ct: &[u8],
) -> Result<SharedSecretKey, &'static str> {
// Validated input: ciphertext c ∈ B^{32(du k+dv )}
// Validated input: decapsulation key dk ∈ B^{768k+96}
// Output: shared key K ∈ B^{32}
// These length checks are a bit redundant...but present for completeness and paranoia
ensure!(ct.len() == 32 * (du as usize * K + dv as usize), "Alg17: ct len not 32 * ...");
// Ciphertext type check
Expand Down
29 changes: 17 additions & 12 deletions src/ntt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use crate::types::Z;

/// Algorithm 8 `NTT(f)` on page 22.
/// Computes the NTT representation `f_hat` of the given polynomial f ∈ `R_q`.
///
/// Input: array `f` ∈ `Z^{256}_q` ▷ the coefficients of the input polynomial <br>
/// Output: array `f_hat` ∈ `Z^{256}_q` ▷ the coefficients of the NTT of the input polynomial
#[must_use]
#[allow(clippy::module_name_repetitions)]
pub fn ntt(array_f: &[Z; 256]) -> [Z; 256] {
// Input: array f ∈ Z^{256}_q ▷ the coeffcients of the input polynomial
// Output: array f_hat ∈ Z^{256}_q ▷ the coeffcients of the NTT of the input polynomial

// 1: f_hat ← f ▷ will compute NTT in-place on a copy of input array
let mut f_hat = [Z::default(); 256];
Expand Down Expand Up @@ -56,12 +57,13 @@ pub fn ntt(array_f: &[Z; 256]) -> [Z; 256] {


/// Algorithm 9 `NTTinv(f)` on page 23.
/// Computes the polynomial f ∈ `R_q` corresponding to the given NTT representation `f_hat` ∈ `T_q`.
/// Computes the polynomial `f` ∈ `R_q` corresponding to the given NTT representation `f_hat` ∈ `T_q`.
///
/// Input: array `f_hat` ∈ `Z^{256}` ▷ the coefficients of input NTT representation <br>
/// Output: array `f` ∈ `Z^{256}` ▷ the coefficients of the inverse-NTT of the input
#[must_use]
#[allow(clippy::module_name_repetitions)]
pub fn ntt_inv(f_hat: &[Z; 256]) -> [Z; 256] {
// Input: array f_hat ∈ Z^{256} ▷ the coeffcients of input NTT representation
// Output: array f ∈ Z^{256} ▷ the coeffcients of the inverse-NTT of the input

// 1: f ← f_hat ▷ will compute in-place on a copy of input array
let mut f: [Z; 256] = [Z::default(); 256];
Expand Down Expand Up @@ -115,12 +117,13 @@ pub fn ntt_inv(f_hat: &[Z; 256]) -> [Z; 256] {


/// Algorithm 10 `MultiplyNTTs(f, g)` on page 24.
/// Computes the product (in the ring Tq ) of two NTT representations.
/// Computes the product (in the ring `T_q` ) of two NTT representations.
///
/// Input: Two arrays `f_hat` ∈ `Z^{256}_q` and `g_hat` ∈ `Z^{256}_q` ▷ the coefficients of two NTT representations <br>
/// Output: An array `h_hat` ∈ `Z^{256}_q` ▷ the coefficients of the product of the inputs
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn multiply_ntts(f_hat: &[Z; 256], g_hat: &[Z; 256]) -> [Z; 256] {
// Input: Two arrays f_hat ∈ Z^{256}_q and g_hat ∈ Z^{256}_q ▷ the coeffcients of two NTT representations
// Output: An array h_hat ∈ Z^{256}_q ▷ the coeffcients of the product of the inputs
let mut h_hat: [Z; 256] = [Z::default(); 256];

// for (i ← 0; i < 128; i ++)
Expand All @@ -144,11 +147,12 @@ pub fn multiply_ntts(f_hat: &[Z; 256], g_hat: &[Z; 256]) -> [Z; 256] {

/// Algorithm 11 `BaseCaseMultiply(a0, a1, b0, b1, gamma)` on page 24.
/// Computes the product of two degree-one polynomials with respect to a quadratic modulus.
///
/// Input: `a0`, `a1`, `b0`, `b1` ∈ `Z_q` ▷ the coefficients of `a0` + `a1` X and `b0` + `b1` X
/// Input: `γ` ∈ `Z_q` ▷ the modulus is `X^2 − γ`
/// Output: `c0`, `c1` ∈ `Z_q` ▷ the coefficients of the product of the two polynomials
#[must_use]
pub fn base_case_multiply(a0: Z, a1: Z, b0: Z, b1: Z, gamma: Z) -> (Z, Z) {
// Input: a0 , a1 , b0 , b1 ∈ Z_q ▷ the coefficients of a0 + a1 X and b0 + b1 X
// Input: γ ∈ Z_q ▷ the modulus is X^2 − γ
// Output: c0 , c1 ∈ Z_q ▷ the coeffcients of the product of the two polynomials
// 1: c0 ← a0 · b0 + a1 · b1 · γ ▷ steps 1-2 done modulo q
let c0 = a0.mul(b0).add(a1.mul(b1).mul(gamma));

Expand All @@ -161,7 +165,7 @@ pub fn base_case_multiply(a0: Z, a1: Z, b0: Z, b1: Z, gamma: Z) -> (Z, Z) {


// ----------
// The functionality below calculates the Zeta array at compile-time. Thus, not particularly optimum or CT.
// The functionality below calculates the Zeta array at compile-time. Thus, not particularly optimal or CT.


/// HAC Algorithm 14.76 Right-to-left binary exponentiation mod Q.
Expand Down Expand Up @@ -199,6 +203,7 @@ const fn gen_zeta_table() -> [u16; 256] {

pub(crate) static ZETA_TABLE: [u16; 256] = gen_zeta_table();


#[cfg(test)]
mod tests {
use crate::ntt::{gen_zeta_table, pow_mod_q};
Expand Down
Loading

0 comments on commit 4e850ef

Please sign in to comment.