#![no_std]
#![deny(clippy::pedantic, missing_docs, unsafe_code)]
#![deny(absolute_paths_not_starting_with_crate, dead_code)]
#![deny(elided_lifetimes_in_paths, explicit_outlives_requirements, keyword_idents)]
#![deny(let_underscore_drop, macro_use_extern_crate, meta_variable_misuse, missing_abi)]
#![deny(non_ascii_idents, rust_2021_incompatible_closure_captures)]
#![deny(rust_2021_incompatible_or_patterns, rust_2021_prefixes_incompatible_syntax)]
#![deny(rust_2021_prelude_collisions, single_use_lifetimes, trivial_casts)]
#![deny(trivial_numeric_casts, unreachable_pub, unsafe_op_in_unsafe_fn, unstable_features)]
#![deny(unused_extern_crates, unused_import_braces, unused_lifetimes, unused_macro_rules)]
#![deny(unused_qualifications, unused_results, variant_size_differences)]
#![cfg_attr(
not(any(
feature = "slh-dsa-sha2-128s",
feature = "slh-dsa-sha2-128f",
feature = "slh-dsa-sha2-192s",
feature = "slh-dsa-sha2-192f",
feature = "slh-dsa-sha2-256s",
feature = "slh-dsa-sha2-256f",
feature = "slh-dsa-shake-128s",
feature = "slh-dsa-shake-128f",
feature = "slh-dsa-shake-192s",
feature = "slh-dsa-shake-192f",
feature = "slh-dsa-shake-256s",
feature = "slh-dsa-shake-256f"
)),
allow(dead_code, unreachable_pub, unused_macros)
)]
#![doc = include_str!("../README.md")]
pub mod traits;
pub use crate::types::pre_hash;
const MAX_PREHASH_LEN: usize = 1024;
fn check_pre_hash(hash: &[u8], hash_oid: &[u8]) -> Result<(), &'static str> {
helpers::ensure!(!hash_oid.is_empty(), "HashSLH-DSA.Sign: OID is empty");
helpers::ensure!(
hash_oid.len() >= 3
&& hash_oid[0] == 0x06
&& hash_oid[1] < 0x80
&& usize::from(hash_oid[1]) == hash_oid.len() - 2,
"HashSLH-DSA.Sign: OID is not DER encoded"
);
helpers::ensure!(
hash.len() <= MAX_PREHASH_LEN,
"Hash of message is too long, should not be more than 1KiB"
);
if let Some(len) = pre_hash::digest_len(hash_oid) {
helpers::ensure!(
hash.len() == len,
"HashSLH-DSA.Sign: digest length does not match the OID"
);
}
Ok(())
}
pub use rand_core::{CryptoRng, Error as RngError, RngCore};
mod fors;
mod hashers;
mod helpers;
mod hypertree;
mod slh;
mod types;
mod wots;
mod xmss;
const LGW: u32 = 4;
const W: u32 = 16;
const LEN2: u32 = 3;
macro_rules! functionality {
() => {
use crate::helpers::ensure;
use crate::traits::{KeyGen, SerDes, Signer, Verifier};
use crate::types::{SlhDsaSig, SlhPrivateKey, SlhPublicKey};
use rand_core::CryptoRngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct PrivateKey(SlhPrivateKey<N>);
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct PublicKey(SlhPublicKey<N>);
pub struct KG();
#[cfg(feature = "default-rng")]
pub fn try_keygen() -> Result<(PublicKey, PrivateKey), &'static str> { KG::try_keygen() }
pub fn try_keygen_with_rng(
rng: &mut impl CryptoRngCore,
) -> Result<(PublicKey, PrivateKey), &'static str> {
KG::try_keygen_with_rng(rng)
}
impl KeyGen for KG {
type PrivateKey = PrivateKey;
type PublicKey = PublicKey;
fn try_keygen_with_rng(
rng: &mut impl CryptoRngCore,
) -> Result<(PublicKey, PrivateKey), &'static str> {
let res = crate::slh::slh_keygen_with_rng::<D, H, HP, K, LEN, M, N>(rng, &HASHERS);
res.map(|(sk, pk)| (PublicKey(pk), PrivateKey(sk)))
}
fn keygen_from_seed<const SN: usize>(
sk_seed: &[u8; SN], sk_prf: &[u8; SN], pk_seed: &[u8; SN],
) -> (PublicKey, PrivateKey) {
const { assert!(SN == N, "keygen_from_seed: each seed must be n bytes") };
let mut rng = crate::traits::DummyRng { data: [*sk_seed, *sk_prf, *pk_seed], i: 0 };
Self::try_keygen_with_rng(&mut rng).expect("rng will not fail")
}
}
impl Signer for PrivateKey {
type Signature = [u8; SIG_LEN];
type PublicKey = PublicKey;
fn try_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, m: &[u8], ctx: &[u8], hedged: bool,
) -> Result<[u8; SIG_LEN], &'static str> {
ensure!(ctx.len() < 256, "SLH-DSA.Sign: ctx too long");
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, hedged,
);
sig.map(|s| s.serialize())
}
fn try_hash_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, hash: &[u8], ctx: &[u8], hash_oid: &[u8],
hedged: bool,
) -> Result<Self::Signature, &'static str> {
ensure!(ctx.len() < 256, "HashSLH-DSA.Sign: ctx too long");
crate::check_pre_hash(hash, hash_oid)?;
let mp: &[&[u8]] = &[&[1u8], &[ctx.len().to_le_bytes()[0]], ctx, hash_oid, hash];
let sig = crate::slh::slh_sign_with_rng::<A, D, H, HP, K, LEN, M, N>(
rng, &HASHERS, &mp, &self.0, hedged,
);
sig.map(|s| s.serialize())
}
fn get_public_key(&self) -> Self::PublicKey {
PublicKey(SlhPublicKey { pk_seed: self.0.pk_seed, pk_root: self.0.pk_root })
}
}
#[cfg(feature = "acvp-internal")]
impl PrivateKey {
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
pub fn sign_internal(
&self, rng: &mut impl CryptoRngCore, m: &[u8], hedged: bool,
) -> Result<[u8; SIG_LEN], &'static str> {
let mut opt_rand = zeroize::Zeroizing::new((self.0).pk_seed);
if hedged {
rng.try_fill_bytes(&mut opt_rand[..]).map_err(|_| "Alg 22: rng failed")?;
}
let sig = crate::slh::slh_sign_internal::<A, D, H, HP, K, LEN, M, N>(
&HASHERS,
&[m],
&self.0,
&opt_rand,
);
sig.map(|s| s.serialize())
}
}
impl Verifier for PublicKey {
type Signature = [u8; SIG_LEN];
fn verify(&self, m: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8]) -> bool {
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,
);
res
}
fn hash_verify(
&self, hash: &[u8], sig_bytes: &[u8; SIG_LEN], ctx: &[u8], hash_oid: &[u8],
) -> bool {
if ctx.len() > 255 || crate::check_pre_hash(hash, hash_oid).is_err() {
return false;
};
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::deserialize(sig_bytes);
let mp: &[&[u8]] = &[&[1u8], &[ctx.len().to_le_bytes()[0]], ctx, hash_oid, hash];
crate::slh::slh_verify::<A, D, H, HP, K, LEN, M, N>(&HASHERS, &mp, &sig, &self.0)
}
}
#[cfg(feature = "acvp-internal")]
impl PublicKey {
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
#[must_use]
pub fn verify_internal(&self, m: &[u8], sig_bytes: &[u8; SIG_LEN]) -> bool {
let sig = SlhDsaSig::<A, D, HP, K, LEN, N>::deserialize(sig_bytes);
crate::slh::slh_verify_internal::<A, D, H, HP, K, LEN, M, N>(
&HASHERS,
&[m],
&sig,
&self.0,
)
}
}
impl SerDes for PublicKey {
type ByteArray = [u8; PK_LEN];
fn into_bytes(self) -> Self::ByteArray {
let mut out = [0u8; PK_LEN];
out[0..(PK_LEN / 2)].copy_from_slice(&self.0.pk_seed);
out[(PK_LEN / 2)..].copy_from_slice(&self.0.pk_root);
out
}
fn try_from_bytes(ba: Self::ByteArray) -> Result<Self, &'static str> {
let mut pk = SlhPublicKey { pk_seed: [0u8; N], pk_root: [0u8; N] };
pk.pk_seed.copy_from_slice(&ba[..(PK_LEN / 2)]);
pk.pk_root.copy_from_slice(&ba[(PK_LEN / 2)..]);
Ok(PublicKey(pk))
}
}
impl SerDes for PrivateKey {
type ByteArray = [u8; SK_LEN];
fn into_bytes(self) -> Self::ByteArray {
let mut bytes = [0u8; SK_LEN];
bytes[0..(SK_LEN / 4)].copy_from_slice(&self.0.sk_seed);
bytes[(SK_LEN / 4)..(SK_LEN / 2)].copy_from_slice(&self.0.sk_prf);
bytes[(SK_LEN / 2)..(3 * SK_LEN / 4)].copy_from_slice(&self.0.pk_seed);
bytes[(3 * SK_LEN / 4)..].copy_from_slice(&self.0.pk_root);
bytes
}
fn try_from_bytes(ba: Self::ByteArray) -> Result<Self, &'static str> {
let mut sk = SlhPrivateKey {
sk_seed: [0u8; N],
sk_prf: [0u8; N],
pk_seed: [0u8; N],
pk_root: [0u8; N],
};
sk.sk_seed.copy_from_slice(&ba[0..(SK_LEN / 4)]);
sk.sk_prf.copy_from_slice(&ba[(SK_LEN / 4)..(SK_LEN / 2)]);
sk.pk_seed.copy_from_slice(&ba[(SK_LEN / 2)..(3 * SK_LEN / 4)]);
sk.pk_root.copy_from_slice(&ba[(3 * SK_LEN / 4)..]);
let (sk_test, _) = crate::slh::slh_keygen_internal::<D, H, HP, K, LEN, M, N>(
&HASHERS, &sk.sk_seed, &sk.sk_prf, &sk.pk_seed,
);
ensure!(sk_test.pk_root == sk.pk_root, "Corrupted key");
Ok(PrivateKey(sk))
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_chacha::rand_core::SeedableRng;
use sha2::{Digest, Sha256, Sha512};
use sha3::digest::{ExtendableOutput, Update, XofReader};
use sha3::{Shake128, Shake256};
#[test]
fn simple_round_trips() {
let message = [0u8, 1, 2, 3];
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
let (pk1, sk1) = KG::try_keygen_with_rng(&mut rng).unwrap();
let pk1_bytes = pk1.into_bytes();
let sk1_bytes = sk1.into_bytes();
let pk2 = PublicKey::try_from_bytes(pk1_bytes).unwrap();
let sk2 = PrivateKey::try_from_bytes(sk1_bytes).unwrap();
assert_eq!(sk2.get_public_key().into_bytes(), pk1_bytes);
for i in [0, SK_LEN - 1] {
let mut bad = sk1_bytes;
bad[i] ^= 1;
assert_eq!(PrivateKey::try_from_bytes(bad).err(), Some("Corrupted key"));
}
let sig = sk2.try_sign_with_rng(&mut rng, &message, b"context", true).unwrap();
let result = pk2.verify(&message, &sig, b"context");
assert!(result, "Signature failed to verify");
let (pk3, sk3) = KG::keygen_from_seed(&[0u8; N], &[1u8; N], &[2u8; N]);
let sig = sk3.try_sign_with_rng(&mut rng, &message, b"context", true).unwrap();
let result = pk3.verify(&message, &sig, b"context");
assert!(result, "Signature failed to verify");
let result = pk3.verify(&message, &sig, b"some other context");
assert!(!result, "Signature should not have verified");
let sha256 = Sha256::digest(message);
let sha512 = Sha512::digest(message);
let mut shake128 = [0u8; 32];
let mut shake256 = [0u8; 64];
let mut h = Shake128::default();
h.update(&message);
h.finalize_xof().read(&mut shake128);
let mut h = Shake256::default();
h.update(&message);
h.finalize_xof().read(&mut shake256);
let digests: [(&[u8], &[u8]); 4] = [
(&crate::pre_hash::SHA2_256, &sha256),
(&crate::pre_hash::SHA2_512, &sha512),
(&crate::pre_hash::SHAKE_128, &shake128),
(&crate::pre_hash::SHAKE_256, &shake256),
];
assert!(sk2.try_hash_sign_with_rng(&mut rng, &sha256, &[], &[], true).is_err());
assert!(!pk2.hash_verify(&sha256, &sig, &[], &[]));
let too_long = [0u8; crate::MAX_PREHASH_LEN + 1];
assert!(sk2
.try_hash_sign_with_rng(
&mut rng,
&too_long,
&[],
&crate::pre_hash::SHA2_256,
true
)
.is_err());
assert!(!pk2.hash_verify(&too_long, &sig, &[], &crate::pre_hash::SHA2_256));
for (oid, digest) in digests {
let hash_sig = sk2
.try_hash_sign_with_rng(&mut rng, digest, b"context", oid, true)
.unwrap();
let result = pk2.hash_verify(digest, &hash_sig, b"context", oid);
assert!(result, "Signature failed to verify");
let result = pk2.hash_verify(digest, &hash_sig, b"some other context", oid);
assert!(!result, "Signature should not have verified");
}
}
}
};
}
#[cfg(feature = "slh-dsa-sha2-128s")]
pub mod slh_dsa_sha2_128s {
use crate::hashers::sha2_cat_1::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 16;
const H: usize = 63;
const D: usize = 7;
const HP: usize = 9;
const A: usize = 12;
const K: usize = 14;
const M: usize = 30;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 7856;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-128s")]
pub mod slh_dsa_shake_128s {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 16;
const H: usize = 63;
const D: usize = 7;
const HP: usize = 9;
const A: usize = 12;
const K: usize = 14;
const M: usize = 30;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 7856;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-sha2-128f")]
pub mod slh_dsa_sha2_128f {
use crate::hashers::sha2_cat_1::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 16;
const H: usize = 66;
const D: usize = 22;
const HP: usize = 3;
const A: usize = 6;
const K: usize = 33;
const M: usize = 34;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 17088;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-128f")]
pub mod slh_dsa_shake_128f {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 16;
const H: usize = 66;
const D: usize = 22;
const HP: usize = 3;
const A: usize = 6;
const K: usize = 33;
const M: usize = 34;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 32;
pub const SIG_LEN: usize = 17088;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-sha2-192s")]
pub mod slh_dsa_sha2_192s {
use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 24;
const H: usize = 63;
const D: usize = 7;
const HP: usize = 9;
const A: usize = 14;
const K: usize = 17;
const M: usize = 39;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 16224;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-192s")]
pub mod slh_dsa_shake_192s {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 24;
const H: usize = 63;
const D: usize = 7;
const HP: usize = 9;
const A: usize = 14;
const K: usize = 17;
const M: usize = 39;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 16224;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-sha2-192f")]
pub mod slh_dsa_sha2_192f {
use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 24;
const H: usize = 66;
const D: usize = 22;
const HP: usize = 3;
const A: usize = 8;
const K: usize = 33;
const M: usize = 42;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 35664;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-192f")]
pub mod slh_dsa_shake_192f {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 24;
const H: usize = 66;
const D: usize = 22;
const HP: usize = 3;
const A: usize = 8;
const K: usize = 33;
const M: usize = 42;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 48;
pub const SIG_LEN: usize = 35664;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-sha2-256s")]
pub mod slh_dsa_sha2_256s {
use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 32;
const H: usize = 64;
const D: usize = 8;
const HP: usize = 8;
const A: usize = 14;
const K: usize = 22;
const M: usize = 47;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 29792;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-256s")]
pub mod slh_dsa_shake_256s {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 32;
const H: usize = 64;
const D: usize = 8;
const HP: usize = 8;
const A: usize = 14;
const K: usize = 22;
const M: usize = 47;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 29792;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-sha2-256f")]
pub mod slh_dsa_sha2_256f {
use crate::hashers::sha2_cat_3_5::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 32;
const H: usize = 68;
const D: usize = 17;
const HP: usize = 4;
const A: usize = 9;
const K: usize = 35;
const M: usize = 49;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 49856;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}
#[cfg(feature = "slh-dsa-shake-256f")]
pub mod slh_dsa_shake_256f {
use crate::hashers::shake::{f, h, h_msg, prf, prf_msg, t_l};
use crate::hashers::Hashers;
pub const N: usize = 32;
const H: usize = 68;
const D: usize = 17;
const HP: usize = 4;
const A: usize = 9;
const K: usize = 35;
const M: usize = 49;
const LEN: usize = 2 * N + 3;
pub const PK_LEN: usize = 64;
pub const SIG_LEN: usize = 49856;
pub const SK_LEN: usize = PK_LEN * 2;
static HASHERS: Hashers<K, LEN, M, N> =
Hashers::<K, LEN, M, N> { h_msg, prf, prf_msg, f, h, t_l, t_len: t_l };
functionality!();
}