align comments with released spec

This commit is contained in:
eschorn1 2024-10-02 14:50:54 -05:00
parent f0576386fc
commit 834e75b603
12 changed files with 397 additions and 438 deletions

View file

@ -7,14 +7,14 @@
![Rust Version][rustc-image] ![Rust Version][rustc-image]
[FIPS 205] Stateless Hash-Based Digital Signature Standard written in pure Rust for server, [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 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, 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 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 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. 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 <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.205.pdf> for a full description of the target functionality. See <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.205.pdf> for a full description of the target functionality.
@ -50,7 +50,9 @@ desired [security parameter](#modules) below.
## Notes ## 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. * 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`. * 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, * Requires Rust **1.70** or higher. The minimum supported Rust version may be changed in the future,

View file

@ -60,7 +60,7 @@ Thank you to Daniel Kahn Gillmor for providing an example for FIPS 203.
## See Also ## See Also
- https://doi.org/10.6028/NIST.FIPS.205.ipd - https://csrc.nist.gov/pubs/fips/205/final
- https://github.com/integritychain/fips205 - https://github.com/integritychain/fips205
""" """

View file

@ -3,8 +3,8 @@ use crate::helpers::base_2b;
use crate::types::{Adrs, Auth, ForsPk, ForsSig, FORS_PRF, FORS_ROOTS}; use crate::types::{Adrs, Auth, ForsPk, ForsSig, FORS_PRF, FORS_ROOTS};
/// Algorithm 13: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29. /// Algorithm 14: `fors_SKgen(SK.seed, PK.seed, ADRS, idx)` on page 29.
/// Generate a FORS private-key value. /// Generates a FORS private-key value.
/// ///
/// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`, secret key index `idx`. <br> /// Input: Secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`, secret key index `idx`. <br>
/// Output: n-byte FORS private-key value. /// Output: n-byte FORS private-key value.
@ -29,8 +29,8 @@ pub(crate) fn fors_sk_gen<const K: usize, const LEN: usize, const M: usize, cons
} }
/// Algorithm 14: `fors_node(SK.seed, i, z, PK.seed, ADRS)` on page 30. /// Algorithm 15: `fors_node(SK.seed, i, z, PK.seed, ADRS)` on page 30.
/// Compute the root of a Merkle subtree of FORS public values. /// Computes 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`, /// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
/// address `ADRS`. <br> /// address `ADRS`. <br>
@ -45,62 +45,56 @@ pub(crate) fn fors_node<
>( >(
hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs, hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
) -> Result<[u8; N], &'static str> { ) -> Result<[u8; N], &'static str> {
let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap());
let mut adrs = adrs.clone(); let mut adrs = adrs.clone();
// 1: if z > a or i ≥ k · 2^(az) then // Note this bounds check was only specified in the draft specification
if (z > a32) | (i > k32 * (1 << (a32 - z))) { // let (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap());
// // debug_assert!((z > a32) | (i > k32 * (1 << (a32 - z))), "Alg15 fails");
// 2: return NULL
return Err("Alg14 fails");
// 3: end if // 1: if z = 0 then
}
// 4: if z = 0 then
let node = if z == 0 { 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); 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); adrs.set_tree_height(0);
// 7: ADRS.setTreeIndex(i) // 4: ADRS.setTreeIndex(i)
adrs.set_tree_index(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) (hashers.f)(pk_seed, &adrs, &sk)
// 9: else // 6: else
} 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::<A, K, LEN, M, N>(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?; let lnode = fors_node::<A, K, LEN, M, N>(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 = let rnode =
fors_node::<A, K, LEN, M, N>(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?; fors_node::<A, K, LEN, M, N>(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?;
// 12: ADRS.setTreeHeight(z) // 9: ADRS.setTreeHeight(z)
adrs.set_tree_height(z); adrs.set_tree_height(z);
// 13: ADRS.setTreeIndex(i) // 10: ADRS.setTreeIndex(i)
adrs.set_tree_index(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) (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
// 15: end if // 12: end if
}; };
// 16: return node // 13: return node
Ok(node) Ok(node)
} }
/// Algorithm 15: `fors_sign(md, SK.seed, PK.seed, ADRS)` /// Algorithm 16: `fors_sign(md, SK.seed, PK.seed, ADRS)` on page 31.
/// Generate a FORS signature. /// Generates a FORS signature.
/// ///
/// Input: Message digest `md`, secret seed `SK.seed`, address `ADRS`, public seed `PK.seed`. <br> /// Input: Message digest `md`, secret seed `SK.seed`, address `ADRS`, public seed `PK.seed`. <br>
/// Output: FORS signature `SIG_FORS`. /// 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()); 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 // 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 { let mut sig_fors = ForsSig {
private_key_value: [[0u8; N]; K], private_key_value: [[0u8; N]; K],
auth: core::array::from_fn(|_| Auth { tree: [[0u8; N]; A] }), 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]; let mut indices = [0u32; K];
base_2b(md, a32, k32, &mut indices); base_2b(md, a32, k32, &mut indices);
@ -135,42 +130,41 @@ pub(crate) fn fors_sign<
sk_seed, sk_seed,
pk_seed, pk_seed,
adrs, adrs,
i * (1 << a32) + indices[i as usize], (i << a32) + indices[i as usize],
); );
// 5: // 5: for j from 0 to a 1 do ▷ Compute auth path
// 6: for j from 0 to a 1 do ▷ Compute auth path
for j in 0..a32 { 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; let s = (indices[i as usize] >> j) ^ 1;
// 8: AUTH[j] ← fors_node(SK.seed, i · 2^{aj} + s, j, PK.seed, ADRS) // 7: AUTH[j] ← fors_node(SK.seed, i · 2^{aj} + s, j, PK.seed, ADRS)
sig_fors.auth[i as usize].tree[j as usize] = fors_node::<A, K, LEN, M, N>( sig_fors.auth[i as usize].tree[j as usize] = fors_node::<A, K, LEN, M, N>(
hashers, hashers,
sk_seed, sk_seed,
i * (1 << (a32 - j)) + s, (i << (a32 - j)) + s,
j, j,
pk_seed, pk_seed,
adrs, adrs,
)?; )?;
// 9: end for // 8: end for
} }
// 10: SIG_FORS ← SIG_FORS ∥ AUTH // 9: SIG_FORS ← SIG_FORS ∥ AUTH
// built within inner loop above // built within inner loop above (step 7)
// 11: end for // 10: end for
} }
// 12: return SIG_FORS // 11: return SIG_FORS
Ok(sig_fors) Ok(sig_fors)
} }
/// Algorithm 16: `fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)` on page 32. /// Algorithm 17: `fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)` on page 32.
/// Compute a FORS public key from a FORS signature. /// Computes a FORS public key from a FORS signature.
/// ///
/// Input: FORS signature `SIG_FORS`, message digest `md`, public seed `PK.seed`, address `ADRS`. <br> /// Input: FORS signature `SIG_FORS`, message digest `md`, public seed `PK.seed`, address `ADRS`. <br>
/// Output: FORS public key. /// 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 (a32, k32) = (u32::try_from(A).unwrap(), u32::try_from(K).unwrap());
let mut adrs = adrs.clone(); 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]; let mut indices = [0u32; K];
base_2b(md, a32, k32, &mut indices); base_2b(md, a32, k32, &mut indices);
@ -203,68 +197,67 @@ pub(crate) fn fors_pk_from_sig<
adrs.set_tree_height(0); adrs.set_tree_height(0);
// 5: ADRS.setTreeIndex(i · 2^a + indices[i]) // 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) // 6: node[0] ← F(PK.seed, ADRS, sk)
let mut node_0 = (hashers.f)(pk_seed, &adrs, &sk); let mut node_0 = (hashers.f)(pk_seed, &adrs, &sk);
// 7: // 7: auth ← SIGFORS.getAUTH(i) ▷ SIGFORS [(i · (a + 1) + 1) · n : (i + 1) · (a + 1) · n]
// 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(); 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 { for j in 0..a32 {
// //
// 10: ADRS.setTreeHeight(j + 1) // 9: ADRS.setTreeHeight(j + 1)
adrs.set_tree_height(j + 1); adrs.set_tree_height(j + 1);
// 11: if indices[i]/2^j is even then // 10: if indices[i]/2^j is even then
let node_1 = if ((indices[i as usize] >> j) % 2) == 0 { 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; let tmp = adrs.get_tree_index() / 2;
adrs.set_tree_index(tmp); 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]) (hashers.h)(pk_seed, &adrs, &node_0, &auth.tree[j as usize])
// 14: else // 13: else
} else { } else {
// //
// 15: ADRS.setTreeIndex((ADRS.getTreeIndex() 1)/2) // 14: ADRS.setTreeIndex((ADRS.getTreeIndex() 1)/2)
let tmp = (adrs.get_tree_index() - 1) / 2; let tmp = (adrs.get_tree_index() - 1) / 2;
adrs.set_tree_index(tmp); 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) (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; 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; 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(); 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); 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()); 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); let pk = (hashers.t_len)(pk_seed, &fors_pk_adrs, &root);
// 26: return pk; // 25: return pk;
ForsPk { key: pk } ForsPk { key: pk }
} }

