diff --git a/src/algs.rs b/src/algs.rs
deleted file mode 100644
index 04e7387..0000000
--- a/src/algs.rs
+++ /dev/null
@@ -1,1244 +0,0 @@
-use crate::hashers::Hashers;
-use crate::types::{
- Adrs, ForsPk, ForsSig, HtSig, SlhDsaSig, SlhPrivateKey, SlhPublicKey, WotsPk, WotsSig, XmssSig,
-};
-use crate::types::{FORS_PRF, FORS_ROOTS, FORS_TREE, TREE, WOTS_HASH, WOTS_PK, WOTS_PRF};
-use generic_array::{ArrayLength, GenericArray};
-use rand_core::CryptoRngCore;
-
-
-/// Algorithm 1: `toInt(X, n)` on page 14.
-/// Convert a byte string to an integer.
-///
-/// Input: n-byte string `X`, string length `n`.
-/// Output: Integer value of `X`.
-pub(crate) fn to_int(x: &[u8], n: u32) -> u64 {
- debug_assert_eq!(x.len(), n as usize);
- debug_assert!(n <= 8);
-
- // 1: total ← 0
- let mut total = 0;
-
- // 2:
- // 3: for i from 0 to n − 1 do
- for item in x.iter().take(n as usize) {
- //
- // 4: total ← 256 · total + X[i]
- total = (total << 8) + u64::from(*item);
-
- // 5: end for
- }
-
- // 6: return total
- total
-}
-
-
-/// Algorithm 2: `toByte(x, n)` on page 15.
-/// Convert an integer to a byte string.
-///
-/// Input: Integer `x`, string length `n`.
-/// Output: Byte string of length `n` containing binary representation of `x` in big-endian byte-order.
-pub(crate) fn to_byte(x: u32, n: u32) -> [u8; ((crate::LEN2 * crate::LGW + 7) / 8) as usize] {
- let mut s = [0u8; ((crate::LEN2 * crate::LGW + 7) / 8) as usize]; // Size fixed across all profiles (2)
- debug_assert_eq!(n, ((crate::LEN2 * crate::LGW + 7) / 8)); // just in case life changes
- debug_assert_eq!(n, 2); // optimize: this resolves into a two-byte (be) write!
-
- // 1: total ← x
- let mut total = x;
-
- // 2:
- // 3: for i from 0 to n − 1 do
- for i in 0..n {
- //
- // 4: S[n − 1 − i] ← total mod 256 ▷ Least significant 8 bits of total
- s[(n - 1 - i) as usize] = total.to_le_bytes()[0];
-
- // 5: total ← total ≫ 8
- total >>= 8;
-
- // 6: end for
- }
-
- // 7: return S
- s
-}
-
-
-/// Algorithm 3: `base_2^b(X, b, out_len)` on page 15.
-/// Compute the base 2^b representation of X.
-///
-/// Input: Byte string `X` of length at least `ceil(out_len·b/8)`, integer `b`, output length `out_len`.
-/// Output: Array of `out_len` integers in the range `[0, . . . , 2^b − 1]`.
-pub(crate) fn base_2b(x: &[u8], b: u32, out_len: u32, baseb: &mut [u32]) {
- debug_assert!(x.len() >= (out_len * b).div_ceil(8) as usize);
- debug_assert!(b < 16); // Consider optimizing `baseb` output to be u16
- debug_assert_eq!(out_len as usize, baseb.len());
-
- // 1: in ← 0
- let mut inn = 0;
-
- // 2: bits ← 0
- let mut bits = 0;
-
- // 3: total ← 0
- let mut total = 0;
-
- // 4:
- // 5: for out from 0 to out_len − 1 do
- for item in baseb.iter_mut() {
- //
- // 6: while bits < b do
- while bits < b {
- //
- // 7: total ← (total ≪ 8) + X[in]
- total = (total << 8) + u32::from(x[inn]);
-
- // 8: in ← in + 1
- inn += 1;
-
- // 9: bits ← bits + 8
- bits += 8;
-
- // 10: end while
- }
-
- // 11: bits ← bits − b
- bits -= b;
-
- // 12: baseb[out] ← (total ≫ bits) mod 2^b
- *item = (total >> bits) & (u32::MAX >> (32 - b));
-
- // 13: end for
- }
-
- // 14: return baseb (mutable parameter)
-}
-
-
-/// Algorithm 4: `chain(X, i, s, PK.seed, ADRS)` on page 17.
-/// Chaining function used in WOTS+. The chain function takes as input an n-byte string `X` and integers `s` and `i`
-/// and returns the result of iterating the hash function `F` on the input `s` times, starting from an index of `i`.
-/// The chain function also requires as input PK.seed, which is part of the SLH-DSA public key, and an address `ADRS`.
-/// The type in `ADRS` must be set to `WOTS_HASH`, and the layer address, tree address, key pair address, and chain
-/// address must be set to the address of the chain being computed. The chain function updates the hash address in
-/// `ADRS` with each iteration to specify the current position in the chain prior to ADRS’s use in `F`.
-///
-/// Input: Input string `X`, start index `i`, number of steps `s`, public seed `PK.seed`, address `ADRS`.
-/// Output: Value of `F` iterated `s` times on `X`.
-pub(crate) fn chain(
- hashers: &Hashers, cap_x: GenericArray, i: u32, s: u32, pk_seed: &[u8],
- adrs: &Adrs,
-) -> Option> {
- debug_assert!(i + s < u32::MAX);
- let mut adrs = adrs.clone();
-
- // 1: if (i + s) ≥ w then
- if (i + s) >= crate::W {
- //
- // 2: return NULL
- return None;
-
- // 3: end if
- }
-
- // 4:
- // 5: tmp ← X
- let mut tmp = cap_x;
-
- // 6:
- // 7: for j from i to i + s − 1 do
- for j in i..(i + s) {
- //
- // 8: ADRS.setHashAddress(j)
- adrs.set_hash_address(j);
-
- // 9: tmp ← F(PK.seed, ADRS, tmp)
- tmp = (hashers.f)(pk_seed, &adrs, &tmp);
-
- // 10: end for
- }
-
- // 11: return tmp
- Some(tmp)
-}
-
-
-/// Algorithm 5: `wots_PKgen(SK.seed, PK.seed, ADRS)` on page 18.
-/// Generate a WOTS+ public key. The `wots_PKgen` function generates WOTS+ public keys. It takes as input `SK.seed`
-/// and `PK.seed` from the SLH-DSA private key and an address. The type in the address `ADRS` must be set to
-/// `WOTS_HASH`, and the layer address, tree address, and key pair address must encode the address of the `WOTS+`
-/// public key to be generated.
-///
-/// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`.
-/// Output: WOTS+ public key `pk`.
-#[allow(clippy::similar_names)]
-pub(crate) fn wots_pkgen(
- hashers: &Hashers, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs,
-) -> Result, &'static str> {
- let mut adrs = adrs.clone();
- let mut tmp: GenericArray, LEN> = GenericArray::default();
-
- // 1: skADRS ← ADRS ▷ Copy address to create key generation key address
- let mut sk_adrs = adrs.clone();
-
- // 2: skADRS.setTypeAndClear(WOTS_PRF)
- sk_adrs.set_type_and_clear(WOTS_PRF);
-
- // 3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- sk_adrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 4: for i from 0 to len − 1 do
- for i in 0..LEN::to_u32() {
- //
- // 5: skADRS.setChainAddress(i)
- sk_adrs.set_chain_address(i);
-
- // 6: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i
- let sk = (hashers.prf)(pk_seed, sk_seed, &sk_adrs);
-
- // 7: ADRS.setChainAddress(i)
- adrs.set_chain_address(i);
-
- // 8: tmp[i] ← chain(sk, 0, w − 1, PK.seed, ADRS) ▷ Compute public value for chain i
- tmp[i as usize] =
- chain(hashers, sk, 0, crate::W - 1, pk_seed, &adrs).ok_or("chain broke")?;
-
- // 9: end for
- }
-
- // 10: wotspkADRS ← ADRS ▷ Copy address to create WOTS+ public key address
- let mut wotspk_adrs = adrs.clone();
-
- // 11: wotspkADRS.setTypeAndClear(WOTS_PK)
- wotspk_adrs.set_type_and_clear(WOTS_PK);
-
- // 12: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 13: pk ← Tlen (PK.seed, wotspkADRS, tmp) ▷ Compress public key
- let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
-
- // 14: return pk
- Ok(WotsPk(pk))
-}
-
-
-/// Algorithm 6: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 19.
-/// Generate a WOTS+ signature on an n-byte message.
-///
-/// Input: Message `M`, secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`.
-/// Output: WOTS+ signature sig.
-#[allow(clippy::similar_names)]
-pub(crate) fn wots_sign(
- hashers: &Hashers, m: &[u8], sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs,
-) -> WotsSig {
- let mut adrs = adrs.clone();
- let mut sig: WotsSig = WotsSig::default();
-
- // 1: csum ← 0
- let mut csum = 0_u32;
-
- // 2:
- // 3: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
- let mut msg = GenericArray::::default(); // note: 3 entries left over, used step 10
- base_2b(m, crate::LGW, 2 * N::to_u32(), &mut msg[0..(2 * N::to_usize())]);
-
- // 4:
- // 5: for i from 0 to len1 − 1 do ▷ Compute checksum
- for item in msg.iter().take(2 * N::to_usize()) {
- //
- // 6: csum ← csum + w − 1 − msg[i]
- csum += crate::W - 1 - *item;
-
- // 7: end for
- }
-
- // 8:
- // 9: csum ← csum ≪ ((8 − ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
- csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
-
- // 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
- base_2b(
- &to_byte(csum, (crate::LEN2 * crate::LGW).div_ceil(8)),
- crate::LGW,
- crate::LEN2,
- &mut msg[(2 * N::to_usize())..],
- );
-
- // 11:
- // 12: skADRS ← ADRS
- let mut sk_addrs = adrs.clone();
-
- // 13: skADRS.setTypeAndClear(WOTS_PRF)
- sk_addrs.set_type_and_clear(WOTS_PRF);
-
- // 14: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- sk_addrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 15: for i from 0 to len − 1 do
- //#[allow(clippy::cast_possible_truncation)] // step 19
- for (item, i) in msg.iter().zip(0u32..) {
- //
- // 16: skADRS.setChainAddress(i)
- sk_addrs.set_chain_address(i);
-
- // 17: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i
- let sk = (hashers.prf)(pk_seed, sk_seed, &sk_addrs);
-
- // 18: ADRS.setChainAddress(i)
- adrs.set_chain_address(i);
-
- // 19: sig[i] ← chain(sk, 0, msg[i], PK.seed, ADRS) ▷ Compute signature value for chain i
- sig.data[i as usize] = chain(hashers, sk, 0, *item, pk_seed, &adrs).unwrap();
-
- // 20: end for
- }
-
- // 21: return sig
- sig
-}
-
-
-/// Algorithm 7: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 20.
-/// Compute a WOTS+ public key from a message and its signature.
-///
-/// Input: WOTS+ signature `sig`, message `M`, public seed `PK.seed`, address `ADRS`.
-/// Output: WOTS+ public key `pksig` derived from `sig`.
-pub(crate) fn wots_pk_from_sig(
- hashers: &Hashers, sig: &WotsSig, m: &[u8], pk_seed: &[u8], adrs: &Adrs,
-) -> WotsPk {
- let mut adrs = adrs.clone();
- let mut tmp: GenericArray, LEN> = GenericArray::default();
-
- // 1: csum ← 0
- let mut csum = 0_u32;
-
- // 2:
- // 3: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
- let mut msg: GenericArray = GenericArray::default();
- base_2b(m, crate::LGW, 2 * N::to_u32(), &mut msg[0..(2 * N::to_usize())]);
-
- // 4:
- // 5: for i from 0 to len1 − 1 do ▷ Compute checksum
- for item in msg.iter().take(2 * N::to_usize()) {
- //
- // 6: csum ← csum + w − 1 − msg[i]
- csum += crate::W - 1 - item;
-
- // 7: end for
- }
-
- // 8:
- // 9: csum ← csum ≪ ((8 − ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
- csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
-
- // 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
- base_2b(
- &to_byte(csum, (crate::LEN2 * crate::LGW).div_ceil(8)),
- crate::LGW,
- crate::LEN2,
- &mut msg[(2 * N::to_usize())..],
- );
-
- // 11: for i from 0 to len − 1 do
- #[allow(clippy::cast_possible_truncation)] // steps 12 and 13
- for i in 0..LEN::to_usize() {
- //
- // 12: ADRS.setChainAddress(i)
- adrs.set_chain_address(i as u32);
-
- // 13: tmp[i] ← chain(sig[i], msg[i], w − 1 − msg[i], PK.seed, ADRS)
- tmp[i] = chain::(
- hashers,
- sig.data[i].clone(),
- msg[i],
- crate::W - 1 - msg[i],
- pk_seed,
- &adrs,
- )
- .expect("chain broke2!");
-
- // 14: end for
- }
-
- // 15: wotspkADRS ← ADRS
- let mut wotspk_adrs = adrs.clone();
-
- // 16: wotspkADRS.setTypeAndClear(WOTS_PK)
- wotspk_adrs.set_type_and_clear(WOTS_PK);
-
- // 17: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 18: pksig ← Tlen (PK.seed, wotspkADRS, tmp)
- let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
-
- // 19: return pksig
- WotsPk(pk)
-}
-
-
-/// Algorithm 8: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22.
-/// Compute the root of a Merkle subtree of WOTS+ public keys.
-///
-/// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
-/// `address ADRS`.
-/// Output: n-byte root `node`.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn xmss_node<
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
-) -> Result, &'static str> {
- let mut adrs = adrs.clone();
-
- // 1: if z > h′ or i ≥ 2^{h −z} then
- if (z > HP::to_u32()) | (u64::from(i) >= 2u64.pow(HP::to_u32() - z)) {
- //
- // 2: return NULL
- return Err("Alg8: fail");
-
- // 3: end if
- }
-
- // 4: if z = 0 then
- let node = if z == 0 {
- //
- // 5: ADRS.setTypeAndClear(WOTS_HASH)
- adrs.set_type_and_clear(WOTS_HASH);
-
- // 6: ADRS.setKeyPairAddress(i)
- adrs.set_key_pair_address(i);
-
- // 7: node ← wots_PKgen(SK.seed, PK.seed, ADRS)
- wots_pkgen::(hashers, sk_seed, pk_seed, &adrs)?
- .0
- .clone()
-
- // 8: else
- } else {
- //
- // 9: lnode ← xmss_node(SK.seed, 2 * i, z − 1, PK.seed, ADRS)
- let lnode =
- xmss_node::(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?;
-
- // 10: rnode ← xmss_node(SK.seed, 2 * i + 1, z − 1, PK.seed, ADRS)
- let rnode =
- xmss_node::(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?;
-
- // 11: ADRS.setTypeAndClear(TREE)
- adrs.set_type_and_clear(TREE);
-
- // 12: ADRS.setTreeHeight(z)
- adrs.set_tree_height(z);
-
- // 13: ADRS.setTreeIndex(i)
- adrs.set_tree_index(i);
-
- // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode)
- (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
-
- // 15: end if
- };
-
- // 16: return node
- Ok(node)
-}
-
-
-/// Algorithm 9: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23.
-/// Generate an XMSS signature.
-///
-/// Input: n-byte message `M`, secret seed `SK.seed`, index `idx`, public seed `PK.seed`, address `ADRS`.
-/// Output: XMSS signature SIGXMSS = (sig ∥ AUTH).
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn xmss_sign<
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, m: &[u8], sk_seed: &[u8], idx: u32, pk_seed: &[u8],
- adrs: &Adrs,
-) -> Result, &'static str> {
- let mut adrs = adrs.clone();
- let mut sig_xmss = XmssSig::default();
-
- // 1: for j from 0 to h′-1 do ▷ Build authentication path
- for j in 0..HP::to_u32() {
- //
- // 2: k ← idx/2 ^j xor 1
- let k = (idx >> j) ^ 1;
-
- // 3: AUTH[j] ← xmss_node(SK.seed, k, j, PK.seed, ADRS)
- sig_xmss.auth[j as usize] =
- xmss_node::(hashers, sk_seed, k, j, pk_seed, &adrs)?;
-
- // 4: end for
- }
-
- // 5:
- // 6: ADRS.setTypeAndClear(WOTS_HASH)
- adrs.set_type_and_clear(WOTS_HASH);
-
- // 7: ADRS.setKeyPairAddress(idx)
- adrs.set_key_pair_address(idx);
-
- // 8: sig ← wots_sign(M, SK.seed, PK.seed, ADRS)
- sig_xmss.sig_wots = wots_sign::(hashers, m, sk_seed, pk_seed, &adrs); // TODO: polish out BB!
-
- // 9: SIG_XMSS ← sig ∥ AUTH
- // struct built above
-
- // 10: return SIG_XMSS
- Ok(sig_xmss)
-}
-
-
-/// Algorithm 10: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)`
-/// Compute an XMSS public key from an XMSS signature.
-///
-/// Input: Index `idx`, XMSS signature `SIG_XMSS = (sig ∥ AUTH)`, n-byte message `M`, public seed `PK.seed`,
-/// address `ADRS`.
-/// Output: n-byte root value `node[0]`.
-pub(crate) fn xmss_pk_from_sig<
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, idx: u32, sig_xmss: &XmssSig, m: &[u8],
- pk_seed: &[u8], adrs: &Adrs,
-) -> GenericArray {
- let mut adrs = adrs.clone();
-
- // 1: ADRS.setTypeAndClear(WOTS_HASH) ▷ Compute WOTS+ pk from WOTS+ sig
- adrs.set_type_and_clear(WOTS_HASH);
-
- // 2: ADRS.setKeyPairAddress(idx)
- adrs.set_key_pair_address(idx);
-
- // 3: sig ← SIG_XMSS.getWOTSSig() ▷ SIG_XMSS [0 : len · n]
- let sig = sig_xmss.get_wots_sig();
-
- // 4: AUTH ← SIG_XMSS.getXMSSAUTH() ▷ SIG_XMSS [len · n : (len + h′) · n]
- let auth = sig_xmss.get_xmss_auth();
-
- // 5: node[0] ← wots_PKFromSig(sig, M, PK.seed, ADRS)
- let mut node_0 = wots_pk_from_sig::(hashers, sig, m, pk_seed, &adrs)
- .0
- .clone();
-
- // 6:
- // 7: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH
- adrs.set_type_and_clear(TREE);
-
- // 8: ADRS.setTreeIndex(idx)
- adrs.set_tree_index(idx);
-
- // 9: for k from 0 to h′ − 1 do
- for k in 0..HP::to_u32() {
- //
- // 10: ADRS.setTreeHeight(k + 1)
- adrs.set_tree_height(k + 1);
-
- // 11: if idx/2^k is even then
- #[allow(clippy::if_not_else)] // Follows the algorithm as written
- let node_1 = if ((idx >> k) & 1) == 0 {
- //
- // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
- let tmp = adrs.get_tree_index() / 2;
- adrs.set_tree_index(tmp);
-
- // 13: node[1] ← H(PK.seed, ADRS, node[0] ∥ AUTH[k])
- (hashers.h)(pk_seed, &adrs, &node_0, &auth[k as usize])
-
- // 14: else
- } else {
- //
- // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2)
- let tmp = (adrs.get_tree_index() - 1) / 2;
- adrs.set_tree_index(tmp);
-
- // 16: node[1] ← H(PK.seed, ADRS, AUTH[k] ∥ node[0])
- (hashers.h)(pk_seed, &adrs, &auth[k as usize], &node_0)
-
- // 17: end if
- };
-
- // 18: node[0] ← node[1]
- node_0 = node_1;
-
- // 19: end for
- }
-
- // 20: return node[0]
- node_0
-}
-
-
-/// Algorithm 11: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27.
-/// Generate a hypertree signature.
-///
-/// Input: Message `M`, private seed `SK.seed`, public seed `PK.seed`, tree index `idx_tree`, leaf
-/// index `idx_leaf`.
-/// Output: HT signature `SIG_HT`.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn ht_sign<
- D: ArrayLength,
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, m: &[u8], sk_seed: &[u8], pk_seed: &[u8], idx_tree: u64,
- idx_leaf: u32,
-) -> Result, &'static str> {
- let mut idx_tree = idx_tree;
- //
- // 1: ADRS ← toByte(0, 32)
- let mut adrs = Adrs::default();
-
- // 2:
- // 3: ADRS.setTreeAddress(idxtree)
- adrs.set_tree_address(idx_tree);
-
- // 4: SIG_tmp ← xmss_sign(M, SK.seed, idxleaf, PK.seed, ADRS)
- let mut sig_tmp =
- xmss_sign::(hashers, m, sk_seed, idx_leaf, pk_seed, &adrs)?;
-
- // 5: SIG_HT ← SIG_tmp
- let mut sig_ht = HtSig::default();
- sig_ht.xmss_sigs[0] = sig_tmp.clone();
-
- // 6: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, M, PK.seed, ADRS)
- let mut root =
- xmss_pk_from_sig::(hashers, idx_leaf, &sig_tmp, m, pk_seed, &adrs);
-
- // 7: for j from 1 to d − 1 do
- for j in 1..D::to_u32() {
- //
- // 8: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree
- let idx_leaf = u32::try_from(idx_tree % 2u64.pow(HP::to_u32()))
- .map_err(|_| "Alg11: oversized idx leaf")?;
-
- // 9: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree
- idx_tree >>= HP::to_u32();
-
- // 10: ADRS.setLayerAddress(j)
- adrs.set_layer_address(j);
-
- // 11: ADRS.setTreeAddress(idx_tree)
- adrs.set_tree_address(idx_tree);
-
- // 12: SIG_tmp ← xmss_sign(root, SK.seed, idx_leaf, PK.seed, ADRS)
- sig_tmp =
- xmss_sign::(hashers, &root, sk_seed, idx_leaf, pk_seed, &adrs)?;
-
- // 13: SIG_HT ← SIG_HT ∥ SIG_tmp
- sig_ht.xmss_sigs[j as usize] = sig_tmp.clone();
-
- // 14: if j < d − 1 then
- if j < (D::to_u32() - 1) {
- //
- // 15: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, root, PK.seed, ADRS)
- root = xmss_pk_from_sig::(
- hashers, idx_leaf, &sig_tmp, &root, pk_seed, &adrs,
- );
-
- // 16: end if
- }
-
- // 17: end for
- }
-
- // 18: return SIGHT
- Ok(sig_ht)
-}
-
-
-/// Algorithm 12: `ht_verify(M, SIG_HT, PK.seed, idx_tree, idx_leaf, PK.root)` on page 28.
-/// Verify a hypertree signature.
-///
-/// Input: Message `M`, signature `SIG_HT`, public seed `PK.seed`, tree index `idx_tree`, leaf index `idx_leaf`,
-/// HT public key `PK.root`.
-/// Output: Boolean.
-pub(crate) fn ht_verify<
- D: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, m: &[u8], sig_ht: &HtSig, pk_seed: &[u8],
- idx_tree: u64, idx_leaf: u32, pk_root: &GenericArray,
-) -> bool {
- let mut idx_tree = idx_tree;
- //
- // 1: ADRS ← toByte(0, 32)
- let mut adrs = Adrs::default();
-
- // 2:
- // 3: ADRS.setTreeAddress(idx_tree)
- adrs.set_tree_address(idx_tree);
-
- // 4: SIG_tmp ← SIG_HT.getXMSSSignature(0) ▷ SIG_HT [0 : (h′ + len) · n]
- let sig_tmp = sig_ht.xmss_sigs[0].clone();
-
- // 5: node ← xmss_PKFromSig(idx_leaf, SIG_tmp, M, PK.seed, ADRS)
- let mut node = xmss_pk_from_sig(hashers, idx_leaf, &sig_tmp, m, pk_seed, &adrs);
-
- // 6: for j from 1 to d − 1 do
- for j in 1..D::to_u32() {
- //
- // 7: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree
- let idx_leaf = u32::try_from(idx_tree % 2u64.pow(HP::to_u32())); // TODO: clean
- if idx_leaf.is_err() {
- return false;
- };
- let idx_leaf = idx_leaf.unwrap();
-
- // 8: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree
- idx_tree >>= HP::to_u32();
-
- // 9: ADRS.setLayerAddress(j)
- adrs.set_layer_address(j);
-
- // 10: ADRS.setTreeAddress(idx_tree)
- adrs.set_tree_address(idx_tree);
-
- // 11: SIG_tmp ← SIG_HT.getXMSSSignature(j) ▷ SIGHT [ j · (h′ + len) · n : ( j + 1)(h′ + len) · n]
- let sig_tmp = sig_ht.xmss_sigs[j as usize].clone();
-
- // 12: node ← xmss_PKFromSig(idx_leaf, SIG_tmp, node, PK.seed, ADRS)
- node = xmss_pk_from_sig(hashers, idx_leaf, &sig_tmp, &node, pk_seed, &adrs);
-
- // 13: end for
- }
-
- // 14: if node = PK.root then
- // 15: return true
- // 16: else
- // 17: return false
- // 18: end if
- node == *pk_root // TODO: CT equal
-}
-
-
-/// Algorithm 13: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29.
-/// Generate a FORS private-key value.
-///
-/// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`, secret key index `idx`.
-/// Output: n-byte FORS private-key value.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn fors_sk_gen(
- hashers: &Hashers, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs, idx: u32,
-) -> GenericArray {
- // 1: skADRS ← ADRS ▷ Copy address to create key generation address
- let mut sk_adrs = adrs.clone();
-
- // 2: skADRS.setTypeAndClear(FORS_PRF)
- sk_adrs.set_type_and_clear(FORS_PRF);
-
- // 3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- sk_adrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 4: skADRS.setTreeIndex(idx)
- sk_adrs.set_tree_index(idx);
-
- // 5: return PRF(PK.seed, SK.seed, skADRS)
- (hashers.prf)(pk_seed, sk_seed, &sk_adrs)
-}
-
-
-/// Algorithm 14: `fors_node(SK.seed, i, z, PK.seed, ADRS)` on page 30.
-/// Compute the root of a Merkle subtree of FORS public values.
-///
-/// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
-/// address `ADRS`.
-/// Output: n-byte root node.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn fors_node<
- A: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
-) -> Result, &'static str> {
- let mut adrs = adrs.clone();
-
- // 1: if z > a or i ≥ k · 2^(a−z) then
- if (z > A::to_u32()) | (i > K::to_u32() * 2u32.pow(A::to_u32() - z)) {
- //
- // 2: return NULL
- return Err("Alg14 fails");
-
- // 3: end if
- }
-
- // 4: if z = 0 then
- let node = if z == 0 {
- //
- // 5: sk ← fors_SKgen(SK.seed, PK.seed, ADRS, i)
- let sk: GenericArray = fors_sk_gen(hashers, sk_seed, pk_seed, &adrs, i);
-
- // 6: ADRS.setTreeHeight(0)
- adrs.set_tree_height(0);
-
- // 7: ADRS.setTreeIndex(i)
- adrs.set_tree_index(i);
-
- // 8: node ← F(PK.seed, ADRS, sk)
- (hashers.f)(pk_seed, &adrs, &sk)
-
- // 9: else
- } else {
- //
- // 10: lnode ← fors_node(SK.seed, 2i, z − 1, PK.seed, ADRS)
- let lnode = fors_node::(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?;
-
- // 11: rnode ← fors_node(SK.seed, 2i + 1, z − 1, PK.seed, ADRS)
- let rnode =
- fors_node::(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?;
-
- // 12: ADRS.setTreeHeight(z)
- adrs.set_tree_height(z);
-
- // 13: ADRS.setTreeIndex(i)
- adrs.set_tree_index(i);
-
- // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode)
- (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
-
- // 15: end if
- };
-
- // 16: return node
- Ok(node)
-}
-
-
-/// Algorithm 15: `fors_sign(md, SK.seed, PK.seed, ADRS)`
-/// Generate a FORS signature.
-///
-/// Input: Message digest `md`, secret seed `SK.seed`, address `ADRS`, public seed `PK.seed`.
-/// Output: FORS signature `SIG_FORS`.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn fors_sign<
- A: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, md: &[u8], sk_seed: &[u8], adrs: &Adrs, pk_seed: &[u8],
-) -> Result, &'static str> {
- // 1: SIG_FORS = NULL ▷ Initialize SIG_FORS as a zero-length byte string
- let mut sig_fors = ForsSig::default();
-
- // 2: indices ← base_2^b(md, a, k)
- let mut indices: GenericArray = GenericArray::default();
- base_2b(md, A::to_u32(), K::to_u32(), &mut indices);
-
-
- // 3: for i from 0 to k − 1 do ▷ Compute signature elements
- #[allow(clippy::cast_possible_truncation)]
- for i in 0..K::to_u32() {
- //
- // 4: SIG_FORS ← SIG_FORS ∥ fors_SKgen(SK.seed, PK.seed, ADRS, i · 2^a + indices[i])
- sig_fors.private_key_value[i as usize] = fors_sk_gen::(
- hashers,
- sk_seed,
- pk_seed,
- adrs,
- i * 2u32.pow(A::to_u32()) + indices[i as usize],
- );
-
- // 5:
- // 6: for j from 0 to a − 1 do ▷ Compute auth path
- for j in 0..A::to_u32() {
- //
- // 7: s ← indices[i]/2^j xor 1
- let s = (indices[i as usize] >> j) ^ 1;
-
- // 8: AUTH[j] ← fors_node(SK.seed, i · 2^{a−j} + s, j, PK.seed, ADRS)
- sig_fors.auth[i as usize].tree[j as usize] = fors_node::(
- hashers,
- sk_seed,
- i * 2u32.pow(A::to_u32() - j) + s,
- j,
- pk_seed,
- adrs,
- )?;
-
- // 9: end for
- }
-
- // 10: SIG_FORS ← SIG_FORS ∥ AUTH
- // built within inner loop above
-
- // 11: end for
- }
-
- // 12: return SIG_FORS
- Ok(sig_fors)
-}
-
-
-/// Algorithm 16: `fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)` on page 32.
-/// Compute a FORS public key from a FORS signature.
-///
-/// Input: FORS signature `SIG_FORS`, message digest `md`, public seed `PK.seed`, address `ADRS`.
-/// Output: FORS public key.
-pub(crate) fn fors_pk_from_sig<
- A: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, sig_fors: &ForsSig, md: &[u8], pk_seed: &[u8],
- adrs: &Adrs,
-) -> ForsPk {
- let mut adrs = adrs.clone();
-
- // 1: indices ← base_2^b(md, a, k)
- let mut indices: GenericArray = GenericArray::default();
- base_2b(md, A::to_u32(), K::to_u32(), &mut indices);
-
-
- // 2: for i from 0 to k − 1 do
- let mut root: GenericArray, K> = GenericArray::default();
- #[allow(clippy::cast_possible_truncation)] // Step 5
- for i in 0..K::to_u32() {
- //
- // 3: sk ← SIG_FORS.getSK(i) ▷ SIG_FORS [i · (a + 1) · n : (i · (a + 1) + 1) · n]
- let sk = sig_fors.private_key_value[i as usize].clone();
-
- // 4: ADRS.setTreeHeight(0) ▷ Compute leaf
- adrs.set_tree_height(0);
-
- // 5: ADRS.setTreeIndex(i · 2^a + indices[i])
- adrs.set_tree_index(i * 2u32.pow(A::to_u32()) + indices[i as usize]);
-
- // 6: node[0] ← F(PK.seed, ADRS, sk)
- let mut node_0 = (hashers.f)(pk_seed, &adrs, &sk);
-
- // 7:
- // 8: auth ← SIGFORS.getAUTH(i) ▷ SIGFORS [(i · (a + 1) + 1) · n : (i + 1) · (a + 1) · n]
- let auth = sig_fors.auth[i as usize].clone();
-
- // 9: for j from 0 to a − 1 do ▷ Compute root from leaf and AUTH
- for j in 0..A::to_u32() {
- //
- // 10: ADRS.setTreeHeight(j + 1)
- adrs.set_tree_height(j + 1);
-
- // 11: if indices[i]/2^j is even then
- let node_1 = if ((indices[i as usize] >> j) % 2) == 0 {
- //
- // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
- let tmp = adrs.get_tree_index() / 2;
- adrs.set_tree_index(tmp);
-
- // 13: node[1] ← H(PK.seed, ADRS, node[0] ∥ auth[j])
- (hashers.h)(pk_seed, &adrs, &node_0, &auth.tree[j as usize])
-
- // 14: else
- } else {
- //
- // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2)
- let tmp = (adrs.get_tree_index() - 1) / 2;
- adrs.set_tree_index(tmp);
-
- // 16: node[1] ← H(PK.seed, ADRS, auth[j] ∥ node[0])
- (hashers.h)(pk_seed, &adrs, &auth.tree[j as usize], &node_0)
-
- // 17: end if
- };
-
- // 18: node[0] ← node[1]
- node_0 = node_1;
-
- // 19: end for
- }
-
- // 20: root[i] ← node[0]
- root[i as usize] = node_0;
-
- // 21: end for
- }
-
- // 22: forspkADRS ← ADRS ▷ Compute the FORS public key from the Merkle tree roots
- let mut fors_pk_adrs = adrs.clone();
-
- // 23: forspkADRS.setTypeAndClear(FORS_ROOTS)
- fors_pk_adrs.set_type_and_clear(FORS_ROOTS);
-
- // 24: forspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
- fors_pk_adrs.set_key_pair_address(adrs.get_key_pair_address());
-
- // 25: pk ← Tk(PK.seed, forspkADRS, root)
- let pk = (hashers.t_len)(pk_seed, &fors_pk_adrs, &root);
-
- // 26: return pk;
- ForsPk { key: pk }
-}
-
-
-/// Algorithm 17: `slh_keygen()` on page 34.
-/// Generate an SLH-DSA key pair.
-///
-/// Input: (none)
-/// Output: SLH-DSA key pair `(SK, PK)`.
-#[allow(clippy::similar_names)] // sk_seed and pk_seed
-pub(crate) fn slh_keygen_with_rng<
- D: ArrayLength,
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- rng: &mut impl CryptoRngCore, hashers: &Hashers,
-) -> Result<(SlhPrivateKey, SlhPublicKey), &'static str> {
- // 1: SK.seed ←$ B^n ▷ Set SK.seed, SK.prf, and PK.seed to random n-byte
- let mut sk_seed = GenericArray::default();
- rng.try_fill_bytes(&mut sk_seed)
- .map_err(|_| "Alg17: rng failed1")?;
-
- // 2: SK.prf ←$ B^n ▷ strings using an approved random bit generator
- let mut sk_prf = GenericArray::default();
- rng.try_fill_bytes(&mut sk_prf)
- .map_err(|_| "Alg17: rng failed2")?;
-
- // 3: PK.seed ←$ B^n
- let mut pk_seed = GenericArray::default();
- rng.try_fill_bytes(&mut pk_seed)
- .map_err(|_| "Alg17: rng failed3")?;
-
- // 4:
- // 5: ADRS ← toByte(0, 32) ▷ Generate the public key for the top-level XMSS tree
- let mut adrs = Adrs::default();
-
- // 6: ADRS.setLayerAddress(d − 1)
- adrs.set_layer_address(D::to_u32() - 1);
-
- // 7: PK.root ← xmss_node(SK.seed, 0, h′, PK.seed, ADRS)
- let pk_root =
- xmss_node::(hashers, &sk_seed, 0, HP::to_u32(), &pk_seed, &adrs)?;
-
- // 8:
- // 9: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )
- let pk = SlhPublicKey { pk_seed: pk_seed.clone(), pk_root: pk_root.clone() };
- let sk = SlhPrivateKey { sk_seed, sk_prf, pk_seed, pk_root };
- Ok((sk, pk))
-}
-
-
-/// Algorithm 18: `slh_sign(M, SK)` on page 35.
-/// Generate an SLH-DSA signature.
-///
-/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`.
-/// Output: SLH-DSA signature `SIG`.
-#[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes
-pub(crate) fn slh_sign_with_rng<
- A: ArrayLength,
- D: ArrayLength,
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- rng: &mut impl CryptoRngCore, hashers: &Hashers, m: &[u8], sk: &SlhPrivateKey,
- randomize: bool,
-) -> Result, &'static str> {
- // 1: ADRS ← toByte(0, 32)
- let mut adrs = Adrs::default();
-
- // 2:
- // 3: opt_rand ← PK.seed ▷ Set opt_rand to either PK.seed
- let mut opt_rand = sk.pk_seed.clone();
-
- // 4: if (RANDOMIZE) then ▷ or to a random n-byte string
- if randomize {
- // 5: opt_rand ←$ Bn
- rng.try_fill_bytes(&mut opt_rand)
- .map_err(|_| "Alg17: rng failed")?;
-
- // 6: end if
- }
-
- // 7: R ← PRF_msg(SK.prf, opt_rand, M) ▷ Generate randomizer
- let r = (hashers.prf_msg)(&sk.sk_prf, &opt_rand, m);
-
- // 8: SIG ← R
- let mut sig = SlhDsaSig::default();
- sig.randomness = r.clone();
-
- // 9:
- // 10: digest ← H_msg(R, PK.seed, PK.root, M) ▷ Compute message digest
- let digest = (hashers.h_msg)(&r, &sk.pk_seed, &sk.pk_root, m);
-
- // 11: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes
- let index1 = (K::to_usize() * A::to_usize()).div_ceil(8);
- let md = &digest[0..index1];
-
- // 12: tmp_idx_tree ← digest[ceil(k·a/8) : ceil(k·a/8) + ceil((h-h/d)/8)] ▷ next ceil((h-h/d)/8) bytes
- let index2 = index1 + (H::to_usize() - H::to_usize() / D::to_usize()).div_ceil(8);
- let tmp_idx_tree = &digest[index1..index2];
-
- // 13: tmp_idx_leaf ← digest[ceil(k·a/8) + ceil((h-h/d)/8) : ceil(k·a/8) + ceil((h-h/d)/8) + ceil(h/8d)] ▷ next ceil(h/8d) bytes
- let index3 = index2 + H::to_usize().div_ceil(8 * D::to_usize());
- let tmp_idx_leaf = &digest[index2..index3];
-
- // 14:
- // 15: idx_tree ← toInt(tmp_idx_tree, ceil((h-h/d)/8)) mod 2^{h−h/d}
- let idx_tree = to_int(tmp_idx_tree, (H::to_u32() - H::to_u32() / D::to_u32()).div_ceil(8))
- & (u64::MAX >> (64 - (H::to_u32() - H::to_u32() / D::to_u32())));
-
- // 16: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d}
- let idx_leaf = to_int(tmp_idx_leaf, H::to_u32().div_ceil(8 * D::to_u32()))
- & (u64::MAX >> (64 - H::to_u32() / D::to_u32()));
-
- // 17:
- // 18: ADRS.setTreeAddress(idx_tree)
- adrs.set_tree_address(idx_tree);
-
- // 19: ADRS.setTypeAndClear(FORS_TREE)
- adrs.set_type_and_clear(FORS_TREE);
-
- // 20: ADRS.setKeyPairAddress(idxleaf)
- adrs.set_key_pair_address(idx_leaf as u32);
-
- // 21: SIG_FORS ← fors_sign(md, SK.seed, PK.seed, ADRS)
- // 22: SIG ← SIG ∥ SIG_FORS
- sig.fors_sig = fors_sign(hashers, md, &sk.sk_seed, &adrs, &sk.pk_seed)?;
-
- // 23:
- // 24: PK_FORS ← fors_pkFromSig(SIG_FORS , md, PK.seed, ADRS) ▷ Get FORS key
- let pk_fors =
- fors_pk_from_sig::(hashers, &sig.fors_sig, md, &sk.pk_seed, &adrs);
-
- // 25:
- // 26: SIG_HT ← ht_sign(PK_FORS , SK.seed, PK.seed, idx_tree, idx_leaf)
- // 27: SIG ← SIG ∥ SIG_HT
- sig.ht_sig = ht_sign::(
- hashers,
- &pk_fors.key,
- &sk.sk_seed,
- &sk.pk_seed,
- idx_tree,
- idx_leaf as u32,
- )?;
-
- // 28: return SIG
- Ok(sig)
-}
-
-
-/// Algorithm 19: `slh_verify(M, SIG, PK)`
-/// Verify an SLH-DSA signature.
-///
-/// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`.
-/// Output: Boolean.
-#[allow(clippy::cast_possible_truncation)] // TODO: temporary
-pub(crate) fn slh_verify<
- A: ArrayLength,
- D: ArrayLength,
- H: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- M: ArrayLength,
- N: ArrayLength,
->(
- hashers: &Hashers, m: &[u8], sig: &SlhDsaSig,
- pk: &SlhPublicKey,
-) -> bool {
- // 1: if |SIG| != (1 + k(1 + a) + h + d · len) · n then
- // 2: return false
- // 3: end if
- // The above size is performed in the wrapper/adapter deserialize function
-
- // 4: ADRS ← toByte(0, 32)
- let mut adrs = Adrs::default();
-
- // 5: R ← SIG.getR() ▷ SIG[0 : n]
- let r = &sig.randomness;
-
- // 6: SIG_FORS ← SIG.getSIG_FORS() ▷ SIG[n : (1 + k(1 + a)) · n]
- let sig_fors = &sig.fors_sig;
-
- // 7: SIG_HT ← SIG.getSIG_HT() ▷ SIG[(1 + k(1 + a)) · n : (1 + k(1 + a) + h + d · len) · n]
- let sig_ht = &sig.ht_sig;
-
- // 8:
- // 9: digest ← Hmsg(R, PK.seed, PK.root, M) ▷ Compute message digest
- let digest = (hashers.h_msg)(r, &pk.pk_seed, &pk.pk_root, m);
-
- // 10: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes
- let index1 = (K::to_usize() * A::to_usize()).div_ceil(8);
- let md = &digest[0..index1];
-
- // 11: tmp_idx_tree ← digest[ceil(k·a/8) : ceil(k·a/8) + ceil((h - h/d)/8)] ▷ next ceil((h - h/d)/8) bytes
- let index2 = index1 + (H::to_usize() - H::to_usize() / D::to_usize()).div_ceil(8);
- let tmp_idx_tree = &digest[index1..index2];
-
- // 12: tmp_idx_leaf ← digest[ceil(k·a/8) + ceil((h - h/d)/8) : ceil(k·a/8) + ceil((h - h/d)/8) + ceil(h/8d)] ▷ next ceil(h/8d) bytes
- let index3 = index2 + H::to_usize().div_ceil(8 * D::to_usize());
- let tmp_idx_leaf = &digest[index2..index3];
-
- // 13:
- // 14: idx_tree ← toInt(tmp_idx_tree, ceil((h - h/d)/8)) mod 2^{h−h/d}
- let idx_tree = to_int(tmp_idx_tree, (H::to_u32() - H::to_u32() / D::to_u32()).div_ceil(8))
- & (u64::MAX >> (64 - (H::to_u32() - H::to_u32() / D::to_u32())));
-
- // 15: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d}
- let idx_leaf = to_int(tmp_idx_leaf, H::to_u32().div_ceil(8 * D::to_u32()))
- & (u64::MAX >> (64 - H::to_u32() / D::to_u32()));
-
- // 16:
- // 17: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key
- adrs.set_tree_address(idx_tree);
-
- // 18: ADRS.setTypeAndClear(FORS_TREE)
- adrs.set_type_and_clear(FORS_TREE);
-
- // 19: ADRS.setKeyPairAddress(idx_leaf)
- adrs.set_key_pair_address(idx_leaf as u32);
-
- // 20:
- // 21: PK_FORS ← fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)
- let pk_fors = fors_pk_from_sig::(hashers, sig_fors, md, &pk.pk_seed, &adrs);
-
-
- // 22:
- // 23: return ht_verify(PK_FORS, SIG_HT, PK.seed, idx_tree , idx_leaf, PK.root)
- ht_verify::(
- hashers,
- &pk_fors.key,
- sig_ht,
- &pk.pk_seed,
- idx_tree,
- idx_leaf as u32,
- &pk.pk_root,
- )
-}
diff --git a/src/fors.rs b/src/fors.rs
new file mode 100644
index 0000000..efc7b87
--- /dev/null
+++ b/src/fors.rs
@@ -0,0 +1,265 @@
+use crate::hashers::Hashers;
+use crate::helpers;
+use crate::types::{Adrs, ForsPk, ForsSig, FORS_PRF, FORS_ROOTS};
+use generic_array::{ArrayLength, GenericArray};
+
+
+/// Algorithm 13: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29.
+/// Generate a FORS private-key value.
+///
+/// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`, secret key index `idx`.
+/// Output: n-byte FORS private-key value.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn fors_sk_gen(
+ hashers: &Hashers, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs, idx: u32,
+) -> GenericArray {
+ // 1: skADRS ← ADRS ▷ Copy address to create key generation address
+ let mut sk_adrs = adrs.clone();
+
+ // 2: skADRS.setTypeAndClear(FORS_PRF)
+ sk_adrs.set_type_and_clear(FORS_PRF);
+
+ // 3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ sk_adrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 4: skADRS.setTreeIndex(idx)
+ sk_adrs.set_tree_index(idx);
+
+ // 5: return PRF(PK.seed, SK.seed, skADRS)
+ (hashers.prf)(pk_seed, sk_seed, &sk_adrs)
+}
+
+
+/// Algorithm 14: `fors_node(SK.seed, i, z, PK.seed, ADRS)` on page 30.
+/// Compute the root of a Merkle subtree of FORS public values.
+///
+/// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
+/// address `ADRS`.
+/// Output: n-byte root node.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn fors_node<
+ A: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
+) -> Result, &'static str> {
+ let mut adrs = adrs.clone();
+
+ // 1: if z > a or i ≥ k · 2^(a−z) then
+ if (z > A::to_u32()) | (i > K::to_u32() * 2u32.pow(A::to_u32() - z)) {
+ //
+ // 2: return NULL
+ return Err("Alg14 fails");
+
+ // 3: end if
+ }
+
+ // 4: if z = 0 then
+ let node = if z == 0 {
+ //
+ // 5: sk ← fors_SKgen(SK.seed, PK.seed, ADRS, i)
+ let sk: GenericArray = fors_sk_gen(hashers, sk_seed, pk_seed, &adrs, i);
+
+ // 6: ADRS.setTreeHeight(0)
+ adrs.set_tree_height(0);
+
+ // 7: ADRS.setTreeIndex(i)
+ adrs.set_tree_index(i);
+
+ // 8: node ← F(PK.seed, ADRS, sk)
+ (hashers.f)(pk_seed, &adrs, &sk)
+
+ // 9: else
+ } else {
+ //
+ // 10: lnode ← fors_node(SK.seed, 2i, z − 1, PK.seed, ADRS)
+ let lnode = fors_node::(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?;
+
+ // 11: rnode ← fors_node(SK.seed, 2i + 1, z − 1, PK.seed, ADRS)
+ let rnode =
+ fors_node::(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?;
+
+ // 12: ADRS.setTreeHeight(z)
+ adrs.set_tree_height(z);
+
+ // 13: ADRS.setTreeIndex(i)
+ adrs.set_tree_index(i);
+
+ // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode)
+ (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
+
+ // 15: end if
+ };
+
+ // 16: return node
+ Ok(node)
+}
+
+
+/// Algorithm 15: `fors_sign(md, SK.seed, PK.seed, ADRS)`
+/// Generate a FORS signature.
+///
+/// Input: Message digest `md`, secret seed `SK.seed`, address `ADRS`, public seed `PK.seed`.
+/// Output: FORS signature `SIG_FORS`.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn fors_sign<
+ A: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, md: &[u8], sk_seed: &[u8], adrs: &Adrs, pk_seed: &[u8],
+) -> Result, &'static str> {
+ // 1: SIG_FORS = NULL ▷ Initialize SIG_FORS as a zero-length byte string
+ let mut sig_fors = ForsSig::default();
+
+ // 2: indices ← base_2^b(md, a, k)
+ let mut indices: GenericArray = GenericArray::default();
+ helpers::base_2b(md, A::to_u32(), K::to_u32(), &mut indices);
+
+ // 3: for i from 0 to k − 1 do ▷ Compute signature elements
+ #[allow(clippy::cast_possible_truncation)]
+ for i in 0..K::to_u32() {
+ //
+ // 4: SIG_FORS ← SIG_FORS ∥ fors_SKgen(SK.seed, PK.seed, ADRS, i · 2^a + indices[i])
+ sig_fors.private_key_value[i as usize] = fors_sk_gen::(
+ hashers,
+ sk_seed,
+ pk_seed,
+ adrs,
+ i * 2u32.pow(A::to_u32()) + indices[i as usize],
+ );
+
+ // 5:
+ // 6: for j from 0 to a − 1 do ▷ Compute auth path
+ for j in 0..A::to_u32() {
+ //
+ // 7: s ← indices[i]/2^j xor 1
+ let s = (indices[i as usize] >> j) ^ 1;
+
+ // 8: AUTH[j] ← fors_node(SK.seed, i · 2^{a−j} + s, j, PK.seed, ADRS)
+ sig_fors.auth[i as usize].tree[j as usize] = fors_node::(
+ hashers,
+ sk_seed,
+ i * 2u32.pow(A::to_u32() - j) + s,
+ j,
+ pk_seed,
+ adrs,
+ )?;
+
+ // 9: end for
+ }
+
+ // 10: SIG_FORS ← SIG_FORS ∥ AUTH
+ // built within inner loop above
+
+ // 11: end for
+ }
+
+ // 12: return SIG_FORS
+ Ok(sig_fors)
+}
+
+
+/// Algorithm 16: `fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)` on page 32.
+/// Compute a FORS public key from a FORS signature.
+///
+/// Input: FORS signature `SIG_FORS`, message digest `md`, public seed `PK.seed`, address `ADRS`.
+/// Output: FORS public key.
+pub(crate) fn fors_pk_from_sig<
+ A: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, sig_fors: &ForsSig, md: &[u8], pk_seed: &[u8],
+ adrs: &Adrs,
+) -> ForsPk {
+ let mut adrs = adrs.clone();
+
+ // 1: indices ← base_2^b(md, a, k)
+ let mut indices: GenericArray = GenericArray::default();
+ helpers::base_2b(md, A::to_u32(), K::to_u32(), &mut indices);
+
+ // 2: for i from 0 to k − 1 do
+ let mut root: GenericArray, K> = GenericArray::default();
+ #[allow(clippy::cast_possible_truncation)] // Step 5
+ for i in 0..K::to_u32() {
+ //
+ // 3: sk ← SIG_FORS.getSK(i) ▷ SIG_FORS [i · (a + 1) · n : (i · (a + 1) + 1) · n]
+ let sk = sig_fors.private_key_value[i as usize].clone();
+
+ // 4: ADRS.setTreeHeight(0) ▷ Compute leaf
+ adrs.set_tree_height(0);
+
+ // 5: ADRS.setTreeIndex(i · 2^a + indices[i])
+ adrs.set_tree_index(i * 2u32.pow(A::to_u32()) + indices[i as usize]);
+
+ // 6: node[0] ← F(PK.seed, ADRS, sk)
+ let mut node_0 = (hashers.f)(pk_seed, &adrs, &sk);
+
+ // 7:
+ // 8: auth ← SIGFORS.getAUTH(i) ▷ SIGFORS [(i · (a + 1) + 1) · n : (i + 1) · (a + 1) · n]
+ let auth = sig_fors.auth[i as usize].clone();
+
+ // 9: for j from 0 to a − 1 do ▷ Compute root from leaf and AUTH
+ for j in 0..A::to_u32() {
+ //
+ // 10: ADRS.setTreeHeight(j + 1)
+ adrs.set_tree_height(j + 1);
+
+ // 11: if indices[i]/2^j is even then
+ let node_1 = if ((indices[i as usize] >> j) % 2) == 0 {
+ //
+ // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
+ let tmp = adrs.get_tree_index() / 2;
+ adrs.set_tree_index(tmp);
+
+ // 13: node[1] ← H(PK.seed, ADRS, node[0] ∥ auth[j])
+ (hashers.h)(pk_seed, &adrs, &node_0, &auth.tree[j as usize])
+
+ // 14: else
+ } else {
+ //
+ // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2)
+ let tmp = (adrs.get_tree_index() - 1) / 2;
+ adrs.set_tree_index(tmp);
+
+ // 16: node[1] ← H(PK.seed, ADRS, auth[j] ∥ node[0])
+ (hashers.h)(pk_seed, &adrs, &auth.tree[j as usize], &node_0)
+
+ // 17: end if
+ };
+
+ // 18: node[0] ← node[1]
+ node_0 = node_1;
+
+ // 19: end for
+ }
+
+ // 20: root[i] ← node[0]
+ root[i as usize] = node_0;
+
+ // 21: end for
+ }
+
+ // 22: forspkADRS ← ADRS ▷ Compute the FORS public key from the Merkle tree roots
+ let mut fors_pk_adrs = adrs.clone();
+
+ // 23: forspkADRS.setTypeAndClear(FORS_ROOTS)
+ fors_pk_adrs.set_type_and_clear(FORS_ROOTS);
+
+ // 24: forspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ fors_pk_adrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 25: pk ← Tk(PK.seed, forspkADRS, root)
+ let pk = (hashers.t_len)(pk_seed, &fors_pk_adrs, &root);
+
+ // 26: return pk;
+ ForsPk { key: pk }
+}
diff --git a/src/hashers.rs b/src/hashers.rs
index 8dc70f1..cc8eb8c 100644
--- a/src/hashers.rs
+++ b/src/hashers.rs
@@ -1,7 +1,5 @@
-use crate::types::Adrs;
use generic_array::{ArrayLength, GenericArray};
-
-// TODO: We can do a bit better extracting common functionality versus parameter sets...
+use crate::types::Adrs;
// Holds hasher function references; constructed by each wrapper
@@ -28,10 +26,10 @@ pub(crate) struct Hashers
+/// Output: Integer value of `X`.
+pub(crate) fn to_int(x: &[u8], n: u32) -> u64 {
+ debug_assert_eq!(x.len(), n as usize);
+ debug_assert!(n <= 8);
+
+ // 1: total ← 0
+ let mut total = 0;
+
+ // 2:
+ // 3: for i from 0 to n − 1 do
+ for item in x.iter().take(n as usize) {
+ //
+ // 4: total ← 256 · total + X[i]
+ total = (total << 8) + u64::from(*item);
+
+ // 5: end for
+ }
+
+ // 6: return total
+ total
+}
+
+
+/// Algorithm 2: `toByte(x, n)` on page 15.
+/// Convert an integer to a byte string.
+///
+/// Input: Integer `x`, string length `n`.
+/// Output: Byte string of length `n` containing binary representation of `x` in big-endian byte-order.
+pub(crate) fn to_byte(x: u32, n: u32) -> [u8; ((crate::LEN2 * crate::LGW + 7) / 8) as usize] {
+ let mut s = [0u8; ((crate::LEN2 * crate::LGW + 7) / 8) as usize]; // Size fixed across all profiles (2)
+ debug_assert_eq!(n, ((crate::LEN2 * crate::LGW + 7) / 8)); // just in case life changes
+ debug_assert_eq!(n, 2); // optimize: this resolves into a two-byte (be) write!
+
+ // 1: total ← x
+ let mut total = x;
+
+ // 2:
+ // 3: for i from 0 to n − 1 do
+ for i in 0..n {
+ //
+ // 4: S[n − 1 − i] ← total mod 256 ▷ Least significant 8 bits of total
+ s[(n - 1 - i) as usize] = total.to_le_bytes()[0];
+
+ // 5: total ← total ≫ 8
+ total >>= 8;
+
+ // 6: end for
+ }
+
+ // 7: return S
+ s
+}
+
+
+/// Algorithm 3: `base_2^b(X, b, out_len)` on page 15.
+/// Compute the base 2^b representation of X.
+///
+/// Input: Byte string `X` of length at least `ceil(out_len·b/8)`, integer `b`, output length `out_len`.
+/// Output: Array of `out_len` integers in the range `[0, . . . , 2^b − 1]`.
+pub(crate) fn base_2b(x: &[u8], b: u32, out_len: u32, baseb: &mut [u32]) {
+ debug_assert!(x.len() >= (out_len * b).div_ceil(8) as usize);
+ debug_assert!(b < 16); // Consider optimizing `baseb` output to be u16
+ debug_assert_eq!(out_len as usize, baseb.len());
+
+ // 1: in ← 0
+ let mut inn = 0;
+
+ // 2: bits ← 0
+ let mut bits = 0;
+
+ // 3: total ← 0
+ let mut total = 0;
+
+ // 4:
+ // 5: for out from 0 to out_len − 1 do
+ for item in baseb.iter_mut() {
+ //
+ // 6: while bits < b do
+ while bits < b {
+ //
+ // 7: total ← (total ≪ 8) + X[in]
+ total = (total << 8) + u32::from(x[inn]);
+
+ // 8: in ← in + 1
+ inn += 1;
+
+ // 9: bits ← bits + 8
+ bits += 8;
+
+ // 10: end while
+ }
+
+ // 11: bits ← bits − b
+ bits -= b;
+
+ // 12: baseb[out] ← (total ≫ bits) mod 2^b
+ *item = (total >> bits) & (u32::MAX >> (32 - b));
+
+ // 13: end for
+ }
+
+ // 14: return baseb (mutable parameter)
+}
+
+
+impl<
+ A: ArrayLength,
+ D: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ N: ArrayLength,
+ > SlhDsaSig
+{
+ pub fn deserialize(self) -> [u8; SIG_LEN] {
+ let mut out = [0u8; SIG_LEN];
+ debug_assert_eq!(
+ out.len(),
+ N::to_usize() + // randomness
+ N::to_usize() * K::to_usize() + K::to_usize() * A::to_usize() * N::to_usize() + // ForsSig
+ D::to_usize() * (HP::to_usize() * N::to_usize() + LEN::to_usize() * N::to_usize())
+ );
+ out[0..N::to_usize()].copy_from_slice(&self.randomness);
+ let mut start = N::to_usize();
+ for k in 0..K::to_usize() {
+ out[start..(start + N::to_usize())]
+ .copy_from_slice(&self.fors_sig.private_key_value[k]);
+ start += N::to_usize();
+ for a in 0..A::to_usize() {
+ out[start..(start + N::to_usize())].copy_from_slice(&self.fors_sig.auth[k].tree[a]);
+ start += N::to_usize();
+ }
+ }
+ for d in 0..D::to_usize() {
+ //println!("and we move to xmss {} starting at {}", d, start);
+ for len in 0..LEN::to_usize() {
+ out[start..(start + N::to_usize())]
+ .copy_from_slice(&self.ht_sig.xmss_sigs[d].sig_wots.data[len]);
+ start += N::to_usize();
+ }
+ for hp in 0..HP::to_usize() {
+ out[start..(start + N::to_usize())]
+ .copy_from_slice(&self.ht_sig.xmss_sigs[d].auth[hp]);
+ start += N::to_usize();
+ }
+ }
+ debug_assert_eq!(start, out.len());
+ out
+ }
+
+
+ pub fn serialize(bytes: &[u8]) -> Self {
+ debug_assert_eq!(
+ bytes.len(),
+ N::to_usize() + // randomness
+ N::to_usize() * K::to_usize() + K::to_usize() * A::to_usize() * N::to_usize() + // ForsSig
+ D::to_usize() * (HP::to_usize() * N::to_usize() + LEN::to_usize() * N::to_usize())
+ );
+ let mut output = Self::default();
+ output.randomness.copy_from_slice(&bytes[0..N::to_usize()]);
+ let mut start = N::to_usize();
+ for k in 0..K::to_usize() {
+ output.fors_sig.private_key_value[k]
+ .copy_from_slice(&bytes[start..(start + N::to_usize())]);
+ start += N::to_usize();
+ for a in 0..A::to_usize() {
+ output.fors_sig.auth[k].tree[a]
+ .copy_from_slice(&bytes[start..(start + N::to_usize())]);
+ start += N::to_usize();
+ }
+ }
+ for d in 0..D::to_usize() {
+ for len in 0..LEN::to_usize() {
+ output.ht_sig.xmss_sigs[d].sig_wots.data[len]
+ .copy_from_slice(&bytes[start..(start + N::to_usize())]);
+ start += N::to_usize();
+ }
+ for hp in 0..HP::to_usize() {
+ output.ht_sig.xmss_sigs[d].auth[hp]
+ .copy_from_slice(&bytes[start..(start + N::to_usize())]);
+ start += N::to_usize();
+ }
+ }
+ debug_assert_eq!(start, bytes.len());
+ output
+ }
+}
+
+
+impl Adrs {
+ pub(crate) fn set_layer_address(&mut self, la: u32) { self.f0 = la.to_be_bytes() }
+
+ pub(crate) fn get_key_pair_address(&self) -> u32 { u32::from_be_bytes(self.f5) }
+
+ pub(crate) fn set_key_pair_address(&mut self, kp_addr: u32) { self.f5 = kp_addr.to_be_bytes(); }
+
+ #[allow(clippy::cast_possible_truncation)]
+ pub(crate) fn set_chain_address(&mut self, i: u32) { self.f6 = i.to_be_bytes(); }
+
+ pub(crate) fn set_type_and_clear(&mut self, type_t: u32) {
+ self.f4 = type_t.to_be_bytes();
+ self.f5 = 0u32.to_be_bytes();
+ self.f6 = 0u32.to_be_bytes();
+ self.f7 = 0u32.to_be_bytes();
+ }
+
+ #[allow(clippy::cast_possible_truncation)]
+ pub(crate) fn set_tree_address(&mut self, t: u64) {
+ self.f2 = ((t >> 32) as u32).to_be_bytes();
+ self.f3 = (t as u32).to_be_bytes();
+ }
+
+ pub(crate) fn set_hash_address(&mut self, addr: u32) { self.f7 = addr.to_be_bytes() }
+
+ pub(crate) fn set_tree_height(&mut self, z: u32) { self.f6 = z.to_be_bytes() }
+
+ pub(crate) fn get_tree_index(&mut self) -> u32 { u32::from_be_bytes(self.f7) }
+
+ pub(crate) fn set_tree_index(&mut self, i: u32) { self.f7 = i.to_be_bytes() }
+
+ pub(crate) fn to_32_bytes(&self) -> [u8; 32] {
+ let mut ret = [0u8; 32];
+ let mut start = 0;
+ for sl in [
+ self.f0, self.f1, self.f2, self.f3, self.f4, self.f5, self.f6, self.f7,
+ ] {
+ ret[start..start + 4].copy_from_slice(&sl);
+ start += 4;
+ }
+ ret
+ }
+
+ pub(crate) fn to_22_bytes(&self) -> [u8; 22] {
+ let mut ret = [0u8; 22];
+ ret[0] = self.f0[3];
+ ret[1..5].copy_from_slice(&self.f2);
+ ret[5..9].copy_from_slice(&self.f3);
+ ret[9] = self.f4[3];
+ ret[10..14].copy_from_slice(&self.f5);
+ ret[14..18].copy_from_slice(&self.f6);
+ ret[18..22].copy_from_slice(&self.f7);
+ ret
+ }
+}
diff --git a/src/hypertree.rs b/src/hypertree.rs
new file mode 100644
index 0000000..da01483
--- /dev/null
+++ b/src/hypertree.rs
@@ -0,0 +1,156 @@
+use generic_array::{ArrayLength, GenericArray};
+use crate::hashers::Hashers;
+use crate::types::{Adrs, HtSig};
+use crate::xmss;
+
+
+/// Algorithm 11: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27.
+/// Generate a hypertree signature.
+///
+/// Input: Message `M`, private seed `SK.seed`, public seed `PK.seed`, tree index `idx_tree`, leaf
+/// index `idx_leaf`.
+/// Output: HT signature `SIG_HT`.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn ht_sign<
+ D: ArrayLength,
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, m: &[u8], sk_seed: &[u8], pk_seed: &[u8], idx_tree: u64,
+ idx_leaf: u32,
+) -> Result, &'static str> {
+ let mut idx_tree = idx_tree;
+ //
+ // 1: ADRS ← toByte(0, 32)
+ let mut adrs = Adrs::default();
+
+ // 2:
+ // 3: ADRS.setTreeAddress(idxtree)
+ adrs.set_tree_address(idx_tree);
+
+ // 4: SIG_tmp ← xmss_sign(M, SK.seed, idxleaf, PK.seed, ADRS)
+ let mut sig_tmp =
+ xmss::xmss_sign::(hashers, m, sk_seed, idx_leaf, pk_seed, &adrs)?;
+
+ // 5: SIG_HT ← SIG_tmp
+ let mut sig_ht = HtSig::default();
+ sig_ht.xmss_sigs[0] = sig_tmp.clone();
+
+ // 6: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, M, PK.seed, ADRS)
+ let mut root =
+ xmss::xmss_pk_from_sig::(hashers, idx_leaf, &sig_tmp, m, pk_seed, &adrs);
+
+ // 7: for j from 1 to d − 1 do
+ for j in 1..D::to_u32() {
+ //
+ // 8: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree
+ let idx_leaf = u32::try_from(idx_tree % 2u64.pow(HP::to_u32()))
+ .map_err(|_| "Alg11: oversized idx leaf")?;
+
+ // 9: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree
+ idx_tree >>= HP::to_u32();
+
+ // 10: ADRS.setLayerAddress(j)
+ adrs.set_layer_address(j);
+
+ // 11: ADRS.setTreeAddress(idx_tree)
+ adrs.set_tree_address(idx_tree);
+
+ // 12: SIG_tmp ← xmss_sign(root, SK.seed, idx_leaf, PK.seed, ADRS)
+ sig_tmp = xmss::xmss_sign::(
+ hashers, &root, sk_seed, idx_leaf, pk_seed, &adrs,
+ )?;
+
+ // 13: SIG_HT ← SIG_HT ∥ SIG_tmp
+ sig_ht.xmss_sigs[j as usize] = sig_tmp.clone();
+
+ // 14: if j < d − 1 then
+ if j < (D::to_u32() - 1) {
+ //
+ // 15: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, root, PK.seed, ADRS)
+ root = xmss::xmss_pk_from_sig::(
+ hashers, idx_leaf, &sig_tmp, &root, pk_seed, &adrs,
+ );
+
+ // 16: end if
+ }
+
+ // 17: end for
+ }
+
+ // 18: return SIGHT
+ Ok(sig_ht)
+}
+
+
+/// Algorithm 12: `ht_verify(M, SIG_HT, PK.seed, idx_tree, idx_leaf, PK.root)` on page 28.
+/// Verify a hypertree signature.
+///
+/// Input: Message `M`, signature `SIG_HT`, public seed `PK.seed`, tree index `idx_tree`, leaf index `idx_leaf`,
+/// HT public key `PK.root`.
+/// Output: Boolean.
+pub(crate) fn ht_verify<
+ D: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, m: &[u8], sig_ht: &HtSig, pk_seed: &[u8],
+ idx_tree: u64, idx_leaf: u32, pk_root: &GenericArray,
+) -> bool {
+ let mut idx_tree = idx_tree;
+ //
+ // 1: ADRS ← toByte(0, 32)
+ let mut adrs = Adrs::default();
+
+ // 2:
+ // 3: ADRS.setTreeAddress(idx_tree)
+ adrs.set_tree_address(idx_tree);
+
+ // 4: SIG_tmp ← SIG_HT.getXMSSSignature(0) ▷ SIG_HT [0 : (h′ + len) · n]
+ let sig_tmp = sig_ht.xmss_sigs[0].clone();
+
+ // 5: node ← xmss_PKFromSig(idx_leaf, SIG_tmp, M, PK.seed, ADRS)
+ let mut node = xmss::xmss_pk_from_sig(hashers, idx_leaf, &sig_tmp, m, pk_seed, &adrs);
+
+ // 6: for j from 1 to d − 1 do
+ for j in 1..D::to_u32() {
+ //
+ // 7: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree
+ let idx_leaf = u32::try_from(idx_tree % 2u64.pow(HP::to_u32())); // TODO: clean
+ if idx_leaf.is_err() {
+ return false;
+ };
+ let idx_leaf = idx_leaf.unwrap();
+
+ // 8: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree
+ idx_tree >>= HP::to_u32();
+
+ // 9: ADRS.setLayerAddress(j)
+ adrs.set_layer_address(j);
+
+ // 10: ADRS.setTreeAddress(idx_tree)
+ adrs.set_tree_address(idx_tree);
+
+ // 11: SIG_tmp ← SIG_HT.getXMSSSignature(j) ▷ SIGHT [ j · (h′ + len) · n : ( j + 1)(h′ + len) · n]
+ let sig_tmp = sig_ht.xmss_sigs[j as usize].clone();
+
+ // 12: node ← xmss_PKFromSig(idx_leaf, SIG_tmp, node, PK.seed, ADRS)
+ node = xmss::xmss_pk_from_sig(hashers, idx_leaf, &sig_tmp, &node, pk_seed, &adrs);
+
+ // 13: end for
+ }
+
+ // 14: if node = PK.root then
+ // 15: return true
+ // 16: else
+ // 17: return false
+ // 18: end if
+ node == *pk_root // TODO: CT equal
+}
diff --git a/src/lib.rs b/src/lib.rs
index 207d40c..ae69858 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,16 +6,18 @@
/// TKTK crate doc
// TODO
// 1. General clean-up
-// 2. SerDes on keys
-// 3. Proper traits and non-rng functions
-// 4. Adrs as raw bytes
-// 6. Separate into proper files
// 7. Doc, of course!
-mod algs;
+mod fors;
mod hashers;
+mod helpers;
+mod hypertree;
+mod slh;
mod test;
mod traits;
mod types;
+mod wots;
+mod xmss;
+
// Per eqns 5.1-4 on page 16, LGW=4, W=16 and LEN2=3 are constant across all security parameter sets.
const LGW: u32 = 4;
@@ -31,6 +33,7 @@ macro_rules! functionality {
use rand_core::CryptoRngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
+
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct PublicKey(SlhPublicKey);
@@ -40,6 +43,7 @@ macro_rules! functionality {
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KG(); // Arguable how useful an empty struct+trait is...
+
/// blah
/// # Errors
impl KeyGen for KG {
@@ -49,16 +53,20 @@ macro_rules! functionality {
fn try_keygen_with_rng_vt(
rng: &mut impl CryptoRngCore,
) -> Result<(PublicKey, PrivateKey), &'static str> {
- let res = crate::algs::slh_keygen_with_rng::(rng, &HASHERS);
+ let res = crate::slh::slh_keygen_with_rng::(rng, &HASHERS);
res.map(|(sk, pk)| (PublicKey(pk), PrivateKey(sk)))
}
}
+
+ /// blah
+ /// # Errors
#[cfg(feature = "default-rng")]
pub fn try_keygen_vt() -> Result<(PublicKey, PrivateKey), &'static str> {
KG::try_keygen_vt()
}
+
impl Signer for PrivateKey {
type Signature = [u8; SIG_LEN];
@@ -67,28 +75,30 @@ macro_rules! functionality {
fn try_sign_with_rng_ct(
&self, rng: &mut impl CryptoRngCore, m: &[u8], randomize: bool,
) -> Result<[u8; SIG_LEN], &'static str> {
- let sig = crate::algs::slh_sign_with_rng::(
+ let sig = crate::slh::slh_sign_with_rng::(
rng, &HASHERS, &m, &self.0, randomize,
);
sig.map(|s| s.deserialize())
}
}
+
impl Verifier for PublicKey {
type Signature = [u8; SIG_LEN];
/// blah
- #[must_use]
- fn try_verify_vt(&self,
- m: &[u8], sig_bytes: &[u8; SIG_LEN],
+ fn try_verify_vt(
+ &self, m: &[u8], sig_bytes: &[u8; SIG_LEN],
) -> Result {
let sig = SlhDsaSig::::serialize(sig_bytes);
- let res =
- crate::algs::slh_verify::(&HASHERS, &m, &sig, &self.0);
+ let res = crate::slh::slh_verify::(
+ &HASHERS, &m, &sig, &self.0,
+ );
Ok(res)
}
}
+
impl SerDes for PublicKey {
type ByteArray = [u8; PK_LEN];
@@ -108,6 +118,7 @@ macro_rules! functionality {
}
}
+
impl SerDes for PrivateKey {
type ByteArray = [u8; SK_LEN];
@@ -133,6 +144,7 @@ macro_rules! functionality {
}
}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -177,6 +189,7 @@ pub mod slh_dsa_sha2_128s {
type K = U14;
type M = U30;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 7856;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -202,6 +215,7 @@ pub mod slh_dsa_shake_128s {
type K = U14;
type M = U30;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 7856;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -227,6 +241,7 @@ pub mod slh_dsa_sha2_128f {
type K = U33;
type M = U34;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 17088;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -252,6 +267,7 @@ pub mod slh_dsa_shake_128f {
type K = U33;
type M = U34;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 17088;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -277,6 +293,7 @@ pub mod slh_dsa_sha2_192s {
type K = U17;
type M = U39;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 16224;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -302,6 +319,7 @@ pub mod slh_dsa_shake_192s {
type K = U17;
type M = U39;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 16224;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -327,6 +345,7 @@ pub mod slh_dsa_sha2_192f {
type K = U33;
type M = U42;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 35664;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -352,6 +371,7 @@ pub mod slh_dsa_shake_192f {
type K = U33;
type M = U42;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 35664;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -377,6 +397,7 @@ pub mod slh_dsa_sha2_256s {
type K = U22;
type M = U47;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 29792;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -402,6 +423,7 @@ pub mod slh_dsa_shake_256s {
type K = U22;
type M = U47;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 29792;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -427,6 +449,7 @@ pub mod slh_dsa_sha2_256f {
type K = U35;
type M = U49;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 49856;
pub const SK_LEN: usize = PK_LEN * 2;
@@ -452,6 +475,7 @@ pub mod slh_dsa_shake_256f {
type K = U35;
type M = U49;
type Len = Sum, U3>;
+
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 49856;
pub const SK_LEN: usize = PK_LEN * 2;
diff --git a/src/slh.rs b/src/slh.rs
new file mode 100644
index 0000000..1f0d062
--- /dev/null
+++ b/src/slh.rs
@@ -0,0 +1,261 @@
+use generic_array::{ArrayLength, GenericArray};
+use rand_core::CryptoRngCore;
+use crate::hashers::Hashers;
+use crate::types::FORS_TREE;
+use crate::types::{Adrs, SlhDsaSig, SlhPrivateKey, SlhPublicKey};
+use crate::{fors, helpers, hypertree, xmss};
+
+
+/// Algorithm 17: `slh_keygen()` on page 34.
+/// Generate an SLH-DSA key pair.
+///
+/// Input: (none)
+/// Output: SLH-DSA key pair `(SK, PK)`.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn slh_keygen_with_rng<
+ D: ArrayLength,
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ rng: &mut impl CryptoRngCore, hashers: &Hashers,
+) -> Result<(SlhPrivateKey, SlhPublicKey), &'static str> {
+ //
+ // 1: SK.seed ←$ B^n ▷ Set SK.seed, SK.prf, and PK.seed to random n-byte
+ let mut sk_seed = GenericArray::default();
+ rng.try_fill_bytes(&mut sk_seed)
+ .map_err(|_| "Alg17: rng failed1")?;
+
+ // 2: SK.prf ←$ B^n ▷ strings using an approved random bit generator
+ let mut sk_prf = GenericArray::default();
+ rng.try_fill_bytes(&mut sk_prf)
+ .map_err(|_| "Alg17: rng failed2")?;
+
+ // 3: PK.seed ←$ B^n
+ let mut pk_seed = GenericArray::default();
+ rng.try_fill_bytes(&mut pk_seed)
+ .map_err(|_| "Alg17: rng failed3")?;
+
+ // 4:
+ // 5: ADRS ← toByte(0, 32) ▷ Generate the public key for the top-level XMSS tree
+ let mut adrs = Adrs::default();
+
+ // 6: ADRS.setLayerAddress(d − 1)
+ adrs.set_layer_address(D::to_u32() - 1);
+
+ // 7: PK.root ← xmss_node(SK.seed, 0, h′, PK.seed, ADRS)
+ let pk_root = xmss::xmss_node::(
+ hashers,
+ &sk_seed,
+ 0,
+ HP::to_u32(),
+ &pk_seed,
+ &adrs,
+ )?;
+
+ // 8:
+ // 9: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )
+ let pk = SlhPublicKey { pk_seed: pk_seed.clone(), pk_root: pk_root.clone() };
+ let sk = SlhPrivateKey { sk_seed, sk_prf, pk_seed, pk_root };
+ Ok((sk, pk))
+}
+
+
+/// Algorithm 18: `slh_sign(M, SK)` on page 35.
+/// Generate an SLH-DSA signature.
+///
+/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`.
+/// Output: SLH-DSA signature `SIG`.
+#[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes
+pub(crate) fn slh_sign_with_rng<
+ A: ArrayLength,
+ D: ArrayLength,
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ rng: &mut impl CryptoRngCore, hashers: &Hashers, m: &[u8], sk: &SlhPrivateKey,
+ randomize: bool,
+) -> Result, &'static str> {
+ //
+ // 1: ADRS ← toByte(0, 32)
+ let mut adrs = Adrs::default();
+
+ // 2:
+ // 3: opt_rand ← PK.seed ▷ Set opt_rand to either PK.seed
+ let mut opt_rand = sk.pk_seed.clone();
+
+ // 4: if (RANDOMIZE) then ▷ or to a random n-byte string
+ if randomize {
+ // 5: opt_rand ←$ Bn
+ rng.try_fill_bytes(&mut opt_rand)
+ .map_err(|_| "Alg17: rng failed")?;
+
+ // 6: end if
+ }
+
+ // 7: R ← PRF_msg(SK.prf, opt_rand, M) ▷ Generate randomizer
+ let r = (hashers.prf_msg)(&sk.sk_prf, &opt_rand, m);
+
+ // 8: SIG ← R
+ let mut sig = SlhDsaSig::default();
+ sig.randomness = r.clone();
+
+ // 9:
+ // 10: digest ← H_msg(R, PK.seed, PK.root, M) ▷ Compute message digest
+ let digest = (hashers.h_msg)(&r, &sk.pk_seed, &sk.pk_root, m);
+
+ // 11: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes
+ let index1 = (K::to_usize() * A::to_usize()).div_ceil(8);
+ let md = &digest[0..index1];
+
+ // 12: tmp_idx_tree ← digest[ceil(k·a/8) : ceil(k·a/8) + ceil((h-h/d)/8)] ▷ next ceil((h-h/d)/8) bytes
+ let index2 = index1 + (H::to_usize() - H::to_usize() / D::to_usize()).div_ceil(8);
+ let tmp_idx_tree = &digest[index1..index2];
+
+ // 13: tmp_idx_leaf ← digest[ceil(k·a/8) + ceil((h-h/d)/8) : ceil(k·a/8) + ceil((h-h/d)/8) + ceil(h/8d)] ▷ next ceil(h/8d) bytes
+ let index3 = index2 + H::to_usize().div_ceil(8 * D::to_usize());
+ let tmp_idx_leaf = &digest[index2..index3];
+
+ // 14:
+ // 15: idx_tree ← toInt(tmp_idx_tree, ceil((h-h/d)/8)) mod 2^{h−h/d}
+ let idx_tree =
+ helpers::to_int(tmp_idx_tree, (H::to_u32() - H::to_u32() / D::to_u32()).div_ceil(8))
+ & (u64::MAX >> (64 - (H::to_u32() - H::to_u32() / D::to_u32())));
+
+ // 16: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d}
+ let idx_leaf = helpers::to_int(tmp_idx_leaf, H::to_u32().div_ceil(8 * D::to_u32()))
+ & (u64::MAX >> (64 - H::to_u32() / D::to_u32()));
+
+ // 17:
+ // 18: ADRS.setTreeAddress(idx_tree)
+ adrs.set_tree_address(idx_tree);
+
+ // 19: ADRS.setTypeAndClear(FORS_TREE)
+ adrs.set_type_and_clear(FORS_TREE);
+
+ // 20: ADRS.setKeyPairAddress(idxleaf)
+ adrs.set_key_pair_address(idx_leaf as u32);
+
+ // 21: SIG_FORS ← fors_sign(md, SK.seed, PK.seed, ADRS)
+ // 22: SIG ← SIG ∥ SIG_FORS
+ sig.fors_sig = fors::fors_sign(hashers, md, &sk.sk_seed, &adrs, &sk.pk_seed)?;
+
+ // 23:
+ // 24: PK_FORS ← fors_pkFromSig(SIG_FORS , md, PK.seed, ADRS) ▷ Get FORS key
+ let pk_fors =
+ fors::fors_pk_from_sig::(hashers, &sig.fors_sig, md, &sk.pk_seed, &adrs);
+
+ // 25:
+ // 26: SIG_HT ← ht_sign(PK_FORS , SK.seed, PK.seed, idx_tree, idx_leaf)
+ // 27: SIG ← SIG ∥ SIG_HT
+ sig.ht_sig = hypertree::ht_sign::(
+ hashers,
+ &pk_fors.key,
+ &sk.sk_seed,
+ &sk.pk_seed,
+ idx_tree,
+ idx_leaf as u32,
+ )?;
+
+ // 28: return SIG
+ Ok(sig)
+}
+
+
+/// Algorithm 19: `slh_verify(M, SIG, PK)`
+/// Verify an SLH-DSA signature.
+///
+/// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`.
+/// Output: Boolean.
+#[allow(clippy::cast_possible_truncation)] // TODO: temporary
+pub(crate) fn slh_verify<
+ A: ArrayLength,
+ D: ArrayLength,
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, m: &[u8], sig: &SlhDsaSig,
+ pk: &SlhPublicKey,
+) -> bool {
+ // 1: if |SIG| != (1 + k(1 + a) + h + d · len) · n then
+ // 2: return false
+ // 3: end if
+ // The above size is performed in the wrapper/adapter deserialize function
+
+ // 4: ADRS ← toByte(0, 32)
+ let mut adrs = Adrs::default();
+
+ // 5: R ← SIG.getR() ▷ SIG[0 : n]
+ let r = &sig.randomness;
+
+ // 6: SIG_FORS ← SIG.getSIG_FORS() ▷ SIG[n : (1 + k(1 + a)) · n]
+ let sig_fors = &sig.fors_sig;
+
+ // 7: SIG_HT ← SIG.getSIG_HT() ▷ SIG[(1 + k(1 + a)) · n : (1 + k(1 + a) + h + d · len) · n]
+ let sig_ht = &sig.ht_sig;
+
+ // 8:
+ // 9: digest ← Hmsg(R, PK.seed, PK.root, M) ▷ Compute message digest
+ let digest = (hashers.h_msg)(r, &pk.pk_seed, &pk.pk_root, m);
+
+ // 10: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes
+ let index1 = (K::to_usize() * A::to_usize()).div_ceil(8);
+ let md = &digest[0..index1];
+
+ // 11: tmp_idx_tree ← digest[ceil(k·a/8) : ceil(k·a/8) + ceil((h - h/d)/8)] ▷ next ceil((h - h/d)/8) bytes
+ let index2 = index1 + (H::to_usize() - H::to_usize() / D::to_usize()).div_ceil(8);
+ let tmp_idx_tree = &digest[index1..index2];
+
+ // 12: tmp_idx_leaf ← digest[ceil(k·a/8) + ceil((h - h/d)/8) : ceil(k·a/8) + ceil((h - h/d)/8) + ceil(h/8d)] ▷ next ceil(h/8d) bytes
+ let index3 = index2 + H::to_usize().div_ceil(8 * D::to_usize());
+ let tmp_idx_leaf = &digest[index2..index3];
+
+ // 13:
+ // 14: idx_tree ← toInt(tmp_idx_tree, ceil((h - h/d)/8)) mod 2^{h−h/d}
+ let idx_tree =
+ helpers::to_int(tmp_idx_tree, (H::to_u32() - H::to_u32() / D::to_u32()).div_ceil(8))
+ & (u64::MAX >> (64 - (H::to_u32() - H::to_u32() / D::to_u32())));
+
+ // 15: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d}
+ let idx_leaf = helpers::to_int(tmp_idx_leaf, H::to_u32().div_ceil(8 * D::to_u32()))
+ & (u64::MAX >> (64 - H::to_u32() / D::to_u32()));
+
+ // 16:
+ // 17: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key
+ adrs.set_tree_address(idx_tree);
+
+ // 18: ADRS.setTypeAndClear(FORS_TREE)
+ adrs.set_type_and_clear(FORS_TREE);
+
+ // 19: ADRS.setKeyPairAddress(idx_leaf)
+ adrs.set_key_pair_address(idx_leaf as u32);
+
+ // 20:
+ // 21: PK_FORS ← fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)
+ let pk_fors =
+ fors::fors_pk_from_sig::(hashers, sig_fors, md, &pk.pk_seed, &adrs);
+
+
+ // 22:
+ // 23: return ht_verify(PK_FORS, SIG_HT, PK.seed, idx_tree , idx_leaf, PK.root)
+ hypertree::ht_verify::(
+ hashers,
+ &pk_fors.key,
+ sig_ht,
+ &pk.pk_seed,
+ idx_tree,
+ idx_leaf as u32,
+ &pk.pk_root,
+ )
+}
diff --git a/src/test.rs b/src/test.rs
index 4bf0f12..0b7f0b2 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -1,18 +1,18 @@
#[cfg(test)]
mod tests {
extern crate alloc;
- use hex::decode;
use alloc::vec::Vec;
- //use rand::{Rng, SeedableRng};
- use crate::traits::{KeyGen, Signer, Verifier};
+ use hex::decode;
use rand_core::{CryptoRng, RngCore};
+ use crate::traits::{KeyGen, Signer, Verifier};
struct TestRng {
data: Vec>,
}
+
impl RngCore for TestRng {
fn next_u32(&mut self) -> u32 { unimplemented!() }
@@ -29,8 +29,10 @@ mod tests {
}
}
+
impl CryptoRng for TestRng {}
+
impl TestRng {
fn new() -> Self { TestRng { data: Vec::new() } }
@@ -43,7 +45,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_128s() {
- use crate::slh_dsa_shake_128s::{KG};
+ use crate::slh_dsa_shake_128s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -67,7 +69,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_128f() {
- use crate::slh_dsa_shake_128f::{KG};
+ use crate::slh_dsa_shake_128f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -91,7 +93,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_192s() {
- use crate::slh_dsa_shake_192s::{KG};
+ use crate::slh_dsa_shake_192s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -115,7 +117,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_192f() {
- use crate::slh_dsa_shake_192f::{KG};
+ use crate::slh_dsa_shake_192f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -139,7 +141,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_256s() {
- use crate::slh_dsa_shake_256s::{KG};
+ use crate::slh_dsa_shake_256s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -163,7 +165,7 @@ mod tests {
#[test]
fn vector_slh_dsa_shake_256f() {
- use crate::slh_dsa_shake_256f::{KG};
+ use crate::slh_dsa_shake_256f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -187,7 +189,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_128s() {
- use crate::slh_dsa_sha2_128s::{KG};
+ use crate::slh_dsa_sha2_128s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -211,7 +213,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_128f() {
- use crate::slh_dsa_sha2_128f::{KG};
+ use crate::slh_dsa_sha2_128f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -235,7 +237,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_192s() {
- use crate::slh_dsa_sha2_192s::{KG};
+ use crate::slh_dsa_sha2_192s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -259,7 +261,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_192f() {
- use crate::slh_dsa_sha2_192f::{KG};
+ use crate::slh_dsa_sha2_192f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -283,7 +285,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_256s() {
- use crate::slh_dsa_sha2_256s::{KG};
+ use crate::slh_dsa_sha2_256s::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
@@ -307,7 +309,7 @@ mod tests {
#[test]
fn vector_slh_dsa_sha2_256f() {
- use crate::slh_dsa_sha2_256f::{KG};
+ use crate::slh_dsa_sha2_256f::KG;
let m =
decode("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8").unwrap();
let mut rnd = TestRng::new();
diff --git a/src/traits.rs b/src/traits.rs
index 261b28a..10f88b4 100644
--- a/src/traits.rs
+++ b/src/traits.rs
@@ -1,4 +1,5 @@
use rand_core::CryptoRngCore;
+
#[cfg(feature = "default-rng")]
use rand_core::OsRng;
diff --git a/src/types.rs b/src/types.rs
index 3b4070d..6d69a5b 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,10 +1,10 @@
-//use alloc::vec::Vec;
use generic_array::{ArrayLength, GenericArray};
use zeroize::{Zeroize, ZeroizeOnDrop};
+
/// Fig 16 on page 34
#[derive(Clone, Debug, Default, Zeroize, ZeroizeOnDrop)]
-pub struct SlhDsaSig<
+pub(crate) struct SlhDsaSig<
A: ArrayLength,
D: ArrayLength,
HP: ArrayLength,
@@ -17,88 +17,6 @@ pub struct SlhDsaSig<
pub(crate) ht_sig: HtSig,
}
-impl<
- A: ArrayLength,
- D: ArrayLength,
- HP: ArrayLength,
- K: ArrayLength,
- LEN: ArrayLength,
- N: ArrayLength,
- > SlhDsaSig
-{
- pub fn deserialize(self) -> [u8; SIG_LEN] {
- let mut out = [0u8; SIG_LEN];
- debug_assert_eq!(
- out.len(),
- N::to_usize() + // randomness
- N::to_usize() * K::to_usize() + K::to_usize() * A::to_usize() * N::to_usize() + // ForsSig
- D::to_usize() * (HP::to_usize() * N::to_usize() + LEN::to_usize() * N::to_usize())
- );
- out[0..N::to_usize()].copy_from_slice(&self.randomness);
- let mut start = N::to_usize();
- for k in 0..K::to_usize() {
- out[start..(start + N::to_usize())]
- .copy_from_slice(&self.fors_sig.private_key_value[k]);
- start += N::to_usize();
- for a in 0..A::to_usize() {
- out[start..(start + N::to_usize())].copy_from_slice(&self.fors_sig.auth[k].tree[a]);
- start += N::to_usize();
- }
- }
- for d in 0..D::to_usize() {
- //println!("and we move to xmss {} starting at {}", d, start);
- for len in 0..LEN::to_usize() {
- out[start..(start + N::to_usize())]
- .copy_from_slice(&self.ht_sig.xmss_sigs[d].sig_wots.data[len]);
- start += N::to_usize();
- }
- for hp in 0..HP::to_usize() {
- out[start..(start + N::to_usize())]
- .copy_from_slice(&self.ht_sig.xmss_sigs[d].auth[hp]);
- start += N::to_usize();
- }
- }
- debug_assert_eq!(start, out.len());
- out
- }
-
- pub fn serialize(bytes: &[u8]) -> Self {
- debug_assert_eq!(
- bytes.len(),
- N::to_usize() + // randomness
- N::to_usize() * K::to_usize() + K::to_usize() * A::to_usize() * N::to_usize() + // ForsSig
- D::to_usize() * (HP::to_usize() * N::to_usize() + LEN::to_usize() * N::to_usize())
- );
- let mut output = Self::default();
- output.randomness.copy_from_slice(&bytes[0..N::to_usize()]);
- let mut start = N::to_usize();
- for k in 0..K::to_usize() {
- output.fors_sig.private_key_value[k]
- .copy_from_slice(&bytes[start..(start + N::to_usize())]);
- start += N::to_usize();
- for a in 0..A::to_usize() {
- output.fors_sig.auth[k].tree[a]
- .copy_from_slice(&bytes[start..(start + N::to_usize())]);
- start += N::to_usize();
- }
- }
- for d in 0..D::to_usize() {
- for len in 0..LEN::to_usize() {
- output.ht_sig.xmss_sigs[d].sig_wots.data[len]
- .copy_from_slice(&bytes[start..(start + N::to_usize())]);
- start += N::to_usize();
- }
- for hp in 0..HP::to_usize() {
- output.ht_sig.xmss_sigs[d].auth[hp]
- .copy_from_slice(&bytes[start..(start + N::to_usize())]);
- start += N::to_usize();
- }
- }
- debug_assert_eq!(start, bytes.len());
- output
- }
-}
-
#[derive(Clone, Default, Zeroize, ZeroizeOnDrop)]
pub struct SlhPublicKey {
@@ -106,25 +24,6 @@ pub struct SlhPublicKey {
pub(crate) pk_root: GenericArray,
}
-#[allow(dead_code)]
-impl SlhPublicKey {
- pub fn serialize(self) -> [u8; PK_LEN] {
- let mut out = [0u8; PK_LEN];
- debug_assert_eq!(out.len(), 2 * N::to_usize());
- out[0..N::to_usize()].copy_from_slice(&self.pk_seed);
- out[N::to_usize()..2 * N::to_usize()].copy_from_slice(&self.pk_root);
- out
- }
-
- pub fn deserialize(bytes: [u8; PK_LEN]) -> Self {
- let mut pub_key = Self::default();
- pub_key.pk_seed.copy_from_slice(&bytes[0..N::to_usize()]);
- pub_key
- .pk_root
- .copy_from_slice(&bytes[N::to_usize()..2 * N::to_usize()]);
- pub_key
- }
-}
#[derive(Clone, Debug, Default, Zeroize, ZeroizeOnDrop)]
pub struct SlhPrivateKey {
@@ -142,27 +41,32 @@ pub(crate) struct ForsSig {
pub(crate) auth: GenericArray, K>,
}
+
#[derive(Clone, Default, Zeroize, ZeroizeOnDrop)]
pub(crate) struct ForsPk {
pub(crate) key: GenericArray,
}
+
/// Fig 10?
#[derive(Clone, Debug, Default, Zeroize, ZeroizeOnDrop)]
pub(crate) struct Auth {
pub(crate) tree: GenericArray, A>,
}
+
#[derive(Clone, Debug, Default, Zeroize, ZeroizeOnDrop)]
pub(crate) struct HtSig {
pub(crate) xmss_sigs: GenericArray, D>,
}
+
#[derive(Clone, Debug, Default, Zeroize, ZeroizeOnDrop)]
pub struct WotsSig {
pub(crate) data: GenericArray, LEN>,
}
+
#[derive(Clone, Default, Zeroize, ZeroizeOnDrop)]
pub struct WotsPk(pub(crate) GenericArray);
@@ -173,12 +77,14 @@ pub struct XmssSig {
pub(crate) auth: GenericArray, HP>,
}
+
impl XmssSig {
pub(crate) fn get_wots_sig(&self) -> &WotsSig { &self.sig_wots }
pub(crate) fn get_xmss_auth(&self) -> &GenericArray, HP> { &self.auth }
}
+
pub(crate) const WOTS_HASH: u32 = 0;
pub(crate) const WOTS_PK: u32 = 1;
pub(crate) const TREE: u32 = 2;
@@ -187,74 +93,24 @@ pub(crate) const FORS_ROOTS: u32 = 4;
pub(crate) const WOTS_PRF: u32 = 5;
pub(crate) const FORS_PRF: u32 = 6;
+
/// Straddling the line between struct, enum and union...
#[derive(Clone, Default, Zeroize, ZeroizeOnDrop)]
#[repr(align(32))]
-pub struct Adrs {
- f0: [u8; 4], // layer address
- f1: [u8; 4], // tree address (LSB?)
- f2: [u8; 4], // tree address
- f3: [u8; 4], // tree address (MSB)
- f4: [u8; 4], // type
- f5: [u8; 4], // key pair address OR padding
- f6: [u8; 4], // chain address OR padding OR tree height
- f7: [u8; 4], // hash address OR padding OR tree index OR hash address = 0
-}
-
-impl Adrs {
- pub(crate) fn set_layer_address(&mut self, la: u32) { self.f0 = la.to_be_bytes() }
-
- pub(crate) fn get_key_pair_address(&self) -> u32 { u32::from_be_bytes(self.f5) }
-
- pub(crate) fn set_key_pair_address(&mut self, kp_addr: u32) { self.f5 = kp_addr.to_be_bytes(); }
-
- #[allow(clippy::cast_possible_truncation)]
- pub(crate) fn set_chain_address(&mut self, i: u32) { self.f6 = i.to_be_bytes(); }
-
- pub(crate) fn set_type_and_clear(&mut self, type_t: u32) {
- self.f4 = type_t.to_be_bytes();
- self.f5 = 0u32.to_be_bytes();
- self.f6 = 0u32.to_be_bytes();
- self.f7 = 0u32.to_be_bytes();
- }
-
- #[allow(clippy::cast_possible_truncation)]
- pub(crate) fn set_tree_address(&mut self, t: u64) {
- self.f2 = ((t >> 32) as u32).to_be_bytes();
- self.f3 = (t as u32).to_be_bytes();
- }
-
- // TODO: revisit 16 bytes
-
- pub(crate) fn set_hash_address(&mut self, addr: u32) { self.f7 = addr.to_be_bytes() }
-
- pub(crate) fn set_tree_height(&mut self, z: u32) { self.f6 = z.to_be_bytes() }
-
- pub(crate) fn get_tree_index(&mut self) -> u32 { u32::from_be_bytes(self.f7) }
-
- pub(crate) fn set_tree_index(&mut self, i: u32) { self.f7 = i.to_be_bytes() }
-
- pub(crate) fn to_32_bytes(&self) -> [u8; 32] {
- let mut ret = [0u8; 32];
- let mut start = 0;
- for sl in [
- self.f0, self.f1, self.f2, self.f3, self.f4, self.f5, self.f6, self.f7,
- ] {
- ret[start..start + 4].copy_from_slice(&sl);
- start += 4;
- }
- ret
- }
-
- pub(crate) fn to_22_bytes(&self) -> [u8; 22] {
- let mut ret = [0u8; 22];
- ret[0] = self.f0[3];
- ret[1..5].copy_from_slice(&self.f2);
- ret[5..9].copy_from_slice(&self.f3);
- ret[9] = self.f4[3];
- ret[10..14].copy_from_slice(&self.f5);
- ret[14..18].copy_from_slice(&self.f6);
- ret[18..22].copy_from_slice(&self.f7);
- ret
- }
+pub(crate) struct Adrs {
+ pub(crate) f0: [u8; 4],
+ // layer address
+ pub(crate) f1: [u8; 4],
+ // tree address
+ pub(crate) f2: [u8; 4],
+ // tree address
+ pub(crate) f3: [u8; 4],
+ // tree address
+ pub(crate) f4: [u8; 4],
+ // type
+ pub(crate) f5: [u8; 4],
+ // key pair address OR padding
+ pub(crate) f6: [u8; 4],
+ // chain address OR padding OR tree height
+ pub(crate) f7: [u8; 4], // hash address OR padding OR tree index OR hash address = 0
}
diff --git a/src/wots.rs b/src/wots.rs
new file mode 100644
index 0000000..6d799ef
--- /dev/null
+++ b/src/wots.rs
@@ -0,0 +1,267 @@
+use generic_array::{ArrayLength, GenericArray};
+use crate::hashers::Hashers;
+use crate::helpers;
+use crate::types::{Adrs, WotsPk, WotsSig, WOTS_PK, WOTS_PRF};
+
+
+/// Algorithm 4: `chain(X, i, s, PK.seed, ADRS)` on page 17.
+/// Chaining function used in WOTS+. The chain function takes as input an n-byte string `X` and integers `s` and `i`
+/// and returns the result of iterating the hash function `F` on the input `s` times, starting from an index of `i`.
+/// The chain function also requires as input PK.seed, which is part of the SLH-DSA public key, and an address `ADRS`.
+/// The type in `ADRS` must be set to `WOTS_HASH`, and the layer address, tree address, key pair address, and chain
+/// address must be set to the address of the chain being computed. The chain function updates the hash address in
+/// `ADRS` with each iteration to specify the current position in the chain prior to ADRS’s use in `F`.
+///
+/// Input: Input string `X`, start index `i`, number of steps `s`, public seed `PK.seed`, address `ADRS`.
+/// Output: Value of `F` iterated `s` times on `X`.
+pub(crate) fn chain(
+ hashers: &Hashers, cap_x: GenericArray, i: u32, s: u32, pk_seed: &[u8],
+ adrs: &Adrs,
+) -> Option> {
+ debug_assert!(i + s < u32::MAX);
+ let mut adrs = adrs.clone();
+
+ // 1: if (i + s) ≥ w then
+ if (i + s) >= crate::W {
+ //
+ // 2: return NULL
+ return None;
+
+ // 3: end if
+ }
+
+ // 4:
+ // 5: tmp ← X
+ let mut tmp = cap_x;
+
+ // 6:
+ // 7: for j from i to i + s − 1 do
+ for j in i..(i + s) {
+ //
+ // 8: ADRS.setHashAddress(j)
+ adrs.set_hash_address(j);
+
+ // 9: tmp ← F(PK.seed, ADRS, tmp)
+ tmp = (hashers.f)(pk_seed, &adrs, &tmp);
+
+ // 10: end for
+ }
+
+ // 11: return tmp
+ Some(tmp)
+}
+
+
+/// Algorithm 5: `wots_PKgen(SK.seed, PK.seed, ADRS)` on page 18.
+/// Generate a WOTS+ public key. The `wots_PKgen` function generates WOTS+ public keys. It takes as input `SK.seed`
+/// and `PK.seed` from the SLH-DSA private key and an address. The type in the address `ADRS` must be set to
+/// `WOTS_HASH`, and the layer address, tree address, and key pair address must encode the address of the `WOTS+`
+/// public key to be generated.
+///
+/// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`.
+/// Output: WOTS+ public key `pk`.
+#[allow(clippy::similar_names)]
+pub(crate) fn wots_pkgen(
+ hashers: &Hashers, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs,
+) -> Result, &'static str> {
+ let mut adrs = adrs.clone();
+ let mut tmp: GenericArray, LEN> = GenericArray::default();
+
+ // 1: skADRS ← ADRS ▷ Copy address to create key generation key address
+ let mut sk_adrs = adrs.clone();
+
+ // 2: skADRS.setTypeAndClear(WOTS_PRF)
+ sk_adrs.set_type_and_clear(WOTS_PRF);
+
+ // 3: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ sk_adrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 4: for i from 0 to len − 1 do
+ for i in 0..LEN::to_u32() {
+ //
+ // 5: skADRS.setChainAddress(i)
+ sk_adrs.set_chain_address(i);
+
+ // 6: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i
+ let sk = (hashers.prf)(pk_seed, sk_seed, &sk_adrs);
+
+ // 7: ADRS.setChainAddress(i)
+ adrs.set_chain_address(i);
+
+ // 8: tmp[i] ← chain(sk, 0, w − 1, PK.seed, ADRS) ▷ Compute public value for chain i
+ tmp[i as usize] =
+ chain(hashers, sk, 0, crate::W - 1, pk_seed, &adrs).ok_or("chain broke")?;
+
+ // 9: end for
+ }
+
+ // 10: wotspkADRS ← ADRS ▷ Copy address to create WOTS+ public key address
+ let mut wotspk_adrs = adrs.clone();
+
+ // 11: wotspkADRS.setTypeAndClear(WOTS_PK)
+ wotspk_adrs.set_type_and_clear(WOTS_PK);
+
+ // 12: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 13: pk ← Tlen (PK.seed, wotspkADRS, tmp) ▷ Compress public key
+ let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
+
+ // 14: return pk
+ Ok(WotsPk(pk))
+}
+
+
+/// Algorithm 6: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 19.
+/// Generate a WOTS+ signature on an n-byte message.
+///
+/// Input: Message `M`, secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`.
+/// Output: WOTS+ signature sig.
+#[allow(clippy::similar_names)]
+pub(crate) fn wots_sign(
+ hashers: &Hashers, m: &[u8], sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs,
+) -> WotsSig {
+ let mut adrs = adrs.clone();
+ let mut sig: WotsSig = WotsSig::default();
+
+ // 1: csum ← 0
+ let mut csum = 0_u32;
+
+ // 2:
+ // 3: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
+ let mut msg = GenericArray::::default(); // note: 3 entries left over, used step 10
+ helpers::base_2b(m, crate::LGW, 2 * N::to_u32(), &mut msg[0..(2 * N::to_usize())]);
+
+ // 4:
+ // 5: for i from 0 to len1 − 1 do ▷ Compute checksum
+ for item in msg.iter().take(2 * N::to_usize()) {
+ //
+ // 6: csum ← csum + w − 1 − msg[i]
+ csum += crate::W - 1 - *item;
+
+ // 7: end for
+ }
+
+ // 8:
+ // 9: csum ← csum ≪ ((8 − ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
+ csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
+
+ // 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
+ helpers::base_2b(
+ &helpers::to_byte(csum, (crate::LEN2 * crate::LGW).div_ceil(8)),
+ crate::LGW,
+ crate::LEN2,
+ &mut msg[(2 * N::to_usize())..],
+ );
+
+ // 11:
+ // 12: skADRS ← ADRS
+ let mut sk_addrs = adrs.clone();
+
+ // 13: skADRS.setTypeAndClear(WOTS_PRF)
+ sk_addrs.set_type_and_clear(WOTS_PRF);
+
+ // 14: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ sk_addrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 15: for i from 0 to len − 1 do
+ //#[allow(clippy::cast_possible_truncation)] // step 19
+ for (item, i) in msg.iter().zip(0u32..) {
+ //
+ // 16: skADRS.setChainAddress(i)
+ sk_addrs.set_chain_address(i);
+
+ // 17: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i
+ let sk = (hashers.prf)(pk_seed, sk_seed, &sk_addrs);
+
+ // 18: ADRS.setChainAddress(i)
+ adrs.set_chain_address(i);
+
+ // 19: sig[i] ← chain(sk, 0, msg[i], PK.seed, ADRS) ▷ Compute signature value for chain i
+ sig.data[i as usize] = chain(hashers, sk, 0, *item, pk_seed, &adrs).unwrap();
+
+ // 20: end for
+ }
+
+ // 21: return sig
+ sig
+}
+
+
+/// Algorithm 7: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 20.
+/// Compute a WOTS+ public key from a message and its signature.
+///
+/// Input: WOTS+ signature `sig`, message `M`, public seed `PK.seed`, address `ADRS`.
+/// Output: WOTS+ public key `pksig` derived from `sig`.
+pub(crate) fn wots_pk_from_sig(
+ hashers: &Hashers, sig: &WotsSig, m: &[u8], pk_seed: &[u8], adrs: &Adrs,
+) -> WotsPk {
+ let mut adrs = adrs.clone();
+ let mut tmp: GenericArray, LEN> = GenericArray::default();
+
+ // 1: csum ← 0
+ let mut csum = 0_u32;
+
+ // 2:
+ // 3: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
+ let mut msg: GenericArray = GenericArray::default();
+ helpers::base_2b(m, crate::LGW, 2 * N::to_u32(), &mut msg[0..(2 * N::to_usize())]);
+
+ // 4:
+ // 5: for i from 0 to len1 − 1 do ▷ Compute checksum
+ for item in msg.iter().take(2 * N::to_usize()) {
+ //
+ // 6: csum ← csum + w − 1 − msg[i]
+ csum += crate::W - 1 - item;
+
+ // 7: end for
+ }
+
+ // 8:
+ // 9: csum ← csum ≪ ((8 − ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
+ csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
+
+ // 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
+ helpers::base_2b(
+ &helpers::to_byte(csum, (crate::LEN2 * crate::LGW).div_ceil(8)),
+ crate::LGW,
+ crate::LEN2,
+ &mut msg[(2 * N::to_usize())..],
+ );
+
+ // 11: for i from 0 to len − 1 do
+ #[allow(clippy::cast_possible_truncation)] // steps 12 and 13
+ for i in 0..LEN::to_usize() {
+ //
+ // 12: ADRS.setChainAddress(i)
+ adrs.set_chain_address(i as u32);
+
+ // 13: tmp[i] ← chain(sig[i], msg[i], w − 1 − msg[i], PK.seed, ADRS)
+ tmp[i] = chain::(
+ hashers,
+ sig.data[i].clone(),
+ msg[i],
+ crate::W - 1 - msg[i],
+ pk_seed,
+ &adrs,
+ )
+ .expect("chain broke2!");
+
+ // 14: end for
+ }
+
+ // 15: wotspkADRS ← ADRS
+ let mut wotspk_adrs = adrs.clone();
+
+ // 16: wotspkADRS.setTypeAndClear(WOTS_PK)
+ wotspk_adrs.set_type_and_clear(WOTS_PK);
+
+ // 17: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
+ wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address());
+
+ // 18: pksig ← Tlen (PK.seed, wotspkADRS, tmp)
+ let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
+
+ // 19: return pksig
+ WotsPk(pk)
+}
diff --git a/src/xmss.rs b/src/xmss.rs
new file mode 100644
index 0000000..33ea477
--- /dev/null
+++ b/src/xmss.rs
@@ -0,0 +1,211 @@
+use generic_array::{ArrayLength, GenericArray};
+use crate::hashers::Hashers;
+use crate::types::{Adrs, XmssSig, TREE, WOTS_HASH};
+use crate::wots;
+
+
+/// Algorithm 8: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22.
+/// Compute the root of a Merkle subtree of WOTS+ public keys.
+///
+/// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
+/// `address ADRS`.
+/// Output: n-byte root `node`.
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn xmss_node<
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
+) -> Result, &'static str> {
+ let mut adrs = adrs.clone();
+
+ // 1: if z > h′ or i ≥ 2^{h −z} then
+ if (z > HP::to_u32()) | (u64::from(i) >= 2u64.pow(HP::to_u32() - z)) {
+ //
+ // 2: return NULL
+ return Err("Alg8: fail");
+
+ // 3: end if
+ }
+
+ // 4: if z = 0 then
+ let node = if z == 0 {
+ //
+ // 5: ADRS.setTypeAndClear(WOTS_HASH)
+ adrs.set_type_and_clear(WOTS_HASH);
+
+ // 6: ADRS.setKeyPairAddress(i)
+ adrs.set_key_pair_address(i);
+
+ // 7: node ← wots_PKgen(SK.seed, PK.seed, ADRS)
+ wots::wots_pkgen::(hashers, sk_seed, pk_seed, &adrs)?
+ .0
+ .clone()
+
+ // 8: else
+ } else {
+ //
+ // 9: lnode ← xmss_node(SK.seed, 2 * i, z − 1, PK.seed, ADRS)
+ let lnode =
+ xmss_node::(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?;
+
+ // 10: rnode ← xmss_node(SK.seed, 2 * i + 1, z − 1, PK.seed, ADRS)
+ let rnode =
+ xmss_node::(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?;
+
+ // 11: ADRS.setTypeAndClear(TREE)
+ adrs.set_type_and_clear(TREE);
+
+ // 12: ADRS.setTreeHeight(z)
+ adrs.set_tree_height(z);
+
+ // 13: ADRS.setTreeIndex(i)
+ adrs.set_tree_index(i);
+
+ // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode)
+ (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
+
+ // 15: end if
+ };
+
+ // 16: return node
+ Ok(node)
+}
+
+
+/// Algorithm 9: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23.
+/// Generate an XMSS signature.
+///
+/// Input: n-byte message `M`, secret seed `SK.seed`, index `idx`, public seed `PK.seed`, address `ADRS`.
+/// Output: XMSS signature SIGXMSS = (sig ∥ AUTH).
+#[allow(clippy::similar_names)] // sk_seed and pk_seed
+pub(crate) fn xmss_sign<
+ H: ArrayLength,
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, m: &[u8], sk_seed: &[u8], idx: u32, pk_seed: &[u8],
+ adrs: &Adrs,
+) -> Result, &'static str> {
+ let mut adrs = adrs.clone();
+ let mut sig_xmss = XmssSig::default();
+
+ // 1: for j from 0 to h′-1 do ▷ Build authentication path
+ for j in 0..HP::to_u32() {
+ //
+ // 2: k ← idx/2 ^j xor 1
+ let k = (idx >> j) ^ 1;
+
+ // 3: AUTH[j] ← xmss_node(SK.seed, k, j, PK.seed, ADRS)
+ sig_xmss.auth[j as usize] =
+ xmss_node::(hashers, sk_seed, k, j, pk_seed, &adrs)?;
+
+ // 4: end for
+ }
+
+ // 5:
+ // 6: ADRS.setTypeAndClear(WOTS_HASH)
+ adrs.set_type_and_clear(WOTS_HASH);
+
+ // 7: ADRS.setKeyPairAddress(idx)
+ adrs.set_key_pair_address(idx);
+
+ // 8: sig ← wots_sign(M, SK.seed, PK.seed, ADRS)
+ sig_xmss.sig_wots = wots::wots_sign::(hashers, m, sk_seed, pk_seed, &adrs); // TODO: polish out BB!
+
+ // 9: SIG_XMSS ← sig ∥ AUTH
+ // struct built above
+
+ // 10: return SIG_XMSS
+ Ok(sig_xmss)
+}
+
+
+/// Algorithm 10: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)`
+/// Compute an XMSS public key from an XMSS signature.
+///
+/// Input: Index `idx`, XMSS signature `SIG_XMSS = (sig ∥ AUTH)`, n-byte message `M`, public seed `PK.seed`,
+/// address `ADRS`.
+/// Output: n-byte root value `node[0]`.
+pub(crate) fn xmss_pk_from_sig<
+ HP: ArrayLength,
+ K: ArrayLength,
+ LEN: ArrayLength,
+ M: ArrayLength,
+ N: ArrayLength,
+>(
+ hashers: &Hashers, idx: u32, sig_xmss: &XmssSig, m: &[u8],
+ pk_seed: &[u8], adrs: &Adrs,
+) -> GenericArray {
+ let mut adrs = adrs.clone();
+
+ // 1: ADRS.setTypeAndClear(WOTS_HASH) ▷ Compute WOTS+ pk from WOTS+ sig
+ adrs.set_type_and_clear(WOTS_HASH);
+
+ // 2: ADRS.setKeyPairAddress(idx)
+ adrs.set_key_pair_address(idx);
+
+ // 3: sig ← SIG_XMSS.getWOTSSig() ▷ SIG_XMSS [0 : len · n]
+ let sig = sig_xmss.get_wots_sig();
+
+ // 4: AUTH ← SIG_XMSS.getXMSSAUTH() ▷ SIG_XMSS [len · n : (len + h′) · n]
+ let auth = sig_xmss.get_xmss_auth();
+
+ // 5: node[0] ← wots_PKFromSig(sig, M, PK.seed, ADRS)
+ let mut node_0 = wots::wots_pk_from_sig::(hashers, sig, m, pk_seed, &adrs)
+ .0
+ .clone();
+
+ // 6:
+ // 7: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH
+ adrs.set_type_and_clear(TREE);
+
+ // 8: ADRS.setTreeIndex(idx)
+ adrs.set_tree_index(idx);
+
+ // 9: for k from 0 to h′ − 1 do
+ for k in 0..HP::to_u32() {
+ //
+ // 10: ADRS.setTreeHeight(k + 1)
+ adrs.set_tree_height(k + 1);
+
+ // 11: if idx/2^k is even then
+ #[allow(clippy::if_not_else)] // Follows the algorithm as written
+ let node_1 = if ((idx >> k) & 1) == 0 {
+ //
+ // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2)
+ let tmp = adrs.get_tree_index() / 2;
+ adrs.set_tree_index(tmp);
+
+ // 13: node[1] ← H(PK.seed, ADRS, node[0] ∥ AUTH[k])
+ (hashers.h)(pk_seed, &adrs, &node_0, &auth[k as usize])
+
+ // 14: else
+ } else {
+ //
+ // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2)
+ let tmp = (adrs.get_tree_index() - 1) / 2;
+ adrs.set_tree_index(tmp);
+
+ // 16: node[1] ← H(PK.seed, ADRS, AUTH[k] ∥ node[0])
+ (hashers.h)(pk_seed, &adrs, &auth[k as usize], &node_0)
+
+ // 17: end if
+ };
+
+ // 18: node[0] ← node[1]
+ node_0 = node_1;
+
+ // 19: end for
+ }
+
+ // 20: return node[0]
+ node_0
+}