use super::SlhDsaError;
use super::address::{Adrs, FORS_PRF, FORS_ROOTS, FORS_TREE};
use super::hash;
use super::params::Params;
use crate::secret::SecretBytes;
use alloc::vec::Vec;
fn fors_sk_gen<P: Params>(sk_seed: &[u8], pk_seed: &[u8], adrs: &mut Adrs, idx: u32) -> Vec<u8> {
let mut sk_adrs = adrs.clone();
sk_adrs.set_type_and_clear(FORS_PRF);
sk_adrs.set_key_pair_address(adrs.get_key_pair_address());
sk_adrs.set_tree_index(idx);
hash::prf::<P>(pk_seed, sk_seed, &sk_adrs)
}
pub fn fors_node<P: Params>(sk_seed: &[u8], pk_seed: &[u8], i: u32, z: u32, adrs: &mut Adrs) -> Vec<u8> {
if z == 0 {
let sk = fors_sk_gen::<P>(sk_seed, pk_seed, adrs, i);
let kp = adrs.get_key_pair_address();
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(0);
adrs.set_tree_index(i);
return hash::f_hash::<P>(pk_seed, adrs, &sk);
}
let num_leaves = 1u32 << z;
let base = i;
let kp = adrs.get_key_pair_address();
let mut stack: Vec<(Vec<u8>, u32)> = Vec::with_capacity((z + 1) as usize);
for j in 0..num_leaves {
let leaf_idx = base + j;
let sk = fors_sk_gen::<P>(sk_seed, pk_seed, adrs, leaf_idx);
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(0);
adrs.set_tree_index(leaf_idx);
let mut node = hash::f_hash::<P>(pk_seed, adrs, &sk);
let mut height = 0u32;
let mut tree_idx = leaf_idx;
while let Some(&(_, top_h)) = stack.last() {
if top_h != height {
break;
}
let (left, _) = stack.pop().expect("stack non-empty in merge loop");
tree_idx >>= 1;
height += 1;
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(height);
adrs.set_tree_index(tree_idx);
node = hash::hash_h::<P>(pk_seed, adrs, &left, &node);
}
stack.push((node, height));
}
stack.pop().expect("treehash produces exactly one root").0
}
pub fn fors_sign<P: Params>(
md: &[u8],
sk_seed: &[u8],
pk_seed: &[u8],
adrs_template: &Adrs,
) -> Result<Vec<u8>, SlhDsaError> {
let mut sig_fors = alloc::vec![0u8; P::K * (1 + P::A) * P::N];
fors_sign_into::<P>(md, sk_seed, pk_seed, adrs_template, &mut sig_fors)?;
Ok(sig_fors)
}
pub fn fors_sign_into<P: Params>(
md: &[u8],
sk_seed: &[u8],
pk_seed: &[u8],
adrs_template: &Adrs,
out: &mut [u8],
) -> Result<(), SlhDsaError> {
let entry_size = (1 + P::A) * P::N;
debug_assert!(out.len() >= P::K * entry_size);
let indices = message_to_indices::<P>(md);
let mut adrs = adrs_template.clone();
let kp = adrs.get_key_pair_address();
for i in 0..P::K {
let idx = indices[i];
let slot = &mut out[i * entry_size..(i + 1) * entry_size];
let (leaf_slot, auth_slot) = slot.split_at_mut(P::N);
let base = (i as u32) * (1u32 << P::A);
#[cfg(feature = "sca-fors-dummy-siblings")]
{
let mut stack: Vec<(Vec<u8>, u32)> = Vec::with_capacity(P::A + 1);
let num_leaves = 1u32 << P::A;
for k in 0..num_leaves {
let leaf_idx = base + k;
let sk = fors_sk_gen::<P>(sk_seed, pk_seed, &mut adrs, leaf_idx);
silentops::ct_copy(leaf_slot, &sk, silentops::ct_eq_u32(k, idx));
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(0);
adrs.set_tree_index(leaf_idx);
let mut node = hash::f_hash::<P>(pk_seed, &mut adrs, &sk);
let mut height: u32 = 0;
let mut local_pos: u32 = k;
silentops::ct_copy(&mut auth_slot[0..P::N], &node, silentops::ct_eq_u32(local_pos, idx ^ 1));
while let Some(&(_, top_h)) = stack.last() {
if top_h != height {
break;
}
let (left, _) = stack.pop().expect("non-empty");
local_pos >>= 1;
height += 1;
let absolute_pos = (i as u32) * (1u32 << (P::A - height as usize)) + local_pos;
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(height);
adrs.set_tree_index(absolute_pos);
node = hash::hash_h::<P>(pk_seed, &mut adrs, &left, &node);
if (height as usize) < P::A {
let sibling_pos = (idx >> height) ^ 1;
let slot_off = (height as usize) * P::N;
silentops::ct_copy(
&mut auth_slot[slot_off..slot_off + P::N],
&node,
silentops::ct_eq_u32(local_pos, sibling_pos),
);
}
}
stack.push((node, height));
}
drop(stack);
}
#[cfg(not(feature = "sca-fors-dummy-siblings"))]
{
let sk = fors_sk_gen::<P>(sk_seed, pk_seed, &mut adrs, base + idx);
leaf_slot.copy_from_slice(&sk);
for j in 0..P::A {
let s = (idx >> j) ^ 1;
let node = fors_node::<P>(sk_seed, pk_seed, base + s * (1 << j), j as u32, &mut adrs);
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
auth_slot[j * P::N..(j + 1) * P::N].copy_from_slice(&node);
}
}
}
#[cfg(feature = "sca-fors-indices-check")]
fors_indices_consistency_check::<P>(md, &indices)?;
Ok(())
}
#[cfg(feature = "sca-fors-indices-check")]
pub(crate) fn fors_indices_consistency_check<P: Params>(md: &[u8], used: &[u32]) -> Result<(), SlhDsaError> {
let recomputed = message_to_indices::<P>(md);
if recomputed.len() != used.len() {
return Err(SlhDsaError::FaultDetected);
}
let used_bytes: Vec<u8> = used.iter().flat_map(|x| x.to_le_bytes()).collect();
let recomputed_bytes: Vec<u8> = recomputed.iter().flat_map(|x| x.to_le_bytes()).collect();
if silentops::ct_eq(&used_bytes, &recomputed_bytes) != 1 {
return Err(SlhDsaError::FaultDetected);
}
Ok(())
}
pub fn fors_pk_from_sig<P: Params>(sig_fors: &[u8], md: &[u8], pk_seed: &[u8], adrs_template: &Adrs) -> Vec<u8> {
let indices = message_to_indices::<P>(md);
let mut adrs = adrs_template.clone();
let kp = adrs.get_key_pair_address();
let entry_size = (1 + P::A) * P::N;
let mut roots = Vec::with_capacity(P::K * P::N);
let mut left = alloc::vec![0u8; P::N];
let mut right = alloc::vec![0u8; P::N];
for i in 0..P::K {
let idx = indices[i];
let offset = i * entry_size;
let sk = &sig_fors[offset..offset + P::N];
let auth = &sig_fors[offset + P::N..offset + entry_size];
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height(0);
adrs.set_tree_index((i as u32) * (1u32 << P::A) + idx);
let mut node = hash::f_hash::<P>(pk_seed, &mut adrs, sk);
for j in 0..P::A {
let auth_j = &auth[j * P::N..(j + 1) * P::N];
let bit = ((idx >> j) & 1) as u8;
for k in 0..P::N {
left[k] = silentops::ct_select_u8(auth_j[k], node[k], bit);
right[k] = silentops::ct_select_u8(node[k], auth_j[k], bit);
}
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(kp);
adrs.set_tree_height((j + 1) as u32);
adrs.set_tree_index(((i as u32) * (1u32 << P::A) + idx) >> (j + 1));
node = hash::hash_h::<P>(pk_seed, &mut adrs, &left, &right);
}
roots.extend_from_slice(&node);
}
silentops::ct_zeroize(&mut left);
silentops::ct_zeroize(&mut right);
let mut fors_pk_adrs = adrs.clone();
fors_pk_adrs.set_type_and_clear(FORS_ROOTS);
fors_pk_adrs.set_key_pair_address(kp);
hash::t_l::<P>(pk_seed, &fors_pk_adrs, &roots)
}
pub fn fors_sign_into_redundant<P: Params>(
md: &[u8],
sk_seed: &[u8],
pk_seed: &[u8],
adrs_template: &Adrs,
out: &mut [u8],
) -> Result<Vec<u8>, SlhDsaError> {
let sig_len = P::K * (1 + P::A) * P::N;
debug_assert!(out.len() >= sig_len);
let mut sig1 = SecretBytes::from_vec(alloc::vec![0u8; sig_len]);
let mut sig2 = SecretBytes::from_vec(alloc::vec![0u8; sig_len]);
fors_sign_into::<P>(md, sk_seed, pk_seed, adrs_template, &mut sig1)?;
fors_sign_into::<P>(md, sk_seed, pk_seed, adrs_template, &mut sig2)?;
let pk1 = fors_pk_from_sig::<P>(sig1.as_bytes(), md, pk_seed, adrs_template);
let pk2 = fors_pk_from_sig::<P>(sig2.as_bytes(), md, pk_seed, adrs_template);
fors_redundancy_compare(sig1.as_bytes(), sig2.as_bytes(), &pk1, &pk2)?;
out[..sig_len].copy_from_slice(sig1.as_bytes());
Ok(pk1)
}
#[inline]
fn fors_redundancy_compare(sig1: &[u8], sig2: &[u8], pk1: &[u8], pk2: &[u8]) -> Result<(), SlhDsaError> {
let sigs_equal = silentops::ct_eq(sig1, sig2) == 1;
let pks_equal = silentops::ct_eq(pk1, pk2) == 1;
if !sigs_equal || !pks_equal {
return Err(SlhDsaError::FaultDetected);
}
Ok(())
}
fn message_to_indices<P: Params>(md: &[u8]) -> Vec<u32> {
super::wots::base_2b(md, P::A, P::K)
}
#[cfg(test)]
mod tests {
use super::super::address::{Adrs, FORS_TREE};
use super::super::params::{Params, Shake128f, Shake128s};
use super::*;
fn round_trip<P: Params>(seed_byte: u8, message_byte: u8) {
let sk_seed = alloc::vec![seed_byte; P::N];
let pk_seed = alloc::vec![seed_byte.wrapping_add(0x80); P::N];
let md_bytes = (P::K * P::A + 7) / 8;
let md: Vec<u8> = (0..md_bytes).map(|i| message_byte.wrapping_add(i as u8)).collect();
let mut sig_fors = alloc::vec![0u8; P::K * (1 + P::A) * P::N];
let mut adrs = Adrs::new();
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(0);
super::fors_sign_into::<P>(&md, &sk_seed, &pk_seed, &adrs, &mut sig_fors)
.expect("round-trip — clean signing must succeed");
let pk_a = super::fors_pk_from_sig::<P>(&sig_fors, &md, &pk_seed, &adrs);
let pk_b = super::fors_pk_from_sig::<P>(&sig_fors, &md, &pk_seed, &adrs);
assert_eq!(pk_a.len(), P::N, "FORS pk must be N bytes wide");
assert_eq!(pk_a, pk_b, "fors_pk_from_sig must be deterministic");
}
#[test]
fn fors_pk_from_sig_round_trip_shake128s() {
round_trip::<Shake128s>(0x00, 0x00);
round_trip::<Shake128s>(0x55, 0xAA);
round_trip::<Shake128s>(0xFF, 0xFF);
round_trip::<Shake128s>(0x42, 0x13);
}
#[test]
fn fors_pk_from_sig_round_trip_shake128f() {
round_trip::<Shake128f>(0x00, 0x00);
round_trip::<Shake128f>(0x55, 0xAA);
round_trip::<Shake128f>(0xFF, 0xFF);
}
fn redundancy_equivalence<P: Params>(seed_byte: u8, message_byte: u8) {
let sk_seed = alloc::vec![seed_byte; P::N];
let pk_seed = alloc::vec![seed_byte.wrapping_add(0x80); P::N];
let md_bytes = (P::K * P::A + 7) / 8;
let md: Vec<u8> = (0..md_bytes).map(|i| message_byte.wrapping_add(i as u8)).collect();
let sig_len = P::K * (1 + P::A) * P::N;
let mut sig_ref = alloc::vec![0u8; sig_len];
let mut adrs = Adrs::new();
adrs.set_type_and_clear(FORS_TREE);
adrs.set_key_pair_address(0);
super::fors_sign_into::<P>(&md, &sk_seed, &pk_seed, &adrs, &mut sig_ref)
.expect("redundancy_equivalence — clean reference sign must succeed");
let mut sig_red = alloc::vec![0u8; sig_len];
let pk_red = super::fors_sign_into_redundant::<P>(&md, &sk_seed, &pk_seed, &adrs, &mut sig_red)
.expect("clean run must succeed");
assert_eq!(
sig_red, sig_ref,
"redundant signature must equal the non-redundant signature on a clean run"
);
let pk_classic = super::fors_pk_from_sig::<P>(&sig_red, &md, &pk_seed, &adrs);
assert_eq!(pk_red, pk_classic, "redundant-returned pk must match fors_pk_from_sig");
}
#[test]
fn fors_sign_into_redundant_matches_reference_shake128s() {
redundancy_equivalence::<Shake128s>(0x00, 0x00);
redundancy_equivalence::<Shake128s>(0x55, 0xAA);
redundancy_equivalence::<Shake128s>(0xFF, 0xFF);
}
#[test]
fn fors_sign_into_redundant_matches_reference_shake128f() {
redundancy_equivalence::<Shake128f>(0x00, 0x00);
redundancy_equivalence::<Shake128f>(0x55, 0xAA);
}
#[test]
fn fors_redundancy_compare_detects_divergence() {
let sig_a = alloc::vec![0x42u8; 32];
let sig_b_eq = sig_a.clone();
let mut sig_b_diff = sig_a.clone();
sig_b_diff[5] ^= 0x01;
let pk_a = alloc::vec![0xAAu8; 16];
let pk_b_eq = pk_a.clone();
let mut pk_b_diff = pk_a.clone();
pk_b_diff[3] ^= 0x80;
assert!(super::fors_redundancy_compare(&sig_a, &sig_b_eq, &pk_a, &pk_b_eq).is_ok());
assert!(matches!(
super::fors_redundancy_compare(&sig_a, &sig_b_diff, &pk_a, &pk_b_eq),
Err(SlhDsaError::FaultDetected)
));
assert!(matches!(
super::fors_redundancy_compare(&sig_a, &sig_b_eq, &pk_a, &pk_b_diff),
Err(SlhDsaError::FaultDetected)
));
assert!(matches!(
super::fors_redundancy_compare(&sig_a, &sig_b_diff, &pk_a, &pk_b_diff),
Err(SlhDsaError::FaultDetected)
));
}
#[cfg(feature = "sca-fors-indices-check")]
fn indices_check_accepts<P: Params>(seed_byte: u8) {
let md_bytes = (P::K * P::A + 7) / 8;
let md: Vec<u8> = (0..md_bytes).map(|i| seed_byte.wrapping_add(i as u8)).collect();
let indices = super::message_to_indices::<P>(&md);
assert!(super::fors_indices_consistency_check::<P>(&md, &indices).is_ok());
}
#[test]
#[cfg(feature = "sca-fors-indices-check")]
fn fors_indices_check_accepts_correct_shake128s() {
indices_check_accepts::<Shake128s>(0x00);
indices_check_accepts::<Shake128s>(0x42);
indices_check_accepts::<Shake128s>(0xFF);
}
#[test]
#[cfg(feature = "sca-fors-indices-check")]
fn fors_indices_check_accepts_correct_shake128f() {
indices_check_accepts::<Shake128f>(0x00);
indices_check_accepts::<Shake128f>(0x55);
}
#[test]
#[cfg(feature = "sca-fors-indices-check")]
fn fors_indices_check_rejects_flipped_index() {
type P = Shake128s;
let md_bytes = (P::K * P::A + 7) / 8;
let md: Vec<u8> = (0..md_bytes).map(|i| i as u8).collect();
let mut indices = super::message_to_indices::<P>(&md);
indices[0] ^= 1;
assert!(matches!(
super::fors_indices_consistency_check::<P>(&md, &indices),
Err(SlhDsaError::FaultDetected)
));
let mut short = super::message_to_indices::<P>(&md);
short.pop();
assert!(matches!(
super::fors_indices_consistency_check::<P>(&md, &short),
Err(SlhDsaError::FaultDetected)
));
}
}