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
//! RISC-V 32-bit constant-time primitives.
//!
//! Standard RISC-V (RV32I) has NO conditional move instruction.
//! The Zicond extension adds `czero.eqz`/`czero.nez` but is not widely deployed.
//!
//! We use pure bitwise operations (AND/OR/XOR) similar to the Cortex-M0 approach.
//! The mask is built using SUB + arithmetic shift (SRA).
//!
//! Targets: ESP32-C3 (RV32IMC), GD32VF103, SiFive FE310.

use core::arch::asm;

/// Constant-time select using AND/OR/XOR (no cmov on RV32I).
///
/// ```asm
///   neg   mask, condition      # mask = -condition (0 or negative)
///   srai  mask, mask, 31       # 0x00000000 or 0xFFFFFFFF
///   xor   t, a, b              # t = a ^ b
///   and   t, t, mask           # t = (a ^ b) & mask
///   xor   result, b, t         # result = b ^ ((a ^ b) & mask)
/// ```
#[inline(always)]
pub fn ct_select_u8(a: u8, b: u8, condition: u8) -> u8 {
    let result: u32;
    unsafe {
        asm!(
            "neg  {mask}, {cond}",
            "srai {mask}, {mask}, 31",
            "xor  {t}, {a}, {b}",
            "and  {t}, {t}, {mask}",
            "xor  {out}, {b}, {t}",
            a = in(reg) a as u32,
            b = in(reg) b as u32,
            cond = in(reg) condition as u32,
            mask = out(reg) _,
            t = out(reg) _,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as u8
}

#[inline(always)]
pub fn ct_select_i16(a: i16, b: i16, condition: u8) -> i16 {
    let result: u32;
    unsafe {
        asm!(
            "neg  {mask}, {cond}",
            "srai {mask}, {mask}, 31",
            "xor  {t}, {a}, {b}",
            "and  {t}, {t}, {mask}",
            "xor  {out}, {b}, {t}",
            a = in(reg) a as u32,
            b = in(reg) b as u32,
            cond = in(reg) condition as u32,
            mask = out(reg) _,
            t = out(reg) _,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as i16
}

#[inline(always)]
pub fn ct_select_i32(a: i32, b: i32, condition: u8) -> i32 {
    let result: u32;
    unsafe {
        asm!(
            "neg  {mask}, {cond}",
            "srai {mask}, {mask}, 31",
            "xor  {t}, {a}, {b}",
            "and  {t}, {t}, {mask}",
            "xor  {out}, {b}, {t}",
            a = in(reg) a as u32,
            b = in(reg) b as u32,
            cond = in(reg) condition as u32,
            mask = out(reg) _,
            t = out(reg) _,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as i32
}

/// Constant-time equality of two `u32`s: returns 1 if `a == b`, 0 otherwise.
#[inline(always)]
pub fn ct_eq_u32(a: u32, b: u32) -> u8 {
    let diff = a ^ b;
    let mask = (diff | diff.wrapping_neg()) >> 31;
    ((mask as u8) ^ 1) & 1
}

#[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];
    }
    let d32 = diff as u32;
    let result: u32;
    unsafe {
        asm!(
            "neg  {t}, {d}",
            "or   {t}, {t}, {d}",
            "srli {t}, {t}, 31",    // t = 1 if diff!=0, 0 if diff==0
            "xori {out}, {t}, 1",   // out = 1 - t
            d = in(reg) d32,
            t = out(reg) _,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as u8
}

#[inline(never)]
pub fn ct_copy(dst: &mut [u8], src: &[u8], condition: u8) {
    let len = dst.len().min(src.len());
    for i in 0..len {
        dst[i] = ct_select_u8(src[i], dst[i], condition);
    }
}

/// Constant-time slice select: writes `a` to `out` if `condition != 0`,
/// else writes `b`. Both `a` and `b` are read in full byte-by-byte; no
/// secret-dependent control flow or memory access. 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 len = out.len().min(a.len()).min(b.len());
    for i in 0..len {
        out[i] = ct_select_u8(a[i], b[i], condition);
    }
}

#[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);
}

#[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);
}