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
//! AArch64 (ARMv8-A) constant-time primitives using inline assembly.
//!
//! Uses `csel` (conditional select) which executes in constant time on all
//! AArch64 implementations — no branch prediction, no speculative execution
//! side effects.
//!
//! Targets: Cortex-A53 (Pi 3), Cortex-A72 (Pi 4), Cortex-A76 (Pi 5), servers.

use core::arch::asm;

/// Constant-time select using `csel` (AArch64 conditional select).
///
/// `csel Xd, Xn, Xm, cond` — sets Xd to Xn if cond, else Xm.
/// Executes in exactly 1 cycle regardless of condition.
#[inline(always)]
pub fn ct_select_u8(a: u8, b: u8, condition: u8) -> u8 {
    let result: u64;
    unsafe {
        asm!(
            "cmp {cond:w}, #0",
            "csel {out:x}, {a:x}, {b:x}, ne",
            a = in(reg) a as u64,
            b = in(reg) b as u64,
            cond = in(reg) condition as u64,
            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: u64;
    unsafe {
        asm!(
            "cmp {cond:w}, #0",
            "csel {out:x}, {a:x}, {b:x}, ne",
            a = in(reg) a as i64 as u64,
            b = in(reg) b as i64 as u64,
            cond = in(reg) condition as u64,
            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: u64;
    unsafe {
        asm!(
            "cmp {cond:w}, #0",
            "csel {out:x}, {a:x}, {b:x}, ne",
            a = in(reg) a as i64 as u64,
            b = in(reg) b as i64 as u64,
            cond = in(reg) condition as u64,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as i32
}

/// Constant-time equality using XOR accumulation (no early exit).
/// 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 result: u64;
    unsafe {
        asm!(
            "cmp {diff:w}, #0",
            "cset {out:x}, eq",  // out = 1 if diff==0, else 0
            diff = in(reg) diff as u64,
            out = out(reg) result,
            options(pure, nomem, nostack),
        );
    }
    result as u8
}

/// Conditional copy using `csel` per byte.
#[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);
    }
}

/// Secure zeroization with volatile writes.
#[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);
}