Skip to content

Commit

Permalink
SLH-DSA integration into SPHINCS+
Browse files Browse the repository at this point in the history
This commit applies the changes from SPHINCS+ Round 3.1 to SLH-DSA
(FIPS 205). The documentation is updated accordingly.
  • Loading branch information
FAlbertDev authored and reneme committed Oct 14, 2024
1 parent ed74c95 commit 74b0e5e
Show file tree
Hide file tree
Showing 35 changed files with 800 additions and 316 deletions.
31 changes: 23 additions & 8 deletions doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,34 @@ signatures, then the whole scheme becomes insecure, and signatures can be
forged.

HSS-LMS
-------
~~~~~~~

A post-quantum secure hash-based signature scheme similar to XMSS. Contains
support for multitrees. It is stateful, meaning the private key changes after
each signature.

SPHINCS+
~~~~~~~~~
SLH-DSA (FIPS 205)
~~~~~~~~~~~~~~~~~~

A post-quantum secure signature scheme whose security is based (only) on the
security of a hash function. Unlike XMSS, it is a stateless signature
scheme, meaning that the private key does not change with each signature. It
has high security but very long signatures and high runtime.
The Stateless Hash-Based Digital Signature Standard (SLH-DSA)
is the FIPS 205 post-quantum secure signature scheme whose security is solely
based on the security of a hash function. Unlike XMSS, it is a stateless
signature scheme, meaning that the private key does not change with each
signature. It has high security but very long signatures and high runtime.

Support for SLH-DSA is implemented in the modules ``slh_dsa_sha2`` and ``slh_dsa_shake``.

Additionally, support for the pre-standardized version "SPHINCS+" is retained
for the time being. The implemented specification is commonly referred to as
version 3.1 of the SPHINCS+ submission to NIST's third round of the
PQC competition. This is not compatible with the "Initial Public Draft" version of
FIPS 205 for which Botan does not offer an implementation. Also, Botan does not
support the Haraka hash function.

Currently, two flavors of SPHINCS+ are implemented in separate Botan modules:

* ``sphincsplus_shake``, that uses Keccak (SHAKE) hash functions
* ``sphincsplus_sha2``, that uses SHA-256

FrodoKEM
~~~~~~~~
Expand Down Expand Up @@ -809,7 +824,7 @@ Botan implements the following signature algorithms:

#. Dilithium.
Takes the optional parameter ``Deterministic`` (default) or ``Randomized``.
#. SPHINCS+.
#. SLH-DSA.
Takes the optional parameter ``Deterministic`` (default) or ``Randomized``.
#. XMSS. Takes no parameter.
#. HSS-LMS. Takes no parameter.
Expand Down
4 changes: 2 additions & 2 deletions doc/credits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ snail-mail address (S), and Bitcoin address (B).
N: René Meusel
E: [email protected]
W: https://www.rohde-schwarz.com/cybersecurity
D: CI, TLS 1.3, Kyber, Dilithium, SPHINCS+, FrodoKEM
D: CI, TLS 1.3, Kyber, Dilithium, SLH-DSA, FrodoKEM
S: Berlin, Germany

N: Philippe Lieser
Expand All @@ -175,5 +175,5 @@ snail-mail address (S), and Bitcoin address (B).
N: Fabian Albert
E: [email protected]
W: https://www.rohde-schwarz.com/cybersecurity
D: SPHINCS+, HSS/LMS
D: SLH-DSA, HSS/LMS
S: Bochum, Germany
2 changes: 1 addition & 1 deletion readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Public Key Cryptography
* RSA signatures and encryption
* DH and ECDH key agreement
* Signature schemes ECDSA, DSA, Ed25519, Ed448, ECGDSA, ECKCDSA, SM2, GOST 34.10
* Post-quantum signature schemes Dilithium, HSS/LMS, SPHINCS+, XMSS
* Post-quantum signature schemes Dilithium, HSS/LMS, SLH-DSA (SPHINCS+), XMSS
* Post-quantum key agreement schemes McEliece, Kyber, and FrodoKEM
* ElGamal encryption
* Padding schemes OAEP, PSS, PKCS #1 v1.5, X9.31
Expand Down
14 changes: 14 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@
1.3.6.1.4.1.25258.1.12.3.5 = SphincsPlus-haraka-256s-r3.1
1.3.6.1.4.1.25258.1.12.3.6 = SphincsPlus-haraka-256f-r3.1

