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]
[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 <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
* 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,

View file

@ -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
"""

View file

@ -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`. <br>
/// 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.
/// Compute the root of a Merkle subtree of FORS public values.
/// Algorithm 15: `fors_node(SK.seed, i, z, PK.seed, ADRS)` on page 30.
/// 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`,
/// 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,
) -> 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^(az) 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::<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 =
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);
// 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`. <br>
/// 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^{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>(
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`. <br>
/// 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 }
}

View file

@ -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;
}

View file

@ -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`. <br>
/// 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`. <br>
/// 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`. <br>
/// 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<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];
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

View file

@ -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`. <br>
@ -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::<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 {
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::<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 {
//
// 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::<H, HP, K, LEN, M, N>(
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::<HP, K, LEN, M, N>(
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`. <br>
@ -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??)
}

View file

@ -4,40 +4,42 @@
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
// Implements FIPS 205 draft Stateless Hash-Based Digital Signature Standard.
// See <https://csrc.nist.gov/pubs/fips/205/ipd>
// Implements FIPS 205 Stateless Hash-Based Digital Signature Standard.
// See <https://csrc.nist.gov/pubs/fips/205/final>
//
// 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::<A, D, H, HP, K, LEN, M, N>(
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<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 (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::<A, D, H, HP, K, LEN, M, N>(
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::<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 res = crate::slh::slh_verify::<A, D, H, HP, K, LEN, M, N>(
&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::<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 (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<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>(
&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.
///

View file

@ -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) <br>
/// 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>,
) -> 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
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::<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.
/// 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) <br>
/// Input: Secret seed `SK.seed`, PRF key `SK.prf`, public seed `PK.seed` <br>
/// 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<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());
//
// //
// // 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::<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:
// 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)`. <br>
/// Input: Message `M`, context string `ctx`, private key `SK`. `randomize` == hedged variant <br>
/// 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<N>, randomize: bool,
) -> Result<SlhDsaSig<A, D, HP, K, LEN, N>, &'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::<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.
///
/// 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`.
#[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^{hh/d}
// 9: 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)
& (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::<A, K, LEN, M, N>(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::<D, H, HP, K, LEN, M, N>(
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)`. <br>
/// Input: Message `M`, signature `SIG`, context string `ctx`, public key `PK = (PK.seed, PK.root)`. <br>
/// Output: Boolean.
#[allow(clippy::cast_possible_truncation)] // TODO: temporary
#[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>,
pk: &SlhPublicKey<N>,
) -> 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::<A, D, H, HP, K, LEN, M, N>(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)`. <br>
/// 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^{hh/d}
// 12: 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)
& (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::<A, K, LEN, M, N>(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::<D, HP, K, LEN, M, N>(
hashers,
&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
/// 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

View file

@ -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<const N: usize> {
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)]
pub struct SlhPrivateKey<const N: usize> {
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)]
pub(crate) struct ForsSig<const A: usize, const K: usize, const N: usize> {
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)]
pub(crate) struct Auth<const A: usize, const N: usize> {
pub(crate) tree: [[u8; N]; A],
}
/// Fig 13 on page 26
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
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],
}
/// Fig 10 on page 19
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub struct WotsSig<const LEN: usize, const N: usize> {
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]);
/// Fig 11 on page 22
#[derive(Clone, Debug, Zeroize, ZeroizeOnDrop)]
pub struct XmssSig<const HP: usize, const LEN: usize, const N: usize> {
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...
#[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

View file

@ -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<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,
) -> 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<const K: usize, const LEN: usize, const M: usize, const N: u
#[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>(
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 mut adrs = adrs.clone();
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
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
}
@ -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);
// 14: return pk
Ok(WotsPk(pk))
WotsPk(pk)
}
/// Algorithm 6: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 19.
/// Generate a WOTS+ signature on an n-byte message.
/// Algorithm 7: `wots_sign(M, SK.seed, PK.seed, ADRS)` on page 20.
/// Generates a WOTS+ signature on an n-byte message.
///
/// Input: Message `M`, secret seed `SK.seed`, public seed `PK.seed`, address `ADRS`. <br>
/// 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
let mut csum = 0_u32;
// 2:
// 3: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
// 2: msg ← base_2b(M, lgw, len1) ▷ Convert message to base w
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:
// 5: for i from 0 to len1 1 do ▷ Compute checksum
// 3: for i from 0 to len1 1 do ▷ Compute checksum
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;
// 7: end for
// 5: end for
}
// 8:
// 9: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
// 6: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
// 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
helpers::base_2b(
// 7: msg ← msg ∥ base_2b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
base_2b(
&helpers::to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8),
crate::LGW,
crate::LEN2,
&mut msg[(2 * N)..],
);
// 11:
// 12: skADRS ← ADRS
// 8: skADRS ← ADRS
let mut sk_addrs = adrs.clone();
// 13: skADRS.setTypeAndClear(WOTS_PRF)
// 9: skADRS.setTypeAndClear(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());
// 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..) {
//
// 16: skADRS.setChainAddress(i)
// 12: skADRS.setChainAddress(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);
// 18: ADRS.setChainAddress(i)
// 14: ADRS.setChainAddress(i)
adrs.set_chain_address(i);
// 19: sig[i] ← chain(sk, 0, msg[i], PK.seed, ADRS) ▷ Compute signature value for chain i
sig.data[i as usize] = chain(hashers, sk, 0, *item, pk_seed, &adrs).unwrap();
// 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);
// 20: end for
// 16: end for
}
// 21: return sig
// 17: return sig
sig
}
/// Algorithm 7: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 20.
/// Compute a WOTS+ public key from a message and its signature.
/// Algorithm 8: `wots_PKFromSig(sig, M, PK.seed, ADRS)` on page 21.
/// Computes a WOTS+ public key from a message and its signature.
///
/// Input: WOTS+ signature `sig`, message `M`, public seed `PK.seed`, address `ADRS`. <br>
/// 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
let mut csum = 0_u32;
// 2:
// 3: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
// 2: msg ← base_2b (M, lgw , len1 ) ▷ Convert message to base w
let mut msg = [0u32; LEN];
base_2b(m, crate::LGW, 2 * n32, &mut msg[0..(2 * N)]);
// 4:
// 5: for i from 0 to len1 1 do ▷ Compute checksum
// 3: for i from 0 to len1 1 do ▷ Compute checksum
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;
// 7: end for
// 5: end for
}
// 8:
// 9: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
// 6: csum ← csum ≪ ((8 ((len2·lgw) mod 8)) mod 8) ▷ For lgw = 4 left shift by 4
csum <<= (8 - ((crate::LEN2 * crate::LGW) & 0x07)) & 0x07;
// 10: msg ← msg ∥ base_2^b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
// 7: msg ← msg ∥ base_2b(toByte(csum, ceil(len2·lgw/8)), lgw, len2) ▷ Convert csum to base w
base_2b(
&to_byte(csum, (crate::LEN2 * crate::LGW + 7) / 8),
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)..],
);
// 11: for i from 0 to len 1 do
#[allow(clippy::cast_possible_truncation)] // steps 12 and 13
// 8: for i from 0 to len 1 do
#[allow(clippy::cast_possible_truncation)] // steps 9 and 10
for i in 0..LEN {
//
// 12: ADRS.setChainAddress(i)
// 9: ADRS.setChainAddress(i)
adrs.set_chain_address(i as u32);
// 13: tmp[i] ← chain(sig[i], msg[i], w 1 msg[i], PK.seed, ADRS)
// 10: tmp[i] ← chain(sig[i], msg[i], w 1 msg[i], PK.seed, ADRS)
tmp[i] = chain::<K, LEN, M, N>(
hashers,
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],
pk_seed,
&adrs,
)
.expect("chain broke2!");
);
// 14: end for
// 11: end for
}
// 15: wotspkADRS ← ADRS
// 12: wotspkADRS ← ADRS
let mut wotspk_adrs = adrs.clone();
// 16: wotspkADRS.setTypeAndClear(WOTS_PK)
// 13: wotspkADRS.setTypeAndClear(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());
// 18: pksig ← Tlen (PK.seed, wotspkADRS, tmp)
// 15: pksig ← Tlen (PK.seed, wotspkADRS, tmp)
let pk = (hashers.t_l)(pk_seed, &wotspk_adrs, &tmp);
// 19: return pksig
// 16: return pksig
WotsPk(pk)
}

View file

@ -3,13 +3,13 @@ use crate::types::{Adrs, WotsSig, XmssSig, TREE, WOTS_HASH};
use crate::wots;
/// Algorithm 8: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22.
/// Compute the root of a Merkle subtree of WOTS+ public keys.
/// Algorithm 9: `xmss_node(SK.seed, i, z, PK.seed, ADRS)` on page 22.
/// 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`,
/// `address ADRS`. <br>
/// 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<K, LEN, M, N>, 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::<K, LEN, M, N>(hashers, sk_seed, pk_seed, &adrs)?.0
// 4: node ← wots_PKgen(SK.seed, PK.seed, ADRS)
wots::wots_pkgen::<K, LEN, M, N>(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::<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 =
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);
// 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`. <br>
/// 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],
adrs: &Adrs,
) -> Result<XmssSig<HP, LEN, N>, &'static str> {
) -> XmssSig<HP, LEN, N> {
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::<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
}
// 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::<K, LEN, M, N>(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`. <br>
@ -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::<K, LEN, M, N>(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
}