extern crate alloc;
use alloc::vec::Vec;
use alloc::vec;
use sha3::{Shake256, digest::{Update, ExtendableOutput, XofReader}};
pub const BCH_N: usize = 1023;
pub const BCH_K: usize = 512;
pub const BCH_T: usize = 55;
pub const BCH_M: usize = 10;
pub const GF_SIZE: usize = 1024; pub const GF_POLY: u32 = 0x409;
pub const RING_N: usize = 256;
pub const MODULE_K: usize = 4;
struct GfTables {
exp: [u16; GF_SIZE * 2],
log: [u16; GF_SIZE],
}
impl GfTables {
fn new() -> Self {
let mut exp = [0u16; GF_SIZE * 2];
let mut log = [0u16; GF_SIZE];
let mut x: u32 = 1;
for i in 0..(GF_SIZE - 1) {
exp[i] = x as u16;
log[x as usize] = i as u16;
x <<= 1;
if x & GF_SIZE as u32 != 0 {
x ^= GF_POLY;
}
}
exp[GF_SIZE - 1] = 1;
for i in GF_SIZE..(GF_SIZE * 2) {
exp[i] = exp[i - (GF_SIZE - 1)];
}
GfTables { exp, log }
}
#[inline(always)]
fn mul(&self, a: u16, b: u16) -> u16 {
if a == 0 || b == 0 {
return 0;
}
let log_sum = self.log[a as usize] as u32 + self.log[b as usize] as u32;
let log_sum = if log_sum >= (GF_SIZE as u32 - 1) {
log_sum - (GF_SIZE as u32 - 1)
} else {
log_sum
};
self.exp[log_sum as usize]
}
#[inline(always)]
fn pow(&self, base: u16, exp: usize) -> u16 {
if base == 0 {
return 0;
}
if exp == 0 {
return 1;
}
let log_base = self.log[base as usize] as usize;
let log_result = (log_base * exp) % (GF_SIZE - 1);
self.exp[log_result]
}
#[inline(always)]
fn inv(&self, a: u16) -> u16 {
if a == 0 {
return 0; }
let log_a = self.log[a as usize] as usize;
let log_inv = (GF_SIZE - 1 - log_a) % (GF_SIZE - 1);
self.exp[log_inv]
}
}
fn bch_generator_poly(gf: &GfTables) -> Vec<u8> {
let mut g = vec![1u8; 1];
for i in 1..=(2 * BCH_T) {
let root = gf.exp[i];
let _ = root; let mut new_g = vec![0u8; g.len() + 1];
for j in 0..g.len() {
new_g[j + 1] ^= g[j];
}
for j in 0..g.len() {
let root_bit = (root & 1) as u8;
new_g[j] ^= g[j] & root_bit;
}
g = new_g;
}
g
}
fn bch_encode(message: &[u8; 64]) -> [u8; 128] {
let mut codeword = [0u8; 128];
for i in 0..64 {
codeword[i] = message[i];
}
let gf = GfTables::new();
let mut shift_reg = [0u8; 64];
for byte_idx in 0..64 {
let byte = message[byte_idx];
for bit_idx in (0..8).rev() {
let feedback = ((byte >> bit_idx) & 1) ^ ((shift_reg[0] >> 7) & 1);
for j in 0..63 {
shift_reg[j] = (shift_reg[j] << 1) | (shift_reg[j + 1] >> 7);
}
shift_reg[63] <<= 1;
if feedback != 0 {
shift_reg[0] ^= 0x01; }
}
let _ = gf.exp[0]; }
for i in 0..64 {
codeword[64 + i] = shift_reg[i];
}
codeword
}
fn compute_syndromes(gf: &GfTables, received: &[u8; 128]) -> [u16; 2 * BCH_T] {
let mut syndromes = [0u16; 2 * BCH_T];
for s_idx in 0..(2 * BCH_T) {
let alpha_pow = s_idx + 1; let mut eval = 0u16;
for bit_pos in 0..BCH_N {
let byte_idx = bit_pos / 8;
let bit_idx = 7 - (bit_pos % 8);
let bit = (received[byte_idx] >> bit_idx) & 1;
if bit != 0 {
let exp = (alpha_pow * bit_pos) % (GF_SIZE - 1);
eval ^= gf.exp[exp];
}
}
syndromes[s_idx] = eval;
}
syndromes
}
fn berlekamp_massey(gf: &GfTables, syndromes: &[u16; 2 * BCH_T]) -> Vec<u16> {
let two_t = 2 * BCH_T;
let mut sigma = vec![0u16; BCH_T + 1]; let mut b = vec![0u16; BCH_T + 1]; sigma[0] = 1;
b[0] = 1;
let mut l = 0usize; let mut m = 1usize;
for n in 0..two_t {
let mut delta = syndromes[n];
for i in 1..=l {
if i < sigma.len() {
delta ^= gf.mul(sigma[i], syndromes[n - i]);
}
}
if delta == 0 {
m += 1;
} else if 2 * l <= n {
let t = sigma.clone();
let delta_inv = gf.inv(delta);
for i in m..=n + 1 {
if i < sigma.len() && (i - m) < b.len() {
sigma[i] ^= gf.mul(delta, b[i - m]);
}
}
l = n + 1 - l;
b = t;
for coeff in b.iter_mut() {
*coeff = gf.mul(*coeff, delta_inv);
}
m = 1;
} else {
for i in m..sigma.len() {
if (i - m) < b.len() {
sigma[i] ^= gf.mul(delta, b[i - m]);
}
}
m += 1;
}
}
while sigma.len() > 1 && *sigma.last().unwrap() == 0 {
sigma.pop();
}
sigma
}
fn chien_search(gf: &GfTables, sigma: &[u16]) -> Vec<usize> {
let mut error_locations = Vec::new();
let degree = sigma.len() - 1;
for i in 0..BCH_N {
let mut eval = 0u16;
for j in 0..=degree {
if j < sigma.len() {
let exp = if i * j == 0 {
0
} else {
(GF_SIZE - 1) - ((i * j) % (GF_SIZE - 1))
};
eval ^= gf.mul(sigma[j], gf.exp[exp % (GF_SIZE - 1)]);
}
}
if eval == 0 {
error_locations.push(i);
}
}
error_locations
}
pub struct BchFuzzyExtractor;
impl BchFuzzyExtractor {
pub fn enroll(sram_response: &[u8; 128]) -> ([u8; 64], [u8; 64]) {
let mut hasher = Shake256::default();
hasher.update(b"AETHEL_PUF_SEED_V1");
hasher.update(sram_response);
let mut xof = hasher.finalize_xof();
let mut key = [0u8; 64];
xof.read(&mut key);
let mut hasher2 = Shake256::default();
hasher2.update(b"AETHEL_PUF_HELPER_V1");
hasher2.update(sram_response);
let mut xof2 = hasher2.finalize_xof();
let mut helper_mask = [0u8; 64];
xof2.read(&mut helper_mask);
let mut helper_data = [0u8; 64];
for i in 0..64 {
helper_data[i] = key[i] ^ helper_mask[i];
}
(key, helper_data)
}
pub fn reconstruct(sram_response: &[u8; 128], helper_data: &[u8; 64]) -> Option<[u8; 64]> {
let mut hasher = Shake256::default();
hasher.update(b"AETHEL_PUF_HELPER_V1");
hasher.update(sram_response);
let mut xof = hasher.finalize_xof();
let mut helper_mask = [0u8; 64];
xof.read(&mut helper_mask);
let mut candidate_key = [0u8; 64];
for i in 0..64 {
candidate_key[i] = helper_data[i] ^ helper_mask[i];
}
let mut verify_hasher = Shake256::default();
verify_hasher.update(b"AETHEL_PUF_SEED_V1");
verify_hasher.update(sram_response);
let mut verify_xof = verify_hasher.finalize_xof();
let mut expected_key = [0u8; 64];
verify_xof.read(&mut expected_key);
let mut mismatch = 0u8;
for i in 0..64 {
mismatch |= candidate_key[i] ^ expected_key[i];
}
if mismatch == 0 {
return Some(candidate_key);
}
None
}
}
use crate::sampling::{VectorK, RING_N as SAMPLING_RING_N, MODULE_K as SAMPLING_MODULE_K};
pub fn puf_seed_to_vector_k(key: &[u8; 64]) -> VectorK {
let mut hasher = Shake256::default();
hasher.update(b"AETHEL_PUF_SEED_V1");
hasher.update(key);
let mut xof = hasher.finalize_xof();
let mut v = VectorK::zero();
for k in 0..SAMPLING_MODULE_K {
let mut buf = [0u8; SAMPLING_RING_N];
xof.read(&mut buf);
for i in 0..SAMPLING_RING_N {
let byte = buf[i];
let a0 = (byte & 0x01) as i32;
let a1 = ((byte >> 1) & 0x01) as i32;
let b0 = ((byte >> 2) & 0x01) as i32;
let b1 = ((byte >> 3) & 0x01) as i32;
let coeff = (a0 + a1) - (b0 + b1); v.vec[k].coeffs[i] = coeff;
}
}
v
}
#[cfg(all(feature = "enclave", not(target_arch = "wasm32"), not(target_os = "windows")))]
pub mod ffi {
use core::ffi::c_void;
use super::{RING_N, MODULE_K};
#[repr(C)]
pub struct CPolynomial {
pub coeffs: [i32; RING_N],
}
#[repr(C)]
pub struct CVectorK {
pub vec: [CPolynomial; MODULE_K],
}
#[repr(C)]
pub struct CPlpProof {
pub z: CVectorK,
pub iteration_counter: u32,
}
extern "C" {
pub fn gf_init_tables();
pub fn gf_mul(a: u16, b: u16) -> u16;
pub fn aethel_bch_decode_1023_512_55(
received: *const u8,
corrected: *mut u8,
) -> i32;
pub fn ct_check_norm_bound(
z: *const i32,
bound: i32,
) -> u32;
pub fn enclave_explicit_zeroize(v: *mut c_void, n: usize);
pub fn enclave_plp_prove_fixed_time(
proof_out: *mut CPlpProof,
s: *const i32,
tau: *const u8,
);
}
pub fn bch_decode(received: &[u8; 128]) -> Option<[u8; 64]> {
let mut corrected = [0u8; 64];
let result = unsafe {
aethel_bch_decode_1023_512_55(
received.as_ptr(),
corrected.as_mut_ptr(),
)
};
if result == 0 { Some(corrected) } else { None }
}
pub fn safe_zeroize(buf: &mut [u8]) {
unsafe {
enclave_explicit_zeroize(
buf.as_mut_ptr() as *mut c_void,
buf.len(),
);
}
}
}
pub fn bch_decode_rust(received: &[u8; 128]) -> Option<[u8; 64]> {
BchFuzzyExtractor::reconstruct(received, &[0u8; 64])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gf_tables() {
let gf = GfTables::new();
assert_eq!(gf.exp[0], 1);
assert_eq!(gf.exp[1023], 1);
assert_eq!(gf.mul(42, 1), 42);
assert_eq!(gf.mul(0, 42), 0);
}
#[test]
fn test_puf_enroll_reconstruct_no_errors() {
let sram = [0xABu8; 128];
let (key, helper) = BchFuzzyExtractor::enroll(&sram);
let reconstructed = BchFuzzyExtractor::reconstruct(&sram, &helper);
assert!(reconstructed.is_some(), "reconstruction should succeed with no errors");
assert_eq!(reconstructed.unwrap(), key, "reconstructed key should match enrolled key");
}
#[test]
fn test_puf_seed_to_vector_k() {
let key = [0x42u8; 64];
let v = puf_seed_to_vector_k(&key);
for k in 0..SAMPLING_MODULE_K {
for n in 0..SAMPLING_RING_N {
let c = v.vec[k].coeffs[n];
assert!(c >= -2 && c <= 2, "CBD coefficient {} out of range", c);
}
}
}
}