use crate::sphincs::xmss::{xmss_node, xmss_pk_from_sig, xmss_sign, XmssParams, XmssSignature};
use crate::sphincs::Adrs;
pub struct HypertreeParams {
pub d: u8,
pub xmss: XmssParams,
pub h: u8,
}
impl HypertreeParams {
pub fn new(d: u8, xmss: XmssParams) -> Self {
debug_assert!(xmss.h_prime < 32, "h_prime must be < 32");
let h = d
.checked_mul(xmss.h_prime)
.expect("h = d * h_prime overflowed u8");
HypertreeParams { d, xmss, h }
}
}
pub struct HypertreeSignature {
pub xmss: Vec<XmssSignature>,
}
impl HypertreeSignature {
pub fn size_in_bytes(&self) -> usize {
self.xmss.iter().map(|s| s.size_in_bytes()).sum()
}
}
fn advance_indices(idx_tree: &mut u64, idx_leaf: &mut u32, h_prime: u8) {
*idx_leaf = (*idx_tree & ((1u64 << h_prime) - 1)) as u32;
*idx_tree >>= h_prime;
}
pub fn ht_pk_root(params: &HypertreeParams, sk_seed: &[u8], pk_seed: &[u8]) -> Vec<u8> {
let adrs = Adrs::new().with_xmss_height(params.d - 1);
xmss_node(¶ms.xmss, sk_seed, 0, params.xmss.h_prime, pk_seed, adrs)
}
pub fn ht_sign(
params: &HypertreeParams,
sk_seed: &[u8],
pk_seed: &[u8],
msg: &[u8],
mut idx_tree: u64,
mut idx_leaf: u32,
) -> HypertreeSignature {
let h_prime = params.xmss.h_prime;
let mut sigs = Vec::with_capacity(params.d as usize);
let mut to_sign: Vec<u8> = msg.to_vec();
for j in 0..params.d {
let adrs = Adrs::new().with_xmss_height(j).with_xmss_index(idx_tree);
let sig_j = xmss_sign(¶ms.xmss, sk_seed, &to_sign, idx_leaf, pk_seed, adrs);
if j < params.d - 1 {
to_sign = xmss_pk_from_sig(¶ms.xmss, idx_leaf, &sig_j, &to_sign, pk_seed, adrs);
advance_indices(&mut idx_tree, &mut idx_leaf, h_prime);
}
sigs.push(sig_j);
}
HypertreeSignature { xmss: sigs }
}
pub fn ht_verify(
params: &HypertreeParams,
pk_seed: &[u8],
pk_root: &[u8],
msg: &[u8],
sig: &HypertreeSignature,
mut idx_tree: u64,
mut idx_leaf: u32,
) -> bool {
if sig.xmss.len() != params.d as usize {
return false;
}
let h_prime = params.xmss.h_prime;
let mut node: Vec<u8> = msg.to_vec();
for j in 0..params.d {
let adrs = Adrs::new().with_xmss_height(j).with_xmss_index(idx_tree);
node = xmss_pk_from_sig(
¶ms.xmss,
idx_leaf,
&sig.xmss[j as usize],
&node,
pk_seed,
adrs,
);
if j < params.d - 1 {
advance_indices(&mut idx_tree, &mut idx_leaf, h_prime);
}
}
node == pk_root
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sphincs::params::{
FipsParams, FIPS_128F, FIPS_128S, FIPS_192F, FIPS_192S, FIPS_256F, FIPS_256S,
};
use crate::sphincs::winternitz_ots::WotsParams;
fn run_e2e(p: &FipsParams) {
let params = HypertreeParams::new(
p.d,
XmssParams {
h_prime: p.h_prime,
wots: WotsParams::new(p.n, p.lg_w),
},
);
let n = p.n as usize;
let pk_seed: Vec<u8> = (0..n).map(|i| 0xA0u8.wrapping_add(i as u8)).collect();
let sk_seed: Vec<u8> = (0..n).map(|i| 0x50u8.wrapping_add(i as u8)).collect();
let msg: Vec<u8> = (0..n).map(|i| 0x10u8.wrapping_add(i as u8)).collect();
let pk_root = ht_pk_root(¶ms, &sk_seed, &pk_seed);
let global_idx: u64 = if params.h >= 64 {
u64::MAX / 3
} else {
((1u64 << params.h) - 1) / 3
};
let idx_leaf = (global_idx & ((1u64 << p.h_prime) - 1)) as u32;
let idx_tree = global_idx >> p.h_prime;
let sig = ht_sign(¶ms, &sk_seed, &pk_seed, &msg, idx_tree, idx_leaf);
assert!(
ht_verify(¶ms, &pk_seed, &pk_root, &msg, &sig, idx_tree, idx_leaf),
"[{}] verify must accept good signature",
p.name,
);
let mut bad_msg = msg.clone();
bad_msg[0] ^= 1;
assert!(
!ht_verify(¶ms, &pk_seed, &pk_root, &bad_msg, &sig, idx_tree, idx_leaf),
"[{}] verify must reject tampered msg",
p.name,
);
let wrong_leaf = idx_leaf ^ 1;
assert!(
!ht_verify(¶ms, &pk_seed, &pk_root, &msg, &sig, idx_tree, wrong_leaf),
"[{}] verify must reject wrong idx_leaf",
p.name,
);
let num_elems = params.xmss.wots.num_elements() as usize;
let per_xmss = n * (num_elems + p.h_prime as usize);
assert_eq!(sig.size_in_bytes(), p.d as usize * per_xmss, "[{}]", p.name);
}
#[test]
fn test_ht_128s() {
run_e2e(&FIPS_128S);
}
#[test]
fn test_ht_128f() {
run_e2e(&FIPS_128F);
}
#[test]
fn test_ht_192s() {
run_e2e(&FIPS_192S);
}
#[test]
fn test_ht_192f() {
run_e2e(&FIPS_192F);
}
#[test]
fn test_ht_256s() {
run_e2e(&FIPS_256S);
}
#[test]
fn test_ht_256f() {
run_e2e(&FIPS_256F);
}
}