slh-dsa: initial implementation (#812)

This commit is contained in:
Tjaden Hess
2024-04-13 09:21:36 -05:00
committed by GitHub
parent 11ea638090
commit 3697688721
19 changed files with 4446 additions and 5 deletions
Generated
+851 -4
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -6,7 +6,8 @@ members = [
"ed448",
"ed25519",
"lms",
"rfc6979"
"rfc6979",
"slh-dsa"
]
[profile.dev]
+42
View File
@@ -0,0 +1,42 @@
[package]
name = "slh-dsa"
version = "0.0.1"
edition = "2021"
[dependencies]
hybrid-array = {version = "0.2.0-rc.8", features = ["extra-sizes"]}
typenum = {version = "1.17.0", features = ["const-generics"]}
sha3 = "0.10.8"
zerocopy = "0.7.32"
zerocopy-derive = "0.7.32"
rand_core = {version = "0.6.4"}
signature = {version = "2.3.0-pre.0", features = ["rand_core"]}
hmac = "0.12.1"
sha2 = "0.10.8"
digest = "0.10.7"
[dev-dependencies]
hex-literal = "0.4.1"
hex = "0.4.1"
num-bigint = "0.4.4"
quickcheck = "1"
quickcheck_macros = "1"
proptest = "1.4.0"
criterion = "0.5"
aes = "0.8.4"
cipher = "0.4.4"
ctr = "0.9.2"
rand_core = "0.6.4"
paste = "1.0.14"
rand = "0.8.5"
[lib]
bench = false
[[bench]]
name = "sign_verify"
harness = false
[features]
alloc = []
default = ["alloc"]
+19
View File
@@ -0,0 +1,19 @@
# SLH-DSA
Pure Rust implementation of the SLH-DSA (aka SPHINCS+) signature scheme.
Implemented based on the [FIPS-205 Inital Public Draft](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.205.ipd.pdf)
## ⚠️ Security Warning
The implementation contained in this crate has never been independently audited!
USE AT YOUR OWN RISK!
## Minimum Supported Version
This crate has only been tested with Rust 1.75+
+47
View File
@@ -0,0 +1,47 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use signature::{Keypair, Signer, Verifier};
use slh_dsa::*;
pub fn sign_benchmark<P: ParameterSet>(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let sk = SigningKey::<P>::new(&mut rng);
c.bench_function(&format!("sign: {}", P::NAME), |b| {
b.iter(|| {
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
black_box(sig)
})
});
}
pub fn verify_benchmark<P: ParameterSet>(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let sk = SigningKey::<P>::new(&mut rng);
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
let vk = sk.verifying_key();
c.bench_function(&format!("verify: {}", P::NAME), |b| {
b.iter(|| {
let ok = vk.verify(msg, &sig);
black_box(ok)
})
});
}
criterion_group!(name = sign_benches;
config = Criterion::default().sample_size(10);
targets = sign_benchmark<Shake128s>, sign_benchmark<Shake192s>, sign_benchmark<Shake256s>,
sign_benchmark<Shake128f>, sign_benchmark<Shake192f>, sign_benchmark<Shake256f>,
sign_benchmark<Sha2_128s>, sign_benchmark<Sha2_192s>, sign_benchmark<Sha2_256s>,
sign_benchmark<Sha2_128f>, sign_benchmark<Sha2_192f>, sign_benchmark<Sha2_256f>,
);
criterion_group!(name = verify_benches;
config = Criterion::default().sample_size(10);
targets = sign_benchmark<Shake128s>, sign_benchmark<Shake192s>, sign_benchmark<Shake256s>,
sign_benchmark<Shake128f>, sign_benchmark<Shake192f>, sign_benchmark<Shake256f>,
sign_benchmark<Sha2_128s>, sign_benchmark<Sha2_192s>, sign_benchmark<Sha2_256s>,
sign_benchmark<Sha2_128f>, sign_benchmark<Sha2_192f>, sign_benchmark<Sha2_256f>,
);
criterion_main!(sign_benches, verify_benches);
+273
View File
@@ -0,0 +1,273 @@
//! Hash address definitions and serialization
//!
//! From FIPS-205 section 4.2:
//! > An ADRS
//! > consists of public values that indicate the position of the value being computed by the function. A
//! > different ADRS value is used for each call to each function. In the case of PRF, this is in order
//! > to generate a large number of different secret values from a single seed. In the case of T, H, and
//! > F, it is used to mitigate multi-target attacks.
//!
//! Address fields are big-endian integers. We use zero-copyable structs to represent the addresses
//! and serialize transparently to bytes using the `zerocopy` crate.
//!
//! Note that `tree_adrs_high` is unused in all parameter sets currently defined by FIPS-205
//!
//! Rather than implementing a generic `setTypeAndClear` as specified in FIPS-205, we define specific transitions for those
//! address conversions which are actually used.
use hybrid_array::Array;
use typenum::U22;
use zerocopy::byteorder::big_endian::{U32, U64};
use zerocopy::AsBytes;
use zerocopy_derive::AsBytes;
/// `Address` represents a hash address as defined by FIPS-205 section 4.2
pub trait Address: AsRef<[u8]> {
const TYPE_CONST: u32;
#[allow(clippy::doc_markdown)] // False positive
/// Returns the address as a compressed 22-byte array
/// ADRSc = ADRS[3] ∥ ADRS[8 : 16] ∥ ADRS[19] ∥ ADRS[20 : 32]
fn compressed(&self) -> Array<u8, U22> {
let bytes = self.as_ref();
let mut compressed = Array::<u8, U22>::default();
compressed[0] = bytes[3];
compressed[1..9].copy_from_slice(&bytes[8..16]);
compressed[9] = bytes[19];
compressed[10..22].copy_from_slice(&bytes[20..32]);
compressed
}
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct WotsHash {
pub layer_adrs: U32,
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 0
pub key_pair_adrs: U32,
pub chain_adrs: U32,
pub hash_adrs: U32,
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct WotsPk {
pub layer_adrs: U32,
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 1
pub key_pair_adrs: U32,
padding: U64, // 0
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct HashTree {
pub layer_adrs: U32,
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 2
padding: U32, // 0
pub tree_height: U32,
pub tree_index: U32,
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct ForsTree {
layer_adrs: U32, // 0
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 3
pub key_pair_adrs: U32,
pub tree_height: U32,
pub tree_index: U32,
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct ForsRoots {
layer_adrs: U32, // 0
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 4
pub key_pair_adrs: U32,
padding: U64, // 0
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct WotsPrf {
pub layer_adrs: U32,
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 5
pub key_pair_adrs: U32,
pub chain_adrs: U32,
hash_adrs: U32, // 0
}
#[derive(Clone, AsBytes)]
#[repr(C)]
pub struct ForsPrf {
layer_adrs: U32, // 0
pub tree_adrs_high: U32,
pub tree_adrs_low: U64,
type_const: U32, // 6
pub key_pair_adrs: U32,
tree_height: U32, // 0
pub tree_index: U32,
}
impl Address for WotsHash {
const TYPE_CONST: u32 = 0;
}
impl AsRef<[u8]> for WotsHash {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for WotsPk {
const TYPE_CONST: u32 = 1;
}
impl AsRef<[u8]> for WotsPk {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for HashTree {
const TYPE_CONST: u32 = 2;
}
impl AsRef<[u8]> for HashTree {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for ForsTree {
const TYPE_CONST: u32 = 3;
}
impl AsRef<[u8]> for ForsTree {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for ForsRoots {
const TYPE_CONST: u32 = 4;
}
impl AsRef<[u8]> for ForsRoots {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for WotsPrf {
const TYPE_CONST: u32 = 5;
}
impl AsRef<[u8]> for WotsPrf {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl Address for ForsPrf {
const TYPE_CONST: u32 = 6;
}
impl AsRef<[u8]> for ForsPrf {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl WotsHash {
pub fn prf_adrs(&self) -> WotsPrf {
WotsPrf {
layer_adrs: self.layer_adrs,
tree_adrs_low: self.tree_adrs_low,
tree_adrs_high: self.tree_adrs_high,
type_const: WotsPrf::TYPE_CONST.into(),
key_pair_adrs: self.key_pair_adrs,
chain_adrs: 0.into(),
hash_adrs: 0.into(),
}
}
pub fn pk_adrs(&self) -> WotsPk {
WotsPk {
layer_adrs: self.layer_adrs,
tree_adrs_low: self.tree_adrs_low,
tree_adrs_high: self.tree_adrs_high,
type_const: WotsPk::TYPE_CONST.into(),
key_pair_adrs: self.key_pair_adrs,
padding: 0.into(),
}
}
pub fn tree_adrs(&self) -> HashTree {
HashTree {
layer_adrs: self.layer_adrs,
tree_adrs_low: self.tree_adrs_low,
tree_adrs_high: self.tree_adrs_high,
type_const: HashTree::TYPE_CONST.into(),
padding: 0.into(),
tree_height: 0.into(),
tree_index: 0.into(),
}
}
}
impl ForsTree {
pub fn new(tree_adrs_low: u64, key_pair_adrs: u32) -> ForsTree {
ForsTree {
layer_adrs: 0.into(),
tree_adrs_low: tree_adrs_low.into(),
tree_adrs_high: 0.into(),
type_const: ForsTree::TYPE_CONST.into(),
key_pair_adrs: key_pair_adrs.into(),
tree_height: 0.into(),
tree_index: 0.into(),
}
}
pub fn prf_adrs(&self) -> ForsPrf {
ForsPrf {
layer_adrs: 0.into(),
tree_adrs_low: self.tree_adrs_low,
tree_adrs_high: self.tree_adrs_high,
type_const: ForsPrf::TYPE_CONST.into(),
key_pair_adrs: self.key_pair_adrs,
tree_height: 0.into(),
tree_index: self.tree_index,
}
}
pub fn fors_roots(&self) -> ForsRoots {
ForsRoots {
layer_adrs: 0.into(),
tree_adrs_low: self.tree_adrs_low,
tree_adrs_high: self.tree_adrs_high,
type_const: ForsRoots::TYPE_CONST.into(),
key_pair_adrs: self.key_pair_adrs,
padding: 0.into(),
}
}
}
impl Default for WotsHash {
fn default() -> Self {
WotsHash {
layer_adrs: 0.into(),
tree_adrs_low: 0.into(),
tree_adrs_high: 0.into(),
type_const: 0.into(),
key_pair_adrs: 0.into(),
chain_adrs: 0.into(),
hash_adrs: 0.into(),
}
}
}
+543
View File
@@ -0,0 +1,543 @@
use core::fmt::Debug;
use hybrid_array::{Array, ArraySize};
use typenum::Unsigned;
use crate::{address, PkSeed, SkSeed};
use crate::hypertree::HypertreeParams;
use crate::util::base_2b;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForsMTSig<P: ForsParams> {
sk: Array<u8, P::N>,
auth: Array<Array<u8, P::N>, P::A>,
}
impl<P: ForsParams> ForsMTSig<P> {
const SIZE: usize = P::N::USIZE + P::A::USIZE * P::N::USIZE;
fn write_to(&self, slice: &mut [u8]) {
debug_assert!(
slice.len() == Self::SIZE,
"Writing FORS MT sig to slice of incorrect length"
);
slice
.chunks_exact_mut(P::N::USIZE)
.enumerate()
.for_each(|(i, c)| {
if i == 0 {
c.copy_from_slice(&self.sk);
} else {
c.copy_from_slice(&self.auth[i - 1]);
}
});
}
}
impl<P: ForsParams> Default for ForsMTSig<P> {
fn default() -> Self {
Self {
sk: Array::default(),
auth: Array::default(),
}
}
}
impl<P: ForsParams> TryFrom<&[u8]> for ForsMTSig<P> {
// TODO - real error type
type Error = ();
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
if slice.len() != ForsMTSig::<P>::SIZE {
return Err(());
}
let sk = Array::clone_from_slice(&slice[..P::N::USIZE]);
let mut auth: Array<Array<u8, P::N>, P::A> = Array::default();
for i in 0..P::A::USIZE {
auth[i].copy_from_slice(
&slice[P::N::USIZE + i * P::N::USIZE..P::N::USIZE + (i + 1) * P::N::USIZE],
);
}
Ok(Self { sk, auth })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForsSignature<P: ForsParams>(Array<ForsMTSig<P>, P::K>);
impl<P: ForsParams> TryFrom<&[u8]> for ForsSignature<P> {
// TODO - real error type
type Error = ();
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
if slice.len() != Self::SIZE {
return Err(());
}
Ok(Self(
slice
.chunks(ForsMTSig::<P>::SIZE)
.map(|c| c.try_into().unwrap())
.collect(),
))
}
}
impl<P: ForsParams> Default for ForsSignature<P> {
fn default() -> Self {
Self(Array::default())
}
}
impl<P: ForsParams> ForsSignature<P> {
pub const SIZE: usize = P::K::USIZE * (P::A::USIZE + 1) * P::N::USIZE;
pub fn write_to(&self, slice: &mut [u8]) {
debug_assert!(
slice.len() == Self::SIZE,
"Writing FORS sig to slice of incorrect length"
);
slice
.chunks_exact_mut(ForsMTSig::<P>::SIZE)
.enumerate()
.for_each(|(i, c)| self.0[i].write_to(c));
}
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
let mut v = vec![0u8; Self::SIZE];
self.write_to(&mut v);
v
}
}
pub(crate) trait ForsParams: HypertreeParams {
type K: ArraySize + Eq + Debug;
type A: ArraySize + Eq + Debug;
type MD: ArraySize; // ceil(K*A/8)
fn fors_sk_gen(
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::ForsTree,
idx: u32,
) -> Array<u8, Self::N> {
let mut adrs = adrs.prf_adrs();
adrs.tree_index.set(idx);
Self::prf_sk(pk_seed, sk_seed, &adrs)
}
fn fors_node(
sk_seed: &SkSeed<Self::N>,
i: u32,
z: u32,
pk_seed: &PkSeed<Self::N>,
adrs: &address::ForsTree,
) -> Array<u8, Self::N> {
debug_assert!(z <= Self::A::U32);
debug_assert!(i < (Self::K::U32 << (Self::A::U32 - z)));
let mut adrs = adrs.clone(); // TODO: do we really need clone or should we take mut ref?
if z == 0 {
let sk = Self::fors_sk_gen(sk_seed, pk_seed, &adrs, i);
adrs.tree_height.set(0);
adrs.tree_index.set(i);
Self::f(pk_seed, &adrs, &sk)
} else {
let lnode = Self::fors_node(sk_seed, 2 * i, z - 1, pk_seed, &adrs);
let rnode = Self::fors_node(sk_seed, 2 * i + 1, z - 1, pk_seed, &adrs);
adrs.tree_height.set(z);
adrs.tree_index.set(i);
Self::h(pk_seed, &adrs, &lnode, &rnode)
}
}
fn fors_sign(
md: &Array<u8, Self::MD>,
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::ForsTree,
) -> ForsSignature<Self> {
let mut sig = ForsSignature::<Self>::default();
let indices = base_2b::<Self::K, Self::A>(md);
for i in 0..Self::K::U32 {
sig.0[i as usize].sk = Self::fors_sk_gen(
sk_seed,
pk_seed,
adrs,
(i << Self::A::U32) + u32::from(indices[i as usize]),
);
for j in 0..Self::A::U32 {
let s = (indices[i as usize] >> j) ^ 1;
sig.0[i as usize].auth[j as usize] = Self::fors_node(
sk_seed,
(i << (Self::A::U32 - j)) + u32::from(s),
j,
pk_seed,
adrs,
);
}
}
sig
}
fn fors_pk_from_sig(
sig: &ForsSignature<Self>,
md: &Array<u8, Self::MD>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::ForsTree,
) -> Array<u8, Self::N> {
let mut adrs = adrs.clone();
let indices = base_2b::<Self::K, Self::A>(md);
let mut roots = Array::<Array<u8, Self::N>, Self::K>::default();
for i in 0..Self::K::U32 {
let sk = &sig.0[i as usize].sk;
adrs.tree_height.set(0);
adrs.tree_index
.set((i << Self::A::U32) + u32::from(indices[i as usize]));
let mut node = Self::f(pk_seed, &adrs, sk);
for j in 0..Self::A::U32 {
adrs.tree_height.set(j + 1);
adrs.tree_index.set(adrs.tree_index.get() >> 1);
if indices[i as usize] >> j & 1 == 0 {
node = Self::h(pk_seed, &adrs, &node, &sig.0[i as usize].auth[j as usize]);
} else {
node = Self::h(pk_seed, &adrs, &sig.0[i as usize].auth[j as usize], &node);
}
}
roots[i as usize] = node;
}
Self::t(pk_seed, &adrs.fors_roots(), &roots)
}
}
#[cfg(test)]
mod tests {
use self::address::ForsTree;
use crate::util::macros::test_parameter_sets;
use crate::Shake128f;
use rand::{thread_rng, Rng, RngCore};
use super::*;
use hex_literal::hex;
#[test]
#[cfg(feature = "alloc")]
#[allow(clippy::too_many_lines)] // KAT is long
fn fors_sign_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let adrs = ForsTree::new(3, 5);
let md = Array([3; 25]);
let sig = <Shake128f as ForsParams>::fors_sign(&md, &sk_seed, &pk_seed, &adrs);
let expected = hex!(
"2cac88fad4eeae791048fe07aa3544a9
ab0db7949e4abe2d767811bce716bc00
8b512f3dc7992fe8d5fe70c0f822f65b
c3c8f23ec667ac82a899d62267431e59
57d9471da7421fc353f3a4e3117e5b82
6dbe311ae7149847237fcb470e4ca87a
d9a1aac408b8b5e3083abdabbd7b4383
5cab0d526d48edb9394d2de1e336f032
c927304c7d98006b391a246c026b4db4
a7f9a4b9e23098d7979fa9e12596e91e
d2e211744d165a6fa345a46f75466d7f
9cf4246210a029514bacff2c5a7e388e
d9367fb58b5e0822c3d626763ab28448
7c0ce3e00ef878da1eb86e79a644adb9
a9594b1965a681ab3808b7449ccc3fad
92c18b2dcf30f6039ba6bc905c0120c0
007ee543f98332209796725f60c5215a
9e22bbc28bc53a9ed7e80cd2b1a749ca
15b17e02f21a655154fd0e3766728432
08e41bfcb86d13453a9acf9fa4fab1f9
f0bbca7e8061a902626d4cf67daa1efd
c250a680f1f7c73edd342e306fd5b6c5
83c863db88fc2ad9aa7cab71df150c80
8ee52368c247c00cdb6cd172148f621b
05fb4c57760821ec6e0ff89b34a48d50
70e9e31aace4ea4b3baab68d6fd87426
29f156fd3f24cd7af90bd16bb7925ff6
ae35d668a5fa16a6dccf53bb6a1fd84f
43c6e5553c24889437ce33d1eaadcc7d
d1a5725d47e1373d7b630afc3e6a4881
f5884498333213de34b874f9e7156abe
24df0d49ac1b47061682b1206adf3a90
b7b187467375aa88e31b1998d2ca9ffb
4f5b403cfa4986f058385d355d057049
c1e39cba529da7ac0564d1042e7e19b9
1e45b9d93dcd7b47fe320e86065340aa
02e982b098c7d4de76d35f90b49bc769
f2fed7692d65bedd7e7faed34ac0d2b6
6e6cad8acb9c21403b6f188759e1624d
7f3acc1475dc67b10120a9cdb61e5066
ae48c47623acb8a22a1c448ae0a7526e
8640c4c3c5fc39b102dcd5bf96e14a0c
f92209e13e7de627d1dbc35efeec0adb
0bededc8ce1e04726336114f8193fc22
ed7c3fa25c57d2739e61c013a701e1f2
6b84e638d4a162a952da631b83ec82bd
5c117842a1f3c90e4bd098c201ad01fa
4826bb3f8f677f5a28bfb1341ae73830
c2bde99049a98dd2e72203fe129ebb74
df772dd23af9b65509ad64c9fe570c37
a2dec3937d65a8742d15eb43232ade15
770abf59f1d58dfb9e162288cb5704e5
75917584d91861d4d05c72802d8b0d20
d60de536a559ec19863cf3df994f7f57
7e780d48fdc539505cf9cbe7d3366a98
b19a3b4ff7c8ac873ba0fc7d8b62eb97
dedc2b8fde1cd48769393c814858e26c
5154a7a68f8fe04e89d51f2e9ea558ad
893a93cf89c25e10560ecb52a1824a69
50d9681113386ca46256d2f315196e49
1d7fddc302f2a6b13d209d01f1a931d7
3db8c7e526a3adb66374d421d2856bac
4ae2273f5dbbfb41730e094037116e8c
2c2142a2e99000a8651223be1d809f86
4dba1df1a351bd141e3823623b2ef412
67c78b83d8348909e655ac0a6d7de7bc
6869592c0c8098fdb0582d8d3f7b4b8d
0cff1364c27ed916cc605ba0756c7de9
547aeb18856b1a7ad47ab4216e652240
64b781cdf331ec8b90069b8ca8957119
42ed015d94563e15cd15269691b2f5da
daa4de5fb65b446f56cdb15a56097a7e
c21d2b8a383a5d57c9212d97ff49bda9
e9fd7e4ac97a63a5d766b23951c46ae1
17ff035a8a01b6b13d552929030a39c9
3a6ce3514e849b9846d7cb50477d2f50
c75defe9cf2e581aa6472c2d5091b174
ea125546262026f88b809e883df0ca55
5542ccbf45463888eb69c4ae776d223f
bc4aa9a3226a6902e08879e8f26cc5cc
3c10e957b8b9df1d0f63ab2302d3848f
a279887737582214fbaf2ca8c6d4db9a
2dccbbf77436fc1c92094cc95f829c7d
01537cf3e050db557f3af9f066629256
6f866cf423ed7b3d319451f1c3a149fc
dab3d0af11df4cea9f03091a50724d7e
0828d959f75ee7a8dbd77173e0a8d960
1d68359917b8322f1404d2df90bbbdff
30cf95015991d269d8750342f87a9d12
8f71c1e2f59c4195c88568c33b2d9e31
01c3d2eb8333dba37032ad1cecf93e1d
9549eadcc51402b6facc0e9dc49eb319
dd219e310d8685aad7eafa9eeb3c6a9f
f8b0d92dd69ee21ae5ccba033aed106d
353b3cf6f3ebc571bdd52d3564fae36f
b72beb2c1e5560c10fc63bae4fd899e2
477fc22f24f840bf707836ffc7555330
a9d598529222e0a5236cd98a3fd7fe71
397cfd5aadf18308e26952723ecf68fa
fef58ab686e103099877e3dcaf54017c
3845037737dddc5cee97be961fc143c4
6c174debbe5dd50b54b78a6c3ed296e7
ba807221df6f4edbcede1e112a8532ad
4152a0451ee1ad5119f0b64febc9f708
18fa91d3c20487846f6a4e0b6fdb58cd
8c898b4674f89f58d340147da10af553
4cc0a257f11ba2c56d5df3caf98dfe60
1bfbe5db517a4962ad1b5aef4e35fc09
504564da0bcadff2a978c6bc5771db63
afce16ca77afea52025207222936de1a
4301b9c22cb7a82faae7e6de51c2a964
ef6c5cd90f387438ada33d83d1298df5
9fc6655b89a44eeaa2193415d9a74288
e1b938f7f8b3e3ff7b6cbd4dcaa4f94b
dff08cc1d146be2b1b98288daee9465b
9a559e0f45b3688a3e15d608625a605a
b61c1ed68830af15af0a420a892b1a7d
931398d38c693a682b831dcb2bd597cc
6c688f6f8e1fef3af478e787b3fdcc97
3bfaec54bf35f95885f9dcbd4c2dec0e
77685949e2e719f9efdcf87f68d53cc4
08a18cae49765c7e069db3589f8a40ef
d8079a2ce3d3f642ae810798ef005f16
4bc49a460f489fb6636de626cc9e15d9
bac5b681a5778fcc47992067370685e2
fe13fad1524b074ecc0c22b538f6a4dc
cc04e74bfeb8555e2ca70668e795891f
2f29e90ee399860bbd304e4adc4b0fb9
06903cf76c14eae445f1264e9d02c9fd
f8f136891a2edd673fd618fa9087cda3
ee848ec664db24040ee9984b87b32f17
426f874a1df9caf48e56189e7c77c5dc
c7d67ae43ad8981090d194e7296ebe7f
a4d2079e9459b3c94a9aea25417ea56c
6b534a33f522e8a84dd72d36775098b0
197bc35de831d4dd2e1b285ab3dc48f7
0e093b8e8371d163b4caa433b02300fa
5c2e13151db1c007639f7fab7daddc7d
61cecc434d98da5cd935bff6d6ff0249
de2499f600247f45380421b8bca738f7
706b3ac2eb72c0f063ec2fa49b4cd3ee
a81d78c05097af13e0627624f05b2a8e
e35c1aee48d793d71376e520035a9adf
3b4da3e5589fa9feb181e0760e42bebc
cc732d75278c8e3db0204ac4286dee76
831debc5c747f739ce9ba8033c88395e
c5c545f84e56b859af1e8ec8bed15ef9
5376dfd94080277f9a46c57be0d8dc95
d8c081215984612108ce867d660219d8
2af26fc92ea0612984d54e2e9919ae21
f9e707447b568fe377805ae910971300
66735a71ffb3d2ee302a8238af655f78
312a3114429d9229b70c0ca8a6b3610e
fdc3e255becaea51e210b3a953164461
c2989ad008df28ee01894c6d004f3aff
8330a0705e6dc310c114b507637df65b
a9536eee7333a0aec3d066210876f18c
68337e1c01557778babb9c42a3c22750
062eb3d85f0b483d0df6fd1257ee50c5
1410c61bb736de6c5b299af5e1adea19
4a90b0df2141d4f09825ab4fb9b0677a
81e0082b272d1d285de6f75d71be139e
4c2600dea5382f25d6d31d2948fa2fb7
b249f2c9d7d878b7d4fb83248ed9bf1d
115f60790e04878b7aa6a06fc4777f61
73167d6670f528654155181c837e1f3c
e7a8878da886b2272edfecb480b01140
0d21e113516fb7e5ed586777c8b869b0
daf770a8ee057a4ea9ec0c3e6173b569
d4a9584d94154dd2dd398871e96fe740
86565ad3de7f4082ac854cca03db02e1
743a60610d0e9b280b4cf07324609d8a
60696152e8a43e8d54dde2e4fa8520a2
dfb1ad52e52b02d3a59f7efe0002fadb
13cc4459a83adc9c95c7c629cffc4969
0e31e939838b10b099e6805e5f836afe
87ddc555a60a170e420c4689602688e1
bfdaf2acc8b8b7b5a2baa7fea26cee53
a8234624f3bfa46b42de7e88b55be6b2
70673e514506dae3a3315721e12fcb6c
9351840dc9190cc10a33b12a2c8458e2
c7d604940278c225f4be63fb0aaa2b04
4cca2caa877f616ebc6c7c3d0925d8fc
fa85ff9d7bb34cb583f67c2491404e36
0b8d44ba7bd5781212f14508b201ddec
32f782a66f99524dc671dd9d6504b515
7e05a9868de15601bac2adf0dd11a098
641d2eb7e5e4bebcd0c0ac5bcd677ea2
68ab2e99bd60342400b5c212f627972c
be7131f09d96688254758bb15e0866ae
f3cf49d71e271bb5013e9678a2fe6b56
dffc42f24fc8d3ffe2181c9396f84984
be88f3fe6cb54876daa601e44294a340
c52b64cd7e85b7a928639521f69ed9f6
2b2697d1a102f1f2dbd8ed8ac5f41c66
206291fc7ded397c707edd225023323c
c0065ea2d842feb8422e2c4cc91c7e0d
eb69e80cf3347a75585413fe33f6ec7b
e06ad09a68cc238b48cd58b8d4c90028
1b8cfa79d2afabeada78d80ff1c67844
ad4277bf118864649a3ec6b28ae522ff
d7533f98a5502b523d369a8a6a02de6f
0840f44620f206a48b6ebd0120e8e7f3
11e3b61f09a57f499c6931d475021bb9
637ce37c0e657720e2fc2c04595a3607
25575c7f9e90eb41dee5254476d1fba1
1f3c19b4ade31e888e919d3f30030f3a
19605154fa89d14f5fe7d1c787aac421
f5c3ddd3b69f0a335c5605ab674f7053
05b0a5bbebf4202cc5023b47b21eef68
15e0f6b08d2b2da46323938729bdd55f
5341ff14d36df69803229c6046b2503a
62d4e47da0195d0028cbfe0a67bf5230
b4b8a8d2c1c31203ceac2873a860c235
5e5f859a2be634f323dc30efe4ce3ffa
7a61e0a32ca8d3a4cc611bee7c0f08ae
0e7c17c2a9c8e42d5dc22e315b39800c
435391233c937ed90125ffc463573afb
4c15b452d653e03de6b13497a3e6e275
7e7f218b4b8c0430d9a27c26997b092f
20985c4af70d1cf2fd0d42d86732d807
11fb25ce2e0a2a1ae179073350832cf4
495ec887a77739598aabd4f3caf2a2c7
0b70631e0fe67e3ee4aa7843c41c2f25
3bc696d6b859844e28f9a416314a1d74
0cb5e757000ff1ca5422dafc98a3cd85
6ee679d11f9d4fe0ff7f1e6e9217f53e
74bacc9f8098d1c7e54e9ed32f69f06e
2b5a345df79c4d3864078ec89befb558
5cdc3d79e51bd5fe70896089ea2af20e
99b6ca2ce814190f602542a1bfa738ea"
);
assert_eq!(sig.to_vec(), expected);
}
fn test_sign_verify<Fors: ForsParams>() {
// Generate random sk_seed, pk_seed, message, index, address
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, Fors::MD>::default();
rng.fill_bytes(msg.as_mut_slice());
let idx_tree = rng.gen_range(
0..=(1u64
.wrapping_shl(Fors::H::U32 - Fors::HPrime::U32)
.wrapping_sub(1)),
);
let idx_leaf = rng.gen_range(0..(1 << (Fors::HPrime::USIZE)));
let mut adrs = ForsTree::new(idx_tree, idx_leaf);
let mut pks = Array::<Array<u8, Fors::N>, Fors::K>::default();
for i in 0..Fors::K::U32 {
adrs.tree_index.set(i);
pks[i as usize] = Fors::fors_node(&sk_seed, i, Fors::A::U32, &pk_seed, &adrs);
}
let pk = Fors::t(&pk_seed, &adrs.fors_roots(), &pks);
let sig = Fors::fors_sign(&msg, &sk_seed, &pk_seed, &adrs);
let pk_recovered = Fors::fors_pk_from_sig(&sig, &msg, &pk_seed, &adrs);
assert_eq!(pk, pk_recovered);
}
test_parameter_sets!(test_sign_verify);
fn test_sign_verify_failure<Fors: ForsParams>() {
// Generate random sk_seed, pk_seed, message, index, address
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, Fors::MD>::default();
rng.fill_bytes(msg.as_mut_slice());
let idx_tree = rng.gen_range(
0..=(1u64
.wrapping_shl(Fors::H::U32 - Fors::HPrime::U32)
.wrapping_sub(1)),
);
let idx_leaf = rng.gen_range(0..(1 << (Fors::HPrime::USIZE)));
let mut adrs = ForsTree::new(idx_tree, idx_leaf);
let mut pks = Array::<Array<u8, Fors::N>, Fors::K>::default();
for i in 0..Fors::K::U32 {
adrs.tree_index.set(i);
pks[i as usize] = Fors::fors_node(&sk_seed, i, Fors::A::U32, &pk_seed, &adrs);
}
let pk = Fors::t(&pk_seed, &adrs.fors_roots(), &pks);
let sig = Fors::fors_sign(&msg, &sk_seed, &pk_seed, &adrs);
// Modify the message
msg[0] ^= 0xff; // Invert the first byte of the message
let pk_recovered = Fors::fors_pk_from_sig(&sig, &msg, &pk_seed, &adrs);
assert_ne!(
pk, pk_recovered,
"Signature verification should fail with a modified message"
);
}
test_parameter_sets!(test_sign_verify_failure);
}
+118
View File
@@ -0,0 +1,118 @@
//! Hash functions used in the SLH-DSA signature scheme
//!
//! Each parameter set defines several functions derived from the core hash function (SHA2 or SHAKE)
//! A `HashSuite` contains all of these functions, defined in FIPS-205 section 10
mod sha2;
mod shake;
use core::fmt::Debug;
use hybrid_array::{Array, ArraySize};
pub use sha2::*;
pub use shake::*;
use crate::{address::Address, PkSeed, SkPrf, SkSeed};
/// A trait specifying the hash functions described in FIPS-205 section 10
pub(crate) trait HashSuite: Sized + Clone + Debug + PartialEq + Eq {
type N: ArraySize + Debug + Clone + PartialEq + Eq;
type M: ArraySize + Debug + Clone + PartialEq + Eq;
/// Pseudorandom function that generates the randomizer for the randomized hashing of the message to be signed.
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::N>;
/// Hashes a message using a given randomizer
fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::M>;
/// PRF that is used to generate the secret values in WOTS+ and FORS private keys.
fn prf_sk(
pk_seed: &PkSeed<Self::N>,
sk_seed: &SkSeed<Self::N>,
adrs: &impl Address,
) -> Array<u8, Self::N>;
/// A hash function that maps an L*N-byte string to an N-byte string. Used for the chain function in WOTS+.
/// Message length must be a multiple of `N`. Panics otherwise.
fn t<L: ArraySize>(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<Array<u8, Self::N>, L>,
) -> Array<u8, Self::N>;
/// Specialization of `t` for 2*chunk messages. Used to compute Merkle tree nodes.
/// May be reimplemented for better performance.
fn h(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m1: &Array<u8, Self::N>,
m2: &Array<u8, Self::N>,
) -> Array<u8, Self::N>;
/// Hash function that takes an N-byte input to an N-byte output
/// Used for the WOTS+ chain function
fn f(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<u8, Self::N>,
) -> Array<u8, Self::N>;
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
fn prf_msg<H: HashSuite>(expected: &[u8]) {
let sk_prf = SkPrf(Array::<u8, H::N>::from_fn(|_| 0));
let opt_rand = Array::<u8, H::N>::from_fn(|_| 1);
let msg = [2u8; 32];
let result = H::prf_msg(&sk_prf, &opt_rand, msg);
assert_eq!(result.as_slice(), expected);
}
fn h_msg<H: HashSuite>(expected: &[u8]) {
let rand = Array::<u8, H::N>::from_fn(|_| 0);
let pk_seed = PkSeed(Array::<u8, H::N>::from_fn(|_| 1));
let pk_root = Array::<u8, H::N>::from_fn(|_| 2);
let msg = [3u8; 32];
let result = H::h_msg(&rand, &pk_seed, &pk_root, msg);
assert_eq!(result.as_slice(), expected);
}
#[test]
fn prf_msg_shake128f() {
prf_msg::<Shake128f>(&hex!("bc5c062307df0a41aeeae19ad655f7b2"));
}
#[test]
fn prf_msg_sha2_128_f() {
prf_msg::<Sha2_128f>(&hex!("6a4b5cf23911d4f3a6591d7003445316"));
}
// Exercises the mgf1_sha256 function
#[test]
fn h_msg_sha2_128_f() {
h_msg::<Sha2_128f>(&hex!(
"56658221f675d907a309255e8faef639d11e6a1118fa05d3bbd26179a7e0a54a7f5b"
));
}
// Exercises the mgf1_sha512 function
#[test]
fn h_msg_sha2_256_f() {
h_msg::<Sha2_256f>(&hex!("8c86dfb66392d1b647df0deab90be68fb6f988513e84d3ef75fa68591122bb5d74f6413672db5164e56492b7ca2c2e0335"));
}
}
+394
View File
@@ -0,0 +1,394 @@
use core::fmt::Debug;
use crate::hashes::HashSuite;
use crate::{
address::Address, fors::ForsParams, hypertree::HypertreeParams, wots::WotsParams,
xmss::XmssParams, ParameterSet,
};
use crate::{PkSeed, SkPrf, SkSeed};
use digest::{Digest, Mac};
use hmac::Hmac;
use hybrid_array::{Array, ArraySize};
use sha2::{Sha256, Sha512};
use typenum::{Diff, Sum, U, U128, U16, U24, U30, U32, U34, U39, U42, U47, U49, U64};
/// Implementation of the MGF1 XOF
fn mgf1<H: Digest, L: ArraySize>(seed: &[u8]) -> Array<u8, L> {
let mut result = Array::<u8, L>::default();
result
.chunks_mut(<H as Digest>::output_size())
.enumerate()
.for_each(|(counter, chunk)| {
let counter: u32 = counter
.try_into()
.expect("L should be less than (2^32 * Digest::output_size) bytes");
let mut hasher = H::new();
hasher.update(seed);
hasher.update(counter.to_be_bytes());
let result = hasher.finalize();
chunk.copy_from_slice(&result[..chunk.len()]);
});
result
}
/// Implementation of the component hash functions using SHA2 at Security Category 1
///
/// Follows section 10.2 of FIPS-205
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sha2L1<N, M> {
_n: core::marker::PhantomData<N>,
_m: core::marker::PhantomData<M>,
}
impl<N: ArraySize, M: ArraySize> HashSuite for Sha2L1<N, M>
where
N: core::ops::Add<N>,
Sum<N, N>: ArraySize,
Sum<N, N>: core::ops::Add<U32>,
Sum<Sum<N, N>, U32>: ArraySize,
U64: core::ops::Sub<N>,
Diff<U64, N>: ArraySize,
N: Debug + PartialEq + Eq,
M: Debug + PartialEq + Eq,
{
type N = N;
type M = M;
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::N> {
let mut mac = Hmac::<Sha256>::new_from_slice(sk_prf.as_ref()).unwrap();
mac.update(opt_rand.as_slice());
mac.update(msg.as_ref());
let result = mac.finalize().into_bytes();
Array::clone_from_slice(&result[..Self::N::USIZE])
}
fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::M> {
let mut h = Sha256::new();
h.update(rand);
h.update(pk_seed);
h.update(pk_root);
h.update(msg.as_ref());
let result = Array(h.finalize().into());
let seed = rand.clone().concat(pk_seed.0.clone()).concat(result);
mgf1::<Sha256, Self::M>(&seed)
}
fn prf_sk(
pk_seed: &PkSeed<Self::N>,
sk_seed: &SkSeed<Self::N>,
adrs: &impl Address,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let hash = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(sk_seed)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn t<L: ArraySize>(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<Array<u8, Self::N>, L>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let mut sha = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed());
m.iter().for_each(|x| sha.update(x.as_slice()));
let hash = sha.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn h(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m1: &Array<u8, Self::N>,
m2: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let hash = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(m1)
.chain_update(m2)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn f(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let hash = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(m)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
}
/// SHA2 at L1 security with small signatures
pub type Sha2_128s = Sha2L1<U16, U30>;
impl WotsParams for Sha2_128s {
type WotsMsgLen = U<32>;
type WotsSigLen = U<35>;
}
impl XmssParams for Sha2_128s {
type HPrime = U<9>;
}
impl HypertreeParams for Sha2_128s {
type D = U<7>;
type H = U<63>;
}
impl ForsParams for Sha2_128s {
type K = U<14>;
type A = U<12>;
type MD = U<{ (12 * 14 + 7) / 8 }>;
}
impl ParameterSet for Sha2_128s {
const NAME: &'static str = "SLH-DSA-SHA2-128s";
}
/// SHA2 at L1 security with fast signatures
pub type Sha2_128f = Sha2L1<U16, U34>;
impl WotsParams for Sha2_128f {
type WotsMsgLen = U<32>;
type WotsSigLen = U<35>;
}
impl XmssParams for Sha2_128f {
type HPrime = U<3>;
}
impl HypertreeParams for Sha2_128f {
type D = U<22>;
type H = U<66>;
}
impl ForsParams for Sha2_128f {
type K = U<33>;
type A = U<6>;
type MD = U<25>;
}
impl ParameterSet for Sha2_128f {
const NAME: &'static str = "SLH-DSA-SHA2-128f";
}
/// Implementation of the component hash functions using SHA2 at Security Category 3 and 5
///
/// Follows section 10.2 of FIPS-205
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sha2L35<N, M> {
_n: core::marker::PhantomData<N>,
_m: core::marker::PhantomData<M>,
}
impl<N: ArraySize, M: ArraySize> HashSuite for Sha2L35<N, M>
where
N: core::ops::Add<N>,
Sum<N, N>: ArraySize,
Sum<N, N>: core::ops::Add<U64>,
Sum<Sum<N, N>, U64>: ArraySize,
U64: core::ops::Sub<N>,
Diff<U64, N>: ArraySize,
U128: core::ops::Sub<N>,
Diff<U128, N>: ArraySize,
N: core::fmt::Debug + PartialEq + Eq,
M: core::fmt::Debug + PartialEq + Eq,
{
type N = N;
type M = M;
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::N> {
let mut mac = Hmac::<Sha512>::new_from_slice(sk_prf.as_ref()).unwrap();
mac.update(opt_rand.as_slice());
mac.update(msg.as_ref());
let result = mac.finalize().into_bytes();
Array::clone_from_slice(&result[..Self::N::USIZE])
}
fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::M> {
let mut h = Sha512::new();
h.update(rand);
h.update(pk_seed);
h.update(pk_root);
h.update(msg.as_ref());
let result = Array(h.finalize().into());
let seed = rand.clone().concat(pk_seed.0.clone()).concat(result);
mgf1::<Sha512, Self::M>(&seed)
}
fn prf_sk(
pk_seed: &PkSeed<Self::N>,
sk_seed: &SkSeed<Self::N>,
adrs: &impl Address,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let hash = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(sk_seed)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn t<L: ArraySize>(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<Array<u8, Self::N>, L>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U128, N>>::default();
let mut sha = Sha512::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed());
m.iter().for_each(|x| sha.update(x.as_slice()));
let hash = sha.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn h(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m1: &Array<u8, Self::N>,
m2: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U128, N>>::default();
let hash = Sha512::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(m1)
.chain_update(m2)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
fn f(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let zeroes = Array::<u8, Diff<U64, N>>::default();
let hash = Sha256::new()
.chain_update(pk_seed)
.chain_update(&zeroes)
.chain_update(adrs.compressed())
.chain_update(m)
.finalize();
Array::clone_from_slice(&hash[..Self::N::USIZE])
}
}
/// SHA2 at L3 security with small signatures
pub type Sha2_192s = Sha2L35<U24, U39>;
impl WotsParams for Sha2_192s {
type WotsMsgLen = U<{ 24 * 2 }>;
type WotsSigLen = U<{ 24 * 2 + 3 }>;
}
impl XmssParams for Sha2_192s {
type HPrime = U<9>;
}
impl HypertreeParams for Sha2_192s {
type D = U<7>;
type H = U<63>;
}
impl ForsParams for Sha2_192s {
type K = U<17>;
type A = U<14>;
type MD = U<{ (14 * 17 + 7) / 8 }>;
}
impl ParameterSet for Sha2_192s {
const NAME: &'static str = "SLH-DSA-SHA2-192s";
}
/// SHA2 at L3 security with fast signatures
pub type Sha2_192f = Sha2L35<U24, U42>;
impl WotsParams for Sha2_192f {
type WotsMsgLen = U<{ 24 * 2 }>;
type WotsSigLen = U<{ 24 * 2 + 3 }>;
}
impl XmssParams for Sha2_192f {
type HPrime = U<3>;
}
impl HypertreeParams for Sha2_192f {
type D = U<22>;
type H = U<66>;
}
impl ForsParams for Sha2_192f {
type K = U<33>;
type A = U<8>;
type MD = U<{ (33 * 8 + 7) / 8 }>;
}
impl ParameterSet for Sha2_192f {
const NAME: &'static str = "SLH-DSA-SHA2-128f";
}
/// SHA2 at L5 security with small signatures
pub type Sha2_256s = Sha2L35<U32, U47>;
impl WotsParams for Sha2_256s {
type WotsMsgLen = U<{ 32 * 2 }>;
type WotsSigLen = U<{ 32 * 2 + 3 }>;
}
impl XmssParams for Sha2_256s {
type HPrime = U<8>;
}
impl HypertreeParams for Sha2_256s {
type D = U<8>;
type H = U<64>;
}
impl ForsParams for Sha2_256s {
type K = U<22>;
type A = U<14>;
type MD = U<{ (14 * 22 + 7) / 8 }>;
}
impl ParameterSet for Sha2_256s {
const NAME: &'static str = "SLH-DSA-SHA2-256s";
}
/// SHA2 at L5 security with fast signatures
pub type Sha2_256f = Sha2L35<U32, U49>;
impl WotsParams for Sha2_256f {
type WotsMsgLen = U<{ 32 * 2 }>;
type WotsSigLen = U<{ 32 * 2 + 3 }>;
}
impl XmssParams for Sha2_256f {
type HPrime = U<4>;
}
impl HypertreeParams for Sha2_256f {
type D = U<17>;
type H = U<68>;
}
impl ForsParams for Sha2_256f {
type K = U<35>;
type A = U<9>;
type MD = U<{ (35 * 9 + 7) / 8 }>;
}
impl ParameterSet for Sha2_256f {
const NAME: &'static str = "SLH-DSA-SHA2-256f";
}
+279
View File
@@ -0,0 +1,279 @@
use core::fmt::Debug;
use crate::address::Address;
use crate::fors::ForsParams;
use crate::hashes::HashSuite;
use crate::hypertree::HypertreeParams;
use crate::wots::WotsParams;
use crate::xmss::XmssParams;
use crate::{ParameterSet, PkSeed, SkPrf, SkSeed};
use digest::{ExtendableOutput, Update};
use hybrid_array::typenum::consts::{U16, U30, U32};
use hybrid_array::typenum::{U24, U34, U39, U42, U47, U49};
use hybrid_array::{Array, ArraySize};
use sha3::Shake256;
use typenum::U;
/// Implementation of the component hash functions using SHAKE256
///
/// Follows section 10.1 of FIPS-205
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Shake<N, M> {
_n: core::marker::PhantomData<N>,
_m: core::marker::PhantomData<M>,
}
impl<N: ArraySize, M: ArraySize> HashSuite for Shake<N, M>
where
N: Debug + Clone + PartialEq + Eq,
M: Debug + Clone + PartialEq + Eq,
{
type N = N;
type M = M;
fn prf_msg(
sk_prf: &SkPrf<Self::N>,
opt_rand: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::N> {
let mut hasher = Shake256::default();
hasher.update(sk_prf.as_ref());
hasher.update(opt_rand.as_slice());
hasher.update(msg.as_ref());
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
}
fn h_msg(
rand: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
pk_root: &Array<u8, Self::N>,
msg: impl AsRef<[u8]>,
) -> Array<u8, Self::M> {
let mut hasher = Shake256::default();
hasher.update(rand.as_slice());
hasher.update(pk_seed.as_ref());
hasher.update(pk_root.as_ref());
hasher.update(msg.as_ref());
let mut output = Array::<u8, Self::M>::default();
hasher.finalize_xof_into(&mut output);
output
}
fn prf_sk(
pk_seed: &PkSeed<Self::N>,
sk_seed: &SkSeed<Self::N>,
adrs: &impl Address,
) -> Array<u8, Self::N> {
let mut hasher = Shake256::default();
hasher.update(pk_seed.as_ref());
hasher.update(adrs.as_ref());
hasher.update(sk_seed.as_ref());
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
}
fn t<L: ArraySize>(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<Array<u8, Self::N>, L>,
) -> Array<u8, Self::N> {
let mut hasher = Shake256::default();
hasher.update(pk_seed.as_ref());
hasher.update(adrs.as_ref());
for i in 0..L::USIZE {
hasher.update(m[i].as_slice());
}
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
}
fn h(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m1: &Array<u8, Self::N>,
m2: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let mut hasher = Shake256::default();
hasher.update(pk_seed.as_ref());
hasher.update(adrs.as_ref());
hasher.update(m1.as_slice());
hasher.update(m2.as_slice());
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
}
fn f(
pk_seed: &PkSeed<Self::N>,
adrs: &impl Address,
m: &Array<u8, Self::N>,
) -> Array<u8, Self::N> {
let mut hasher = Shake256::default();
hasher.update(pk_seed.as_ref());
hasher.update(adrs.as_ref());
hasher.update(m.as_slice());
let mut output = Array::<u8, Self::N>::default();
hasher.finalize_xof_into(&mut output);
output
}
}
// TODO: Consolidate parameters between Shake and SHA2 instances
/// SHAKE256 at L1 security with small signatures
pub type Shake128s = Shake<U16, U30>;
impl WotsParams for Shake128s {
type WotsMsgLen = U<32>;
type WotsSigLen = U<35>;
}
impl XmssParams for Shake128s {
type HPrime = U<9>;
}
impl HypertreeParams for Shake128s {
type D = U<7>;
type H = U<63>;
}
impl ForsParams for Shake128s {
type K = U<14>;
type A = U<12>;
type MD = U<{ (12 * 14 + 7) / 8 }>;
}
impl ParameterSet for Shake128s {
const NAME: &'static str = "SLH-DSA-SHAKE-128s";
}
/// SHAKE256 at L1 security with fast signatures
pub type Shake128f = Shake<U16, U34>;
impl WotsParams for Shake128f {
type WotsMsgLen = U<32>;
type WotsSigLen = U<35>;
}
impl XmssParams for Shake128f {
type HPrime = U<3>;
}
impl HypertreeParams for Shake128f {
type D = U<22>;
type H = U<66>;
}
impl ForsParams for Shake128f {
type K = U<33>;
type A = U<6>;
type MD = U<25>;
}
impl ParameterSet for Shake128f {
const NAME: &'static str = "SLH-DSA-SHAKE-128f";
}
/// SHAKE256 at L3 security with small signatures
pub type Shake192s = Shake<U24, U39>;
impl WotsParams for Shake192s {
type WotsMsgLen = U<{ 24 * 2 }>;
type WotsSigLen = U<{ 24 * 2 + 3 }>;
}
impl XmssParams for Shake192s {
type HPrime = U<9>;
}
impl HypertreeParams for Shake192s {
type D = U<7>;
type H = U<63>;
}
impl ForsParams for Shake192s {
type K = U<17>;
type A = U<14>;
type MD = U<{ (14 * 17 + 7) / 8 }>;
}
impl ParameterSet for Shake192s {
const NAME: &'static str = "SLH-DSA-SHAKE-192s";
}
/// SHAKE256 at L3 security with fast signatures
pub type Shake192f = Shake<U24, U42>;
impl WotsParams for Shake192f {
type WotsMsgLen = U<{ 24 * 2 }>;
type WotsSigLen = U<{ 24 * 2 + 3 }>;
}
impl XmssParams for Shake192f {
type HPrime = U<3>;
}
impl HypertreeParams for Shake192f {
type D = U<22>;
type H = U<66>;
}
impl ForsParams for Shake192f {
type K = U<33>;
type A = U<8>;
type MD = U<{ (33 * 8 + 7) / 8 }>;
}
impl ParameterSet for Shake192f {
const NAME: &'static str = "SLH-DSA-SHAKE-192f";
}
/// SHAKE256 at L5 security with small signatures
pub type Shake256s = Shake<U32, U47>;
impl WotsParams for Shake256s {
type WotsMsgLen = U<{ 32 * 2 }>;
type WotsSigLen = U<{ 32 * 2 + 3 }>;
}
impl XmssParams for Shake256s {
type HPrime = U<8>;
}
impl HypertreeParams for Shake256s {
type D = U<8>;
type H = U<64>;
}
impl ForsParams for Shake256s {
type K = U<22>;
type A = U<14>;
type MD = U<{ (14 * 22 + 7) / 8 }>;
}
impl ParameterSet for Shake256s {
const NAME: &'static str = "SLH-DSA-SHAKE-256s";
}
/// SHAKE256 at L5 security with fast signatures
pub type Shake256f = Shake<U32, U49>;
impl WotsParams for Shake256f {
type WotsMsgLen = U<{ 32 * 2 }>;
type WotsSigLen = U<{ 32 * 2 + 3 }>;
}
impl XmssParams for Shake256f {
type HPrime = U<4>;
}
impl HypertreeParams for Shake256f {
type D = U<17>;
type H = U<68>;
}
impl ForsParams for Shake256f {
type K = U<35>;
type A = U<9>;
type MD = U<{ (35 * 9 + 7) / 8 }>;
}
impl ParameterSet for Shake256f {
const NAME: &'static str = "SLH-DSA-SHAKE-256f";
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
fn prf_msg<H: HashSuite>() {
let sk_prf = SkPrf(Array::<u8, H::N>::from_fn(|_| 0));
let opt_rand = Array::<u8, H::N>::from_fn(|_| 1);
let msg = [2u8; 32];
let expected = hex!("bc5c062307df0a41aeeae19ad655f7b2");
let result = H::prf_msg(&sk_prf, &opt_rand, msg);
assert_eq!(result.as_slice(), expected);
}
#[test]
fn prf_msg_16_30() {
prf_msg::<Shake128f>();
}
}
+225
View File
@@ -0,0 +1,225 @@
use crate::{signing_key::SkSeed, PkSeed};
use core::fmt::Debug;
use hybrid_array::{Array, ArraySize};
use typenum::Unsigned;
use crate::{
address::WotsHash,
xmss::{XmssParams, XmssSig},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HypertreeSig<P: HypertreeParams>(Array<XmssSig<P>, P::D>);
impl<P: HypertreeParams> HypertreeSig<P> {
pub const SIZE: usize = XmssSig::<P>::SIZE * P::D::USIZE;
pub fn write_to(&self, buf: &mut [u8]) {
debug_assert!(
buf.len() == Self::SIZE,
"HT serialize length mismatch: {}, {}",
buf.len(),
Self::SIZE
);
buf.chunks_exact_mut(XmssSig::<P>::SIZE)
.zip(self.0.iter())
.for_each(|(buf, sig)| sig.write_to(buf));
}
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![0u8; Self::SIZE];
self.write_to(&mut buf);
buf
}
}
impl<P: HypertreeParams> TryFrom<&[u8]> for HypertreeSig<P> {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != Self::SIZE {
return Err(());
}
let sig = value
.chunks(XmssSig::<P>::SIZE)
.map(|c| XmssSig::try_from(c).unwrap())
.collect();
Ok(HypertreeSig(sig))
}
}
pub trait HypertreeParams: XmssParams + Sized {
type D: ArraySize + Debug + Eq;
type H: ArraySize; // HPrime * D
fn ht_sign(
m: &Array<u8, Self::N>,
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
mut idx_tree: u64,
mut idx_leaf: u32,
) -> HypertreeSig<Self> {
let mut adrs = WotsHash::default();
// Currently no parameter set supports more than 2^64 trees
// So tree_adrs_high is always unset
adrs.tree_adrs_low.set(idx_tree);
// Pre-allocate the array - Option should have no overhead after optimization
let mut sig = Array::<_, Self::D>::default();
sig[0] = Some(Self::xmss_sign(m, sk_seed, pk_seed, idx_leaf, &adrs));
let mut root =
Self::xmss_pk_from_sig(idx_leaf, sig[0].as_ref().unwrap(), m, pk_seed, &adrs);
for j in 1..Self::D::U32 {
// H' least significant bits of idx_leaf. H' is always less than 32 in FIPS-205 parameter sets
idx_leaf = (idx_tree & ((1 << Self::HPrime::U32) - 1))
.try_into()
.expect("H' is less than 32");
idx_tree >>= Self::HPrime::U64;
adrs.layer_adrs.set(j);
adrs.tree_adrs_low.set(idx_tree);
sig[j as usize] = Some(Self::xmss_sign(&root, sk_seed, pk_seed, idx_leaf, &adrs));
if j != Self::D::U32 - 1 {
root = Self::xmss_pk_from_sig(
idx_leaf,
sig[j as usize].as_ref().unwrap(),
&root,
pk_seed,
&adrs,
);
}
}
// TODO: Validate that these clones get optimized away
HypertreeSig(sig.iter().cloned().map(Option::unwrap).collect())
}
fn ht_verify(
m: &Array<u8, Self::N>,
sig: &HypertreeSig<Self>,
pk_seed: &PkSeed<Self::N>,
mut idx_tree: u64,
mut idx_leaf: u32,
pk_root: &Array<u8, Self::N>,
) -> bool {
let mut adrs = WotsHash::default();
adrs.tree_adrs_low.set(idx_tree);
let mut root = Self::xmss_pk_from_sig(idx_leaf, &sig.0[0], m, pk_seed, &adrs);
for j in 1..Self::D::U32 {
// H' least significant bits of idx_leaf. H' is always less than 32 in FIPS-205 parameter sets
idx_leaf = (idx_tree & ((1 << Self::HPrime::U32) - 1))
.try_into()
.expect("H' is less than 32");
idx_tree >>= Self::HPrime::U64;
adrs.layer_adrs.set(j);
adrs.tree_adrs_low.set(idx_tree);
root = Self::xmss_pk_from_sig(idx_leaf, &sig.0[j as usize], &root, pk_seed, &adrs);
}
&root == pk_root
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{hashes::Shake128f, util::macros::test_parameter_sets, PkSeed};
use hex_literal::hex;
use hybrid_array::Array;
use rand::{thread_rng, Rng};
use sha3::{digest::ExtendableOutput, Shake256};
fn test_ht_sign_verify<HTMode: HypertreeParams>() {
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut m = Array::<u8, HTMode::N>::default();
rng.fill(m.as_mut_slice());
let idx_tree = rng.gen_range(
0..=(1u64
.wrapping_shl(HTMode::H::U32 - HTMode::HPrime::U32)
.wrapping_sub(1)),
);
let idx_leaf = rng.gen_range(0..(1 << (HTMode::HPrime::USIZE)));
let mut adrs = WotsHash::default();
adrs.tree_adrs_low.set(0);
adrs.layer_adrs.set(HTMode::D::U32 - 1);
let pk_root = HTMode::xmss_node(&sk_seed, 0, HTMode::HPrime::U32, &pk_seed, &adrs);
let sig = HTMode::ht_sign(&m, &sk_seed, &pk_seed, idx_tree, idx_leaf);
assert!(HTMode::ht_verify(
&m, &sig, &pk_seed, idx_tree, idx_leaf, &pk_root
));
}
test_parameter_sets!(test_ht_sign_verify);
fn test_ht_sign_verify_fail<HTMode: HypertreeParams>() {
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut m = Array::<u8, HTMode::N>::default();
rng.fill(m.as_mut_slice());
let idx_tree = rng.gen_range(
0..=(1u64
.wrapping_shl(HTMode::H::U32 - HTMode::HPrime::U32)
.wrapping_sub(1)),
);
let idx_leaf = rng.gen_range(0..(1 << (HTMode::HPrime::USIZE)));
let mut adrs = WotsHash::default();
adrs.tree_adrs_low.set(0);
adrs.layer_adrs.set(HTMode::D::U32 - 1);
let pk_root = HTMode::xmss_node(&sk_seed, 0, HTMode::HPrime::U32, &pk_seed, &adrs);
let sig = HTMode::ht_sign(&m, &sk_seed, &pk_seed, idx_tree, idx_leaf);
// Tweak the message to ensure verification fails
m[0] ^= 0xff; // Invert the first byte of the message
// Verification should fail since the message was tweaked
assert!(!HTMode::ht_verify(
&m, &sig, &pk_seed, idx_tree, idx_leaf, &pk_root
));
}
test_parameter_sets!(test_ht_sign_verify_fail);
#[test]
#[cfg(feature = "alloc")]
fn test_ht_sign_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let m = Array([3; 16]);
let sig = <Shake128f as HypertreeParams>::ht_sign(&m, &sk_seed, &pk_seed, 3, 5);
let sig_flattened = sig.to_vec();
// We compare H(sig) rather than the full sig for test case brevity
let mut sig_hash = [0u8; 16];
Shake256::digest_xof(sig_flattened, sig_hash.as_mut_slice());
let expected = hex!("7daa15a56a5b51d42cd0ff6903f10702");
assert_eq!(sig_hash, expected);
}
}
+215
View File
@@ -0,0 +1,215 @@
#![cfg_attr(not(feature = "alloc"), no_std)]
#![doc = include_str!("../README.md")]
#![warn(clippy::pedantic)] // Be pedantic by default
//#![allow(non_snake_case)] // Allow notation matching the spec
#![allow(clippy::module_name_repetitions)] // There are many types of signature and otherwise this gets confusing
#![allow(clippy::similar_names)] // TODO: Consider resolving these
#![allow(clippy::clone_on_copy)] // Be explicit about moving data
#![deny(missing_docs)] // Require all public interfaces to be documented
//! # Usage
//! This crate implements the Stateless Hash-based Digital Signature Algorithm (SLH-DSA) based on the draft
//! standard by NIST in FIPS-205. SLH-DSA (based on the SPHINCS+ submission) is a signature algorithm designed
//! to be resistant to quantum computers.
//!
//! While the API exposed by SLH-DSA is the same as conventional signature schemes, it is important
//! to note that the signatures produced by the algorithm are much larger than classical schemes like EdDSA,
//! ranging from over 7KB for the smallest parameter set to nearly 50KB at the largest
//!
//! This crate currently allocates signatures and intermediate values on the stack, which may cause problems for
//! environments with limited stack space.
//!
//!
//! ```
//! use slh_dsa::*;
//! use signature::*;
//!
//! let mut rng = rand::thread_rng();
//!
//! // Generate a signing key using the SHAKE128f parameter set
//! let sk = SigningKey::<Shake128f>::new(&mut rng);
//!
//! // Generate the corresponding public key
//! let vk = sk.verifying_key();
//!
//! // Serialize the verifying key and distribute
//! let vk_bytes = vk.to_bytes();
//!
//! // Sign a message
//! let message = b"Hello world";
//! let sig = sk.sign_with_rng(&mut rng, message); // .sign() can be used for deterministic signatures
//!
//! // Deserialize a verifying key
//! let vk_deserialized = vk_bytes.try_into().unwrap();
//! assert_eq!(vk, vk_deserialized);
//!
//! assert!(vk_deserialized.verify(message, &sig).is_ok())
//! ```
pub use signature;
mod address;
mod fors;
mod hashes;
mod hypertree;
mod signature_encoding;
mod signing_key;
mod util;
mod verifying_key;
mod wots;
mod xmss;
pub use signature_encoding::*;
pub use signing_key::*;
pub use verifying_key::*;
use fors::ForsParams;
pub use hashes::*;
/// Specific parameters for each of the 12 FIPS parameter sets
#[allow(private_bounds)] // Intentionally un-usable type
pub trait ParameterSet:
ForsParams + SigningKeyLen + VerifyingKeyLen + SignatureLen + PartialEq + Eq
{
/// Human-readable name for parameter set, matching the FIPS-205 designations
const NAME: &'static str;
}
#[cfg(test)]
mod tests {
use super::*;
use rand::Rng;
use signature::*;
fn test_sign_verify<P: ParameterSet>() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<P>::new(&mut rng);
let vk = sk.verifying_key();
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
vk.verify(msg, &sig).unwrap();
}
#[test]
fn test_sign_verify_shake_128f() {
test_sign_verify::<Shake128f>();
}
#[test]
fn test_sign_verify_shake_128s() {
test_sign_verify::<Shake128s>();
}
#[test]
fn test_sign_verify_shake_192f() {
test_sign_verify::<Shake192f>();
}
#[test]
fn test_sign_verify_shake_192s() {
test_sign_verify::<Shake192s>();
}
#[test]
fn test_sign_verify_shake_256f() {
test_sign_verify::<Shake256f>();
}
#[test]
fn test_sign_verify_shake_256s() {
test_sign_verify::<Shake256s>();
}
#[test]
fn test_sign_verify_sha2_128f() {
test_sign_verify::<Sha2_128f>();
}
#[test]
fn test_sign_verify_sha2_128s() {
test_sign_verify::<Sha2_128s>();
}
#[test]
fn test_sign_verify_sha2_192f() {
test_sign_verify::<Sha2_192f>();
}
#[test]
fn test_sign_verify_sha2_192s() {
test_sign_verify::<Sha2_192s>();
}
#[test]
fn test_sign_verify_sha2_256f() {
test_sign_verify::<Sha2_256f>();
}
#[test]
fn test_sign_verify_sha2_256s() {
test_sign_verify::<Sha2_256s>();
}
// Check signature fails on modified message
#[test]
fn test_sign_verify_shake_128f_fail_on_modified_message() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let msg = b"Hello, world!";
let modified_msg = b"Goodbye, world!";
let sig = sk.try_sign(msg).unwrap();
let vk = sk.verifying_key();
assert!(vk.verify(msg, &sig).is_ok());
assert!(vk.verify(modified_msg, &sig).is_err());
}
#[test]
fn test_sign_verify_fail_with_wrong_verifying_key() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let wrong_sk = SigningKey::<Shake128f>::new(&mut rng); // Generate a different signing key
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
let vk = sk.verifying_key();
let wrong_vk = wrong_sk.verifying_key(); // Get the verifying key of the wrong signing key
assert!(vk.verify(msg, &sig).is_ok());
assert!(wrong_vk.verify(msg, &sig).is_err()); // This should fail because the verifying key does not match the signing key used
}
#[test]
fn test_sign_verify_fail_on_modified_signature() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let msg = b"Hello, world!";
let mut sig_bytes = sk.try_sign(msg).unwrap().to_bytes();
// Randomly modify one byte in the signature
let sig_len = sig_bytes.len();
let random_byte_index = rng.gen_range(0..sig_len);
sig_bytes[random_byte_index] ^= 0xff; // Invert one byte to ensure it's different
let sig = (&sig_bytes).into();
let vk = sk.verifying_key();
assert!(
vk.verify(msg, &sig).is_err(),
"Verification should fail with a modified signature"
);
}
#[test]
fn test_successive_signatures_not_equal() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let msg = b"Hello, world!";
let sig1 = sk.try_sign_with_rng(&mut rng, msg).unwrap();
let sig2 = sk.try_sign_with_rng(&mut rng, msg).unwrap();
assert_ne!(
sig1, sig2,
"Two successive randomized signatures over the same message should not be equal"
);
}
}
+215
View File
@@ -0,0 +1,215 @@
use crate::hashes::{
Sha2_128f, Sha2_128s, Sha2_192f, Sha2_192s, Sha2_256f, Sha2_256s, Shake128f, Shake192f,
Shake192s, Shake256f, Shake256s,
};
use crate::hypertree::HypertreeSig;
use crate::ParameterSet;
use crate::{fors::ForsSignature, Shake128s};
use ::signature::{Error, SignatureEncoding};
use hybrid_array::sizes::{U16224, U17088, U29792, U35664, U49856, U7856};
use hybrid_array::{Array, ArraySize};
use typenum::Unsigned;
#[derive(Debug, Clone, PartialEq, Eq)]
/// A parsed SLH-DSA signature for a given parameter set
///
/// Note that this is a large stack-allocated value and may overflow the stack on
/// small devices. The stack representation consumes `P::SigLen` bytes
///
/// There are no invariants maintained by this struct - every field is a hash value
pub struct Signature<P: ParameterSet> {
pub(crate) randomizer: Array<u8, P::N>,
pub(crate) fors_sig: ForsSignature<P>,
pub(crate) ht_sig: HypertreeSig<P>,
}
impl<P: ParameterSet> Signature<P> {
#[cfg(feature = "alloc")]
/// Serialize the signature to a `Vec<u8>` of length `P::SigLen`.
pub fn to_vec(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(P::SigLen::USIZE);
bytes.extend_from_slice(&self.randomizer);
bytes.extend_from_slice(&self.fors_sig.to_vec());
bytes.extend_from_slice(&self.ht_sig.to_vec());
debug_assert!(bytes.len() == P::SigLen::USIZE);
bytes
}
/// Serialize the signature to a new stack-allocated array
/// This clones the underlying fields
pub fn to_bytes(&self) -> Array<u8, P::SigLen> {
let mut bytes = Array::<u8, P::SigLen>::default();
let r_size = P::N::USIZE;
let fors_size = ForsSignature::<P>::SIZE;
bytes[..r_size].copy_from_slice(&self.randomizer);
self.fors_sig
.write_to(&mut bytes[r_size..r_size + fors_size]);
self.ht_sig.write_to(&mut bytes[r_size + fors_size..]);
bytes
}
}
impl<P: ParameterSet> TryFrom<&[u8]> for Signature<P> {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != P::SigLen::USIZE {
return Err(Error::new()); // TODO: Real error
}
let (rand_bytes, rest) = bytes.split_at(P::N::USIZE);
let randomizer = Array::clone_from_slice(rand_bytes);
let (fors_bytes, ht_bytes) = rest.split_at(ForsSignature::<P>::SIZE);
let fors_sig = ForsSignature::try_from(fors_bytes).map_err(|()| Error::new())?;
let ht_sig = HypertreeSig::try_from(ht_bytes).map_err(|()| Error::new())?;
Ok(Signature {
randomizer,
fors_sig,
ht_sig,
})
}
}
#[cfg(feature = "alloc")]
impl<P: ParameterSet> From<&Signature<P>> for Vec<u8> {
fn from(sig: &Signature<P>) -> Vec<u8> {
sig.to_vec()
}
}
/// A trait specifying the length of a serialized signature for a given parameter set
pub trait SignatureLen {
/// The length of the signature in bytes
type SigLen: ArraySize;
}
impl<P: ParameterSet> SignatureEncoding for Signature<P> {
type Repr = Array<u8, P::SigLen>;
fn encoded_len(&self) -> usize {
P::SigLen::USIZE
}
}
impl<P: ParameterSet> From<Signature<P>> for Array<u8, P::SigLen> {
fn from(sig: Signature<P>) -> Array<u8, P::SigLen> {
sig.to_bytes()
}
}
impl<P: ParameterSet> From<&Array<u8, P::SigLen>> for Signature<P> {
fn from(bytes: &Array<u8, P::SigLen>) -> Signature<P> {
Signature::try_from(bytes.as_slice()).unwrap()
}
}
impl SignatureLen for Shake128s {
type SigLen = U7856;
}
impl SignatureLen for Shake128f {
type SigLen = U17088;
}
impl SignatureLen for Shake192s {
type SigLen = U16224;
}
impl SignatureLen for Shake192f {
type SigLen = U35664;
}
impl SignatureLen for Shake256s {
type SigLen = U29792;
}
impl SignatureLen for Shake256f {
type SigLen = U49856;
}
impl SignatureLen for Sha2_128s {
type SigLen = U7856;
}
impl SignatureLen for Sha2_128f {
type SigLen = U17088;
}
impl SignatureLen for Sha2_192s {
type SigLen = U16224;
}
impl SignatureLen for Sha2_192f {
type SigLen = U35664;
}
impl SignatureLen for Sha2_256s {
type SigLen = U29792;
}
impl SignatureLen for Sha2_256f {
type SigLen = U49856;
}
#[cfg(test)]
mod tests {
use crate::signature_encoding::Signature;
use crate::util::macros::test_parameter_sets;
use crate::SigningKey;
use crate::{hashes::*, ParameterSet};
use hybrid_array::Array;
use signature::{SignatureEncoding, Signer};
fn test_serialize_deserialize<P: ParameterSet>() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<P>::new(&mut rng);
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
let sig_bytes = sig.to_bytes();
assert_eq!(
sig.encoded_len(),
sig_bytes.len(),
"sig.encoded_len() should equal encoded byte length"
);
let sig2 = Signature::<P>::try_from(sig_bytes.as_slice()).unwrap();
assert_eq!(sig, sig2);
}
test_parameter_sets!(test_serialize_deserialize);
#[cfg(feature = "alloc")]
fn test_serialize_deserialize_vec<P: ParameterSet>() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<P>::new(&mut rng);
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
let sig_vec: Vec<u8> = (&sig).into();
assert_eq!(
sig.encoded_len(),
sig_vec.len(),
"sig.encoded_len() should equal encoded byte length"
);
let sig2 = Signature::<P>::try_from(sig_vec.as_slice()).unwrap();
assert_eq!(sig, sig2);
}
test_parameter_sets!(test_serialize_deserialize_vec);
#[test]
fn test_deserialize_fail_on_incorrect_length() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let msg = b"Hello, world!";
let sig = sk.try_sign(msg).unwrap();
let sig_bytes: Array<u8, _> = sig.into();
// Modify the signature bytes to an incorrect length
let incorrect_sig_bytes = &sig_bytes[..sig_bytes.len() - 1];
assert!(
Signature::<Shake128f>::try_from(incorrect_sig_bytes).is_err(),
"Deserialization should fail on incorrect length"
);
}
}
+176
View File
@@ -0,0 +1,176 @@
use crate::address::{ForsTree, WotsHash};
use crate::signature_encoding::Signature;
use crate::util::split_digest;
use crate::verifying_key::VerifyingKey;
use crate::{ParameterSet, PkSeed, Sha2L1, Sha2L35, Shake, VerifyingKeyLen};
use ::signature::{Error, KeypairRef, RandomizedSigner, Signer};
use hybrid_array::{Array, ArraySize};
use typenum::{Unsigned, U, U16, U24, U32};
// NewTypes for ensuring hash argument order correctness
#[derive(Clone)]
pub(crate) struct SkSeed<N: ArraySize>(pub(crate) Array<u8, N>);
impl<N: ArraySize> AsRef<[u8]> for SkSeed<N> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<N: ArraySize> From<&[u8]> for SkSeed<N> {
fn from(slice: &[u8]) -> Self {
Self(Array::clone_from_slice(slice))
}
}
impl<N: ArraySize> SkPrf<N> {
pub(crate) fn new(rng: &mut impl rand_core::CryptoRngCore) -> Self {
let mut bytes = Array::<u8, N>::default();
rng.fill_bytes(bytes.as_mut_slice());
Self(bytes)
}
}
#[derive(Clone)]
pub(crate) struct SkPrf<N: ArraySize>(pub(crate) Array<u8, N>);
impl<N: ArraySize> AsRef<[u8]> for SkPrf<N> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<N: ArraySize> From<&[u8]> for SkPrf<N> {
fn from(slice: &[u8]) -> Self {
Self(Array::clone_from_slice(slice))
}
}
impl<N: ArraySize> SkSeed<N> {
pub(crate) fn new(rng: &mut impl rand_core::CryptoRngCore) -> Self {
let mut bytes = Array::<u8, N>::default();
rng.fill_bytes(bytes.as_mut_slice());
Self(bytes)
}
}
/// A `SigningKey` allows signing messages with a fixed parameter set
#[derive(Clone)]
pub struct SigningKey<P: ParameterSet> {
pub(crate) sk_seed: SkSeed<P::N>,
pub(crate) sk_prf: SkPrf<P::N>,
pub(crate) verifying_key: VerifyingKey<P>,
}
/// A trait specifying the length of a serialized signing key for a given parameter set
pub trait SigningKeyLen: VerifyingKeyLen {
/// The length of the serialized signing key in bytes
type SkLen: ArraySize;
}
impl<P: ParameterSet> SigningKey<P> {
/// Create a new `SigningKey` from a cryptographic random number generator
pub fn new(rng: &mut impl rand_core::CryptoRngCore) -> Self {
let sk_seed = SkSeed::new(rng);
let sk_prf = SkPrf::new(rng);
let pk_seed = PkSeed::new(rng);
let mut adrs = WotsHash::default();
adrs.layer_adrs.set(P::D::U32 - 1);
let pk_root = P::xmss_node(&sk_seed, 0, P::HPrime::U32, &pk_seed, &adrs);
let verifying_key = VerifyingKey { pk_seed, pk_root };
SigningKey {
sk_seed,
sk_prf,
verifying_key,
}
}
/// Serialize the signing key to a new stack-allocated array
///
/// This clones the underlying fields
pub fn to_bytes(&self) -> Array<u8, P::SkLen> {
let mut bytes = Array::<u8, P::SkLen>::default();
bytes[..P::N::USIZE].copy_from_slice(&self.sk_seed.0);
bytes[P::N::USIZE..2 * P::N::USIZE].copy_from_slice(&self.sk_prf.0);
bytes[2 * P::N::USIZE..].copy_from_slice(&self.verifying_key.to_bytes());
bytes
}
/// Serialize the signing key to a new heap-allocated vector
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8>
where
P: VerifyingKeyLen,
{
self.to_bytes().to_vec()
}
}
fn sign_with_opt_rng<P: ParameterSet>(
sk: &SigningKey<P>,
msg: &[u8],
opt_rand: &Array<u8, P::N>,
) -> Signature<P> {
let sk_seed = &sk.sk_seed;
let pk_seed = &sk.verifying_key.pk_seed;
let randomizer = P::prf_msg(&sk.sk_prf, opt_rand, msg);
let digest = P::h_msg(&randomizer, pk_seed, &sk.verifying_key.pk_root, msg);
let (md, idx_tree, idx_leaf) = split_digest::<P>(&digest);
let adrs = ForsTree::new(idx_tree, idx_leaf);
let fors_sig = P::fors_sign(md, sk_seed, pk_seed, &adrs);
let fors_pk = P::fors_pk_from_sig(&fors_sig, md, pk_seed, &adrs);
let ht_sig = P::ht_sign(&fors_pk, sk_seed, pk_seed, idx_tree, idx_leaf);
Signature {
randomizer,
fors_sig,
ht_sig,
}
}
impl<P: ParameterSet> Signer<Signature<P>> for SigningKey<P> {
fn try_sign(&self, msg: &[u8]) -> Result<Signature<P>, Error> {
Ok(sign_with_opt_rng(self, msg, &self.verifying_key.pk_seed.0))
}
}
impl<P: ParameterSet> RandomizedSigner<Signature<P>> for SigningKey<P> {
fn try_sign_with_rng(
&self,
rng: &mut impl signature::rand_core::CryptoRngCore,
msg: &[u8],
) -> Result<Signature<P>, signature::Error> {
let mut randomizer = Array::<u8, P::N>::default();
rng.fill_bytes(randomizer.as_mut_slice());
Ok(sign_with_opt_rng(self, msg, &randomizer))
}
}
impl<P: ParameterSet> AsRef<VerifyingKey<P>> for SigningKey<P> {
fn as_ref(&self) -> &VerifyingKey<P> {
&self.verifying_key
}
}
impl<P: ParameterSet> KeypairRef for SigningKey<P> {
type VerifyingKey = VerifyingKey<P>;
}
impl<M> SigningKeyLen for Sha2L1<U16, M> {
type SkLen = U<{ 4 * 16 }>;
}
impl<M> SigningKeyLen for Sha2L35<U24, M> {
type SkLen = U<{ 4 * 24 }>;
}
impl<M> SigningKeyLen for Sha2L35<U32, M> {
type SkLen = U<{ 4 * 32 }>;
}
impl<M> SigningKeyLen for Shake<U16, M> {
type SkLen = U<{ 4 * 16 }>;
}
impl<M> SigningKeyLen for Shake<U24, M> {
type SkLen = U<{ 4 * 24 }>;
}
impl<M> SigningKeyLen for Shake<U32, M> {
type SkLen = U<{ 4 * 32 }>;
}
+155
View File
@@ -0,0 +1,155 @@
use crate::fors::ForsParams;
use hybrid_array::{typenum::Unsigned, Array, ArraySize};
// Algorithm 3
pub fn base_2b<OutLen: ArraySize, B: Unsigned>(x: &[u8]) -> Array<u16, OutLen> {
debug_assert!(x.len() >= (OutLen::USIZE * B::USIZE + 7) / 8);
debug_assert!(B::USIZE <= 16);
let mut bits = 0usize;
let mut i = 0;
let mut total = 0usize;
Array::<u16, OutLen>::from_fn(|_: usize| {
while bits < B::USIZE {
total = (total << 8) + x[i] as usize;
bits += 8;
i += 1;
}
bits -= B::USIZE;
let out = (total >> bits) & ((1 << B::U8) - 1);
total &= (1 << bits) - 1; // Deviation from spec pseudocode - clear used component to prevent usize overflow
out.try_into().expect("B is less than 16")
})
}
/// Separates the digest into the FORS message, the Xmss tree index, and the Xmss leaf index.
pub fn split_digest<P: ForsParams>(digest: &Array<u8, P::M>) -> (&Array<u8, P::MD>, u64, u32) {
let m = Array::from_slice(&digest[..P::MD::USIZE]);
let idx_tree_size = (P::H::USIZE - P::HPrime::USIZE).div_ceil(8);
let idx_leaf_size = P::HPrime::USIZE.div_ceil(8);
let mut idx_tree_bytes = [0u8; 8];
let mut idx_leaf_bytes = [0u8; 4];
idx_tree_bytes[8 - idx_tree_size..]
.copy_from_slice(&digest[P::MD::USIZE..P::MD::USIZE + idx_tree_size]);
idx_leaf_bytes[4 - idx_leaf_size..].copy_from_slice(
&digest[P::MD::USIZE + idx_tree_size..P::MD::USIZE + idx_tree_size + idx_leaf_size],
);
// For 256-bit parameters sets, Self::H::U32 - Self::HPrime::U32 = 64
let mask: u64 = 1u64
.checked_shl(P::H::U32 - P::HPrime::U32)
.unwrap_or(0)
.wrapping_sub(1);
let idx_tree = u64::from_be_bytes(idx_tree_bytes) & mask;
let idx_leaf = u32::from_be_bytes(idx_leaf_bytes) & ((1 << P::HPrime::USIZE) - 1);
(m, idx_tree, idx_leaf)
}
#[cfg(test)]
pub mod macros {
#[macro_export]
macro_rules! gen_test {
($name:ident, $t:ty) => {
paste::paste! {
#[test]
fn [<$name _ $t:lower>]() {
$name::<$t>()
}
}
};
}
macro_rules! test_parameter_sets {
($name:ident) => {
#[allow(unused_imports)]
use crate::hashes::*;
crate::gen_test!($name, Shake128f);
crate::gen_test!($name, Shake128s);
crate::gen_test!($name, Shake192f);
crate::gen_test!($name, Shake192s);
crate::gen_test!($name, Shake256f);
crate::gen_test!($name, Shake256s);
crate::gen_test!($name, Sha2_128f);
crate::gen_test!($name, Sha2_128s);
crate::gen_test!($name, Sha2_192f);
crate::gen_test!($name, Sha2_192s);
crate::gen_test!($name, Sha2_256f);
crate::gen_test!($name, Sha2_256s);
};
}
pub(crate) use test_parameter_sets;
}
#[cfg(test)]
mod tests {
use super::*;
use num_bigint::BigUint;
use proptest::prelude::*;
use typenum::U;
fn test_base_2b<OutLen: ArraySize, B: Unsigned>(x: &[u8]) {
if x.len() < (OutLen::USIZE * B::USIZE + 7) / 8 {
return; // TODO: enforce this at the prop level
}
let a = base_2b::<OutLen, B>(x);
let mut b = BigUint::from_bytes_be(&x[..((OutLen::USIZE * B::USIZE + 7) / 8)]);
if (B::USIZE * OutLen::USIZE) % 8 != 0 {
// Clear lower bits of b
b >>= 8 - ((B::USIZE * OutLen::USIZE) % 8);
}
let c: BigUint = a.iter().fold(0u8.into(), |acc, x| (acc << B::U8) + x);
assert_eq!(b, c);
}
proptest! {
// These are all the OutLen, B combinations used in the FIPS spec
// TODO - explicitly tie to individual parameter sets
#[test]
fn test_base_2b_32_4(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<32>, U<4>>(&x);
}
#[test]
fn test_base_2b_64_4(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<64>, U<4>>(&x);
}
#[test]
fn test_base_2b_14_12(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<14>, U<12>>(&x);
}
#[test]
fn test_base_2b_33_6(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<33>, U<6>>(&x);
}
#[test]
fn test_base_2b_17_14(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<17>, U<14>>(&x);
}
#[test]
fn test_base_2b_33_8(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<33>, U<8>>(&x);
}
#[test]
fn test_base_2b_22_14(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<22>, U<14>>(&x);
}
#[test]
fn test_base_2b_35_9(x in prop::collection::vec(any::<u8>(), 0..100)){
test_base_2b::<U<35>, U<9>>(&x);
}
}
}
+156
View File
@@ -0,0 +1,156 @@
use crate::address::ForsTree;
use crate::signature_encoding::Signature;
use crate::util::split_digest;
use crate::ParameterSet;
use crate::Sha2L1;
use crate::Sha2L35;
use crate::Shake;
use ::signature::{Error, Verifier};
use hybrid_array::{Array, ArraySize};
use typenum::{Unsigned, U, U16, U24, U32};
/// A trait specifying the length of a serialized verifying key for a given parameter set
pub trait VerifyingKeyLen {
/// The length of the serialized verifying key in bytes
type VkLen: ArraySize;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct PkSeed<N: ArraySize>(pub(crate) Array<u8, N>);
impl<N: ArraySize> AsRef<[u8]> for PkSeed<N> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl<N: ArraySize> From<&[u8]> for PkSeed<N> {
fn from(slice: &[u8]) -> Self {
Self(Array::clone_from_slice(slice))
}
}
impl<N: ArraySize> PkSeed<N> {
pub(crate) fn new(rng: &mut impl rand_core::RngCore) -> Self {
let mut bytes = Array::<u8, N>::default();
rng.fill_bytes(bytes.as_mut_slice());
Self(bytes)
}
}
/// A `VerifyingKey` is an SLH-DSA public key, allowing
/// verification of signatures created with the corresponding
/// `SigningKey`
#[derive(Debug, PartialEq, Eq)]
pub struct VerifyingKey<P: ParameterSet> {
pub(crate) pk_seed: PkSeed<P::N>,
pub(crate) pk_root: Array<u8, P::N>,
}
impl<P: ParameterSet + VerifyingKeyLen> VerifyingKey<P> {
/// Serialize the verifying key to a new stack-allocated array
///
/// This clones the underlying fields
pub fn to_bytes(&self) -> Array<u8, P::VkLen> {
let mut bytes = Array::<u8, P::VkLen>::default();
debug_assert!(P::N::USIZE * 2 == P::VkLen::USIZE);
bytes[..P::N::USIZE].copy_from_slice(&self.pk_seed.0);
bytes[P::N::USIZE..].copy_from_slice(&self.pk_root);
bytes
}
/// Serialize the verifying key to a new heap-allocated vector
#[cfg(feature = "alloc")]
pub fn to_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl<P: ParameterSet> Clone for VerifyingKey<P> {
fn clone(&self) -> Self {
VerifyingKey {
pk_seed: self.pk_seed.clone(),
pk_root: self.pk_root.clone(),
}
}
}
impl<P: ParameterSet> From<&VerifyingKey<P>> for Array<u8, P::VkLen> {
fn from(vk: &VerifyingKey<P>) -> Array<u8, P::VkLen> {
vk.to_bytes()
}
}
impl<P: ParameterSet> From<Array<u8, P::VkLen>> for VerifyingKey<P> {
fn from(bytes: Array<u8, P::VkLen>) -> VerifyingKey<P> {
debug_assert!(P::VkLen::USIZE == 2 * P::N::USIZE);
let pk_seed = PkSeed(Array::clone_from_slice(&bytes[..P::N::USIZE]));
let pk_root = Array::clone_from_slice(&bytes[P::N::USIZE..]);
VerifyingKey { pk_seed, pk_root }
}
}
impl<P: ParameterSet> TryFrom<&[u8]> for VerifyingKey<P> {
type Error = Error;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != P::N::USIZE * 2 {
return Err(Error::new());
}
let pk_seed = PkSeed(Array::clone_from_slice(&bytes[..P::N::USIZE]));
let pk_root = Array::clone_from_slice(&bytes[P::N::USIZE..]);
Ok(VerifyingKey { pk_seed, pk_root })
}
}
impl<P: ParameterSet> Verifier<Signature<P>> for VerifyingKey<P> {
fn verify(&self, msg: &[u8], signature: &Signature<P>) -> Result<(), Error> {
let pk_seed = &self.pk_seed;
let randomizer = &signature.randomizer;
let fors_sig = &signature.fors_sig;
let ht_sig = &signature.ht_sig;
let digest = P::h_msg(randomizer, pk_seed, &self.pk_root, msg);
let (md, idx_tree, idx_leaf) = split_digest::<P>(&digest);
let adrs = ForsTree::new(idx_tree, idx_leaf);
let fors_pk = P::fors_pk_from_sig(fors_sig, md, pk_seed, &adrs);
P::ht_verify(&fors_pk, ht_sig, pk_seed, idx_tree, idx_leaf, &self.pk_root)
.then_some(())
.ok_or(Error::new())
}
}
impl<M> VerifyingKeyLen for Sha2L1<U16, M> {
type VkLen = U<32>;
}
impl<M> VerifyingKeyLen for Sha2L35<U24, M> {
type VkLen = U<48>;
}
impl<M> VerifyingKeyLen for Sha2L35<U32, M> {
type VkLen = U<64>;
}
impl<M> VerifyingKeyLen for Shake<U16, M> {
type VkLen = U<32>;
}
impl<M> VerifyingKeyLen for Shake<U24, M> {
type VkLen = U<48>;
}
impl<M> VerifyingKeyLen for Shake<U32, M> {
type VkLen = U<64>;
}
#[cfg(test)]
mod tests {
use crate::*;
use hybrid_array::Array;
use signature::*;
#[test]
fn test_vk_serialize_deserialize() {
let mut rng = rand::thread_rng();
let sk = SigningKey::<Shake128f>::new(&mut rng);
let vk = sk.verifying_key();
let vk_bytes: Array<u8, _> = (&vk).into();
let vk2 = VerifyingKey::<Shake128f>::try_from(vk_bytes.as_slice()).unwrap();
assert_eq!(vk, vk2);
}
}
+263
View File
@@ -0,0 +1,263 @@
use hybrid_array::{Array, ArraySize};
use typenum::generic_const_mappings::U;
use typenum::Unsigned;
use crate::hashes::HashSuite;
use crate::util::base_2b;
use crate::{address, PkSeed, SkSeed};
use core::fmt::Debug;
// WOTS+ in general is parameterized on these values
// But the FIPS standard uses the same values for all parameter sets
// So we make these global consts for simplicity
const LOG_W: usize = 4;
const W: u32 = 16;
const CK_LEN: usize = 3; // Length of a checksum in chunks
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WotsSig<P: WotsParams>(Array<Array<u8, P::N>, P::WotsSigLen>);
impl<P: WotsParams> WotsSig<P> {
pub const SIZE: usize = P::N::USIZE * P::WotsSigLen::USIZE;
pub fn write_to(&self, buf: &mut [u8]) {
debug_assert!(buf.len() == Self::SIZE, "WOTS+ serialize length mismatch");
buf.chunks_exact_mut(P::N::USIZE)
.zip(self.0.iter())
.for_each(|(buf, sig)| buf.copy_from_slice(sig.as_slice()));
}
#[cfg(feature = "alloc")]
#[cfg(test)]
pub fn to_vec(&self) -> Vec<u8> {
let mut vec = vec![0u8; Self::SIZE];
self.write_to(&mut vec);
vec
}
}
impl<P: WotsParams> TryFrom<&[u8]> for WotsSig<P> {
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != Self::SIZE {
return Err(());
}
let mut sig = Array::<Array<u8, P::N>, P::WotsSigLen>::default();
for i in 0..P::WotsSigLen::USIZE {
sig[i].copy_from_slice(&value[i * P::N::USIZE..(i + 1) * P::N::USIZE]);
}
Ok(WotsSig(sig))
}
}
pub(crate) trait WotsParams: HashSuite {
type WotsMsgLen: ArraySize; // Number of chunks in a WOTS message. Must equal 2 * Self::N
type WotsSigLen: ArraySize + Debug + Eq; // Number of chunks in a WOTS signature. Must equal WotsSigLen + CK_LEN;
/// Algorithm 4
fn wots_chain(
x: &Array<u8, Self::N>,
i: u32,
s: u32,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> Array<u8, Self::N> {
debug_assert!(i + s < 1 << LOG_W, "Invalid wots_chain index");
let mut tmp = x.clone(); //TODO: no clone
let mut adrs = adrs.clone(); // TODO: no clone
for j in i..(i + s) {
adrs.hash_adrs.set(j);
tmp = Self::f(pk_seed, &adrs, &tmp); // TODO: overwrite existing buffer
}
tmp
}
/// Algorithm 5
fn wots_pk_gen(
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> Array<u8, Self::N> {
let mut adrs = adrs.clone();
let mut sk_adrs = adrs.prf_adrs();
let tmp = Array::<Array<u8, Self::N>, Self::WotsSigLen>::from_fn(|i: usize| {
let i: u32 = i.try_into().expect("i is less than 2^32");
sk_adrs.chain_adrs.set(i);
adrs.chain_adrs.set(i);
let sk = Self::prf_sk(pk_seed, sk_seed, &sk_adrs);
Self::wots_chain(&sk, 0, (1 << LOG_W) - 1, pk_seed, &adrs)
});
let pk_adrs = adrs.pk_adrs();
Self::t(pk_seed, &pk_adrs, &tmp)
}
// Algorithm 6
fn wots_sign(
m: &Array<u8, Self::N>,
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> WotsSig<Self> {
let msg = base_2b::<Self::WotsMsgLen, U<LOG_W>>(m.as_slice());
let csum = msg.iter().map(|&x| (1 << LOG_W) - 1 - x).sum::<u16>() << 4; // Algorithm 6 Line 9
let csum_bytes = csum.to_be_bytes();
let csum_chunks = base_2b::<U<CK_LEN>, U<LOG_W>>(&csum_bytes);
let mut msg_csum = msg.iter().chain(csum_chunks.iter());
let mut adrs = adrs.clone();
let mut sk_adrs = adrs.prf_adrs();
let sig = Array::<Array<u8, Self::N>, Self::WotsSigLen>::from_fn(|i: usize| {
let i: u32 = i.try_into().expect("i is less than 2^32");
sk_adrs.chain_adrs.set(i);
adrs.chain_adrs.set(i);
let sk = Self::prf_sk(pk_seed, sk_seed, &sk_adrs);
Self::wots_chain(&sk, 0, u32::from(*msg_csum.next().unwrap()), pk_seed, &adrs)
});
WotsSig(sig)
}
fn wots_pk_from_sig(
sig: &WotsSig<Self>,
m: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> Array<u8, Self::N> {
let msg = base_2b::<Self::WotsMsgLen, U<LOG_W>>(m.as_slice());
let csum = msg.iter().map(|&x| (1 << LOG_W) - 1 - x).sum::<u16>() << 4; // TODO: remove magic 4
let csum_bytes = csum.to_be_bytes();
let csum_chunks = base_2b::<U<CK_LEN>, U<LOG_W>>(&csum_bytes);
let mut msg_csum = msg.iter().chain(csum_chunks.iter());
let mut adrs = adrs.clone();
let tmp = Array::<Array<u8, Self::N>, Self::WotsSigLen>::from_fn(|i: usize| {
adrs.chain_adrs
.set(i.try_into().expect("i is less than 2^32"));
let msg_i = u32::from(*msg_csum.next().unwrap());
Self::wots_chain(&sig.0[i], msg_i, W - 1 - msg_i, pk_seed, &adrs)
});
Self::t(pk_seed, &adrs.pk_adrs(), &tmp)
}
}
#[cfg(test)]
mod tests {
use crate::{util::macros::test_parameter_sets, PkSeed, SkSeed};
use hex_literal::hex;
use hybrid_array::Array;
use rand::{thread_rng, RngCore};
use crate::{address::WotsHash, hashes::Shake128f};
use super::WotsParams;
fn test_sign_verify<Wots: WotsParams>() {
// Generate random sk_seed, pk_seed, message, address
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, _>::default();
rng.fill_bytes(msg.as_mut_slice());
let adrs = &WotsHash::default();
let pk = Wots::wots_pk_gen(&sk_seed, &pk_seed, adrs);
let sig = Wots::wots_sign(&msg, &sk_seed, &pk_seed, adrs);
let pk_recovered = Wots::wots_pk_from_sig(&sig, &msg, &pk_seed, adrs);
assert_eq!(pk, pk_recovered);
}
test_parameter_sets!(test_sign_verify);
fn test_sign_verify_fail<Wots: WotsParams>() {
// Generate random sk_seed, pk_seed, message
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, _>::default();
rng.fill_bytes(msg.as_mut_slice());
let adrs = &WotsHash::default();
// Generate public key
let pk = Wots::wots_pk_gen(&sk_seed, &pk_seed, adrs);
// Sign the message
let sig = Wots::wots_sign(&msg, &sk_seed, &pk_seed, adrs);
// Tweak the message
msg[0] ^= 0xff; // Invert the first byte of the message
// Attempt to recover the public key from the tweaked message and signature
let pk_recovered = Wots::wots_pk_from_sig(&sig, &msg, &pk_seed, adrs);
// Check that the recovered public key does not match the original public key
assert_ne!(
pk, pk_recovered,
"Signature verification should fail with a modified message"
);
}
test_parameter_sets!(test_sign_verify_fail);
#[test]
fn test_pk_gen_shake128f_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let adrs = WotsHash::default();
// Generated by https://github.com/mjosaarinen/slh-dsa-py
let expected = Array(hex!("98b63dd1574484876b1f8a1120421eac"));
let result = Shake128f::wots_pk_gen(&sk_seed, &pk_seed, &adrs);
assert_eq!(result, expected);
}
#[test]
#[cfg(feature = "alloc")]
fn test_sign_shake128f_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let adrs = &WotsHash::default();
let msg = Array([3; 16]);
let expected = &hex!(
"f7bcb9575590faae2e6a8ae33149082d2ec777cff4051f43177ef44bcbd2c18d
a94146c50037c914461dd6ed720192b059bd2be6ed8d8cf26e4e9d68fbf9ded1
6c334bed21677c6a3679f17a8425de40431b4317326c5d825d931b4a54a1b81f
e7ad259086ea665109a7eca79f03e3619d99af5d0419fece8300973f29467f28
d2b18639eeaa826488f6c785d492703463e80f8b088e64de9ca3b373cead611f
d356bf6c22f70f98f229174a9ac815342f0439eb289a78f49f47aa8c3f272a15
f5f0f5020b5d71981254daa9e1f01a90248935c1c67ad1cf71d9224184820cf9
ece9b737ec986c86ba0a9431ff8485c274140bebc9d856316d49128eb075f81a
c00d32b9f949940f2dd684a2e615e16b47093eb49e3bc9d77e69c7944d7063c6
f8b4b5aa46fe759999fa2892ce4c7881b80f38d684427a0b77f3ad43377833d2
d94c600b340ea408a0ad7c32c409bdb4ebaade3b1dda4ac8584acba979c845a9
b0ddfc69ea22ffb415745b779b45d7af00ca9fde87e5d59385d7b5cedec6e30f
3346f573f59a00af993a2ec314ed951e3a8c00f69364a82fa34d14933fe3cdb7
bd5e5d511297695bad5cda22daea8d39f61d4ed34412acd1f5399a54953ae04b
09828f90877ad7f01605631ace0a4e7c773cc887e2d0fa0bd3d6db811794df3a
a8721c308482ccb511c9133311653ce8f9c2336e2980c2ab554c41bad436c0c7
1c394d3f7eafcea2806c153113d6291a912c0e73e44197763b9ead341c298585
bc6e16d8458fc1917ff4ac57de461ee1"
);
let result = Shake128f::wots_sign(&msg, &sk_seed, &pk_seed, adrs);
assert_eq!(result.to_vec(), expected.as_slice());
}
}
+260
View File
@@ -0,0 +1,260 @@
use hybrid_array::{Array, ArraySize};
use typenum::Unsigned;
use crate::wots::WotsSig;
use crate::{address, wots::WotsParams};
use crate::{PkSeed, SkSeed};
use core::fmt::Debug;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct XmssSig<P: XmssParams> {
pub(crate) sig: WotsSig<P>,
pub(crate) auth: Array<Array<u8, P::N>, P::HPrime>,
}
impl<P: XmssParams> XmssSig<P> {
pub const SIZE: usize = WotsSig::<P>::SIZE + P::HPrime::USIZE * P::N::USIZE;
pub fn write_to(&self, buf: &mut [u8]) {
debug_assert!(buf.len() == Self::SIZE, "Xmss serialize length mismatch");
let (wots, auth) = buf.split_at_mut(WotsSig::<P>::SIZE);
self.sig.write_to(wots);
auth.chunks_exact_mut(P::N::USIZE)
.zip(self.auth.iter())
.for_each(|(buf, auth)| buf.copy_from_slice(auth.as_slice()));
}
#[cfg(feature = "alloc")]
#[cfg(test)]
pub fn to_vec(&self) -> Vec<u8> {
let mut buf = vec![0u8; Self::SIZE];
self.write_to(&mut buf);
buf
}
}
impl<P: XmssParams> TryFrom<&[u8]> for XmssSig<P> {
// TODO: Real error
type Error = ();
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
if value.len() != Self::SIZE {
return Err(());
}
let sig = WotsSig::<P>::try_from(&value[..WotsSig::<P>::SIZE])?;
let mut auth = Array::<Array<u8, P::N>, P::HPrime>::default();
for i in 0..P::HPrime::USIZE {
auth[i].copy_from_slice(
&value[WotsSig::<P>::SIZE + i * P::N::USIZE
..WotsSig::<P>::SIZE + (i + 1) * P::N::USIZE],
);
}
Ok(XmssSig { sig, auth })
}
}
pub(crate) trait XmssParams: WotsParams + Sized {
type HPrime: ArraySize + Debug + Eq;
fn xmss_node(
sk_seed: &SkSeed<Self::N>,
node: u32,
height: u32,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> Array<u8, Self::N> {
debug_assert!(height <= Self::HPrime::U32);
debug_assert!(node < (1 << (Self::HPrime::U32 - height)));
if height == 0 {
let mut adrs = adrs.clone();
adrs.key_pair_adrs.set(node);
Self::wots_pk_gen(sk_seed, pk_seed, &adrs)
} else {
let lnode = Self::xmss_node(sk_seed, 2 * node, height - 1, pk_seed, adrs);
let rnode = Self::xmss_node(sk_seed, 2 * node + 1, height - 1, pk_seed, adrs);
let mut adrs = adrs.tree_adrs();
adrs.tree_height.set(height);
adrs.tree_index.set(node);
Self::h(pk_seed, &adrs, &lnode, &rnode)
}
}
fn xmss_sign(
m: &Array<u8, Self::N>,
sk_seed: &SkSeed<Self::N>,
pk_seed: &PkSeed<Self::N>,
idx: u32,
adrs: &address::WotsHash,
) -> XmssSig<Self> {
let mut adrs = adrs.clone();
adrs.key_pair_adrs.set(idx);
let sig = Self::wots_sign(m, sk_seed, pk_seed, &adrs);
let mut auth = Array::<Array<u8, Self::N>, Self::HPrime>::default();
let mut idx = idx;
for j in 0..Self::HPrime::U32 {
let node = Self::xmss_node(sk_seed, idx ^ 1, j, pk_seed, &adrs);
idx >>= 1;
auth[j as usize] = node;
}
XmssSig { sig, auth }
}
fn xmss_pk_from_sig(
idx: u32,
sig: &XmssSig<Self>,
m: &Array<u8, Self::N>,
pk_seed: &PkSeed<Self::N>,
adrs: &address::WotsHash,
) -> Array<u8, Self::N>
where {
let mut adrs = adrs.clone();
adrs.key_pair_adrs.set(idx);
let mut node = Self::wots_pk_from_sig(&sig.sig, m, pk_seed, &adrs);
let mut adrs = adrs.tree_adrs();
let mut idx = idx;
let mut rem;
for j in 0..Self::HPrime::U32 {
adrs.tree_height.set(j + 1);
(idx, rem) = (idx >> 1, idx & 1);
adrs.tree_index.set(idx);
if rem == 0 {
node = Self::h(pk_seed, &adrs, &node, &sig.auth[j as usize]);
} else {
node = Self::h(pk_seed, &adrs, &sig.auth[j as usize], &node);
}
}
node
}
}
#[cfg(test)]
mod tests {
use crate::util::macros::test_parameter_sets;
use crate::PkSeed;
use crate::SkSeed;
use hex_literal::hex;
use hybrid_array::Array;
use rand::thread_rng;
use rand::Rng;
use rand::RngCore;
use typenum::Unsigned;
use crate::{address::WotsHash, hashes::Shake128f, xmss::XmssParams};
#[test]
fn test_xmss_node_shake128f_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let adrs = WotsHash::default();
let node = Shake128f::xmss_node(
&sk_seed,
0,
<Shake128f as XmssParams>::HPrime::U32,
&pk_seed,
&adrs,
);
// Generated by https://github.com/mjosaarinen/slh-dsa-py
let expected = hex!("94e24679fb2460b97332db131c38bec9");
assert_eq!(node.as_slice(), expected);
}
#[test]
#[cfg(feature = "alloc")]
fn test_sign_shake128f_kat() {
let sk_seed = SkSeed(Array([1; 16]));
let pk_seed = PkSeed(Array([2; 16]));
let adrs = WotsHash::default();
let m = Array([3; 16]);
let idx = 3;
let sig = Shake128f::xmss_sign(&m, &sk_seed, &pk_seed, idx, &adrs);
let expected = hex!(
"
a77a0b07e558b023f653a954d886ac66ded67b313f9db7fd93da00686be66a3f
2e2d3e841292bf5a4060d88509e9a2a51e0bbae6835482bceabce76c5653546d
08c2f5f78e7491f755f35380d965598891131bdd4c57df2397eed8062a1038fb
10c758bb30c6ea3859db4eb6296269d170d86cc67804dc63a61e5f30af709aad
2407624eb81549e87c326c2a646c2b995dfad81cc007286b6f50b56f61352fa2
752a30aa4f63cc367a7a1c57140a086cc43387ce5f530d84538d0c503d051be2
9c0040486c2953d34e3817bfcb6f198e545476ddd93930af48333b4e7e0eba03
3bdbc1badca23875d2f4345699075558a68c8f53865c0b2151208a7a5a4b0c7d
270b71d5688c6d727525e3fd9c75b9656e13394777faee925fe8cda6e2b7c52a
684f218679a48b942127f89ffaa069db21659a09266e9304ce870c16094bf585
6ed93c0748b9479a95d4309c74c2da26b2cf2e5f2090f02601b80c3373b14666
f0bd973d10c7eb649966d1ffd3e87979899812fef1e23f5703a99924001d9ba9
522ea93575ad20143eeeeff77b8d192870932b1583459271f634a65441fe1907
370f71e4d9312b930a66e1b85cba8f4a404c703c7c38ada5c6b95824c2c0ff87
b1e3f258189d949430c516d2c2192ffbb8d687b10228d7ecf47f86c1299825a8
b6ee7c560f4bd1720aabdca41c8a5569e9917f906efca17d5f080e65e5a16386
c9bb4f1ad49404340df212e94d77ff5a25b8649b725e1993dc66f37a89058499
107bb57a4f699688406e89a44776b95bd1af01290496fb4f3abba58eb407eff9
c1dfd1362d169170f8b7364c6aa8e6507f049484e5d9b934e86d61b1d3155b5a"
);
assert_eq!(sig.to_vec(), expected);
}
fn test_sign_verify<Xmss: XmssParams>() {
// Generate random sk_seed, pk_seed, message, index, address
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, _>::default();
rng.fill_bytes(msg.as_mut_slice());
let idx = rng.gen_range(0..(1 << Xmss::HPrime::U32));
let adrs = WotsHash::default();
let pk = Xmss::xmss_node(&sk_seed, 0, Xmss::HPrime::U32, &pk_seed, &adrs);
let sig = Xmss::xmss_sign(&msg, &sk_seed, &pk_seed, idx, &adrs);
let pk_recovered = Xmss::xmss_pk_from_sig(idx, &sig, &msg, &pk_seed, &adrs);
assert_eq!(pk, pk_recovered);
}
test_parameter_sets!(test_sign_verify);
fn test_sign_verify_fail<Xmss: XmssParams>() {
// Generate random sk_seed, pk_seed, message, index, address
let mut rng = thread_rng();
let sk_seed = SkSeed::new(&mut rng);
let pk_seed = PkSeed::new(&mut rng);
let mut msg = Array::<u8, _>::default();
rng.fill_bytes(msg.as_mut_slice());
let idx = rng.gen_range(0..(1 << Xmss::HPrime::U32));
let adrs = WotsHash::default();
let pk = Xmss::xmss_node(&sk_seed, 0, Xmss::HPrime::U32, &pk_seed, &adrs);
let sig = Xmss::xmss_sign(&msg, &sk_seed, &pk_seed, idx, &adrs);
// Tweak message
msg[0] ^= 0xff;
let pk_recovered = Xmss::xmss_pk_from_sig(idx, &sig, &msg, &pk_seed, &adrs);
assert_ne!(pk, pk_recovered);
}
test_parameter_sets!(test_sign_verify_fail);
}
+213
View File
@@ -0,0 +1,213 @@
use std::{array::from_fn, fmt::Write};
use aes::Aes256;
use cipher::{KeyIvInit, StreamCipher};
use ctr::Ctr128BE;
use rand_core::{CryptoRng, RngCore};
use sha2::Digest;
use signature::SignatureEncoding;
use signature::{Keypair, RandomizedSigner};
use slh_dsa::*;
use typenum::Unsigned;
/// AES_CTR_DRBG - based RNG used by the SPHINCS+ reference implementation KATs
struct KatRng(Ctr128BE<Aes256>);
impl KatRng {
fn new(entropy: &[u8; 48]) -> Self {
let key = [0u8; 32];
let mut iv = [0u8; 16];
iv[15] = 1;
let mut this = Self(Ctr128BE::<Aes256>::new_from_slices(&key, &iv).unwrap());
this.update(Some(entropy));
this
}
fn update(&mut self, entropy: Option<&[u8; 48]>) {
let mut tmp = entropy.map_or([0u8; 48], |e| *e);
self.0.apply_keystream(&mut tmp);
self.0 = Ctr128BE::<Aes256>::new_from_slices(&tmp[0..32], &tmp[32..48]).unwrap();
self.0.apply_keystream(&mut [0; 16]); // discard one block
}
}
impl RngCore for KatRng {
fn fill_bytes(&mut self, dest: &mut [u8]) {
dest.fill(0);
self.0.apply_keystream(dest);
// Discard up to end of block if not a multiple of 16
let pad = (16 - (dest.len() % 16)) % 16;
self.0.apply_keystream(&mut [0; 16][..pad]);
self.update(None);
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.fill_bytes(dest);
Ok(())
}
fn next_u32(&mut self) -> u32 {
rand_core::impls::next_u32_via_fill(self)
}
fn next_u64(&mut self) -> u64 {
rand_core::impls::next_u64_via_fill(self)
}
}
impl CryptoRng for KatRng {}
// Mock RNG that just returns a pre-determined bytestring
struct ConstRng(Vec<u8>);
impl RngCore for ConstRng {
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.try_fill_bytes(dest).unwrap();
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
let len = dest.len();
if len > self.0.len() {
return Err(rand::Error::new("not enough bytes"));
}
dest.iter_mut()
.zip(self.0.drain(..len))
.for_each(|(d, s)| *d = s);
Ok(())
}
fn next_u32(&mut self) -> u32 {
rand_core::impls::next_u32_via_fill(self)
}
fn next_u64(&mut self) -> u64 {
rand_core::impls::next_u64_via_fill(self)
}
}
impl CryptoRng for ConstRng {}
const ITERS: usize = 10;
fn test_kat<P: ParameterSet + VerifyingKeyLen>(expected: &str)
where
Signature<P>: SignatureEncoding,
{
let mut resp: String = "# SPHINCS+\n\n".to_string();
let mut rng = KatRng::new(&from_fn(|i| i as u8));
let mut seeds = [[0u8; 48]; ITERS];
let mut msgs: Vec<Vec<u8>> = (0..ITERS).map(|i| vec![0; 33 * (i + 1)]).collect();
for (seed, msg) in seeds.iter_mut().zip(msgs.iter_mut()) {
rng.fill_bytes(seed);
rng.fill_bytes(msg.as_mut_slice());
}
for i in 0..ITERS {
let mut rng = KatRng::new(&seeds[i]);
writeln!(&mut resp, "count = {}", i).unwrap();
writeln!(&mut resp, "seed = {}", hex::encode_upper(seeds[i])).unwrap();
let mlen = 33 * (i + 1);
writeln!(resp, "mlen = {}", mlen).unwrap();
let msg = msgs[i].as_slice();
writeln!(resp, "msg = {}", hex::encode_upper(msg)).unwrap();
let mut seed = vec![0; (P::VkLen::USIZE * 3) / 2];
rng.fill_bytes(&mut seed);
let mut seed_rng = ConstRng(seed);
let sk = SigningKey::<P>::new(&mut seed_rng);
let pk = sk.verifying_key();
writeln!(resp, "pk = {}", hex::encode_upper(&pk.to_vec())).unwrap();
writeln!(resp, "sk = {}", hex::encode_upper(&sk.to_vec())).unwrap();
let sig = sk.sign_with_rng(&mut rng, msg).to_bytes();
writeln!(resp, "smlen = {}", sig.as_slice().len() + msg.len()).unwrap();
writeln!(
resp,
"sm = {}{}\n",
hex::encode_upper(&sig),
hex::encode_upper(msg)
)
.unwrap();
}
let shasum = sha2::Sha256::digest(resp.as_bytes());
assert_eq!(hex::encode(shasum.as_slice()), expected);
}
/*
KATs generated via https://github.com/sphincs/sphincsplus on branch consistent_basew (eccdc43a99e194f52d5ef0e4030ef4dd1e31828b)
with PQCgenKAT_sign.c modified on line 59 to reduce iterations from 100 to 10
056ee235f9a8ff3fb3f3799807d82690ffe3ff5681f0c6e3809ddca4fb539b29 sphincs-shake-128f-simple
0d17bdb1d3e7d7f06e88d79f3b65fbeae70694914ec088a3d1c081e65c35a928 sphincs-sha2-192s-simple
1930c23fb13b4f95ec11343d9a68d270879bfbb8821c1cfb59b37cd0b5e4665e sphincs-shake-128s-simple
5e501f7c91fa189dff618dda9cca0511140fb85e133bab986c9ef89ed220389e sphincs-sha2-128s-simple
73519c7365ac46695ea96dae3283f05a2ddcb1e8f9e0ba800544ba737b746206 sphincs-sha2-192f-simple
7c8f16c7b9645df58518b1c0aa7a26f7a2e1b9ee860819f25305cf97aecce1f3 sphincs-shake-256s-simple
7d35e89f91116f0869d7a7591df026c4033f8ca3e33d795f03b25905277175cd sphincs-shake-192s-simple
be37b5222c98b3a1f0d2d3d69bc32205ed17e93c6a4da684c76ee1ca29ec28ef sphincs-shake-256f-simple
c8fcb441611200b19349f2e7fda07c4547bef22f35af6e47a2b8c824e0c2e0be sphincs-sha2-128f-simple
d83825fb99bc22a3eb4ae388a9e88716b5cae0622682a210bd11f7b8ffbd47ee sphincs-shake-192f-simple
e58442029ff40d3f61d5a7d3495fae38500ad4be4db9db1ef4f42a365077b070 sphincs-sha2-256f-simple
f935d6af17fa16f290421c5112c4c2cee445ba7c332a74fe3d88a7a219c2176b sphincs-sha2-256s-simple
*/
#[test]
fn test_kat_sha2_128f() {
test_kat::<Sha2_128f>("c8fcb441611200b19349f2e7fda07c4547bef22f35af6e47a2b8c824e0c2e0be");
}
#[test]
fn test_kat_sha2_192s() {
test_kat::<Sha2_192s>("0d17bdb1d3e7d7f06e88d79f3b65fbeae70694914ec088a3d1c081e65c35a928");
}
#[test]
fn test_kat_sha2_192f() {
test_kat::<Sha2_192f>("73519c7365ac46695ea96dae3283f05a2ddcb1e8f9e0ba800544ba737b746206");
}
#[test]
fn test_kat_sha2_256s() {
test_kat::<Sha2_256s>("f935d6af17fa16f290421c5112c4c2cee445ba7c332a74fe3d88a7a219c2176b");
}
#[test]
fn test_kat_sha2_256f() {
test_kat::<Sha2_256f>("e58442029ff40d3f61d5a7d3495fae38500ad4be4db9db1ef4f42a365077b070");
}
#[test]
fn test_kat_shake_128s() {
test_kat::<Shake128s>("1930c23fb13b4f95ec11343d9a68d270879bfbb8821c1cfb59b37cd0b5e4665e");
}
#[test]
fn test_kat_shake_128f() {
test_kat::<Shake128f>("056ee235f9a8ff3fb3f3799807d82690ffe3ff5681f0c6e3809ddca4fb539b29");
}
#[test]
fn test_kat_shake_192s() {
test_kat::<Shake192s>("7d35e89f91116f0869d7a7591df026c4033f8ca3e33d795f03b25905277175cd");
}
#[test]
fn test_kat_shake_192f() {
test_kat::<Shake192f>("d83825fb99bc22a3eb4ae388a9e88716b5cae0622682a210bd11f7b8ffbd47ee");
}
#[test]
fn test_kat_shake_256s() {
test_kat::<Shake256s>("7c8f16c7b9645df58518b1c0aa7a26f7a2e1b9ee860819f25305cf97aecce1f3");
}
#[test]
fn test_kat_shake_256f() {
test_kat::<Shake256f>("be37b5222c98b3a1f0d2d3d69bc32205ed17e93c6a4da684c76ee1ca29ec28ef");
}