View file

@ -149,9 +149,7 @@ pub(crate) mod sha2_cat_1 {
let mut inner_hasher = Sha256::new(); let mut inner_hasher = Sha256::new();
inner_hasher.update(&padding[..]); inner_hasher.update(&padding[..]);
inner_hasher.update(a0); inner_hasher.update(a0);
for i in m { m.iter().for_each(|item| inner_hasher.update(item));
inner_hasher.update(i);
}
for p in &mut padding { for p in &mut padding {
*p ^= 0x6a; *p ^= 0x6a;
} }
@ -270,9 +268,7 @@ pub(crate) mod sha2_cat_3_5 {
let mut inner_hasher = Sha512::new(); let mut inner_hasher = Sha512::new();
inner_hasher.update(&padding[..]); inner_hasher.update(&padding[..]);
inner_hasher.update(a0); inner_hasher.update(a0);
for i in m { m.iter().for_each(|item| inner_hasher.update(item));
inner_hasher.update(i);
}
for p in &mut padding { for p in &mut padding {
*p ^= 0x6a; *p ^= 0x6a;
} }

View file

@ -1,8 +1,8 @@
use crate::types::{Adrs, Auth, ForsSig, HtSig, SlhDsaSig, WotsSig, XmssSig}; use crate::types::{Adrs, Auth, ForsSig, HtSig, SlhDsaSig, WotsSig, XmssSig};
/// Algorithm 1: `toInt(X, n)` on page 14. /// Algorithm 2: `toInt(X, n)` on page 15.
/// Convert a byte string to an integer. /// Converts a byte string to an integer.
/// ///
/// Input: n-byte string `X`, string length `n`. <br> /// Input: n-byte string `X`, string length `n`. <br>
/// Output: Integer value of `X`. /// Output: Integer value of `X`.
@ -13,23 +13,22 @@ pub(crate) fn to_int(x: &[u8], n: u32) -> u64 {
// 1: total ← 0 // 1: total ← 0
let mut total = 0; let mut total = 0;
// 2: // 2: for i from 0 to n 1 do
// 3: for i from 0 to n 1 do
for item in x.iter().take(n as usize) { 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); total = (total << 8) + u64::from(*item);
// 5: end for // 4: end for
} }
// 6: return total // 5: return total
total total
} }
/// Algorithm 2: `toByte(x, n)` on page 15. /// Algorithm 3: `toByte(x, n)` on page 15.
/// Convert an integer to a byte string. /// Converts an integer to a byte string.
/// ///
/// Input: Integer `x`, string length `n`. <br> /// Input: Integer `x`, string length `n`. <br>
/// Output: Byte string of length `n` containing binary representation of `x` in big-endian byte-order. /// 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 // 1: total ← x
let mut total = x; let mut total = x;
// 2: // 2: for i from 0 to n 1 do
// 3: for i from 0 to n 1 do
for i in 0..n { 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]; s[(n - 1 - i) as usize] = total.to_le_bytes()[0];
// 5: total ← total ≫ 8 // 4: total ← total ≫ 8
total >>= 8; total >>= 8;
// 6: end for // 5: end for
} }
// 7: return S // 6: return S
s s
} }
/// Algorithm 3: `base_2^b(X, b, out_len)` on page 15. /// Algorithm 4: `base_2^b(X, b, out_len)` on page 16.
/// Compute the base 2^b representation of X. /// 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`. <br> /// Input: Byte string `X` of length at least `ceil(out_len·b/8)`, integer `b`, output length `out_len`. <br>
/// Output: Array of `out_len` integers in the range `[0, . . . , 2^b 1]`. /// 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 // 3: total ← 0
let mut total = 0; let mut total = 0;
// 4: // 4: for out from 0 to out_len 1 do
// 5: for out from 0 to out_len 1 do
for item in baseb.iter_mut() { for item in baseb.iter_mut() {
// //
// 6: while bits < b do // 5: while bits < b do
while bits < b { while bits < b {
// //
// 7: total ← (total ≪ 8) + X[in] // 6: total ← (total ≪ 8) + X[in]
total = (total << 8) + u32::from(x[inn]); total = (total << 8) + u32::from(x[inn]);
// 8: in ← in + 1 // 7: in ← in + 1
inn += 1; inn += 1;
// 9: bits ← bits + 8 // 8: bits ← bits + 8
bits += 8; bits += 8;
// 10: end while // 9: end while
} }
// 11: bits ← bits b // 10: bits ← bits b
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)); *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, const N: usize,
> SlhDsaSig<A, D, HP, K, LEN, N> > SlhDsaSig<A, D, HP, K, LEN, N>
{ {
pub(crate) fn deserialize<const SIG_LEN: usize>(self) -> [u8; SIG_LEN] { pub(crate) fn serialize<const SIG_LEN: usize>(self) -> [u8; SIG_LEN] {
let mut out = [0u8; SIG_LEN]; let mut out = [0u8; SIG_LEN];
debug_assert_eq!( debug_assert_eq!(
out.len(), out.len(),
@ -152,7 +149,7 @@ impl<
out out
} }
pub(crate) fn serialize(bytes: &[u8]) -> Self { pub(crate) fn deserialize(bytes: &[u8]) -> Self {
debug_assert_eq!( debug_assert_eq!(
bytes.len(), bytes.len(),
N + // randomness N + // randomness

View file

@ -3,8 +3,8 @@ use crate::types::{Adrs, HtSig, WotsSig, XmssSig};
use crate::xmss; use crate::xmss;
/// Algorithm 11: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27. /// Algorithm 12: `ht_sign(M, SK.seed, PK.seed, idx_tree, idx_leaf)` on page 27.
/// Generate a hypertree signature. /// Generates a hypertree signature.
/// ///
/// Input: Message `M`, private seed `SK.seed`, public seed `PK.seed`, tree index `idx_tree`, leaf /// Input: Message `M`, private seed `SK.seed`, public seed `PK.seed`, tree index `idx_tree`, leaf
/// index `idx_leaf`. <br> /// index `idx_leaf`. <br>
@ -28,15 +28,14 @@ pub(crate) fn ht_sign<
// 1: ADRS ← toByte(0, 32) // 1: ADRS ← toByte(0, 32)
let mut adrs = Adrs::default(); let mut adrs = Adrs::default();
// 2: // 2: ADRS.setTreeAddress(idxtree)
// 3: ADRS.setTreeAddress(idxtree)
adrs.set_tree_address(idx_tree); 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 = let mut sig_tmp =
xmss::xmss_sign::<H, HP, K, LEN, M, N>(hashers, m, sk_seed, idx_leaf, pk_seed, &adrs)?; xmss::xmss_sign::<H, HP, K, LEN, M, N>(hashers, m, sk_seed, idx_leaf, pk_seed, &adrs);
// 5: SIG_HT ← SIG_tmp // 4: SIG_HT ← SIG_tmp
let mut sig_ht = HtSig { let mut sig_ht = HtSig {
xmss_sigs: core::array::from_fn(|_| XmssSig { xmss_sigs: core::array::from_fn(|_| XmssSig {
sig_wots: WotsSig { data: [[0u8; N]; LEN] }, sig_wots: WotsSig { data: [[0u8; N]; LEN] },
@ -45,55 +44,55 @@ pub(crate) fn ht_sign<
}; };
sig_ht.xmss_sigs[0] = sig_tmp.clone(); 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 = let mut root =
xmss::xmss_pk_from_sig::<HP, K, LEN, M, N>(hashers, idx_leaf, &sig_tmp, m, pk_seed, &adrs); xmss::xmss_pk_from_sig::<HP, K, LEN, M, N>(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 { 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 = let idx_leaf =
u32::try_from(idx_tree & ((1 << hp32) - 1)).map_err(|_| "Alg11: oversized 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; idx_tree >>= hp32;
// 10: ADRS.setLayerAddress(j) // 9: ADRS.setLayerAddress(j)
adrs.set_layer_address(j); adrs.set_layer_address(j);
// 11: ADRS.setTreeAddress(idx_tree) // 10: ADRS.setTreeAddress(idx_tree)
adrs.set_tree_address(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::<H, HP, K, LEN, M, N>( sig_tmp = xmss::xmss_sign::<H, HP, K, LEN, M, N>(
hashers, &root, sk_seed, idx_leaf, pk_seed, &adrs, 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(); 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) { 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::<HP, K, LEN, M, N>( root = xmss::xmss_pk_from_sig::<HP, K, LEN, M, N>(
hashers, idx_leaf, &sig_tmp, &root, pk_seed, &adrs, 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) Ok(sig_ht)
} }
/// Algorithm 12: `ht_verify(M, SIG_HT, PK.seed, idx_tree, idx_leaf, PK.root)` on page 28. /// Algorithm 13: `ht_verify(M, SIG_HT, PK.seed, idx_tree, idx_leaf, PK.root)` on page 28.
/// Verify a hypertree signature. /// Verifies a hypertree signature.
/// ///
/// Input: Message `M`, signature `SIG_HT`, public seed `PK.seed`, tree index `idx_tree`, leaf index `idx_leaf`, /// Input: Message `M`, signature `SIG_HT`, public seed `PK.seed`, tree index `idx_tree`, leaf index `idx_leaf`,
/// HT public key `PK.root`. <br> /// HT public key `PK.root`. <br>
@ -115,20 +114,19 @@ pub(crate) fn ht_verify<
// 1: ADRS ← toByte(0, 32) // 1: ADRS ← toByte(0, 32)
let mut adrs = Adrs::default(); let mut adrs = Adrs::default();
// 2: // 2: ADRS.setTreeAddress(idx_tree)
// 3: ADRS.setTreeAddress(idx_tree)
adrs.set_tree_address(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(); 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); 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 { 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)); let idx_leaf = u32::try_from(idx_tree & ((1 << hp32) - 1));
if idx_leaf.is_err() { if idx_leaf.is_err() {
@ -136,28 +134,28 @@ pub(crate) fn ht_verify<
}; };
let idx_leaf = idx_leaf.unwrap(); 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; idx_tree >>= hp32;
// 9: ADRS.setLayerAddress(j) // 8: ADRS.setLayerAddress(j)
adrs.set_layer_address(j); adrs.set_layer_address(j);
// 10: ADRS.setTreeAddress(idx_tree) // 9: ADRS.setTreeAddress(idx_tree)
adrs.set_tree_address(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(); 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); 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 // 13: if node = PK.root then
// 15: return true // 14: return true
// 16: else // 15: else
// 17: return false // 16: return false
// 18: end if // 17: end if
node == *pk_root // TODO: CT equal (is this in signing path??) node == *pk_root // TODO: CT equal (double-check: is this in signing path??)
} }

View file

@ -4,40 +4,42 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
// Implements FIPS 205 draft Stateless Hash-Based Digital Signature Standard. // Implements FIPS 205 Stateless Hash-Based Digital Signature Standard.
// See <https://csrc.nist.gov/pubs/fips/205/ipd> // See <https://csrc.nist.gov/pubs/fips/205/final>
// //
// Algorithm 1 toInt(X, n) --> helpers.rs // Algorithm 1 gen_len2 (n, lgw) --> precomputed
// Algorithm 2 toByte(x, n) --> helpers.rs // Algorithm 2 toInt(X, n) --> helpers.rs
// Algorithm 3 base_2b (X, b, out_len) --> helpers.rs // Algorithm 3 toByte(x, n) --> helpers.rs
// Algorithm 4 chain(X, i, s, PK.seed, ADRS) --> wots.rs // Algorithm 4 base_2b(X, b, out_len) --> helpers.rs
// Algorithm 5 wots_PKgen(SK.seed, PK.seed, ADRS) --> wots.rs // Algorithm 5 chain(X, i, s, PK.seed, ADRS) --> wots.rs
// Algorithm 6 wots_sign(M, SK.seed, PK.seed, ADRS) --> wots.rs // Algorithm 6 wots_PKgen(SK.seed, PK.seed, ADRS) --> wots.rs
// Algorithm 7 wots_PKFromSig(sig, M, PK.seed, ADRS) --> wots.rs // Algorithm 7 wots_sign(M, SK.seed, PK.seed, ADRS) --> wots.rs
// Algorithm 8 xmss_node(SK.seed, i, z, PK.seed, ADRS) --> xmss.rs // Algorithm 8 wots_PKFromSig(sig, M, PK.seed, ADRS) --> wots.rs
// Algorithm 9 xmss_sign(M, SK.seed, idx, PK.seed, ADRS) --> xmss.rs // Algorithm 9 xmss_node(SK.seed, i, z, PK.seed, ADRS) --> xmss.rs
// Algorithm 10 xmss_PKFromSig(idx, SIGXMSS, M, PK.seed, ADRS) --> xmss.rs // Algorithm 10 xmss_sign(M, SK.seed, idx, PK.seed, ADRS) --> xmss.rs
// Algorithm 11 ht_sign(M, SK.seed, PK.seed, idxtree, idxleaf) --> hypertree.rs // Algorithm 11 xmss_PKFromSig(idx, SIGXMSS, M, PK.seed, ADRS) --> xmss.rs
// Algorithm 12 ht_verify(M, SIGHT, PK.seed, idxtree, idxleaf, PK.root) --> hypertree.rs // Algorithm 12 ht_sign(M, SK.seed, PK.seed, idxtree, idxleaf) --> hypertree.rs
// Algorithm 13 fors_SKgen(SK.seed, PK.seed, ADRS, idx) --> fors.rs // Algorithm 13 ht_verify(M, SIGHT, PK.seed, idxtree, idxleaf, PK.root) --> hypertree.rs
// Algorithm 14 fors_node(SK.seed, i, z, PK.seed, ADRS) --> fors.rs // Algorithm 14 fors_SKgen(SK.seed, PK.seed, ADRS, idx) --> fors.rs
// Algorithm 15 fors_sign(md, SK.seed, PK.seed, ADRS) --> fors.rs // Algorithm 15 fors_node(SK.seed, i, z, PK.seed, ADRS) --> fors.rs
// Algorithm 16 fors_pkFromSig(SIGFORS, md, PK.seed, ADRS) --> fors.rs // Algorithm 16 fors_sign(md, SK.seed, PK.seed, ADRS) --> fors.rs
// Algorithm 17 slh_keygen() --> slh.rs // Algorithm 17 fors_pkFromSig(SIGFORS, md, PK.seed, ADRS) --> fors.rs
// Algorithm 18 slh_sign(M, SK) --> slh.rs // Algorithm 18 slh_keygen_internal(SK.seed, SK.prf, PK.seed) --> slh.rs
// Algorithm 19 slh_verify(M, SIG, PK) --> slh.rs // Algorithm 19 slh_sign_internal(M, SK, addrnd) --> slh.rs
// Algorithm 20 gen_len2 (n, lgw) --> precomputed // 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 // Fairly elaborate hashing is found in hashers.rs
// Signature serialize/deserialize and Adrs support can be found in helpers.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 // types are in types.rs, traits are in traits.rs, and lib.rs provides wrappers into slh.rs
// TODO: Roadmap // TODO: Roadmap
// 1. Additional (external) top-level test vectors // 1. Additional (external) top-level test vectors, particularly for hash variants (!!)
// 2. Implement fuzz harness for completeness // 2. Implement fuzz harness, embedded target, code provenance functionality
// 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
/// All functionality is covered by traits, such that consumers can utilize trait objects as desired. /// All functionality is covered by traits, such that consumers can utilize trait objects as desired.
@ -54,7 +56,7 @@ mod wots;
mod xmss; 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 LGW: u32 = 4;
const W: u32 = 16; const W: u32 = 16;
const LEN2: u32 = 3; const LEN2: u32 = 3;
@ -174,11 +176,14 @@ macro_rules! functionality {
fn try_sign_with_rng( fn try_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, m: &[u8], ctx: &[u8], randomize: bool, &self, rng: &mut impl CryptoRngCore, m: &[u8], ctx: &[u8], randomize: bool,
) -> Result<[u8; SIG_LEN], &'static str> { ) -> 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 mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m];
let sig = crate::slh::slh_sign_with_rng::<A, D, H, HP, K, LEN, M, N>( let sig = crate::slh::slh_sign_with_rng::<A, D, H, HP, K, LEN, M, N>(
rng, &HASHERS, &mp, &self.0, randomize, rng, &HASHERS, &mp, &self.0, randomize,
); );
sig.map(|s| s.deserialize()) sig.map(|s| s.serialize())
} }
/// # Errors /// # Errors
@ -186,6 +191,9 @@ macro_rules! functionality {
&self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph, &self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &Ph,
randomize: bool, randomize: bool,
) -> Result<Self::Signature, &'static str> { ) -> Result<Self::Signature, &'static str> {
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 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 (oid, phm_len) = hash_message(message, ph, &mut phm);
let mp: &[&[u8]] = &[ let mp: &[&[u8]] = &[
@ -198,7 +206,7 @@ macro_rules! functionality {
let sig = crate::slh::slh_sign_with_rng::<A, D, H, HP, K, LEN, M, N>( let sig = crate::slh::slh_sign_with_rng::<A, D, H, HP, K, LEN, M, N>(
rng, &HASHERS, &mp, &self.0, randomize, // BAD rng, &HASHERS, &mp, &self.0, randomize, // BAD
); );
sig.map(|s| s.deserialize()) sig.map(|s| s.serialize())
} }
/// blah! /// blah!
@ -222,7 +230,7 @@ macro_rules! functionality {
&self.0, &self.0,
opt_rand, opt_rand,
); );
sig.map(|s| s.deserialize()) sig.map(|s| s.serialize())
} }
} }
@ -231,7 +239,10 @@ macro_rules! functionality {
type Signature = [u8; SIG_LEN]; type Signature = [u8; SIG_LEN];
fn verify(&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8]) -> bool { fn verify(&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8]) -> bool {
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::serialize(sig_bytes); if ctx.len() > 255 {
return false;
};
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::deserialize(sig_bytes);
let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m]; let mp: &[&[u8]] = &[&[0u8], &[ctx.len().to_le_bytes()[0]], ctx, m];
let res = crate::slh::slh_verify::<A, D, H, HP, K, LEN, M, N>( let res = crate::slh::slh_verify::<A, D, H, HP, K, LEN, M, N>(
&HASHERS, &mp, &sig, &self.0, &HASHERS, &mp, &sig, &self.0,
@ -242,7 +253,10 @@ macro_rules! functionality {
fn verify_hash( fn verify_hash(
&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8], ph: &Ph, &self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8], ph: &Ph,
) -> bool { ) -> bool {
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::serialize(sig_bytes); if ctx.len() > 255 {
return false;
};
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::deserialize(sig_bytes);
let mut phm = [0u8; 64]; // hashers don't all play well with each other (varying output size) 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 (oid, phm_len) = hash_message(m, ph, &mut phm);
let mp: &[&[u8]] = &[ let mp: &[&[u8]] = &[
@ -261,7 +275,7 @@ macro_rules! functionality {
fn _test_only_raw_verify( fn _test_only_raw_verify(
&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], &self, m: &[u8], sig_bytes: &[u8; SIG_LEN],
) -> Result<bool, &'static str> { ) -> Result<bool, &'static str> {
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::serialize(sig_bytes); let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::deserialize(sig_bytes);
let res = crate::slh::slh_verify_internal::<A, D, H, HP, K, LEN, M, N>( let res = crate::slh::slh_verify_internal::<A, D, H, HP, K, LEN, M, N>(
&HASHERS, &HASHERS,
&[m], &[m],
@ -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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// 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 /// 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. /// SLH-DSA-SHAKE-256f parameter set is claimed to be in security strength category 5.
/// ///

View file

@ -5,8 +5,8 @@ use crate::{fors, helpers, hypertree, xmss};
use rand_core::CryptoRngCore; use rand_core::CryptoRngCore;
/// Algorithm 17: `slh_keygen()` on page 34. /// Algorithm 21: `slh_keygen()` on page 37.
/// Generate an SLH-DSA key pair. /// Generates an SLH-DSA key pair.
/// ///
/// Input: (none) <br> /// Input: (none) <br>
/// Output: SLH-DSA key pair `(SK, PK)`. /// Output: SLH-DSA key pair `(SK, PK)`.
@ -22,8 +22,6 @@ pub(crate) fn slh_keygen_with_rng<
>( >(
rng: &mut impl CryptoRngCore, hashers: &Hashers<K, LEN, M, N>, rng: &mut impl CryptoRngCore, hashers: &Hashers<K, LEN, M, N>,
) -> Result<(SlhPrivateKey<N>, SlhPublicKey<N>), &'static str> { ) -> Result<(SlhPrivateKey<N>, SlhPublicKey<N>), &'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 // 1: SK.seed ←$ B^n ▷ Set SK.seed, SK.prf, and PK.seed to random n-byte
let mut sk_seed = [0u8; N]; let mut sk_seed = [0u8; N];
@ -40,14 +38,17 @@ pub(crate) fn slh_keygen_with_rng<
rng.try_fill_bytes(&mut pk_seed) rng.try_fill_bytes(&mut pk_seed)
.map_err(|_| "Alg17: rng failed3")?; .map_err(|_| "Alg17: rng failed3")?;
slh_keygen_internal::<D, H, HP, K, LEN, M, N>(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::<D, H, HP, K, LEN, M, N>(hashers, sk_seed, sk_prf, pk_seed))
} }
/// Algorithm 17: `slh_keygen()` on page 34. /// Algorithm 18: `slh_keygen_internal()` on page 34.
/// Generate an SLH-DSA key pair. /// Generates an SLH-DSA key pair. Note: this function **is not** exported.
/// ///
/// Input: (none) <br> /// Input: Secret seed `SK.seed`, PRF key `SK.prf`, public seed `PK.seed` <br>
/// Output: SLH-DSA key pair `(SK, PK)`. /// Output: SLH-DSA key pair `(SK, PK)`.
#[allow(clippy::similar_names)] // sk_seed and pk_seed #[allow(clippy::similar_names)] // sk_seed and pk_seed
pub(crate) fn slh_keygen_internal< pub(crate) fn slh_keygen_internal<
@ -60,48 +61,32 @@ pub(crate) fn slh_keygen_internal<
const N: usize, const N: usize,
>( >(
hashers: &Hashers<K, LEN, M, N>, sk_seed: [u8; N], sk_prf: [u8; N], pk_seed: [u8; N], hashers: &Hashers<K, LEN, M, N>, sk_seed: [u8; N], sk_prf: [u8; N], pk_seed: [u8; N],
) -> Result<(SlhPrivateKey<N>, SlhPublicKey<N>), &'static str> { ) -> (SlhPrivateKey<N>, SlhPublicKey<N>) {
let (d32, hp32) = (u32::try_from(D).unwrap(), u32::try_from(HP).unwrap()); let (d32, hp32) = (u32::try_from(D).unwrap(), u32::try_from(HP).unwrap());
// //
// // // 1: ADRS ← toByte(0, 32) ▷ Generate the public key for the top-level XMSS tree
// // 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
let mut adrs = Adrs::default(); let mut adrs = Adrs::default();
// 6: ADRS.setLayerAddress(d 1) // 2: ADRS.setLayerAddress(d 1)
adrs.set_layer_address(d32 - 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 = let pk_root =
xmss::xmss_node::<H, HP, K, LEN, M, N>(hashers, &sk_seed, 0, hp32, &pk_seed, &adrs)?; xmss::xmss_node::<H, HP, K, LEN, M, N>(hashers, &sk_seed, 0, hp32, &pk_seed, &adrs);
// 8: // 4: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )
// 9: return ( (SK.seed, SK.prf, PK.seed, PK.root), (PK.seed, PK.root) )
let pk = SlhPublicKey { pk_seed, pk_root }; let pk = SlhPublicKey { pk_seed, pk_root };
let sk = SlhPrivateKey { sk_seed, sk_prf, 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. /// Algorithm 22: `slh_sign(M, SK)` on page 39.
/// Generate an SLH-DSA signature. /// 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)`. <br> /// Input: Message `M`, context string `ctx`, private key `SK`. `randomize` == hedged variant <br>
/// Output: SLH-DSA signature `SIG`. /// Output: SLH-DSA signature `SIG`.
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
#[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes #[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes
@ -119,31 +104,39 @@ pub(crate) fn slh_sign_with_rng<
sk: &SlhPrivateKey<N>, randomize: bool, sk: &SlhPrivateKey<N>, randomize: bool,
) -> Result<SlhDsaSig<A, D, HP, K, LEN, N>, &'static str> { ) -> Result<SlhDsaSig<A, D, HP, K, LEN, N>, &'static str> {
// //
// 1: ADRS ← toByte(0, 32) // 1: if |𝑐𝑡𝑥| > 255 then
//let mut adrs = Adrs::default(); // 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: // 4: 𝑎𝑑𝑑𝑟𝑛𝑑 ←− 𝔹𝑛 ▷ skip lines 4 through 7 for the deterministic variant
// 3: opt_rand ← PK.seed ▷ Set opt_rand to either PK.seed
let mut opt_rand = sk.pk_seed; 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 { if randomize {
// 5: opt_rand ←$ Bn //
rng.try_fill_bytes(&mut opt_rand) rng.try_fill_bytes(&mut opt_rand)
.map_err(|_| "Alg17: rng failed")?; .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::<A, D, H, HP, K, LEN, M, N>(hashers, mp, sk, opt_rand) slh_sign_internal::<A, D, H, HP, K, LEN, M, N>(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. /// Generate an SLH-DSA signature.
/// ///
/// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`. <br> /// Input: Message `M`, private key `SK = (SK.seed, SK.prf, PK.seed, PK.root)`,
/// (optional) additional randomness 𝑎𝑑𝑑𝑟𝑛𝑑.<br>
/// Output: SLH-DSA signature `SIG`. /// Output: SLH-DSA signature `SIG`.
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
#[allow(clippy::cast_possible_truncation)] // temporary, investigating idx_leaf int sizes #[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) // 1: ADRS ← toByte(0, 32)
let mut adrs = Adrs::default(); 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); let r = (hashers.prf_msg)(&sk.sk_prf, &opt_rand, m);
// 8: SIG ← R // 4: SIG ← R
let mut sig = SlhDsaSig { let mut sig = SlhDsaSig {
randomness: r, // here! randomness: r, // here!
fors_sig: ForsSig { fors_sig: ForsSig {
@ -195,53 +178,48 @@ pub(crate) fn slh_sign_internal<
}, },
}; };
// 9: // 5: digest ← H_msg(R, PK.seed, PK.root, M) ▷ Compute message digest
// 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); 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 index1 = (K * A + 7) / 8;
let md = &digest[0..index1]; 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 index2 = index1 + (H - H / D + 7) / 8;
let tmp_idx_tree = &digest[index1..index2]; 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 index3 = index2 + (H + 8 * D - 1) / (8 * D);
let tmp_idx_leaf = &digest[index2..index3]; let tmp_idx_leaf = &digest[index2..index3];
// 14: // 9: idx_tree ← toInt(tmp_idx_tree, ceil((h-h/d)/8)) mod 2^{hh/d}
// 15: idx_tree ← toInt(tmp_idx_tree, ceil((h-h/d)/8)) mod 2^{hh/d}
let idx_tree = helpers::to_int(tmp_idx_tree, (h32 - h32 / d32 + 7) / 8) let idx_tree = helpers::to_int(tmp_idx_tree, (h32 - h32 / d32 + 7) / 8)
& (u64::MAX >> (64 - (h32 - h32 / d32))); & (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)) let idx_leaf = helpers::to_int(tmp_idx_leaf, (h32 + 8 * d32 - 1) / (8 * d32))
& (u64::MAX >> (64 - h32 / d32)); & (u64::MAX >> (64 - h32 / d32));
// 17: // 11: ADRS.setTreeAddress(idx_tree)
// 18: ADRS.setTreeAddress(idx_tree)
adrs.set_tree_address(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); adrs.set_type_and_clear(FORS_TREE);
// 20: ADRS.setKeyPairAddress(idxleaf) // 13: ADRS.setKeyPairAddress(idxleaf)
adrs.set_key_pair_address(idx_leaf as u32); adrs.set_key_pair_address(idx_leaf as u32);
// 21: SIG_FORS ← fors_sign(md, SK.seed, PK.seed, ADRS) // 14: SIG_FORS ← fors_sign(md, SK.seed, PK.seed, ADRS)
// 22: SIG ← SIG ∥ SIG_FORS // 15: SIG ← SIG ∥ SIG_FORS
sig.fors_sig = fors::fors_sign(hashers, md, &sk.sk_seed, &adrs, &sk.pk_seed)?; sig.fors_sig = fors::fors_sign(hashers, md, &sk.sk_seed, &adrs, &sk.pk_seed)?;
// 23: // 16: PK_FORS ← fors_pkFromSig(SIG_FORS , md, PK.seed, ADRS) ▷ Get FORS key
// 24: PK_FORS ← fors_pkFromSig(SIG_FORS , md, PK.seed, ADRS) ▷ Get FORS key
let pk_fors = let pk_fors =
fors::fors_pk_from_sig::<A, K, LEN, M, N>(hashers, &sig.fors_sig, md, &sk.pk_seed, &adrs); fors::fors_pk_from_sig::<A, K, LEN, M, N>(hashers, &sig.fors_sig, md, &sk.pk_seed, &adrs);
// 25: // 17: SIG_HT ← ht_sign(PK_FORS , SK.seed, PK.seed, idx_tree, idx_leaf)
// 26: SIG_HT ← ht_sign(PK_FORS , SK.seed, PK.seed, idx_tree, idx_leaf) // 18: SIG ← SIG ∥ SIG_HT
// 27: SIG ← SIG ∥ SIG_HT
sig.ht_sig = hypertree::ht_sign::<D, H, HP, K, LEN, M, N>( sig.ht_sig = hypertree::ht_sign::<D, H, HP, K, LEN, M, N>(
hashers, hashers,
&pk_fors.key, &pk_fors.key,
@ -251,14 +229,17 @@ pub(crate) fn slh_sign_internal<
idx_leaf as u32, idx_leaf as u32,
)?; )?;
// 28: return SIG // 19: return SIG
Ok(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)`. <br> /// Input: Message `M`, signature `SIG`, context string `ctx`, public key `PK = (PK.seed, PK.root)`. <br>
/// Output: Boolean. /// Output: Boolean.
#[allow(clippy::cast_possible_truncation)] // TODO: temporary #[allow(clippy::cast_possible_truncation)] // TODO: temporary
#[allow(clippy::similar_names)] #[allow(clippy::similar_names)]
@ -275,23 +256,24 @@ pub(crate) fn slh_verify<
hashers: &Hashers<K, LEN, M, N>, mp: &[&[u8]], sig: &SlhDsaSig<A, D, HP, K, LEN, N>, hashers: &Hashers<K, LEN, M, N>, mp: &[&[u8]], sig: &SlhDsaSig<A, D, HP, K, LEN, N>,
pk: &SlhPublicKey<N>, pk: &SlhPublicKey<N>,
) -> bool { ) -> bool {
//let (d32, h32) = (u32::try_from(D).unwrap(), u32::try_from(H).unwrap()); // 1: if |𝑐𝑡𝑥| > 255 then
// 1: if |SIG| != (1 + k(1 + a) + h + d · len) · n then
// 2: return false // 2: return false
// 3: end if // 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::<A, D, H, HP, K, LEN, M, N>(hashers, mp, sig, pk) slh_verify_internal::<A, D, H, HP, K, LEN, M, N>(hashers, mp, sig, pk)
} }
/// Algorithm 19: `slh_verify(M, SIG, PK)` /// Algorithm 20: `slh_verify(M, SIG, PK)` on page 36.
/// Verify an SLH-DSA signature. /// Verifies an SLH-DSA signature.
/// ///
/// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`. <br> /// Input: Message `M`, signature `SIG`, public key `PK = (PK.seed, PK.root)`. <br>
/// Output: Boolean. /// 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] // 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; let sig_ht = &sig.ht_sig;
// 8: // 8: digest ← Hmsg(R, PK.seed, PK.root, M) ▷ Compute message digest
// 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); 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 index1 = (K * A + 7) / 8;
let md = &digest[0..index1]; 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 index2 = index1 + (H - H / D + 7) / 8;
let tmp_idx_tree = &digest[index1..index2]; 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 index3 = index2 + (H + 8 * D - 1) / (8 * D);
let tmp_idx_leaf = &digest[index2..index3]; let tmp_idx_leaf = &digest[index2..index3];
// 13: // 12: idx_tree ← toInt(tmp_idx_tree, ceil((h - h/d)/8)) mod 2^{hh/d}
// 14: idx_tree ← toInt(tmp_idx_tree, ceil((h - h/d)/8)) mod 2^{hh/d}
let idx_tree = helpers::to_int(tmp_idx_tree, (h32 - h32 / d32 + 7) / 8) let idx_tree = helpers::to_int(tmp_idx_tree, (h32 - h32 / d32 + 7) / 8)
& (u64::MAX >> (64 - (h32 - h32 / d32))); & (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)) let idx_leaf = helpers::to_int(tmp_idx_leaf, (h32 + 8 * d32 - 1) / (8 * d32))
& (u64::MAX >> (64 - h32 / d32)); & (u64::MAX >> (64 - h32 / d32));
// 16: // 14: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key
// 17: ADRS.setTreeAddress(idx_tree) ▷ Compute FORS public key
adrs.set_tree_address(idx_tree); adrs.set_tree_address(idx_tree);
// 18: ADRS.setTypeAndClear(FORS_TREE) // 15: ADRS.setTypeAndClear(FORS_TREE)
adrs.set_type_and_clear(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); adrs.set_key_pair_address(idx_leaf as u32);
// 20: // 17: PK_FORS ← fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)
// 21: PK_FORS ← fors_pkFromSig(SIG_FORS, md, PK.seed, ADRS)
let pk_fors = let pk_fors =
fors::fors_pk_from_sig::<A, K, LEN, M, N>(hashers, sig_fors, md, &pk.pk_seed, &adrs); fors::fors_pk_from_sig::<A, K, LEN, M, N>(hashers, sig_fors, md, &pk.pk_seed, &adrs);
// 18: return ht_verify(PK_FORS, SIG_HT, PK.seed, idx_tree , idx_leaf, PK.root)
// 22:
// 23: return ht_verify(PK_FORS, SIG_HT, PK.seed, idx_tree , idx_leaf, PK.root)
hypertree::ht_verify::<D, HP, K, LEN, M, N>( hypertree::ht_verify::<D, HP, K, LEN, M, N>(
hashers, hashers,
&pk_fors.key, &pk_fors.key,

View file

@ -158,6 +158,7 @@ pub trait Signer {
/// Attempt to sign the given message, returning a digital signature on success, or an error if /// 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 /// 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). /// with respect to the `PrivateKey` only (not including rejection loop; work in progress).
/// Uses a FIPS 205 context string (default: an empty string).
/// ///
/// # Errors /// # Errors
/// Returns an error when the random number generator fails; propagates internal 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 /// 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 /// 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). /// with respect to the `PrivateKey` only (not including rejection loop; work in progress).
/// Uses a FIPS 205 context string (default: an empty string).
/// ///
/// # Errors /// # Errors
/// Returns an error when the random number generator fails; propagates internal errors. /// Returns an error when the random number generator fails; propagates internal errors.
@ -259,7 +261,7 @@ pub trait Verifier {
type Signature; type Signature;
/// Verifies a digital signature with respect to a `PublicKey`. This function operates in /// 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 /// # Examples
/// ```rust /// ```rust

View file

@ -14,7 +14,7 @@ pub enum Ph {
} }
/// Fig 16 on page 34 /// Fig 17 on page 34
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct SlhDsaSig< pub(crate) struct SlhDsaSig<
const A: usize, const A: usize,
@ -30,6 +30,7 @@ pub(crate) struct SlhDsaSig<
} }
/// Fig 16 on page 33
#[derive(Clone, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct SlhPublicKey<const N: usize> { pub struct SlhPublicKey<const N: usize> {
pub(crate) pk_seed: [u8; N], pub(crate) pk_seed: [u8; N],
@ -37,6 +38,7 @@ pub struct SlhPublicKey<const N: usize> {
} }
/// Fig 15 on page 33
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub struct SlhPrivateKey<const N: usize> { pub struct SlhPrivateKey<const N: usize> {
pub(crate) sk_seed: [u8; N], pub(crate) sk_seed: [u8; N],
@ -46,7 +48,7 @@ pub struct SlhPrivateKey<const N: usize> {
} }
/// Fig 13 on page 29 /// Fig 14 on page 29
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct ForsSig<const A: usize, const K: usize, const N: usize> { pub(crate) struct ForsSig<const A: usize, const K: usize, const N: usize> {
pub(crate) private_key_value: [[u8; N]; K], pub(crate) private_key_value: [[u8; N]; K],
@ -60,19 +62,20 @@ pub(crate) struct ForsPk<const N: usize> {
} }
/// Fig 10
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct Auth<const A: usize, const N: usize> { pub(crate) struct Auth<const A: usize, const N: usize> {
pub(crate) tree: [[u8; N]; A], pub(crate) tree: [[u8; N]; A],
} }
/// Fig 13 on page 26
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub(crate) struct HtSig<const D: usize, const HP: usize, const LEN: usize, const N: usize> { pub(crate) struct HtSig<const D: usize, const HP: usize, const LEN: usize, const N: usize> {
pub(crate) xmss_sigs: [XmssSig<HP, LEN, N>; D], pub(crate) xmss_sigs: [XmssSig<HP, LEN, N>; D],
} }
/// Fig 10 on page 19
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub struct WotsSig<const LEN: usize, const N: usize> { pub struct WotsSig<const LEN: usize, const N: usize> {
pub(crate) data: [[u8; N]; LEN], pub(crate) data: [[u8; N]; LEN],
@ -83,6 +86,7 @@ pub struct WotsSig<const LEN: usize, const N: usize> {
pub struct WotsPk<const N: usize>(pub(crate) [u8; N]); pub struct WotsPk<const N: usize>(pub(crate) [u8; N]);
/// Fig 11 on page 22
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub struct XmssSig<const HP: usize, const LEN: usize, const N: usize> { pub struct XmssSig<const HP: usize, const LEN: usize, const N: usize> {
pub(crate) sig_wots: WotsSig<LEN, N>, pub(crate) sig_wots: WotsSig<LEN, N>,
@ -108,7 +112,7 @@ pub(crate) const FORS_PRF: u32 = 6;
/// Straddling the line between struct, enum and union... /// Straddling the line between struct, enum and union...
#[derive(Clone, Default, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Default, Zeroize, ZeroizeOnDrop)]
#[repr(align(32))] #[repr(align(32))] // TODO: check alignment size perf/requirements
pub(crate) struct Adrs { pub(crate) struct Adrs {
pub(crate) f0: [u8; 4], pub(crate) f0: [u8; 4],
// layer address // layer address

View file

@ -4,7 +4,7 @@ use crate::helpers::{base_2b, to_byte};
use crate::types::{Adrs, WotsPk, WotsSig, WOTS_PK, WOTS_PRF}; 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` /// 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`. /// 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 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`. /// Output: Value of `F` iterated `s` times on `X`.
pub(crate) fn chain<const K: usize, const LEN: usize, const M: usize, const N: usize>( pub(crate) fn chain<const K: usize, const LEN: usize, const M: usize, const N: usize>(
hashers: &Hashers<K, LEN, M, N>, cap_x: [u8; N], i: u32, s: u32, pk_seed: &[u8], adrs: &Adrs, hashers: &Hashers<K, LEN, M, N>, cap_x: [u8; N], i: u32, s: u32, pk_seed: &[u8], adrs: &Adrs,
) -> Option<[u8; N]> { ) -> [u8; N] {
debug_assert!(i + s < u32::MAX); debug_assert!(i + s < u32::MAX);
let mut adrs = adrs.clone(); let mut adrs = adrs.clone();
// 1: if (i + s) ≥ w then // Note this bounds check was only specified in the draft specification
if (i + s) >= crate::W { // (old)1: if (i + s) ≥ w then return NULL;
// // if (i + s) >= crate::W { return None; }
// 2: return NULL
return None;
// 3: end if // 1: tmp ← X
}
// 4:
// 5: tmp ← X
let mut tmp = cap_x; let mut tmp = cap_x;
// 6: // 2: for j from i to i + s 1 do
// 7: for j from i to i + s 1 do
for j in i..(i + s) { for j in i..(i + s) {
// //
// 8: ADRS.setHashAddress(j) // 3: ADRS.setHashAddress(j)
adrs.set_hash_address(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); tmp = (hashers.f)(pk_seed, &adrs, &tmp);
// 10: end for // 5: end for
} }
// 11: return tmp // 6: return tmp
Some(tmp) tmp
} }
/// Algorithm 5: `wots_PKgen(SK.seed, PK.seed, ADRS)` on page 18. /// Algorithm 6: `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` /// 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 /// 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+` /// `WOTS_HASH`, and the layer address, tree address, and key pair address must encode the address of the `WOTS+`
/// public key to be generated. /// public key to be generated.
@ -62,7 +55,7 @@ pub(crate) fn chain<const K: usize, const LEN: usize, const M: usize, const N: u
#[allow(clippy::similar_names)] // pk_seed and sk_seed #[allow(clippy::similar_names)] // pk_seed and sk_seed
pub(crate) fn wots_pkgen<const K: usize, const LEN: usize, const M: usize, const N: usize>( pub(crate) fn wots_pkgen<const K: usize, const LEN: usize, const M: usize, const N: usize>(
hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs, hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], pk_seed: &[u8], adrs: &Adrs,
) -> Result<WotsPk<N>, &'static str> { ) -> WotsPk<N> {
let len32 = u32::try_from(LEN).unwrap(); let len32 = u32::try_from(LEN).unwrap();
let mut adrs = adrs.clone(); let mut adrs = adrs.clone();
let mut tmp = [[0u8; N]; LEN]; let mut tmp = [[0u8; N]; LEN];
@ -90,7 +83,7 @@ pub(crate) fn wots_pkgen<const K: usize, const LEN: usize, const M: usize, const
// 8: tmp[i] ← chain(sk, 0, w 1, PK.seed, ADRS) ▷ Compute public value for chain i // 8: tmp[i] ← chain(sk, 0, w 1, PK.seed, ADRS) ▷ Compute public value for chain i
tmp[i as usize] = tmp[i as usize] =
chain(hashers, sk, 0, crate::W - 1, pk_seed, &adrs).ok_or("chain broke")?; chain(hashers, sk, 0, crate::W - 1, pk_seed, &adrs);
// 9: end for // 9: end for
} }
@ -108,12 +101,12 @@ pub(crate) fn wots_pkgen<const K: usize, const LEN: usize, const M: usize, const
let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp); let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
// 14: return pk // 14: return pk
Ok(WotsPk(pk)) WotsPk(pk)
} }
/// Algorithm 6: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 19. /// Algorithm 7: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 20.
/// Generate a WOTS+ signature on an n-byte message. /// Generates a WOTS+ signature on an n-byte message.
/// ///
/// Input: Message `M`, secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`. <br> /// Input: Message `M`, secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`. <br>
/// Output: WOTS+ signature sig. /// Output: WOTS+ signature sig.
@ -128,68 +121,64 @@ pub(crate) fn wots_sign<const K: usize, const LEN: usize, const M: usize, const
// 1: csum ← 0 // 1: csum ← 0
let mut csum = 0_u32; let mut csum = 0_u32;
// 2: // 2: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
// 3: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
let mut msg = [0u32; LEN]; // note: 3 entries left over, used step 10 let mut msg = [0u32; LEN]; // note: 3 entries left over, used step 10
helpers::base_2b(m, crate::LGW, 2 * n32, &mut msg[0..(2 * N)]); base_2b(m, crate::LGW, 2 * n32, &mut msg[0..(2 * N)]);
// 4: // 3: for i from 0 to len1 1 do ▷ Compute checksum
// 5: for i from 0 to len1 1 do ▷ Compute checksum
for item in msg.iter().take(2 * N) { for item in msg.iter().take(2 * N) {
// //
// 6: csum ← csum + w 1 msg[i] // 4: csum ← csum + w 1 msg[i]
csum += crate::W - 1 - *item; csum += crate::W - 1 - *item;
// 7: end for // 5: end for
} }
// 8: // 6: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
// 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; 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 // 7: msg ← msg ∥ base_2b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
helpers::base_2b( base_2b(
&helpers::to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8), &helpers::to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8),
crate::LGW, crate::LGW,
crate::LEN2, crate::LEN2,
&mut msg[(2 * N)..], &mut msg[(2 * N)..],
); );
// 11: // 8: skADRS ← ADRS
// 12: skADRS ← ADRS
let mut sk_addrs = adrs.clone(); let mut sk_addrs = adrs.clone();
// 13: skADRS.setTypeAndClear(WOTS_PRF) // 9: skADRS.setTypeAndClear(WOTS_PRF)
sk_addrs.set_type_and_clear(WOTS_PRF); sk_addrs.set_type_and_clear(WOTS_PRF);
// 14: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress()) // 10: skADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
sk_addrs.set_key_pair_address(adrs.get_key_pair_address()); sk_addrs.set_key_pair_address(adrs.get_key_pair_address());
// 15: for i from 0 to len 1 do // 11: for i from 0 to len 1 do
for (item, i) in msg.iter().zip(0u32..) { for (item, i) in msg.iter().zip(0u32..) {
// //
// 16: skADRS.setChainAddress(i) // 12: skADRS.setChainAddress(i)
sk_addrs.set_chain_address(i); sk_addrs.set_chain_address(i);
// 17: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i // 13: sk ← PRF(PK.seed, SK.seed, skADRS) ▷ Compute secret value for chain i
let sk = (hashers.prf)(pk_seed, sk_seed, &sk_addrs); let sk = (hashers.prf)(pk_seed, sk_seed, &sk_addrs);
// 18: ADRS.setChainAddress(i) // 14: ADRS.setChainAddress(i)
adrs.set_chain_address(i); adrs.set_chain_address(i);
// 19: sig[i] ← chain(sk, 0, msg[i], PK.seed, ADRS) ▷ Compute signature value for chain i // 15: 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(); sig.data[i as usize] = chain(hashers, sk, 0, *item, pk_seed, &adrs);
// 20: end for // 16: end for
} }
// 21: return sig // 17: return sig
sig sig
} }
/// Algorithm 7: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 20. /// Algorithm 8: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 21.
/// Compute a WOTS+ public key from a message and its signature. /// Computes a WOTS+ public key from a message and its signature.
/// ///
/// Input: WOTS+ signature `sig`, message `M`, public seed `PK.seed`, address `ADRS`. <br> /// Input: WOTS+ signature `sig`, message `M`, public seed `PK.seed`, address `ADRS`. <br>
/// Output: WOTS+ public key `pksig` derived from `sig`. /// Output: WOTS+ public key `pksig` derived from `sig`.
@ -203,26 +192,23 @@ pub(crate) fn wots_pk_from_sig<const K: usize, const LEN: usize, const M: usize,
// 1: csum ← 0 // 1: csum ← 0
let mut csum = 0_u32; let mut csum = 0_u32;
// 2: // 2: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
// 3: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
let mut msg = [0u32; LEN]; let mut msg = [0u32; LEN];
base_2b(m, crate::LGW, 2 * n32, &mut msg[0..(2 * N)]); base_2b(m, crate::LGW, 2 * n32, &mut msg[0..(2 * N)]);
// 4: // 3: for i from 0 to len1 1 do ▷ Compute checksum
// 5: for i from 0 to len1 1 do ▷ Compute checksum
for item in msg.iter().take(2 * N) { for item in msg.iter().take(2 * N) {
// //
// 6: csum ← csum + w 1 msg[i] // 4: csum ← csum + w 1 msg[i]
csum += crate::W - 1 - item; csum += crate::W - 1 - item;
// 7: end for // 5: end for
} }
// 8: // 6: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
// 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; 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 // 7: msg ← msg ∥ base_2b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
base_2b( base_2b(
&to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8), &to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8),
crate::LGW, crate::LGW,
@ -230,14 +216,14 @@ pub(crate) fn wots_pk_from_sig<const K: usize, const LEN: usize, const M: usize,
&mut msg[(2 * N)..], &mut msg[(2 * N)..],
); );
// 11: for i from 0 to len 1 do // 8: for i from 0 to len 1 do
#[allow(clippy::cast_possible_truncation)] // steps 12 and 13 #[allow(clippy::cast_possible_truncation)] // steps 9 and 10
for i in 0..LEN { for i in 0..LEN {
// //
// 12: ADRS.setChainAddress(i) // 9: ADRS.setChainAddress(i)
adrs.set_chain_address(i as u32); adrs.set_chain_address(i as u32);
// 13: tmp[i] ← chain(sig[i], msg[i], w 1 msg[i], PK.seed, ADRS) // 10: tmp[i] ← chain(sig[i], msg[i], w 1 msg[i], PK.seed, ADRS)
tmp[i] = chain::<K, LEN, M, N>( tmp[i] = chain::<K, LEN, M, N>(
hashers, hashers,
sig.data[i], sig.data[i],
@ -245,24 +231,23 @@ pub(crate) fn wots_pk_from_sig<const K: usize, const LEN: usize, const M: usize,
crate::W - 1 - msg[i], crate::W - 1 - msg[i],
pk_seed, pk_seed,
&adrs, &adrs,
) );
.expect("chain broke2!");
// 14: end for // 11: end for
} }
// 15: wotspkADRS ← ADRS // 12: wotspkADRS ← ADRS
let mut wotspk_adrs = adrs.clone(); let mut wotspk_adrs = adrs.clone();
// 16: wotspkADRS.setTypeAndClear(WOTS_PK) // 13: wotspkADRS.setTypeAndClear(WOTS_PK)
wotspk_adrs.set_type_and_clear(WOTS_PK); wotspk_adrs.set_type_and_clear(WOTS_PK);
// 17: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress()) // 14: wotspkADRS.setKeyPairAddress(ADRS.getKeyPairAddress())
wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address()); wotspk_adrs.set_key_pair_address(adrs.get_key_pair_address());
// 18: pksig ← Tlen (PK.seed, wotspkADRS, tmp) // 15: pksig ← Tlen (PK.seed, wotspkADRS, tmp)
let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp); let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
// 19: return pksig // 16: return pksig
WotsPk(pk) WotsPk(pk)
} }

View file

@ -3,13 +3,13 @@ use crate::types::{Adrs, WotsSig, XmssSig, TREE, WOTS_HASH};
use crate::wots; use crate::wots;
/// Algorithm 8: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22. /// Algorithm 9: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22.
/// Compute the root of a Merkle subtree of WOTS+ public keys. /// Computes 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`, /// Input: Secret seed `SK.seed`, target node index `i`, target node height `z`, public seed `PK.seed`,
/// `address ADRS`. <br> /// `address ADRS`. <br>
/// Output: n-byte root `node`. /// 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< pub(crate) fn xmss_node<
const H: usize, const H: usize,
const HP: usize, const HP: usize,
@ -19,64 +19,58 @@ pub(crate) fn xmss_node<
const N: usize, const N: usize,
>( >(
hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs, hashers: &Hashers<K, LEN, M, N>, sk_seed: &[u8], i: u32, z: u32, pk_seed: &[u8], adrs: &Adrs,
) -> Result<[u8; N], &'static str> { ) -> [u8; N] {
let hp32 = u32::try_from(HP).unwrap();
let mut adrs = adrs.clone(); let mut adrs = adrs.clone();
// 1: if z > h or i ≥ 2^{h z} then // Note this bounds check was only specified in the draft specification
if (z > hp32) | (u64::from(i) >= (1 << (hp32 - z))) { // (old)1: if z > h or i ≥ 2^{h z} then
// // if (z > hp32) | (u64::from(i) >= (1 << (hp32 - z))) { return Err("Alg8: fail"); }
// 2: return NULL
return Err("Alg8: fail");
// 3: end if // 1: if z = 0 then
}
// 4: if z = 0 then
let node = if z == 0 { let node = if z == 0 {
// //
// 5: ADRS.setTypeAndClear(WOTS_HASH) // 2: ADRS.setTypeAndClear(WOTS_HASH)
adrs.set_type_and_clear(WOTS_HASH); adrs.set_type_and_clear(WOTS_HASH);
// 6: ADRS.setKeyPairAddress(i) // 3: ADRS.setKeyPairAddress(i)
adrs.set_key_pair_address(i); adrs.set_key_pair_address(i);
// 7: node ← wots_PKgen(SK.seed, PK.seed, ADRS) // 4: node ← wots_PKgen(SK.seed, PK.seed, ADRS)
wots::wots_pkgen::<K, LEN, M, N>(hashers, sk_seed, pk_seed, &adrs)?.0 wots::wots_pkgen::<K, LEN, M, N>(hashers, sk_seed, pk_seed, &adrs).0
// 8: else // 5: else
} 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 = let lnode =
xmss_node::<H, HP, K, LEN, M, N>(hashers, sk_seed, 2 * i, z - 1, pk_seed, &adrs)?; xmss_node::<H, HP, K, LEN, M, N>(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 = let rnode =
xmss_node::<H, HP, K, LEN, M, N>(hashers, sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs)?; xmss_node::<H, HP, K, LEN, M, N>(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); adrs.set_type_and_clear(TREE);
// 12: ADRS.setTreeHeight(z) // 9: ADRS.setTreeHeight(z)
adrs.set_tree_height(z); adrs.set_tree_height(z);
// 13: ADRS.setTreeIndex(i) // 10: ADRS.setTreeIndex(i)
adrs.set_tree_index(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) (hashers.h)(pk_seed, &adrs, &lnode, &rnode)
// 15: end if // 12: end if
}; };
// 16: return node // 13: return node
Ok(node) node
} }
/// Algorithm 9: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23. /// Algorithm 10: `xmss_sign(M, SK.seed, idx, PK.seed, ADRS)` on page 23.
/// Generate an XMSS signature. /// Generates an XMSS signature.
/// ///
/// Input: n-byte message `M`, secret seed `SK.seed`, index `idx`, public seed `PK.seed`, address `ADRS`. <br> /// Input: n-byte message `M`, secret seed `SK.seed`, index `idx`, public seed `PK.seed`, address `ADRS`. <br>
/// Output: XMSS signature SIGXMSS = (sig ∥ AUTH). /// Output: XMSS signature SIGXMSS = (sig ∥ AUTH).
@ -91,7 +85,7 @@ pub(crate) fn xmss_sign<
>( >(
hashers: &Hashers<K, LEN, M, N>, m: &[u8], sk_seed: &[u8], idx: u32, pk_seed: &[u8], hashers: &Hashers<K, LEN, M, N>, m: &[u8], sk_seed: &[u8], idx: u32, pk_seed: &[u8],
adrs: &Adrs, adrs: &Adrs,
) -> Result<XmssSig<HP, LEN, N>, &'static str> { ) -> XmssSig<HP, LEN, N> {
let hp32 = u32::try_from(HP).unwrap(); let hp32 = u32::try_from(HP).unwrap();
let mut adrs = adrs.clone(); let mut adrs = adrs.clone();
let mut sig_xmss = XmssSig { 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) // 3: AUTH[j] ← xmss_node(SK.seed, k, j, PK.seed, ADRS)
sig_xmss.auth[j as usize] = sig_xmss.auth[j as usize] =
xmss_node::<H, HP, K, LEN, M, N>(hashers, sk_seed, k, j, pk_seed, &adrs)?; xmss_node::<H, HP, K, LEN, M, N>(hashers, sk_seed, k, j, pk_seed, &adrs);
// 4: end for // 4: end for
} }
// 5: // 5: ADRS.setTypeAndClear(WOTS_HASH)
// 6: ADRS.setTypeAndClear(WOTS_HASH)
adrs.set_type_and_clear(WOTS_HASH); adrs.set_type_and_clear(WOTS_HASH);
// 7: ADRS.setKeyPairAddress(idx) // 6: ADRS.setKeyPairAddress(idx)
adrs.set_key_pair_address(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::<K, LEN, M, N>(hashers, m, sk_seed, pk_seed, &adrs); sig_xmss.sig_wots = wots::wots_sign::<K, LEN, M, N>(hashers, m, sk_seed, pk_seed, &adrs);
// 9: SIG_XMSS ← sig ∥ AUTH // 8: SIG_XMSS ← sig ∥ AUTH
// struct built above // struct built above
// 10: return SIG_XMSS // 9: return SIG_XMSS
Ok(sig_xmss) sig_xmss
} }
/// Algorithm 10: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)` /// Algorithm 11: `xmss_PKFromSig(idx, SIG_XMSS, M, PK.seed, ADRS)`
/// Compute an XMSS public key from an XMSS signature. /// 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`, /// Input: Index `idx`, XMSS signature `SIG_XMSS = (sig ∥ AUTH)`, n-byte message `M`, public seed `PK.seed`,
/// address `ADRS`. <br> /// address `ADRS`. <br>
@ -164,48 +157,47 @@ pub(crate) fn xmss_pk_from_sig<
// 5: node[0] ← wots_PKFromSig(sig, M, PK.seed, ADRS) // 5: node[0] ← wots_PKFromSig(sig, M, PK.seed, ADRS)
let mut node_0 = wots::wots_pk_from_sig::<K, LEN, M, N>(hashers, sig, m, pk_seed, &adrs).0; let mut node_0 = wots::wots_pk_from_sig::<K, LEN, M, N>(hashers, sig, m, pk_seed, &adrs).0;
// 6: // 6: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH
// 7: ADRS.setTypeAndClear(TREE) ▷ Compute root from WOTS+ pk and AUTH
adrs.set_type_and_clear(TREE); adrs.set_type_and_clear(TREE);
// 8: ADRS.setTreeIndex(idx) // 7: ADRS.setTreeIndex(idx)
adrs.set_tree_index(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 { for k in 0..hp32 {
// //
// 10: ADRS.setTreeHeight(k + 1) // 9: ADRS.setTreeHeight(k + 1)
adrs.set_tree_height(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 { 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; let tmp = adrs.get_tree_index() / 2;
adrs.set_tree_index(tmp); 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]) (hashers.h)(pk_seed, &adrs, &node_0, &auth[k as usize])
// 14: else // 13: else
} else { } else {
// //
// 15: ADRS.setTreeIndex((ADRS.getTreeIndex() 1)/2) // 14: ADRS.setTreeIndex((ADRS.getTreeIndex() 1)/2)
let tmp = (adrs.get_tree_index() - 1) / 2; let tmp = (adrs.get_tree_index() - 1) / 2;
adrs.set_tree_index(tmp); 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) (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; node_0 = node_1;
// 19: end for // 18: end for
} }
// 20: return node[0] // 19: return node[0]
node_0 node_0
} }