# NIST: SLH-DSA
2.16.840.1.101.3.4.3.20 = SLH-DSA-SHA2-128s
2.16.840.1.101.3.4.3.21 = SLH-DSA-SHA2-128f
2.16.840.1.101.3.4.3.22 = SLH-DSA-SHA2-192s
2.16.840.1.101.3.4.3.23 = SLH-DSA-SHA2-192f
2.16.840.1.101.3.4.3.24 = SLH-DSA-SHA2-256s
2.16.840.1.101.3.4.3.25 = SLH-DSA-SHA2-256f
2.16.840.1.101.3.4.3.26 = SLH-DSA-SHAKE-128s
2.16.840.1.101.3.4.3.27 = SLH-DSA-SHAKE-128f
2.16.840.1.101.3.4.3.28 = SLH-DSA-SHAKE-192s
2.16.840.1.101.3.4.3.29 = SLH-DSA-SHAKE-192f
2.16.840.1.101.3.4.3.30 = SLH-DSA-SHAKE-256s
2.16.840.1.101.3.4.3.31 = SLH-DSA-SHAKE-256f

# XMSS
1.3.6.1.4.1.25258.1.5 = XMSS-draft6
1.3.6.1.4.1.25258.1.8 = XMSS-draft12
Expand Down
26 changes: 25 additions & 1 deletion src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* OID maps
*
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-07-23
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2024-09-20
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
Expand Down Expand Up @@ -267,7 +267,19 @@ std::unordered_map<std::string, std::string> OID_Map::load_oid2str_map() {
{"2.16.840.1.101.3.4.3.15", "RSA/EMSA3(SHA-3(384))"},
{"2.16.840.1.101.3.4.3.16", "RSA/EMSA3(SHA-3(512))"},
{"2.16.840.1.101.3.4.3.2", "DSA/SHA-256"},
{"2.16.840.1.101.3.4.3.20", "SLH-DSA-SHA2-128s"},
{"2.16.840.1.101.3.4.3.21", "SLH-DSA-SHA2-128f"},
{"2.16.840.1.101.3.4.3.22", "SLH-DSA-SHA2-192s"},
{"2.16.840.1.101.3.4.3.23", "SLH-DSA-SHA2-192f"},
{"2.16.840.1.101.3.4.3.24", "SLH-DSA-SHA2-256s"},
{"2.16.840.1.101.3.4.3.25", "SLH-DSA-SHA2-256f"},
{"2.16.840.1.101.3.4.3.26", "SLH-DSA-SHAKE-128s"},
{"2.16.840.1.101.3.4.3.27", "SLH-DSA-SHAKE-128f"},
{"2.16.840.1.101.3.4.3.28", "SLH-DSA-SHAKE-192s"},
{"2.16.840.1.101.3.4.3.29", "SLH-DSA-SHAKE-192f"},
{"2.16.840.1.101.3.4.3.3", "DSA/SHA-384"},
{"2.16.840.1.101.3.4.3.30", "SLH-DSA-SHAKE-256s"},
{"2.16.840.1.101.3.4.3.31", "SLH-DSA-SHAKE-256f"},
{"2.16.840.1.101.3.4.3.4", "DSA/SHA-512"},
{"2.16.840.1.101.3.4.3.5", "DSA/SHA-3(224)"},
{"2.16.840.1.101.3.4.3.6", "DSA/SHA-3(256)"},
Expand Down Expand Up @@ -488,6 +500,18 @@ std::unordered_map<std::string, OID> OID_Map::load_str2oid_map() {
{"SHA-512-256", OID({2, 16, 840, 1, 101, 3, 4, 2, 6})},
{"SHAKE-128", OID({2, 16, 840, 1, 101, 3, 4, 2, 11})},
{"SHAKE-256", OID({2, 16, 840, 1, 101, 3, 4, 2, 12})},
{"SLH-DSA-SHA2-128f", OID({2, 16, 840, 1, 101, 3, 4, 3, 21})},
{"SLH-DSA-SHA2-128s", OID({2, 16, 840, 1, 101, 3, 4, 3, 20})},
{"SLH-DSA-SHA2-192f", OID({2, 16, 840, 1, 101, 3, 4, 3, 23})},
{"SLH-DSA-SHA2-192s", OID({2, 16, 840, 1, 101, 3, 4, 3, 22})},
{"SLH-DSA-SHA2-256f", OID({2, 16, 840, 1, 101, 3, 4, 3, 25})},
{"SLH-DSA-SHA2-256s", OID({2, 16, 840, 1, 101, 3, 4, 3, 24})},
{"SLH-DSA-SHAKE-128f", OID({2, 16, 840, 1, 101, 3, 4, 3, 27})},
{"SLH-DSA-SHAKE-128s", OID({2, 16, 840, 1, 101, 3, 4, 3, 26})},
{"SLH-DSA-SHAKE-192f", OID({2, 16, 840, 1, 101, 3, 4, 3, 29})},
{"SLH-DSA-SHAKE-192s", OID({2, 16, 840, 1, 101, 3, 4, 3, 28})},
{"SLH-DSA-SHAKE-256f", OID({2, 16, 840, 1, 101, 3, 4, 3, 31})},
{"SLH-DSA-SHAKE-256s", OID({2, 16, 840, 1, 101, 3, 4, 3, 30})},
{"SM2", OID({1, 2, 156, 10197, 1, 301, 1})},
{"SM2_Enc", OID({1, 2, 156, 10197, 1, 301, 3})},
{"SM2_Kex", OID({1, 2, 156, 10197, 1, 301, 2})},
Expand Down
16 changes: 9 additions & 7 deletions src/lib/pubkey/pk_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
#include <botan/dilithium.h>
#endif

#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
#if defined(BOTAN_HAS_SPHINCS_PLUS_COMMON)
#include <botan/sphincsplus.h>
#endif

Expand Down Expand Up @@ -230,8 +230,9 @@ std::unique_ptr<Public_Key> load_public_key(const AlgorithmIdentifier& alg_id,
}
#endif

#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
if(alg_name == "SPHINCS+" || alg_name.starts_with("SphincsPlus-")) {
#if defined(BOTAN_HAS_SPHINCS_PLUS_COMMON)
if(alg_name == "SPHINCS+" || alg_name.starts_with("SphincsPlus-") || alg_name.starts_with("SLH-DSA-") ||
alg_name.starts_with("Hash-SLH-DSA-")) {
return std::make_unique<SphincsPlus_PublicKey>(alg_id, key_bits);
}
#endif
Expand Down Expand Up @@ -365,8 +366,9 @@ std::unique_ptr<Private_Key> load_private_key(const AlgorithmIdentifier& alg_id,
}
#endif

#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
if(alg_name == "SPHINCS+" || alg_name.starts_with("SphincsPlus-")) {
#if defined(BOTAN_HAS_SPHINCS_PLUS_COMMON)
if(alg_name == "SPHINCS+" || alg_name.starts_with("SphincsPlus-") || alg_name.starts_with("SLH-DSA-") ||
alg_name.starts_with("Hash-SLH-DSA-")) {
return std::make_unique<SphincsPlus_PrivateKey>(alg_id, key_bits);
}
#endif
Expand Down Expand Up @@ -507,8 +509,8 @@ std::unique_ptr<Private_Key> create_private_key(std::string_view alg_name,
}
#endif

#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
if(alg_name == "SPHINCS+" || alg_name == "SphincsPlus-") {
#if defined(BOTAN_HAS_SPHINCS_PLUS_COMMON)
if(alg_name == "SPHINCS+" || alg_name == "SphincsPlus" || alg_name == "SLH-DSA") {
auto sphincs_params = Sphincs_Parameters::create(params);

return std::make_unique<SphincsPlus_PrivateKey>(rng, sphincs_params);
Expand Down
11 changes: 11 additions & 0 deletions src/lib/pubkey/sphincsplus/slh_dsa_sha2/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<defines>
SLH_DSA_WITH_SHA2 -> 20240806
</defines>

<module_info>
name -> "SLH-DSA (SHA-256)"
</module_info>

<requires>
sphincsplus_sha2_base
</requires>
11 changes: 11 additions & 0 deletions src/lib/pubkey/sphincsplus/slh_dsa_shake/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<defines>
SLH_DSA_WITH_SHAKE -> 20240808
</defines>

<module_info>
name -> "SLH-DSA (SHAKE)"
</module_info>

<requires>
sphincsplus_shake_base
</requires>
4 changes: 2 additions & 2 deletions src/lib/pubkey/sphincsplus/sphincsplus_common/info.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ SPHINCS_PLUS_COMMON -> 20230426
</defines>

<module_info>
name -> "SPHINCS+ (common)"
brief -> "Base implementation of SPHINCS+"
name -> "SLH-DSA (common)"
brief -> "Base implementation of Stateless Hash Function DSA"
type -> "Internal"
</module_info>

Expand Down
37 changes: 21 additions & 16 deletions src/lib/pubkey/sphincsplus/sphincsplus_common/sp_address.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
* SPHINCS+ Address
* SLH-DSA Address
* (C) 2023 Jack Lloyd
* 2023 Fabian Albert, René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
**/
*/

#ifndef BOTAN_SPHINCS_PLUS_ADDRESS_H_
#define BOTAN_SPHINCS_PLUS_ADDRESS_H_
Expand All @@ -28,11 +28,12 @@ enum class Sphincs_Address_Type : uint32_t {
};

/**
* Representation of a SPHINCS+ hash function address as specified in
* SPHINCS+ Specification Round 3.1, Section 2.7.3
* Representation of a SLH-DSA hash function address as specified in
* FIPS 205, Section 4.2
*/
class BOTAN_TEST_API Sphincs_Address final {
private:
// Offsets of the address fields in the address array. Counted in 32-bit words.
static constexpr size_t layer_offset = 0;
static constexpr size_t tree_offset = 1; // tree address is 3 words wide
static constexpr size_t type_offset = 4;
Expand All @@ -52,44 +53,46 @@ class BOTAN_TEST_API Sphincs_Address final {

Sphincs_Address(std::array<uint32_t, 8> address) { std::copy(address.begin(), address.end(), m_address.begin()); }

Sphincs_Address& set_layer(HypertreeLayerIndex layer) {
/* Setter member functions as specified in FIPS 205, Section 4.3 */

Sphincs_Address& set_layer_address(HypertreeLayerIndex layer) {
m_address[layer_offset] = layer.get();
return *this;
}

Sphincs_Address& set_tree(XmssTreeIndexInLayer tree) {
Sphincs_Address& set_tree_address(XmssTreeIndexInLayer tree) {
m_address[tree_offset + 0] = 0; // not required by all current instances
m_address[tree_offset + 1] = static_cast<uint32_t>(tree.get() >> 32);
m_address[tree_offset + 2] = static_cast<uint32_t>(tree.get());
return *this;
}

/*
* Sets the type without clearing the other fields (contrary to the specs setTypeAndClear).
* This adaption is used for optimization purposes.
*/
Sphincs_Address& set_type(Sphincs_Address_Type type) {
m_address[type_offset] = static_cast<uint32_t>(type);
return *this;
}

/* These functions are used for WOTS and FORS addresses. */

Sphincs_Address& set_keypair(TreeNodeIndex keypair) {
Sphincs_Address& set_keypair_address(TreeNodeIndex keypair) {
m_address[keypair_offset] = keypair.get();
return *this;
}

Sphincs_Address& set_chain(WotsChainIndex chain) {
Sphincs_Address& set_chain_address(WotsChainIndex chain) {
m_address[chain_offset] = chain.get();
return *this;
}

Sphincs_Address& set_hash(WotsHashIndex hash) {
m_address[hash_offset] = hash.get();
Sphincs_Address& set_tree_height(TreeLayerIndex tree_height) {
m_address[tree_height_offset] = tree_height.get();
return *this;
}

/* These functions are used for all hash tree addresses (including FORS). */

Sphincs_Address& set_tree_height(TreeLayerIndex tree_height) {
m_address[tree_height_offset] = tree_height.get();
Sphincs_Address& set_hash_address(WotsHashIndex hash) {
m_address[hash_offset] = hash.get();
return *this;
}

Expand All @@ -98,6 +101,8 @@ class BOTAN_TEST_API Sphincs_Address final {
return *this;
}

/* Custom helper member functions */

Sphincs_Address& copy_subtree_from(const Sphincs_Address& other) {
m_address[layer_offset] = other.m_address[layer_offset];
m_address[tree_offset + 0] = other.m_address[tree_offset + 0];
Expand Down
35 changes: 26 additions & 9 deletions src/lib/pubkey/sphincsplus/sphincsplus_common/sp_fors.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* FORS - Forest of Random Subsets
* FORS - Forest of Random Subsets (FIPS 205, Section 8)
* (C) 2023 Jack Lloyd
* 2023 Fabian Albert, René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity
*
* Parts of this file have been adapted from https://github.com/sphincs/sphincsplus
*
* Botan is released under the Simplified BSD License (see license.txt)
**/
*/

#include <botan/internal/sp_fors.h>

Expand All @@ -18,23 +20,38 @@
#include <botan/internal/sp_types.h>
#include <botan/internal/stl_util.h>

#include <functional>
#include <utility>

namespace Botan {

namespace {

/// FIPS 205, Algorithm 4: base_2^b(X,b,out_len) with b = a and out_len = k (for usage in FORS)
std::vector<TreeNodeIndex> fors_message_to_indices(std::span<const uint8_t> message, const Sphincs_Parameters& params) {
BOTAN_ASSERT_NOMSG((message.size() * 8) >= (params.k() * params.a()));

std::vector<TreeNodeIndex> indices(params.k());

uint32_t offset = 0;

// This is one of the few places where the logic of SPHINCS+ round 3.1 and SLH-DSA differs
auto update_idx = [&]() -> std::function<void(TreeNodeIndex&, uint32_t)> {
#if defined(BOTAN_HAS_SLH_DSA_WITH_SHA2) || defined(BOTAN_HAS_SLH_DSA_WITH_SHAKE)
if(params.is_slh_dsa()) {
return [&](TreeNodeIndex& idx, uint32_t i) {
idx ^= (((message[offset >> 3] >> (~offset & 0x7)) & 0x1) << (params.a() - 1 - i));
};
}
#endif
#if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE)
if(!params.is_slh_dsa()) {
return [&](TreeNodeIndex& idx, uint32_t i) { idx ^= (((message[offset >> 3] >> (offset & 0x7)) & 0x1) << i); };
}
#endif
throw Internal_Error("Missing FORS index update logic for SPHINCS+ or SLH-DSA");
}();

for(auto& idx : indices) {
for(uint32_t i = 0; i < params.a(); ++i, ++offset) {
idx ^= (((message[offset >> 3] >> (offset & 0x7)) & 0x1) << i);
update_idx(idx, i);
}
}

Expand Down Expand Up @@ -72,9 +89,9 @@ SphincsTreeNode fors_sign_and_pkgen(StrongSpan<ForsSignature> sig_out,
uint32_t idx_offset = i * (1 << params.a());

// Compute the secret leaf given by the chunk of the message and append it to the signature
fors_tree_addr.set_tree_height(TreeLayerIndex(0))
.set_tree_index(indices[i] + idx_offset)
.set_type(Sphincs_Address_Type::ForsKeyGeneration);
fors_tree_addr.set_type(Sphincs_Address_Type::ForsKeyGeneration)
.set_tree_height(TreeLayerIndex(0))
.set_tree_index(indices[i] + idx_offset);

hashes.PRF(sig.next<ForsLeafSecret>(params.n()), secret_seed, fors_tree_addr);

Expand Down
Loading

0 comments on commit 74b0e5e

Please sign in to comment.