From 834e75b603f61c3101a4dbbcd4f858e4d6ef318e Mon Sep 17 00:00:00 2001 From: eschorn1 Date: Wed, 2 Oct 2024 14:50:54 -0500 Subject: [PATCH] align comments with released spec --- README.md | 8 +- ffi/fips205.py | 2 +- src/fors.rs | 123 ++++++++++++++--------------- src/hashers.rs | 8 +- src/helpers.rs | 57 +++++++------- src/hypertree.rs | 80 ++++++++++--------- src/lib.rs | 108 ++++++++++++++------------ src/slh.rs | 196 +++++++++++++++++++++-------------------------- src/traits.rs | 4 +- src/types.rs | 12 ++- src/wots.rs | 129 ++++++++++++++----------------- src/xmss.rs | 108 ++++++++++++-------------- 12 files changed, 397 insertions(+), 438 deletions(-) diff --git a/README.md b/README.md index 052efbc..80c60af 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,14 @@ ![Rust Version][rustc-image] [FIPS 205] Stateless Hash-Based Digital Signature Standard written in pure Rust for server, -desktop, browser and embedded applications. The code repository includes C FFI and Python bindings. +desktop, browser and embedded applications. The source repository includes examples demonstrating +benchmarking, constant-time statistical measurements, and WASM execution. This crate implements the FIPS 205 **final/released** standard in pure Rust with minimal and mainstream dependencies. All twelve (!!) security parameter sets are fully functional. The implementation does not require the standard library, e.g. `#[no_std]`, has no heap allocations, e.g. no `alloc` needed, and exposes the `RNG` so it is suitable for the full range of applications from server down to the bare-metal. The API is stabilized and the code is heavily biased towards safety and correctness; further performance optimizations will be implemented as the standard matures. -This crate will quickly follow any changes to FIPS 205 as they become available. See for a full description of the target functionality. @@ -50,7 +50,9 @@ desired [security parameter](#modules) below. ## Notes -* This crate is fully functional and corresponds to the final/released FIPS 205. +* This crate is fully functional and corresponds to the final/released FIPS 205, including + the pre-hash variants which formalize methods for signing a hash of the message instead of + the message itself (along with metadata about the hasher used). * Constant-time assurances target the source-code level only, and are a work in progress. * Note that FIPS 205 places specific requirements on randomness per section 3.1, hence the exposed `RNG`. * Requires Rust **1.70** or higher. The minimum supported Rust version may be changed in the future, diff --git a/ffi/fips205.py b/ffi/fips205.py index b2c9533..4b3a288 100644 --- a/ffi/fips205.py +++ b/ffi/fips205.py @@ -60,7 +60,7 @@ Thank you to Daniel Kahn Gillmor for providing an example for FIPS 203. ## See Also -- https://doi.org/10.6028/NIST.FIPS.205.ipd +- https://csrc.nist.gov/pubs/fips/205/final - https://github.com/integritychain/fips205 """ diff --git a/src/fors.rs b/src/fors.rs index 9401041..404c130 100644 --- a/src/fors.rs +++ b/src/fors.rs @@ -3,8 +3,8 @@ use crate::helpers::base_2b; use crate::types::{Adrs, Auth, ForsPk, ForsSig, FORS_PRF, FORS_ROOTS}; -/// Algorithm 13: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29. -/// Generate a FORS private-key value. +/// Algorithm 14: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29. +/// Generates 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. @@ -29,8 +29,8 @@ pub(crate) fn fors_sk_gen @@ -45,62 +45,56 @@ pub(crate) fn fors_node< >( hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs, ) -> Result<[u8; N], &'static str> { - let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap()); let mut adrs = adrs.clone(); - // 1: if z > a or i ≥ k · 2^(a−z) then - if (z > a32) | (i > k32 * (1 << (a32 - z))) { - // - // 2: return NULL - return Err("Alg14 fails"); + // Note this bounds check was only specified in the draft specification + // let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap()); + // debug_assert!((z > a32) | (i > k32 * (1 << (a32 - z))), "Alg15 fails"); - // 3: end if - } - - // 4: if z = 0 then + // 1: if z = 0 then let node = if z == 0 { // - // 5: sk ← fors_SKgen(SK.seed, PK.seed, ADRS, i) + // 2: sk ← fors_SKgen(SK.seed, PK.seed, ADRS, i) let sk = fors_sk_gen(hashers, sk_seed, pk_seed, &adrs, i); - // 6: ADRS.setTreeHeight(0) + // 3: ADRS.setTreeHeight(0) adrs.set_tree_height(0); - // 7: ADRS.setTreeIndex(i) + // 4: ADRS.setTreeIndex(i) adrs.set_tree_index(i); - // 8: node ← F(PK.seed, ADRS, sk) + // 5: node ← F(PK.seed, ADRS, sk) (hashers.f)(pk_seed, &adrs, &sk) - // 9: else + // 6: else } else { // - // 10: lnode ← fors_node(SK.seed, 2i, z − 1, PK.seed, ADRS) + // 7: 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) + // 8: 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) + // 9: ADRS.setTreeHeight(z) adrs.set_tree_height(z); - // 13: ADRS.setTreeIndex(i) + // 10: ADRS.setTreeIndex(i) adrs.set_tree_index(i); - // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode) + // 11: node ← H(PK.seed, ADRS, lnode ∥ rnode) (hashers.h)(pk_seed, &adrs, &lnode, &rnode) - // 15: end if + // 12: end if }; - // 16: return node + // 13: return node Ok(node) } -/// Algorithm 15: `fors_sign(md, SK.seed, PK.seed, ADRS)` -/// Generate a FORS signature. +/// Algorithm 16: `fors_sign(md, SK.seed, PK.seed, ADRS)` on page 31. +/// Generates a FORS signature. /// /// Input: Message digest `md`, secret seed `SK.seed`, address `ADRS`, public seed `PK.seed`.
/// Output: FORS signature `SIG_FORS`. @@ -117,12 +111,13 @@ pub(crate) fn fors_sign< let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap()); // 1: SIG_FORS = NULL ▷ Initialize SIG_FORS as a zero-length byte string + // Here, we manually initialize all zeros then overwrite its contents below let mut sig_fors = ForsSig { private_key_value: [[0u8; N]; K], auth: core::array::from_fn(|_| Auth { tree: [[0u8; N]; A] }), }; - // 2: indices ← base_2^b(md, a, k) + // 2: indices ← base_2b(md, a, k) let mut indices = [0u32; K]; base_2b(md, a32, k32, &mut indices); @@ -135,42 +130,41 @@ pub(crate) fn fors_sign< sk_seed, pk_seed, adrs, - i * (1 << a32) + indices[i as usize], + (i << a32) + indices[i as usize], ); - // 5: - // 6: for j from 0 to a − 1 do ▷ Compute auth path + // 5: for j from 0 to a − 1 do ▷ Compute auth path for j in 0..a32 { // - // 7: s ← indices[i]/2^j xor 1 + // 6: 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) + // 7: 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 * (1 << (a32 - j)) + s, + (i << (a32 - j)) + s, j, pk_seed, adrs, )?; - // 9: end for + // 8: end for } - // 10: SIG_FORS ← SIG_FORS ∥ AUTH - // built within inner loop above + // 9: SIG_FORS ← SIG_FORS ∥ AUTH + // built within inner loop above (step 7) - // 11: end for + // 10: end for } - // 12: return SIG_FORS + // 11: 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. +/// Algorithm 17: `fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)` on page 32. +/// Computes 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. @@ -188,7 +182,7 @@ pub(crate) fn fors_pk_from_sig< let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap()); let mut adrs = adrs.clone(); - // 1: indices ← base_2^b(md, a, k) + // 1: indices ← base_2b(md, a, k) let mut indices = [0u32; K]; base_2b(md, a32, k32, &mut indices); @@ -203,68 +197,67 @@ pub(crate) fn fors_pk_from_sig< adrs.set_tree_height(0); // 5: ADRS.setTreeIndex(i · 2^a + indices[i]) - adrs.set_tree_index(i * (1 << a32) + indices[i as usize]); + adrs.set_tree_index((i << a32) + 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] + // 7: 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 + // 8: for j from 0 to a − 1 do ▷ Compute root from leaf and AUTH for j in 0..a32 { // - // 10: ADRS.setTreeHeight(j + 1) + // 9: 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 { + // 10: if indices[i]/2^j is even then + let node_1 = if ((indices[i as usize] >> j) & 0x01) == 0 { // - // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2) + // 11: 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]) + // 12: node[1] ← H(PK.seed, ADRS, node[0] ∥ auth[j]) (hashers.h)(pk_seed, &adrs, &node_0, &auth.tree[j as usize]) - // 14: else + // 13: else } else { // - // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2) + // 14: 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]) + // 15: 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 + // 16: end if }; - // 18: node[0] ← node[1] + // 17: node[0] ← node[1] node_0 = node_1; - // 19: end for + // 18: end for } - // 20: root[i] ← node[0] + // 19: root[i] ← node[0] root[i as usize] = node_0; - // 21: end for + // 20: end for } - // 22: forspkADRS ← ADRS ▷ Compute the FORS public key from the Merkle tree roots + // 21: forspkADRS ← ADRS ▷ Compute the FORS public key from the Merkle tree roots let mut fors_pk_adrs = adrs.clone(); - // 23: forspkADRS.setTypeAndClear(FORS_ROOTS) + // 22: forspkADRS.setTypeAndClear(FORS_ROOTS) fors_pk_adrs.set_type_and_clear(FORS_ROOTS); - // 24: forspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress()) + // 23: forspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress()) fors_pk_adrs.set_key_pair_address(adrs.get_key_pair_address()); - // 25: pk ← Tk(PK.seed, forspkADRS, root) + // 24: pk ← Tk(PK.seed, forspkADRS, root) ▷ compute the FORS public key let pk = (hashers.t_len)(pk_seed, &fors_pk_adrs, &root); - // 26: return pk; + // 25: return pk; ForsPk { key: pk } } diff --git a/src/hashers.rs b/src/hashers.rs index ae224ed..233d2c7 100644 --- a/src/hashers.rs +++ b/src/hashers.rs @@ -149,9 +149,7 @@ pub(crate) mod sha2_cat_1 { let mut inner_hasher = Sha256::new(); inner_hasher.update(&padding[..]); inner_hasher.update(a0); - for i in m { - inner_hasher.update(i); - } + m.iter().for_each(|item| inner_hasher.update(item)); for p in &mut padding { *p ^= 0x6a; } @@ -270,9 +268,7 @@ pub(crate) mod sha2_cat_3_5 { let mut inner_hasher = Sha512::new(); inner_hasher.update(&padding[..]); inner_hasher.update(a0); - for i in m { - inner_hasher.update(i); - } + m.iter().for_each(|item| inner_hasher.update(item)); for p in &mut padding { *p ^= 0x6a; } diff --git a/src/helpers.rs b/src/helpers.rs index 048c2af..6987f6e 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,8 +1,8 @@ use crate::types::{Adrs, Auth, ForsSig, HtSig, SlhDsaSig, WotsSig, XmssSig}; -/// Algorithm 1: `toInt(X, n)` on page 14. -/// Convert a byte string to an integer. +/// Algorithm 2: `toInt(X, n)` on page 15. +/// Converts a byte string to an integer. /// /// Input: n-byte string `X`, string length `n`.
/// Output: Integer value of `X`. @@ -13,23 +13,22 @@ pub(crate) fn to_int(x: &[u8], n: u32) -> u64 { // 1: total ← 0 let mut total = 0; - // 2: - // 3: for i from 0 to n − 1 do + // 2: for i from 0 to n − 1 do for item in x.iter().take(n as usize) { // - // 4: total ← 256 · total + X[i] + // 3: total ← 256 · total + X[i] total = (total << 8) + u64::from(*item); - // 5: end for + // 4: end for } - // 6: return total + // 5: return total total } -/// Algorithm 2: `toByte(x, n)` on page 15. -/// Convert an integer to a byte string. +/// Algorithm 3: `toByte(x, n)` on page 15. +/// Converts 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. @@ -41,26 +40,25 @@ pub(crate) fn to_byte(x: u32, n: u32) -> [u8; ((crate::LEN2 * crate::LGW + 7) / // 1: total ← x let mut total = x; - // 2: - // 3: for i from 0 to n − 1 do + // 2: 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 + // 3: 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 + // 4: total ← total ≫ 8 total >>= 8; - // 6: end for + // 5: end for } - // 7: return S + // 6: return S s } -/// Algorithm 3: `base_2^b(X, b, out_len)` on page 15. -/// Compute the base 2^b representation of X. +/// Algorithm 4: `base_2^b(X, b, out_len)` on page 16. +/// Computes 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]`. @@ -78,35 +76,34 @@ pub(crate) fn base_2b(x: &[u8], b: u32, out_len: u32, baseb: &mut [u32]) { // 3: total ← 0 let mut total = 0; - // 4: - // 5: for out from 0 to out_len − 1 do + // 4: for out from 0 to out_len − 1 do for item in baseb.iter_mut() { // - // 6: while bits < b do + // 5: while bits < b do while bits < b { // - // 7: total ← (total ≪ 8) + X[in] + // 6: total ← (total ≪ 8) + X[in] total = (total << 8) + u32::from(x[inn]); - // 8: in ← in + 1 + // 7: in ← in + 1 inn += 1; - // 9: bits ← bits + 8 + // 8: bits ← bits + 8 bits += 8; - // 10: end while + // 9: end while } - // 11: bits ← bits − b + // 10: bits ← bits − b bits -= b; - // 12: baseb[out] ← (total ≫ bits) mod 2^b + // 11: baseb[out] ← (total ≫ bits) mod 2^b *item = (total >> bits) & (u32::MAX >> (32 - b)); - // 13: end for + // 12: end for } - // 14: return baseb (mutable parameter) + // 13: return baseb (mutable parameter) } @@ -119,7 +116,7 @@ impl< const N: usize, > SlhDsaSig { - pub(crate) fn deserialize(self) -> [u8; SIG_LEN] { + pub(crate) fn serialize(self) -> [u8; SIG_LEN] { let mut out = [0u8; SIG_LEN]; debug_assert_eq!( out.len(), @@ -152,7 +149,7 @@ impl< out } - pub(crate) fn serialize(bytes: &[u8]) -> Self { + pub(crate) fn deserialize(bytes: &[u8]) -> Self { debug_assert_eq!( bytes.len(), N + // randomness diff --git a/src/hypertree.rs b/src/hypertree.rs index 4e8645a..3922fbb 100644 --- a/src/hypertree.rs +++ b/src/hypertree.rs @@ -3,8 +3,8 @@ use crate::types::{Adrs, HtSig, WotsSig, XmssSig}; use crate::xmss; -/// Algorithm 11: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27. -/// Generate a hypertree signature. +/// Algorithm 12: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27. +/// Generates a hypertree signature. /// /// Input: Message `M`, private seed `SK.seed`, public seed `PK.seed`, tree index `idx_tree`, leaf /// index `idx_leaf`.
@@ -28,15 +28,14 @@ pub(crate) fn ht_sign< // 1: ADRS ← toByte(0, 32) let mut adrs = Adrs::default(); - // 2: - // 3: ADRS.setTreeAddress(idxtree) + // 2: ADRS.setTreeAddress(idxtree) adrs.set_tree_address(idx_tree); - // 4: SIG_tmp ← xmss_sign(M, SK.seed, idxleaf, PK.seed, ADRS) + // 3: 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)?; + xmss::xmss_sign::(hashers, m, sk_seed, idx_leaf, pk_seed, &adrs); - // 5: SIG_HT ← SIG_tmp + // 4: SIG_HT ← SIG_tmp let mut sig_ht = HtSig { xmss_sigs: core::array::from_fn(|_| XmssSig { sig_wots: WotsSig { data: [[0u8; N]; LEN] }, @@ -45,55 +44,55 @@ pub(crate) fn ht_sign< }; sig_ht.xmss_sigs[0] = sig_tmp.clone(); - // 6: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, M, PK.seed, ADRS) + // 5: 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 + // 6: for j from 1 to d − 1 do for j in 1..d32 { // - // 8: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree + // 7: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree let idx_leaf = u32::try_from(idx_tree & ((1 << hp32) - 1)).map_err(|_| "Alg11: oversized idx leaf")?; - // 9: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree + // 8: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree idx_tree >>= hp32; - // 10: ADRS.setLayerAddress(j) + // 9: ADRS.setLayerAddress(j) adrs.set_layer_address(j); - // 11: ADRS.setTreeAddress(idx_tree) + // 10: ADRS.setTreeAddress(idx_tree) adrs.set_tree_address(idx_tree); - // 12: SIG_tmp ← xmss_sign(root, SK.seed, idx_leaf, PK.seed, ADRS) + // 11: 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 + // 12: SIG_HT ← SIG_HT ∥ SIG_tmp sig_ht.xmss_sigs[j as usize] = sig_tmp.clone(); - // 14: if j < d − 1 then + // 13: if j < d − 1 then if j < (d32 - 1) { // - // 15: root ← xmss_PKFromSig(idx_leaf, SIG_tmp, root, PK.seed, ADRS) + // 14: 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 + // 15: end if } - // 17: end for + // 16: end for } - // 18: return SIGHT + // 17: 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. +/// Algorithm 13: `ht_verify(M, SIG_HT, PK.seed, idx_tree, idx_leaf, PK.root)` on page 28. +/// Verifies 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`.
@@ -115,20 +114,19 @@ pub(crate) fn ht_verify< // 1: ADRS ← toByte(0, 32) let mut adrs = Adrs::default(); - // 2: - // 3: ADRS.setTreeAddress(idx_tree) + // 2: ADRS.setTreeAddress(idx_tree) adrs.set_tree_address(idx_tree); - // 4: SIG_tmp ← SIG_HT.getXMSSSignature(0) ▷ SIG_HT [0 : (h′ + len) · n] + // 3: 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) + // 4: 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 + // 5: for j from 1 to d − 1 do for j in 1..d32 { // - // 7: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree + // 6: idx_leaf ← idx_tree mod 2^{h′} ▷ h′ least significant bits of idx_tree let idx_leaf = u32::try_from(idx_tree & ((1 << hp32) - 1)); if idx_leaf.is_err() { @@ -136,28 +134,28 @@ pub(crate) fn ht_verify< }; let idx_leaf = idx_leaf.unwrap(); - // 8: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree + // 7: idx_tree ← idx_tree ≫ h′ ▷ Remove least significant h′ bits from idx_tree idx_tree >>= hp32; - // 9: ADRS.setLayerAddress(j) + // 8: ADRS.setLayerAddress(j) adrs.set_layer_address(j); - // 10: ADRS.setTreeAddress(idx_tree) + // 9: 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] + // 10: 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) + // 11: 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 + // 12: 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 (is this in signing path??) + // 13: if node = PK.root then + // 14: return true + // 15: else + // 16: return false + // 17: end if + node == *pk_root // TODO: CT equal (double-check: is this in signing path??) } diff --git a/src/lib.rs b/src/lib.rs index b337e52..93c6bbb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,40 +4,42 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] -// Implements FIPS 205 draft Stateless Hash-Based Digital Signature Standard. -// See +// Implements FIPS 205 Stateless Hash-Based Digital Signature Standard. +// See // -// Algorithm 1 toInt(X, n) --> helpers.rs -// Algorithm 2 toByte(x, n) --> helpers.rs -// Algorithm 3 base_2b (X, b, out_len) --> helpers.rs -// Algorithm 4 chain(X, i, s, PK.seed, ADRS) --> wots.rs -// Algorithm 5 wots_PKgen(SK.seed, PK.seed, ADRS) --> wots.rs -// Algorithm 6 wots_sign(M, SK.seed, PK.seed, ADRS) --> wots.rs -// Algorithm 7 wots_PKFromSig(sig, M, PK.seed, ADRS) --> wots.rs -// Algorithm 8 xmss_node(SK.seed, i, z, PK.seed, ADRS) --> xmss.rs -// Algorithm 9 xmss_sign(M, SK.seed, idx, PK.seed, ADRS) --> xmss.rs -// Algorithm 10 xmss_PKFromSig(idx, SIGXMSS, M, PK.seed, ADRS) --> xmss.rs -// Algorithm 11 ht_sign(M, SK.seed, PK.seed, idxtree, idxleaf) --> hypertree.rs -// Algorithm 12 ht_verify(M, SIGHT, PK.seed, idxtree, idxleaf, PK.root) --> hypertree.rs -// Algorithm 13 fors_SKgen(SK.seed, PK.seed, ADRS, idx) --> fors.rs -// Algorithm 14 fors_node(SK.seed, i, z, PK.seed, ADRS) --> fors.rs -// Algorithm 15 fors_sign(md, SK.seed, PK.seed, ADRS) --> fors.rs -// Algorithm 16 fors_pkFromSig(SIGFORS, md, PK.seed, ADRS) --> fors.rs -// Algorithm 17 slh_keygen() --> slh.rs -// Algorithm 18 slh_sign(M, SK) --> slh.rs -// Algorithm 19 slh_verify(M, SIG, PK) --> slh.rs -// Algorithm 20 gen_len2 (n, lgw) --> precomputed +// Algorithm 1 gen_len2 (n, lgw) --> precomputed +// Algorithm 2 toInt(X, n) --> helpers.rs +// Algorithm 3 toByte(x, n) --> helpers.rs +// Algorithm 4 base_2b(X, b, out_len) --> helpers.rs +// Algorithm 5 chain(X, i, s, PK.seed, ADRS) --> wots.rs +// Algorithm 6 wots_PKgen(SK.seed, PK.seed, ADRS) --> wots.rs +// Algorithm 7 wots_sign(M, SK.seed, PK.seed, ADRS) --> wots.rs +// Algorithm 8 wots_PKFromSig(sig, M, PK.seed, ADRS) --> wots.rs +// Algorithm 9 xmss_node(SK.seed, i, z, PK.seed, ADRS) --> xmss.rs +// Algorithm 10 xmss_sign(M, SK.seed, idx, PK.seed, ADRS) --> xmss.rs +// Algorithm 11 xmss_PKFromSig(idx, SIGXMSS, M, PK.seed, ADRS) --> xmss.rs +// Algorithm 12 ht_sign(M, SK.seed, PK.seed, idxtree, idxleaf) --> hypertree.rs +// Algorithm 13 ht_verify(M, SIGHT, PK.seed, idxtree, idxleaf, PK.root) --> hypertree.rs +// Algorithm 14 fors_SKgen(SK.seed, PK.seed, ADRS, idx) --> fors.rs +// Algorithm 15 fors_node(SK.seed, i, z, PK.seed, ADRS) --> fors.rs +// Algorithm 16 fors_sign(md, SK.seed, PK.seed, ADRS) --> fors.rs +// Algorithm 17 fors_pkFromSig(SIGFORS, md, PK.seed, ADRS) --> fors.rs +// Algorithm 18 slh_keygen_internal(SK.seed, SK.prf, PK.seed) --> slh.rs +// Algorithm 19 slh_sign_internal(M, SK, addrnd) --> slh.rs +// Algorithm 20 slh_verify_internal(M, SIG, PK) --> slh.rs +// Algorithm 21 slh_keygen() --> slh.rs +// Algorithm 22 slh_sign(M, ctx, SK) --> slh.rs +// Algorithm 23 hash_slh_sign(M, ctx, PH, SK) --> slh.rs +// Algorithm 24 slh_verify(M, SIG, ctx, PK) --> slh.rs +// Algorithm 25 hash_slh_verify(M, SIG, ctx, PH, PK) --> slh.rs // Fairly elaborate hashing is found in hashers.rs // Signature serialize/deserialize and Adrs support can be found in helpers.rs // types are in types.rs, traits are in traits.rs, and lib.rs provides wrappers into slh.rs // TODO: Roadmap -// 1. Additional (external) top-level test vectors -// 2. Implement fuzz harness for completeness -// 3. Revisit internal checks/asserts/ensure -// 4. Expansion of testing/functionality for C FFI and Python bindings -// 5. Better exposure of randomize, rng support for testing FFI/Python +// 1. Additional (external) top-level test vectors, particularly for hash variants (!!) +// 2. Implement fuzz harness, embedded target, code provenance functionality /// All functionality is covered by traits, such that consumers can utilize trait objects as desired. @@ -54,7 +56,7 @@ 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. +// Per eqns 5.1-4 on page 17, LGW=4, W=16 and LEN2=3 are constant across all security parameter sets. const LGW: u32 = 4; const W: u32 = 16; const LEN2: u32 = 3; @@ -174,11 +176,14 @@ macro_rules! functionality { fn try_sign_with_rng( &self, rng: &mut impl CryptoRngCore, m: &[u8], ctx: &[u8], randomize: bool, ) -> Result<[u8; SIG_LEN], &'static str> { + if ctx.len() > 255 { + return Err("ctx must be less than 256 bytes"); + }; let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]; let sig = crate::slh::slh_sign_with_rng::( rng, &HASHERS, &mp, &self.0, randomize, ); - sig.map(|s| s.deserialize()) + sig.map(|s| s.serialize()) } /// # Errors @@ -186,6 +191,9 @@ macro_rules! functionality { &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph, randomize: bool, ) -> Result { + if ctx.len() > 255 { + return Err("ctx must be less than 256 bytes"); + }; let mut phm = [0u8; 64]; // hashers don't all play well with each other (varying output size) let (oid, phm_len) = hash_message(message, ph, &mut phm); let mp: &[&[u8]] = &[ @@ -198,7 +206,7 @@ macro_rules! functionality { let sig = crate::slh::slh_sign_with_rng::( rng, &HASHERS, &mp, &self.0, randomize, // BAD ); - sig.map(|s| s.deserialize()) + sig.map(|s| s.serialize()) } /// blah! @@ -222,7 +230,7 @@ macro_rules! functionality { &self.0, opt_rand, ); - sig.map(|s| s.deserialize()) + sig.map(|s| s.serialize()) } } @@ -231,7 +239,10 @@ macro_rules! functionality { type Signature = [u8; SIG_LEN]; fn verify(&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8]) -> bool { - let sig = SlhDsaSig::::serialize(sig_bytes); + if ctx.len() > 255 { + return false; + }; + let sig = SlhDsaSig::::deserialize(sig_bytes); let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]; let res = crate::slh::slh_verify::( &HASHERS, &mp, &sig, &self.0, @@ -242,7 +253,10 @@ macro_rules! functionality { fn verify_hash( &self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8], ph: &Ph, ) -> bool { - let sig = SlhDsaSig::::serialize(sig_bytes); + if ctx.len() > 255 { + return false; + }; + let sig = SlhDsaSig::::deserialize(sig_bytes); let mut phm = [0u8; 64]; // hashers don't all play well with each other (varying output size) let (oid, phm_len) = hash_message(m, ph, &mut phm); let mp: &[&[u8]] = &[ @@ -261,7 +275,7 @@ macro_rules! functionality { fn _test_only_raw_verify( &self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ) -> Result { - let sig = SlhDsaSig::::serialize(sig_bytes); + let sig = SlhDsaSig::::deserialize(sig_bytes); let res = crate::slh::slh_verify_internal::( &HASHERS, &[m], @@ -358,7 +372,7 @@ macro_rules! functionality { let result = pk2.verify_hash(&message, &sig, b"context", &ph); assert!(result, "Signature failed to verify"); let result = pk2.verify_hash(&message, &sig, b"some other context", &ph); - assert!(!result, "Signature should not have verified"); + assert!(!result, "Signature should not have verified"); } } } @@ -366,7 +380,7 @@ macro_rules! functionality { } -/// Functionality for the **SLH-DSA-SHA2-128s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-128s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-128s parameter set is claimed to be in security strength category 1. /// @@ -414,7 +428,7 @@ pub mod slh_dsa_sha2_128s { } -/// Functionality for the **SLH-DSA-SHAKE-128s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-128s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE-128s parameter set is claimed to be in security strength category 1. /// @@ -462,7 +476,7 @@ pub mod slh_dsa_shake_128s { } -/// Functionality for the **SLH-DSA-SHA2-128f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-128f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-128f parameter set is claimed to be in security strength category 1. /// @@ -510,7 +524,7 @@ pub mod slh_dsa_sha2_128f { } -/// Functionality for the **SLH-DSA-SHAKE-128f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-128f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE-128f parameter set is claimed to be in security strength category 1. /// @@ -558,7 +572,7 @@ pub mod slh_dsa_shake_128f { } -/// Functionality for the **SLH-DSA-SHA2-192s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-192s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-192s parameter set is claimed to be in security strength category 3. /// @@ -606,7 +620,7 @@ pub mod slh_dsa_sha2_192s { } -/// Functionality for the **SLH-DSA-SHAKE-192s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-192s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE-192s parameter set is claimed to be in security strength category 3. /// @@ -654,7 +668,7 @@ pub mod slh_dsa_shake_192s { } -/// Functionality for the **SLH-DSA-SHA2-192f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-192f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-192f parameter set is claimed to be in security strength category 3. /// @@ -702,7 +716,7 @@ pub mod slh_dsa_sha2_192f { } -/// Functionality for the **SLH-DSA-SHAKE-192f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-192f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE-192f parameter set is claimed to be in security strength category 3. /// @@ -750,7 +764,7 @@ pub mod slh_dsa_shake_192f { } -/// Functionality for the **SLH-DSA-SHA2-256s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-256s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-256s parameter set is claimed to be in security strength category 5. /// @@ -798,7 +812,7 @@ pub mod slh_dsa_sha2_256s { } -/// Functionality for the **SLH-DSA-SHAKE-256s** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-256s** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE_256s parameter set is claimed to be in security strength category 5. /// @@ -846,7 +860,7 @@ pub mod slh_dsa_shake_256s { } -/// Functionality for the **SLH-DSA-SHA2-256f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHA2-256f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHA2-256f parameter set is claimed to be in security strength category 5. /// @@ -894,7 +908,7 @@ pub mod slh_dsa_sha2_256f { } -/// Functionality for the **SLH-DSA-SHAKE-256f** security parameter set per FIPS 205 section 10. This includes specific +/// Functionality for the **SLH-DSA-SHAKE-256f** security parameter set per FIPS 205 section 11. This includes specific /// sizes for the public key, secret key, and signature along with a number of internal constants. The /// SLH-DSA-SHAKE-256f parameter set is claimed to be in security strength category 5. /// diff --git a/src/slh.rs b/src/slh.rs index 1e3ccc8..12f7db1 100644 --- a/src/slh.rs +++ b/src/slh.rs @@ -5,8 +5,8 @@ use crate::{fors, helpers, hypertree, xmss}; use rand_core::CryptoRngCore; -/// Algorithm 17: `slh_keygen()` on page 34. -/// Generate an SLH-DSA key pair. +/// Algorithm 21: `slh_keygen()` on page 37. +/// Generates an SLH-DSA key pair. /// /// Input: (none)
/// Output: SLH-DSA key pair `(SK, PK)`. @@ -22,8 +22,6 @@ pub(crate) fn slh_keygen_with_rng< >( rng: &mut impl CryptoRngCore, hashers: &Hashers, ) -> Result<(SlhPrivateKey, SlhPublicKey), &'static str> { - //let (d32, hp32) = (u32::try_from(D).unwrap(), u32::try_from(HP).unwrap()); - // // 1: SK.seed ←$ B^n ▷ Set SK.seed, SK.prf, and PK.seed to random n-byte let mut sk_seed = [0u8; N]; @@ -40,14 +38,17 @@ pub(crate) fn slh_keygen_with_rng< rng.try_fill_bytes(&mut pk_seed) .map_err(|_| "Alg17: rng failed3")?; - slh_keygen_internal::(hashers, sk_seed, sk_prf, pk_seed) + // 4/5/6: implemented by ? operator on the above steps; not timing/order sensitive + + // 7: + Ok(slh_keygen_internal::(hashers, sk_seed, sk_prf, pk_seed)) } -/// Algorithm 17: `slh_keygen()` on page 34. -/// Generate an SLH-DSA key pair. +/// Algorithm 18: `slh_keygen_internal()` on page 34. +/// Generates an SLH-DSA key pair. Note: this function **is not** exported. /// -/// Input: (none)
+/// Input: Secret seed `SK.seed`, PRF key `SK.prf`, public seed `PK.seed`
/// Output: SLH-DSA key pair `(SK, PK)`. #[allow(clippy::similar_names)] // sk_seed and pk_seed pub(crate) fn slh_keygen_internal< @@ -60,48 +61,32 @@ pub(crate) fn slh_keygen_internal< const N: usize, >( hashers: &Hashers, sk_seed: [u8; N], sk_prf: [u8; N], pk_seed: [u8; N], -) -> Result<(SlhPrivateKey, SlhPublicKey), &'static str> { +) -> (SlhPrivateKey, SlhPublicKey) { let (d32, hp32) = (u32::try_from(D).unwrap(), u32::try_from(HP).unwrap()); // - // // - // // 1: SK.seed ←$ B^n ▷ Set SK.seed, SK.prf, and PK.seed to random n-byte - // let mut sk_seed = [0u8; N]; - // 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 = [0u8; N]; - // rng.try_fill_bytes(&mut sk_prf) - // .map_err(|_| "Alg17: rng failed2")?; - // - // // 3: PK.seed ←$ B^n - // let mut pk_seed = [0u8; N]; - // 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 + // 1: ADRS ← toByte(0, 32) ▷ Generate the public key for the top-level XMSS tree let mut adrs = Adrs::default(); - // 6: ADRS.setLayerAddress(d − 1) + // 2: ADRS.setLayerAddress(d − 1) adrs.set_layer_address(d32 - 1); - // 7: PK.root ← xmss_node(SK.seed, 0, h′, PK.seed, ADRS) + // 3: PK.root ← xmss_node(SK.seed, 0, h′, PK.seed, ADRS) let pk_root = - xmss::xmss_node::(hashers, &sk_seed, 0, hp32, &pk_seed, &adrs)?; + xmss::xmss_node::(hashers, &sk_seed, 0, hp32, &pk_seed, &adrs); - // 8: - // 9: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) ) + // 4: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) ) let pk = SlhPublicKey { pk_seed, pk_root }; let sk = SlhPrivateKey { sk_seed, sk_prf, pk_seed, pk_root }; - Ok((sk, pk)) + (sk, pk) } -/// Algorithm 18: `slh_sign(M, SK)` on page 35. -/// Generate an SLH-DSA signature. +/// Algorithm 22: `slh_sign(M, SK)` on page 39. +/// Generates a pure SLH-DSA signature. Note that the collection of M' elements is done in the +/// calling function, and this collection proceeds down into the hasher (to help avoid memory +/// allocation, buffer copies, etc). /// -/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`.
+/// Input: Message `M`, context string `ctx`, private key `SK`. `randomize` == hedged variant
/// Output: SLH-DSA signature `SIG`. #[allow(clippy::similar_names)] #[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes @@ -119,31 +104,39 @@ pub(crate) fn slh_sign_with_rng< sk: &SlhPrivateKey, randomize: bool, ) -> Result, &'static str> { // - // 1: ADRS ← toByte(0, 32) - //let mut adrs = Adrs::default(); + // 1: if |𝑐𝑡𝑥| > 255 then + // 2: return ⊥ ▷ return an error indication if the context string is too long + // 3: end if + // The ctx length is checked in both calling functions (where it is a bit more + // visible and immediate): `try_sign_with_rng()` and `try_sign_hash_with_rng()` - // 2: - // 3: opt_rand ← PK.seed ▷ Set opt_rand to either PK.seed + // 4: 𝑎𝑑𝑑𝑟𝑛𝑑 ←− 𝔹𝑛 ▷ skip lines 4 through 7 for the deterministic variant let mut opt_rand = sk.pk_seed; - // 4: if (RANDOMIZE) then ▷ or to a random n-byte string + // 5: if 𝑎𝑑𝑑𝑟𝑛𝑑 = NULL then + // 6: return ⊥ if randomize { - // 5: opt_rand ←$ Bn + // rng.try_fill_bytes(&mut opt_rand) .map_err(|_| "Alg17: rng failed")?; - // 6: end if + // 7: end if } - //let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]; + // 8: 𝑀′ ← toByte(0, 1) ∥ toByte(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ 𝑀 + // The collection of M' elements is done in the calling function, and this collection proceeds + // down into the hasher as `mp` (to help avoid memory allocation, buffer copies, etc). + + // 9: SIG ← slh_sign_internal(𝑀′, SK, 𝑎𝑑𝑑𝑟𝑛𝑑) ▷ omit 𝑎𝑑𝑑𝑟𝑛𝑑 for the deterministic variant slh_sign_internal::(hashers, mp, sk, opt_rand) } -/// Algorithm 18: `slh_sign(M, SK)` on page 35. +/// Algorithm 19: `slh_sign_internal(M, SK, addrnd)` on page 35. /// Generate an SLH-DSA signature. /// -/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`.
+/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`, +/// (optional) additional randomness 𝑎𝑑𝑑𝑟𝑛𝑑.
/// Output: SLH-DSA signature `SIG`. #[allow(clippy::similar_names)] #[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes @@ -163,24 +156,14 @@ pub(crate) fn slh_sign_internal< // // 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; - // - // // 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 + // 2: 𝑜𝑝𝑡_𝑟𝑎𝑛𝑑 ← 𝑎𝑑𝑑𝑟𝑛𝑑 ▷ substitute 𝑜𝑝𝑡_𝑟𝑎𝑛𝑑 ← PK.seed for the deterministic variant + // This is handled in the calling function + + // 3: R ← PRF_msg(SK.prf, opt_rand, M) ▷ Generate randomizer let r = (hashers.prf_msg)(&sk.sk_prf, &opt_rand, m); - // 8: SIG ← R + // 4: SIG ← R let mut sig = SlhDsaSig { randomness: r, // here! fors_sig: ForsSig { @@ -195,53 +178,48 @@ pub(crate) fn slh_sign_internal< }, }; - // 9: - // 10: digest ← H_msg(R, PK.seed, PK.root, M) ▷ Compute message digest + // 5: 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 + // 6: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes let index1 = (K * A + 7) / 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 + // 7: 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 - H / D + 7) / 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 + // 8: 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 + 8 * D - 1) / (8 * D); 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} + // 9: 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, (h32 - h32 / d32 + 7) / 8) & (u64::MAX >> (64 - (h32 - h32 / d32))); - // 16: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d} + // 10: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d} let idx_leaf = helpers::to_int(tmp_idx_leaf, (h32 + 8 * d32 - 1) / (8 * d32)) & (u64::MAX >> (64 - h32 / d32)); - // 17: - // 18: ADRS.setTreeAddress(idx_tree) + // 11: ADRS.setTreeAddress(idx_tree) adrs.set_tree_address(idx_tree); - // 19: ADRS.setTypeAndClear(FORS_TREE) + // 12: ADRS.setTypeAndClear(FORS_TREE) adrs.set_type_and_clear(FORS_TREE); - // 20: ADRS.setKeyPairAddress(idxleaf) + // 13: 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 + // 14: SIG_FORS ← fors_sign(md, SK.seed, PK.seed, ADRS) + // 15: 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 + // 16: 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 + // 17: SIG_HT ← ht_sign(PK_FORS , SK.seed, PK.seed, idx_tree, idx_leaf) + // 18: SIG ← SIG ∥ SIG_HT sig.ht_sig = hypertree::ht_sign::( hashers, &pk_fors.key, @@ -251,14 +229,17 @@ pub(crate) fn slh_sign_internal< idx_leaf as u32, )?; - // 28: return SIG + // 19: return SIG Ok(sig) } -/// Algorithm 19: `slh_verify(M, SIG, PK)` -/// Verify an SLH-DSA signature. + +/// Algorithm 19: `slh_verify(M, SIG, ctx, PK)` on page 41. +/// Verifies a pure SLH-DSA signature. Note that the collection of M' elements is done in the +/// calling function, and this collection proceeds down into the hasher (to help avoid memory +/// allocation, buffer copies, etc). /// -/// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`.
+/// Input: Message `M`, signature `SIG`, context string `ctx`, public key `PK = (PK.seed, PK.root)`.
/// Output: Boolean. #[allow(clippy::cast_possible_truncation)] // TODO: temporary #[allow(clippy::similar_names)] @@ -275,23 +256,24 @@ pub(crate) fn slh_verify< hashers: &Hashers, mp: &[&[u8]], sig: &SlhDsaSig, pk: &SlhPublicKey, ) -> bool { - //let (d32, h32) = (u32::try_from(D).unwrap(), u32::try_from(H).unwrap()); - - // 1: if |SIG| != (1 + k(1 + a) + h + d · len) · n then + // 1: if |𝑐𝑡𝑥| > 255 then // 2: return false // 3: end if - // The above size is performed in the wrapper/adapter deserialize function + // The ctx length is checked in both calling functions (where it is a bit more + // visible and immediate): `verify()` and `verify_hash()` - // 4: ADRS ← toByte(0, 32) - //let mut adrs = Adrs::default(); - //let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]; + // 4: 𝑀 ′ ← toByte(0, 1) ∥ toByte(|𝑐𝑡𝑥|, 1) ∥ 𝑐𝑡𝑥 ∥ 𝑀 + // The collection of M' elements is done in the calling function, and this collection proceeds + // down into the hasher as `mp` (to help avoid memory allocation, buffer copies, etc). + + // 5: return slh_verify_internal(𝑀′, SIG, PK) slh_verify_internal::(hashers, mp, sig, pk) } -/// Algorithm 19: `slh_verify(M, SIG, PK)` -/// Verify an SLH-DSA signature. +/// Algorithm 20: `slh_verify(M, SIG, PK)` on page 36. +/// Verifies an SLH-DSA signature. /// /// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`.
/// Output: Boolean. @@ -329,49 +311,43 @@ pub(crate) fn slh_verify_internal< // 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 + // 8: 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 + // 9: md ← digest[0 : ceil(k·a/8)] ▷ first ceil(k·a/8) bytes let index1 = (K * A + 7) / 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 + // 10: 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 - H / D + 7) / 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 + // 11: 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 + 8 * D - 1) / (8 * D); 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} + // 12: 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, (h32 - h32 / d32 + 7) / 8) & (u64::MAX >> (64 - (h32 - h32 / d32))); - // 15: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d} + // 13: idx_leaf ← toInt(tmp_idx_leaf, ceil(h/8d) mod 2^{h/d} let idx_leaf = helpers::to_int(tmp_idx_leaf, (h32 + 8 * d32 - 1) / (8 * d32)) & (u64::MAX >> (64 - h32 / d32)); - // 16: - // 17: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key + // 14: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key adrs.set_tree_address(idx_tree); - // 18: ADRS.setTypeAndClear(FORS_TREE) + // 15: ADRS.setTypeAndClear(FORS_TREE) adrs.set_type_and_clear(FORS_TREE); - // 19: ADRS.setKeyPairAddress(idx_leaf) + // 16: 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) + // 17: 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) + // 18: return ht_verify(PK_FORS, SIG_HT, PK.seed, idx_tree , idx_leaf, PK.root) hypertree::ht_verify::( hashers, &pk_fors.key, diff --git a/src/traits.rs b/src/traits.rs index b4161fb..76f65c3 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -158,6 +158,7 @@ pub trait Signer { /// Attempt to sign the given message, returning a digital signature on success, or an error if /// something went wrong. This function utilizes the default OS RNG and operates in constant time /// with respect to the `PrivateKey` only (not including rejection loop; work in progress). + /// Uses a FIPS 205 context string (default: an empty string). /// /// # Errors /// Returns an error when the random number generator fails; propagates internal errors. @@ -205,6 +206,7 @@ pub trait Signer { /// Attempt to sign the given message, returning a digital signature on success, or an error if /// something went wrong. This function utilizes a supplied RNG and operates in constant time /// with respect to the `PrivateKey` only (not including rejection loop; work in progress). + /// Uses a FIPS 205 context string (default: an empty string). /// /// # Errors /// Returns an error when the random number generator fails; propagates internal errors. @@ -259,7 +261,7 @@ pub trait Verifier { type Signature; /// Verifies a digital signature with respect to a `PublicKey`. This function operates in - /// variable time. + /// variable time. Uses a FIPS 205 context string (default: an empty string). /// /// # Examples /// ```rust diff --git a/src/types.rs b/src/types.rs index 8d19efd..1a4d405 100644 --- a/src/types.rs +++ b/src/types.rs @@ -14,7 +14,7 @@ pub enum Ph { } -/// Fig 16 on page 34 +/// Fig 17 on page 34 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct SlhDsaSig< const A: usize, @@ -30,6 +30,7 @@ pub(crate) struct SlhDsaSig< } +/// Fig 16 on page 33 #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct SlhPublicKey { pub(crate) pk_seed: [u8; N], @@ -37,6 +38,7 @@ pub struct SlhPublicKey { } +/// Fig 15 on page 33 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub struct SlhPrivateKey { pub(crate) sk_seed: [u8; N], @@ -46,7 +48,7 @@ pub struct SlhPrivateKey { } -/// Fig 13 on page 29 +/// Fig 14 on page 29 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct ForsSig { pub(crate) private_key_value: [[u8; N]; K], @@ -60,19 +62,20 @@ pub(crate) struct ForsPk { } -/// Fig 10 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct Auth { pub(crate) tree: [[u8; N]; A], } +/// Fig 13 on page 26 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub(crate) struct HtSig { pub(crate) xmss_sigs: [XmssSig; D], } +/// Fig 10 on page 19 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub struct WotsSig { pub(crate) data: [[u8; N]; LEN], @@ -83,6 +86,7 @@ pub struct WotsSig { pub struct WotsPk(pub(crate) [u8; N]); +/// Fig 11 on page 22 #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] pub struct XmssSig { pub(crate) sig_wots: WotsSig, @@ -108,7 +112,7 @@ pub(crate) const FORS_PRF: u32 = 6; /// Straddling the line between struct, enum and union... #[derive(Clone, Default, Zeroize, ZeroizeOnDrop)] -#[repr(align(32))] +#[repr(align(32))] // TODO: check alignment size perf/requirements pub(crate) struct Adrs { pub(crate) f0: [u8; 4], // layer address diff --git a/src/wots.rs b/src/wots.rs index 090e589..e10ff40 100644 --- a/src/wots.rs +++ b/src/wots.rs @@ -4,7 +4,7 @@ use crate::helpers::{base_2b, to_byte}; use crate::types::{Adrs, WotsPk, WotsSig, WOTS_PK, WOTS_PRF}; -/// Algorithm 4: `chain(X, i, s, PK.seed, ADRS)` on page 17. +/// Algorithm 5: `chain(X, i, s, PK.seed, ADRS)` on page 18. /// 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`. @@ -16,43 +16,36 @@ use crate::types::{Adrs, WotsPk, WotsSig, WOTS_PK, WOTS_PRF}; /// Output: Value of `F` iterated `s` times on `X`. pub(crate) fn chain( hashers: &Hashers, cap_x: [u8; N], i: u32, s: u32, pk_seed: &[u8], adrs: &Adrs, -) -> Option<[u8; N]> { +) -> [u8; N] { 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; + // Note this bounds check was only specified in the draft specification + // (old)1: if (i + s) ≥ w then return NULL; + // if (i + s) >= crate::W { return None; } - // 3: end if - } - - // 4: - // 5: tmp ← X + // 1: tmp ← X let mut tmp = cap_x; - // 6: - // 7: for j from i to i + s − 1 do + // 2: for j from i to i + s − 1 do for j in i..(i + s) { // - // 8: ADRS.setHashAddress(j) + // 3: ADRS.setHashAddress(j) adrs.set_hash_address(j); - // 9: tmp ← F(PK.seed, ADRS, tmp) + // 4: tmp ← F(PK.seed, ADRS, tmp) tmp = (hashers.f)(pk_seed, &adrs, &tmp); - // 10: end for + // 5: end for } - // 11: return tmp - Some(tmp) + // 6: return tmp + 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` +/// Algorithm 6: `wots_PKgen(SK.seed, PK.seed, ADRS)` on page 18. +/// Generates 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. @@ -62,7 +55,7 @@ pub(crate) fn chain( hashers: &Hashers, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs, -) -> Result, &'static str> { +) -> WotsPk { let len32 = u32::try_from(LEN).unwrap(); let mut adrs = adrs.clone(); let mut tmp = [[0u8; N]; LEN]; @@ -90,7 +83,7 @@ pub(crate) fn wots_pkgen /// Output: WOTS+ signature sig. @@ -128,68 +121,64 @@ pub(crate) fn wots_sign /// Output: WOTS+ public key `pksig` derived from `sig`. @@ -203,26 +192,23 @@ pub(crate) fn wots_pk_from_sig( hashers, sig.data[i], @@ -245,24 +231,23 @@ pub(crate) fn wots_pk_from_sig /// Output: n-byte root `node`. -#[allow(clippy::similar_names)] // sk_seed and pk_seed +#[allow(clippy::similar_names, clippy::let_and_return)] // sk_seed and pk_seed, clarity pub(crate) fn xmss_node< const H: usize, const HP: usize, @@ -19,64 +19,58 @@ pub(crate) fn xmss_node< const N: usize, >( hashers: &Hashers, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs, -) -> Result<[u8; N], &'static str> { - let hp32 = u32::try_from(HP).unwrap(); +) -> [u8; N] { let mut adrs = adrs.clone(); - // 1: if z > h′ or i ≥ 2^{h −z} then - if (z > hp32) | (u64::from(i) >= (1 << (hp32 - z))) { - // - // 2: return NULL - return Err("Alg8: fail"); + // Note this bounds check was only specified in the draft specification + // (old)1: if z > h′ or i ≥ 2^{h −z} then + // if (z > hp32) | (u64::from(i) >= (1 << (hp32 - z))) { return Err("Alg8: fail"); } - // 3: end if - } - - // 4: if z = 0 then + // 1: if z = 0 then let node = if z == 0 { // - // 5: ADRS.setTypeAndClear(WOTS_HASH) + // 2: ADRS.setTypeAndClear(WOTS_HASH) adrs.set_type_and_clear(WOTS_HASH); - // 6: ADRS.setKeyPairAddress(i) + // 3: 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 + // 4: node ← wots_PKgen(SK.seed, PK.seed, ADRS) + wots::wots_pkgen::(hashers, sk_seed, pk_seed, &adrs).0 - // 8: else + // 5: else } else { // - // 9: lnode ← xmss_node(SK.seed, 2 * i, z − 1, PK.seed, ADRS) + // 6: 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)?; + 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) + // 7: 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)?; + xmss_node::(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs); - // 11: ADRS.setTypeAndClear(TREE) + // 8: ADRS.setTypeAndClear(TREE) adrs.set_type_and_clear(TREE); - // 12: ADRS.setTreeHeight(z) + // 9: ADRS.setTreeHeight(z) adrs.set_tree_height(z); - // 13: ADRS.setTreeIndex(i) + // 10: ADRS.setTreeIndex(i) adrs.set_tree_index(i); - // 14: node ← H(PK.seed, ADRS, lnode ∥ rnode) + // 11: node ← H(PK.seed, ADRS, lnode ∥ rnode) (hashers.h)(pk_seed, &adrs, &lnode, &rnode) - // 15: end if + // 12: end if }; - // 16: return node - Ok(node) + // 13: return node + node } -/// Algorithm 9: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23. -/// Generate an XMSS signature. +/// Algorithm 10: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23. +/// Generates 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). @@ -91,7 +85,7 @@ pub(crate) fn xmss_sign< >( hashers: &Hashers, m: &[u8], sk_seed: &[u8], idx: u32, pk_seed: &[u8], adrs: &Adrs, -) -> Result, &'static str> { +) -> XmssSig { let hp32 = u32::try_from(HP).unwrap(); let mut adrs = adrs.clone(); let mut sig_xmss = XmssSig { @@ -107,31 +101,30 @@ pub(crate) fn xmss_sign< // 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)?; + xmss_node::(hashers, sk_seed, k, j, pk_seed, &adrs); // 4: end for } - // 5: - // 6: ADRS.setTypeAndClear(WOTS_HASH) + // 5: ADRS.setTypeAndClear(WOTS_HASH) adrs.set_type_and_clear(WOTS_HASH); - // 7: ADRS.setKeyPairAddress(idx) + // 6: ADRS.setKeyPairAddress(idx) adrs.set_key_pair_address(idx); - // 8: sig ← wots_sign(M, SK.seed, PK.seed, ADRS) + // 7: sig ← wots_sign(M, SK.seed, PK.seed, ADRS) sig_xmss.sig_wots = wots::wots_sign::(hashers, m, sk_seed, pk_seed, &adrs); - // 9: SIG_XMSS ← sig ∥ AUTH + // 8: SIG_XMSS ← sig ∥ AUTH // struct built above - // 10: return SIG_XMSS - Ok(sig_xmss) + // 9: return SIG_XMSS + sig_xmss } -/// Algorithm 10: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)` -/// Compute an XMSS public key from an XMSS signature. +/// Algorithm 11: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)` +/// Computes 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`.
@@ -164,48 +157,47 @@ pub(crate) fn xmss_pk_from_sig< // 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; - // 6: - // 7: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH + // 6: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH adrs.set_type_and_clear(TREE); - // 8: ADRS.setTreeIndex(idx) + // 7: ADRS.setTreeIndex(idx) adrs.set_tree_index(idx); - // 9: for k from 0 to h′ − 1 do + // 8: for k from 0 to h′ − 1 do for k in 0..hp32 { // - // 10: ADRS.setTreeHeight(k + 1) + // 9: ADRS.setTreeHeight(k + 1) adrs.set_tree_height(k + 1); - // 11: if idx/2^k is even then + // 10: if idx/2^k is even then let node_1 = if ((idx >> k) & 1) == 0 { // - // 12: ADRS.setTreeIndex(ADRS.getTreeIndex()/2) + // 11: 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]) + // 12: node[1] ← H(PK.seed, ADRS, node[0] ∥ AUTH[k]) (hashers.h)(pk_seed, &adrs, &node_0, &auth[k as usize]) - // 14: else + // 13: else } else { // - // 15: ADRS.setTreeIndex((ADRS.getTreeIndex() − 1)/2) + // 14: 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]) + // 15: node[1] ← H(PK.seed, ADRS, AUTH[k] ∥ node[0]) (hashers.h)(pk_seed, &adrs, &auth[k as usize], &node_0) - // 17: end if + // 16: end if }; - // 18: node[0] ← node[1] + // 17: node[0] ← node[1] node_0 = node_1; - // 19: end for + // 18: end for } - // 20: return node[0] + // 19: return node[0] node_0 }