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 +}