mirror of
https://github.com/RustCrypto/signatures
synced 2026-06-21 13:45:42 +00:00
slh-dsa: initial implementation (#812)
This commit is contained in:
Generated
+851
-4
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -6,7 +6,8 @@ members = [
|
||||
"ed448",
|
||||
"ed25519",
|
||||
"lms",
|
||||
"rfc6979"
|
||||
"rfc6979",
|
||||
"slh-dsa"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
|
||||
@@ -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"]
|
||||
@@ -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+
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user