Skip to content

Commit

Permalink
align comments with released spec
Browse files Browse the repository at this point in the history
  • Loading branch information
eschorn1 committed Oct 2, 2024
1 parent f057638 commit 834e75b
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 438 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion ffi/fips205.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
## See Also
- https://doi.org/10.6028/NIST.FIPS.205.ipd
- https://csrc.nist.gov/pubs/fips/205/final
- https://github.com/integritychain/fips205
"""
Expand Down
123 changes: 58 additions & 65 deletions src/fors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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>
Expand All @@ -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^(a−z) then
if (z > a32) | (i > k32 * (1 << (a32 - z))) {
//
// 2: return NULL
return Err("Alg14 fails");

// 3: end if
}
// 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");

// 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`.
Expand All @@ -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);

Expand All @@ -135,42 +130,41 @@ pub(crate) fn fors_sign<
sk_seed,
pk_seed,
adrs,
i * (1 << a32) + indices[i as usize],
(i << a32) + indices[i as usize],
);

// 5:
// 6: for j from 0 to a − 1 do ▷ Compute auth path
// 5: for j from 0 to a − 1 do ▷ Compute auth path
for j in 0..a32 {
//
// 7: s ← indices[i]/2^j xor 1
// 6: s ← indices[i]/2^j xor 1
let s = (indices[i as usize] >> j) ^ 1;

// 8: AUTH[j] ← fors_node(SK.seed, i · 2^{a−j} + s, j, PK.seed, ADRS)
// 7: AUTH[j] ← fors_node(SK.seed, i · 2^{a−j} + s, j, PK.seed, ADRS)
sig_fors.auth[i as usize].tree[j as usize] = fors_node::<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.
Expand All @@ -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);

Expand All @@ -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 }
}
8 changes: 2 additions & 6 deletions src/hashers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Loading

0 comments on commit 834e75b

Please sign in to comment.