krypteia-silentops 0.1.0

Side-channel countermeasure toolkit: constant-time primitives, dudect-style timing leakage verifier, and shared SCA helpers for the krypteia workspace.
Documentation
//! Pure Rust constant-time primitives (fallback for all architectures).
//!
//! Uses bitwise operations that compile to branchless code at opt-level >= 1.
//! **Warning**: LLVM may transform these patterns at opt-level=3. Use opt-level=2
//! for crypto builds, or enable an architecture-specific asm backend.

/// Constant-time select: returns `a` if `condition != 0`, else `b`.
///
/// Executes the same instructions regardless of `condition`.
/// No branch, no conditional memory access.
#[inline(always)]
pub fn ct_select_u8(a: u8, b: u8, condition: u8) -> u8 {
    // mask = 0xFF if condition != 0, 0x00 if condition == 0
    let mask = 0u8.wrapping_sub(condition);
    b ^ (mask & (a ^ b))
}

/// Constant-time select for i16 (NTT polynomial coefficients).
#[inline(always)]
pub fn ct_select_i16(a: i16, b: i16, condition: u8) -> i16 {
    let mask = 0u16.wrapping_sub(condition as u16);
    (b as u16 ^ (mask & (a as u16 ^ b as u16))) as i16
}

/// Constant-time select for i32 (ML-DSA coefficients).
#[inline(always)]
pub fn ct_select_i32(a: i32, b: i32, condition: u8) -> i32 {
    let mask = 0u32.wrapping_sub(condition as u32);
    (b as u32 ^ (mask & (a as u32 ^ b as u32))) as i32
}

/// Constant-time equality of two `u32`s: returns 1 if `a == b`, 0 otherwise.
///
/// Branchless bit-twiddle: `(diff | -diff) >> 31` extracts the high bit
/// (1 iff `diff != 0`), then XOR with 1 inverts to the equality predicate.
#[inline(always)]
pub fn ct_eq_u32(a: u32, b: u32) -> u8 {
    let diff = a ^ b;
    let mask = (diff | diff.wrapping_neg()) >> 31; // 1 if diff != 0, else 0
    ((mask as u8) ^ 1) & 1
}

/// Constant-time equality: returns 1 if `a == b` (all bytes), 0 otherwise.
///
/// No early exit — always processes all bytes.
#[inline(never)]
pub fn ct_eq(a: &[u8], b: &[u8]) -> u8 {
    if a.len() != b.len() {
        return 0;
    }
    let mut diff = 0u8;
    for i in 0..a.len() {
        diff |= a[i] ^ b[i];
    }
    // Convert: diff == 0 → 1, diff != 0 → 0
    // (diff | -diff) has MSB set iff diff != 0
    let d = diff as u32;
    let is_nz = ((d | d.wrapping_neg()) >> 31) as u8; // 1 if diff!=0, 0 if diff==0
    is_nz ^ 1
}

/// Constant-time conditional copy: if `condition != 0`, copy `src` to `dst`.
///
/// If `condition == 0`, `dst` is untouched. Always reads both `src` and `dst`
/// (no address-dependent memory access).
#[inline(never)]
pub fn ct_copy(dst: &mut [u8], src: &[u8], condition: u8) {
    let mask = 0u8.wrapping_sub(condition);
    let len = dst.len().min(src.len());
    for i in 0..len {
        dst[i] ^= mask & (dst[i] ^ src[i]);
    }
}

/// Constant-time slice select: writes `a` to `out` if `condition != 0`,
/// else writes `b`. All three slices are read in full byte-by-byte; no
/// secret-dependent control flow or memory access.
///
/// Differs from [`ct_copy`] in that the destination is independent of both
/// candidates — both `a` and `b` are read regardless of the condition, and
/// `out` is overwritten unconditionally. Useful when the caller has two
/// pre-computed candidate buffers and needs to commit one to a fresh
/// destination (e.g. FORS dummy-sibling commit).
///
/// The length used is `out.len().min(a.len()).min(b.len())`.
#[inline(never)]
pub fn ct_select_bytes(out: &mut [u8], a: &[u8], b: &[u8], condition: u8) {
    let mask = 0u8.wrapping_sub(condition);
    let len = out.len().min(a.len()).min(b.len());
    for i in 0..len {
        out[i] = b[i] ^ (mask & (a[i] ^ b[i]));
    }
}

/// Secure zeroization: writes zeros that cannot be elided by the optimizer.
///
/// Uses `core::ptr::write_volatile` and a compiler fence.
#[inline(never)]
pub fn ct_zeroize(buf: &mut [u8]) {
    for byte in buf.iter_mut() {
        unsafe { core::ptr::write_volatile(byte, 0) };
    }
    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}

/// Secure zeroization for i16 slices (polynomial coefficients).
#[inline(never)]
pub fn ct_zeroize_i16(buf: &mut [i16]) {
    for val in buf.iter_mut() {
        unsafe { core::ptr::write_volatile(val, 0) };
    }
    core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}