Skip to content

Commit

Permalink
pre ct
Browse files Browse the repository at this point in the history
  • Loading branch information
eschorn1 committed May 11, 2024
1 parent 63a2c94 commit c7a7e3d
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 34 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ an embedded target, constant-time statistical measurements, fuzzing, WASM execut

This crate implements the FIPS 204 **draft** standard in pure Rust with minimal and mainstream dependencies, **and
without any unsafe code**. All three security parameter sets are fully functional and tested. The implementation
operates in constant-time (TKTK EXCEPTIONS HERE), does not require the standard library, e.g. `#[no_std]`, has no
operates in constant-time (EXCEPTIONS NOTED HERE), does not require the standard library, e.g. `#[no_std]`, has no
heap allocations, e.g. no `alloc` needed, and exposes the `RNG` so it is suitable for the full range of applications
down to the bare-metal. The API is stabilized and the code is heavily biased towards safety and correctness; further
performance optimizations will be implemented as the standard matures. This crate will quickly follow any changes
Expand Down
2 changes: 1 addition & 1 deletion benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use fips204::{ml_dsa_44, ml_dsa_65, ml_dsa_87};
use rand_core::{CryptoRng, RngCore};


// Test RNG to regurgitate incremented values when 'asked'
// Test RNG to supply incremented values when 'asked'
#[repr(align(8))]
struct TestRng {
value: u32,
Expand Down
9 changes: 6 additions & 3 deletions dudect/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@ impl RngCore for TestRng {

fn try_fill_bytes(&mut self, out: &mut [u8]) -> Result<(), rand_core::Error> {
out.iter_mut().for_each(|b| *b = self.value);
self.value = self.value.wrapping_add(1);
//self.value = self.value.wrapping_add(1);
Ok(())
}
}

impl CryptoRng for TestRng {}


// TODO: note to self. goal is to show timing is independent of secret key, not randomness nor message.
// so rnd gen could be kept constant

fn sign(runner: &mut CtRunner, mut _rng: &mut BenchRng) {
const ITERATIONS_INNER: usize = 5;
const ITERATIONS_OUTER: usize = 100_000;

let message = [0u8, 1, 2, 3, 4, 5, 6, 7]; // TODO: consider whether this should be left/right
let message = [0u8, 1, 2, 3, 4, 5, 6, 7]; // TODO: consider whether this should be left/right (no)

let (_pk1, sk_right) = ml_dsa_44::try_keygen_vt().unwrap(); // Generate both public and secret keys
let (_pk2, sk_left) = ml_dsa_44::try_keygen_vt().unwrap(); // Generate both public and secret keys
Expand All @@ -40,7 +43,7 @@ fn sign(runner: &mut CtRunner, mut _rng: &mut BenchRng) {
// Interleave left and right
for i in (0..(ITERATIONS_OUTER)).step_by(2) {
classes[i] = Class::Left;
refs[i] = (34, &sk_left); // 34 = rng seed
refs[i] = (12, &sk_left); // 12 = rng seed
}

for (class, tuple) in classes.into_iter().zip(refs.into_iter()) {
Expand Down
2 changes: 1 addition & 1 deletion src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ pub(crate) fn bit_unpack(v: &[u8], a: i32, b: i32) -> Result<R, &'static str> {
}
}

let bot = i32::abs(b - 2i32.pow(bitlen) + 1); // b − 2^c + 1 (as abs)
let bot = i32::abs(b - (1 << bitlen) + 1); // b − 2^c + 1 (as abs)
ensure!(is_in_range(&w_out, bot, b), "Alg 13: w out of range");
Ok(w_out)
}
Expand Down
1 change: 0 additions & 1 deletion src/encodings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ pub(crate) fn pk_encode<const K: usize, const PK_LEN: usize>(
/// # Errors
/// Returns an error when the internal `simple_bit_unpack()` invocation finds an element of
/// `t1` is out of range.
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn pk_decode<const K: usize, const PK_LEN: usize>(
pk: &[u8; PK_LEN],
) -> Result<(&[u8; 32], [R; K]), &'static str> {
Expand Down
2 changes: 1 addition & 1 deletion src/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ pub(crate) fn expand_s_vartime<const K: usize, const L: usize>(
/// **Output**: Vector `s ∈ R^ℓ_q`.
pub(crate) fn expand_mask<const L: usize>(gamma1: i32, rho: &[u8; 64], mu: u16) -> [R; L] {
let mut s = [R0; L];
let mut v = [0u8; 32 * 20]; // TODO: 640?
let mut v = [0u8; 32 * 20];

// 1: c ← 1 + bitlen (γ1 − 1) ▷ γ1 is always a power of 2
let c = 1 + bit_length(gamma1 - 1); // c will either be 18 or 20
Expand Down
53 changes: 34 additions & 19 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,41 @@ pub(crate) fn is_in_range(w: &R, lo: i32, hi: i32) -> bool {


/// Partial Barrett-style reduction
const M: i128 = (1 << 64) / (Q as i128);
#[allow(clippy::inline_always, clippy::cast_possible_truncation)]
#[inline(always)]
// Arguably very slightly faster than single-step i128 below; worth more experimentation
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn partial_reduce64(a: i64) -> i32 {
let q = (a as i128 * M) >> 64;
(a - (q as i64) * (Q as i64)) as i32
const M: i64 = (1 << 48) / (Q as i64);
debug_assert!(a < (i64::MAX / 64));
let x = a >> 23;
let a = a - x * (Q as i64);
let x = a >> 23;
let a = a - x * (Q as i64);
let q = (a * M) >> 48;
let res = a - q * (Q as i64);
debug_assert!(res < 2 * Q as i64);
res as i32
}


#[allow(dead_code)]
#[allow(clippy::cast_possible_truncation)]
pub(crate) const fn partial_reduce64b(a: i64) -> i32 {
const MM: i64 = ((1 << 64) / (Q as i128)) as i64;
debug_assert!(a < (i64::MAX / 64));
let q = (a as i128 * MM as i128) >> 64; // only top half is relevant
let res = a - (q as i64 * Q as i64);
debug_assert!(res < 2 * Q as i64);
res as i32
}




/// Partially reduce a signed 32-bit value mod Q ---> `-Q <~ result <~ Q`
// Considering the positive case for `a`, bits 23 and above can be loosely
// viewed as the 'number of Q' contained within `a` (with some rounding-down
// error). So, increment these bits and then subtract off the corresponding
// number of Q. The result is within (better than) -Q < res < Q. This
// approach also works for negative values. For the extreme positive `a`
// result, consider all bits set except for position 22 so the increment
// cannot generate a carry (and thus we have maximum rounding-down error
// accumulated), or a = 2**31 - 2**22 - 1, which then suggests (0xFF) Q to
// be subtracted. Then, a - (a >> 23)*Q is 6283008 or 2**23 - 2**21 - 2**8.
// The negative result works out to -6283008. Note Q is 2**23 - 2**13 + 1. TODO: Recheck #s
#[inline(always)]
#[allow(clippy::inline_always)]
// number of Q. The result is within (better than) -Q < res < Q.
pub(crate) const fn partial_reduce32(a: i32) -> i32 {
let x = (a + (1 << 22)) >> 23;
let res = a - x * Q;
Expand All @@ -56,7 +70,9 @@ pub(crate) const fn partial_reduce32(a: i32) -> i32 {

pub(crate) const fn full_reduce32(a: i32) -> i32 {
let x = partial_reduce32(a); // puts us within better than -Q to +Q
x + ((x >> 31) & Q) // add Q if negative
let x = x + ((x >> 31) & Q); // add Q if negative
debug_assert!(x < Q);
x
}

// Note: this is only used on 'fixed' security parameters (not secret values), so as not to impact CT
Expand Down Expand Up @@ -107,7 +123,7 @@ pub(crate) fn vec_add<const K: usize>(vec_a: &[R; K], vec_b: &[R; K]) -> [R; K]
pub(crate) fn to_mont<const L: usize>(vec_a: &[T; L]) -> [T; L] {
let result: [T; L] = core::array::from_fn(|l| {
T(core::array::from_fn(|n| {
partial_reduce64(i64::from(vec_a[l].0[n]).wrapping_mul(1 << 32))
partial_reduce64(i64::from(vec_a[l].0[n]) << 32)
}))
});
result
Expand Down Expand Up @@ -152,7 +168,7 @@ const fn pow_mod_q(g: i32, e: u8) -> i32 {
#[allow(dead_code)]
const QINV: i64 = 58_728_449; // (Q * QINV) % 2**32 = 1

#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_truncation)] // as i32
pub(crate) const fn mont_reduce(a: i64) -> i32 {
let t = a.wrapping_mul(QINV) as i32;
let t = (a - (t as i64).wrapping_mul(Q as i64)) >> 32;
Expand All @@ -169,8 +185,7 @@ const fn gen_zeta_table_mont() -> [i32; 256] {
let mut i = 0_usize;
while i < 256 {
let result_norm = pow_mod_q(ZETA, i.to_le_bytes()[0].reverse_bits());
let result_mont =
(result_norm as i64).wrapping_mul(2i64.pow(32)).rem_euclid(Q as i64) as i32;
let result_mont = (result_norm as i64).wrapping_mul(1 << 32).rem_euclid(Q as i64) as i32;
result[i] = result_mont;
i += 1;
}
Expand Down
1 change: 0 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
// See <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.204.ipd.pdf>

// TODO: Roadmap
// 1. Clean up; resolve (mont) math
// 2. Closer CT inspection -> top level key_gen is vartime, the rest CT outside of rho (? TBC)
// 3. Intensive/extensive pass on documentation
// 4. Revisit/expand unit testing; consider whether to test debug statements: release-vs-test
Expand Down
6 changes: 3 additions & 3 deletions src/ml_dsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::encodings::{
};
use crate::hashing::{expand_a_vartime, expand_mask, expand_s_vartime, h_xof, sample_in_ball};
use crate::helpers::{
bit_length, center_mod, infinity_norm, mat_vec_mul, mont_reduce, partial_reduce32, to_mont,
vec_add,
bit_length, center_mod, ensure, infinity_norm, mat_vec_mul, mont_reduce, partial_reduce32,
to_mont, vec_add,
};
use crate::high_low::{high_bits, low_bits, make_hint, power2round, use_hint};
use crate::ntt::{inv_ntt, ntt};
Expand Down Expand Up @@ -344,7 +344,7 @@ pub(crate) fn verify_finish<
// 4: end if
};
let h = h.unwrap();
debug_assert!(infinity_norm(&z) < gamma1, "Alg 3: i_norm out of range");
ensure!(infinity_norm(&z) <= gamma1, "Alg 3: i_norm out of range");

// 5: cap_a_hat ← ExpandA(ρ) ▷ A is generated and stored in NTT representation as cap_A_hat
// --> calculated in verify_start()
Expand Down
2 changes: 1 addition & 1 deletion src/ntt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub(crate) fn ntt<const X: usize>(w: &[R; X]) -> [T; X] {
pub(crate) fn inv_ntt<const X: usize>(w_hat: &[T; X]) -> [R; X] {
//
#[allow(clippy::cast_possible_truncation)]
const F: i64 = 8_347_681_i128.wrapping_mul(2i128.pow(32)).rem_euclid(Q as i128) as i64;
const F: i64 = 8_347_681_i128.wrapping_mul(1 << 32).rem_euclid(Q as i128) as i64;
//
// 1: for j from 0 to 255 do
// 2: w_j ← w_hat[j]
Expand Down
4 changes: 2 additions & 2 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rand_core::RngCore;

// cargo flamegraph --test integration

// $ cargo test --release -- --nocapture --ignored
// $ cargo test --package fips204 --test integration forever -- --ignored --nocapture
#[ignore]
#[test]
fn forever() {
Expand All @@ -23,7 +23,7 @@ fn forever() {
rng.fill_bytes(&mut flip);
let index = u32::from_le_bytes(flip[0..4].try_into().unwrap()); // index of byte to flip
let mut sig2 = core::array::from_fn(|i| sig[i]);
sig2[index as usize % sig2.len()] ^= if flip[4] != 0 { flip[4] } else { 0x55 }; // investigate sig[last]
sig2[index as usize % (sig2.len() - 2)] ^= if flip[4] != 0 { flip[4] } else { 0x55 }; // investigate sig[last]
let ver = pk.try_verify_vt(&msg, &sig2);
if ver.is_ok() && ver.unwrap() {
eprintln!("Msg is {}\n", hex::encode(msg));
Expand Down

0 comments on commit c7a7e3d

Please sign in to comment.