Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 112 additions & 1 deletion doc/api_ref/pubkey.rst
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ removed in a future major release.

Returns true if this keys operation is stateful, that is if updating the
key is required after each private operation. Currently the only stateful
schemes included are XMSS and LMS.
schemes included are XMSS, XMSS^MT, and LMS.

.. cpp:function:: std::optional<uint64_t> remaining_operations() const

Expand Down Expand Up @@ -217,6 +217,20 @@ forged.
environment carefully designed to maintain consistent state. Prefer
the stateless SLH-DSA in new designs.

XMSS^MT
~~~~~~~~~

XMSS^MT is the multi-tree variant of XMSS.
It makes the generation of larger trees feasible which allows for more signatures.

.. warning::

Maintaining consistent state without replays is extremely difficult,
especially when multiple machines are involved. Even a single error will
compromise the entire signature scheme. XMSS^MT should only be used in an
environment carefully designed to maintain consistent state. Prefer
the stateless SLH-DSA in new designs.

HSS-LMS
~~~~~~~

Expand Down Expand Up @@ -1043,6 +1057,7 @@ Botan implements the following signature algorithms:
#. SLH-DSA.
Takes the optional parameter ``Deterministic`` (default) or ``Randomized``.
#. XMSS. Takes no parameter.
#. XMSSMT. Takes no parameter.
#. HSS-LMS. Takes no parameter.

.. _ecdsa_example:
Expand Down Expand Up @@ -1614,6 +1629,102 @@ signature:
.. literalinclude:: /../src/examples/xmss.cpp
:language: cpp

Multi-Tree XMSS (XMSS^MT)
----------------------------------------

Botan implements the multi-tree version of the eXtended Merkle Signature
Scheme (XMSS^MT) using Winternitz One Time Signatures+ (WOTS+). The implementation
is based on `RFC 8391 "XMSS: eXtended Merkle Signature Scheme"
<https://tools.ietf.org/html/rfc8391>`_
and `Recommendation for Stateful Hash-Based Signature Schemes
<https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-208.pdf>`_.

.. warning::

XMSS^MT is stateful, meaning the private key updates after each signature
creation. Applications are responsible for updating their persistent secret
with the new output of ``Private_Key::private_key_bits()`` after each signature
creation. If the same private key is ever used to generate
two different signatures, then the scheme becomes insecure. For this reason,
it can be challenging to use XMSS^MT securely.

XMSS^MT uses the Botan interfaces for public key cryptography.
The following algorithms are implemented:

1. XMSSMT-SHA2_20/2_256
#. XMSSMT-SHA2_20/4_256
#. XMSSMT-SHA2_40/2_256
#. XMSSMT-SHA2_40/4_256
#. XMSSMT-SHA2_40/8_256
#. XMSSMT-SHA2_60/3_256
#. XMSSMT-SHA2_60/6_256
#. XMSSMT-SHA2_60/12_256
#. XMSSMT-SHA2_20/2_512
#. XMSSMT-SHA2_20/4_512
#. XMSSMT-SHA2_40/2_512
#. XMSSMT-SHA2_40/4_512
#. XMSSMT-SHA2_40/8_512
#. XMSSMT-SHA2_60/3_512
#. XMSSMT-SHA2_60/6_512
#. XMSSMT-SHA2_60/12_512
#. XMSSMT-SHAKE_20/2_256
#. XMSSMT-SHAKE_20/4_256
#. XMSSMT-SHAKE_40/2_256
#. XMSSMT-SHAKE_40/4_256
#. XMSSMT-SHAKE_40/8_256
#. XMSSMT-SHAKE_60/3_256
#. XMSSMT-SHAKE_60/6_256
#. XMSSMT-SHAKE_60/12_256
#. XMSSMT-SHAKE_20/2_512
#. XMSSMT-SHAKE_20/4_512
#. XMSSMT-SHAKE_40/2_512
#. XMSSMT-SHAKE_40/4_512
#. XMSSMT-SHAKE_40/8_512
#. XMSSMT-SHAKE_60/3_512
#. XMSSMT-SHAKE_60/6_512
#. XMSSMT-SHAKE_60/12_512
#. XMSSMT-SHA2_20/2_192
#. XMSSMT-SHA2_20/4_192
#. XMSSMT-SHA2_40/2_192
#. XMSSMT-SHA2_40/4_192
#. XMSSMT-SHA2_40/8_192
#. XMSSMT-SHA2_60/3_192
#. XMSSMT-SHA2_60/6_192
#. XMSSMT-SHA2_60/12_192
#. XMSSMT-SHAKE256_20/2_256
#. XMSSMT-SHAKE256_20/4_256
#. XMSSMT-SHAKE256_40/2_256
#. XMSSMT-SHAKE256_40/4_256
#. XMSSMT-SHAKE256_40/8_256
#. XMSSMT-SHAKE256_60/3_256
#. XMSSMT-SHAKE256_60/6_256
#. XMSSMT-SHAKE256_60/12_256
#. XMSSMT-SHAKE256_20/2_192
#. XMSSMT-SHAKE256_20/4_192
#. XMSSMT-SHAKE256_40/2_192
#. XMSSMT-SHAKE256_40/4_192
#. XMSSMT-SHAKE256_40/8_192
#. XMSSMT-SHAKE256_60/3_192
#. XMSSMT-SHAKE256_60/6_192
#. XMSSMT-SHAKE256_60/12_192

The algorithm name contains the hash function name, tree height, number of layers,
and digest width defined by the corresponding parameter set. Choosing `XMSSMT-SHA2_20/2_256`
for instance will use the SHA2-256 hash function to generate a tree of height
twenty, divided into two layers (of equal height).

.. _xmssmt_example:

Code Example: XMSS^MT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following code snippet shows a minimum example on how to create an XMSS^MT
public/private key pair and how to use these keys to create and verify a
signature:

.. literalinclude:: /../src/examples/xmssmt.cpp
:language: cpp


Hierarchical Signature System with Leighton-Micali Hash-Based Signatures (HSS-LMS)
----------------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions doc/credits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ snail-mail address (S), and Bitcoin address (B).
D: TLS 1.3, Kyber, Dilithium
S: Berlin, Germany

N: Johannes Roth
E: johannes.roth@posteo.de
D: XMSS^MT
S: Darmstadt, Germany

N: Amos Treiber
E: amos.treiber@rohde-schwarz.com
W: https://www.rohde-schwarz.com/cybersecurity
Expand Down
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ are listed below:
* :ref:`ML-KEM <mlkem_example>`
* :ref:`RSA <rsa_example>`
* :ref:`XMSS <xmss_example>`
* :ref:`XMSS^MT <xmssmt_example>`
* :ref:`Stream Ciphers <stream_ciphers_example>`
* :ref:`TLS Client <tls_client_example>`
* :ref:`TLS Client (PQC/hybrid) <tls_hybrid_client_example>`
Expand Down
2 changes: 1 addition & 1 deletion readme.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Public Key Cryptography
* RSA signatures and encryption
* DH, ECDH, X25519 and X448 key agreement
* Elliptic curve signature schemes ECDSA, Ed25519, Ed448, ECGDSA, ECKCDSA, SM2
* Post-quantum signature schemes ML-DSA (Dilithium), SLH-DSA (SPHINCS+), HSS/LMS, XMSS
* Post-quantum signature schemes ML-DSA (Dilithium), SLH-DSA (SPHINCS+), HSS/LMS, XMSS, XMSS^MT
* Post-quantum key encapsulation schemes ML-KEM (Kyber), FrodoKEM, Classic McEliece

Ciphers, hashes, MACs, and checksums
Expand Down
4 changes: 4 additions & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@
1.3.6.1.4.1.25258.1.8 = XMSS-draft12
# draft-vangeest-x509-hash-sigs-03
0.4.0.127.0.15.1.1.13.0 = XMSS
0.4.0.127.0.15.1.1.14.0 = XMSSMT-draft-CBOR
# RFC 9802
1.3.6.1.5.5.7.6.34 = XMSS-RFC9802
1.3.6.1.5.5.7.6.35 = XMSSMT

# X9.62 ecPublicKey, valid for ECDSA and ECDH (RFC 3279 sec 2.3.5)
1.2.840.10045.2.1 = ECDSA
Expand Down
1 change: 1 addition & 0 deletions src/build-data/policy/fips140.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ frodokem
frodokem_aes
frodokem_common
xmss
xmssmt
sm2

# pk_pad
Expand Down
33 changes: 33 additions & 0 deletions src/cli/perf_pk_sig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,39 @@ BOTAN_REGISTER_PERF_TEST("XMSS", PerfTest_XMSS);

#endif

#if defined(BOTAN_HAS_XMSSMT_RFC8391)

class PerfTest_XMSSMT final : public PerfTest_PKSig {
public:
std::string algo() const override { return "XMSSMT"; }

std::string hash() const override { return ""; }

std::vector<std::string> keygen_params(const PerfConfig& /*config*/) const override {
/*
Only test parameter sets that have height 5 or 10 subtrees. For larger trees, it is quite slow.
*/
return {"XMSSMT-SHA2_20/2_192", "XMSSMT-SHA2_20/4_192", "XMSSMT-SHA2_40/4_192",
"XMSSMT-SHA2_40/8_192", "XMSSMT-SHA2_60/6_192", "XMSSMT-SHA2_60/12_192",
"XMSSMT-SHAKE256_20/2_192", "XMSSMT-SHAKE256_20/4_192", "XMSSMT-SHAKE256_40/4_192",
"XMSSMT-SHAKE256_40/8_192", "XMSSMT-SHAKE256_60/6_192", "XMSSMT-SHAKE256_60/12_192",
"XMSSMT-SHA2_20/2_256", "XMSSMT-SHA2_20/4_256", "XMSSMT-SHA2_40/4_256",
"XMSSMT-SHA2_40/8_256", "XMSSMT-SHA2_60/6_256", "XMSSMT-SHA2_60/12_256",
"XMSSMT-SHAKE_20/2_256", "XMSSMT-SHAKE_20/4_256", "XMSSMT-SHAKE_40/4_256",
"XMSSMT-SHAKE_40/8_256", "XMSSMT-SHAKE_60/6_256", "XMSSMT-SHAKE_60/12_256",
"XMSSMT-SHAKE256_20/2_256", "XMSSMT-SHAKE256_20/4_256", "XMSSMT-SHAKE256_40/4_256",
"XMSSMT-SHAKE256_40/8_256", "XMSSMT-SHAKE256_60/6_256", "XMSSMT-SHAKE256_60/12_256",
"XMSSMT-SHA2_20/2_512", "XMSSMT-SHA2_20/4_512", "XMSSMT-SHA2_40/4_512",
"XMSSMT-SHA2_40/8_512", "XMSSMT-SHA2_60/6_512", "XMSSMT-SHA2_60/12_512",
"XMSSMT-SHAKE_20/2_512", "XMSSMT-SHAKE_20/4_512", "XMSSMT-SHAKE_40/4_512",
"XMSSMT-SHAKE_40/8_512", "XMSSMT-SHAKE_60/6_512", "XMSSMT-SHAKE_60/12_512"};
}
};

BOTAN_REGISTER_PERF_TEST("XMSSMT", PerfTest_XMSSMT);

#endif

#if defined(BOTAN_HAS_HSS_LMS)

class PerfTest_HSS_LMS final : public PerfTest_PKSig {
Expand Down
36 changes: 36 additions & 0 deletions src/examples/xmssmt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <botan/auto_rng.h>
#include <botan/pubkey.h>
#include <botan/secmem.h>
#include <botan/xmssmt.h>

#include <iostream>
#include <vector>

int main() {
// Create a random number generator used for key generation.
Botan::AutoSeeded_RNG rng;

// create a new public/private key pair using SHA2 256 as hash
// function and a tree height of 20 with 2 layers.
const Botan::XMSSMT_PrivateKey private_key(Botan::XMSSMT_Parameters::xmssmt_algorithm_t::XMSSMT_SHA2_20_2_256, rng);
const Botan::XMSSMT_PublicKey& public_key(private_key);

// create Public Key Signer using the private key.
Botan::PK_Signer signer(private_key, rng, "");

// create and sign a message using the Public Key Signer.
Botan::secure_vector<uint8_t> msg{0x01, 0x02, 0x03, 0x04};
auto sig = signer.sign_message(msg, rng);

// create Public Key Verifier using the public key
Botan::PK_Verifier verifier(public_key, "");

// verify the signature for the previously generated message.
if(verifier.verify_message(msg, sig)) {
std::cout << "Success.\n";
return 0;
} else {
std::cout << "Error.\n";
return 1;
}
}
14 changes: 13 additions & 1 deletion src/lib/asn1/static_oids.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* This file was automatically generated by ./src/scripts/dev_tools/gen_oids.py on 2026-03-04
* This file was automatically generated by src/scripts/dev_tools/gen_oids.py on 2026-06-23
*
* All manual edits to this file will be lost. Edit the script then regenerate this source file.
*
Expand Down Expand Up @@ -208,6 +208,8 @@ std::optional<std::string_view> OID_Map::lookup_static_oid(const OID& oid) {
return if_match(oid, {1, 3, 36, 3, 2, 1}, "RIPEMD-160");
case 0x4266E:
return if_match(oid, {0, 4, 0, 127, 0, 15, 1, 1, 13, 0}, "XMSS");
case 0x4272F:
return if_match(oid, {0, 4, 0, 127, 0, 15, 1, 1, 14, 0}, "XMSSMT-draft-CBOR");
case 0x478C4:
return if_match(oid, {1, 2, 410, 200004, 1, 4}, "SEED/CBC");
case 0x47D98:
Expand Down Expand Up @@ -582,6 +584,10 @@ std::optional<std::string_view> OID_Map::lookup_static_oid(const OID& oid) {
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 8}, "PKIX.TimeStamping");
case 0x94929:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 3, 9}, "PKIX.OCSPSigning");
case 0x94B85:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 34}, "XMSS-RFC9802");
case 0x94B86:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 6, 35}, "XMSSMT");
case 0x94CEA:
return if_match(oid, {1, 3, 6, 1, 5, 5, 7, 8, 5}, "PKIX.XMPPAddr");
case 0x954DB:
Expand Down Expand Up @@ -838,6 +844,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "KeyWrap.AES-192", {2, 16, 840, 1, 101, 3, 4, 1, 25});
case 0x1D439:
return if_match(req, "SphincsPlus-haraka-192f-r3.1", {1, 3, 6, 1, 4, 1, 25258, 1, 12, 3, 4});
case 0x1F5DC:
return if_match(req, "XMSS-RFC9802", {1, 3, 6, 1, 5, 5, 7, 6, 34});
case 0x2065B:
return if_match(req, "KeyWrap.CAST-128", {1, 2, 840, 113533, 7, 66, 15});
case 0x216A0:
Expand Down Expand Up @@ -976,6 +984,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "eFrodoKEM-976-AES", {1, 3, 6, 1, 4, 1, 25258, 1, 17, 2});
case 0x4C513:
return if_match(req, "DSA/SHA-224", {2, 16, 840, 1, 101, 3, 4, 3, 1});
case 0x4C6C6:
return if_match(req, "XMSSMT", {1, 3, 6, 1, 5, 5, 7, 6, 35});
case 0x4C806:
return if_match(req, "DSA/SHA-256", {2, 16, 840, 1, 101, 3, 4, 3, 2});
case 0x4D740:
Expand Down Expand Up @@ -1278,6 +1288,8 @@ std::optional<OID> OID_Map::lookup_static_oid_name(std::string_view req) {
return if_match(req, "XMSS", {0, 4, 0, 127, 0, 15, 1, 1, 13, 0});
case 0x9A6B2:
return if_match(req, "ECKCDSA/SHA-1", {1, 2, 410, 200004, 1, 100, 4, 3});
case 0x9ADFC:
return if_match(req, "XMSSMT-draft-CBOR", {0, 4, 0, 127, 0, 15, 1, 1, 14, 0});
case 0x9B1CF:
return if_match(req, "SM4/SIV", {1, 3, 6, 1, 4, 1, 25258, 3, 4, 9});
case 0x9B6B2:
Expand Down
31 changes: 30 additions & 1 deletion src/lib/pubkey/pk_algs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
#include <botan/xmss.h>
#endif

#if defined(BOTAN_HAS_XMSSMT_RFC8391)
#include <botan/xmssmt.h>
#endif

#if defined(BOTAN_HAS_SM2)
#include <botan/sm2.h>
#endif
Expand Down Expand Up @@ -242,11 +246,17 @@ std::unique_ptr<Public_Key> load_public_key(const AlgorithmIdentifier& alg_id,
#endif

#if defined(BOTAN_HAS_XMSS_RFC8391)
if(alg_name == "XMSS") {
if(alg_name == "XMSS" or alg_name == "XMSS-RFC9802") {
return std::make_unique<XMSS_PublicKey>(key_bits);
}
#endif

#if defined(BOTAN_HAS_XMSSMT_RFC8391)
if(alg_name == "XMSSMT") {
return std::make_unique<XMSSMT_PublicKey>(key_bits);
}
#endif

#if defined(BOTAN_HAS_DILITHIUM) || defined(BOTAN_HAS_DILITHIUM_AES)
if(alg_name == "Dilithium" || alg_name.starts_with("Dilithium-")) {
return std::make_unique<Dilithium_PublicKey>(alg_id, key_bits);
Expand Down Expand Up @@ -406,6 +416,12 @@ std::unique_ptr<Private_Key> load_private_key(const AlgorithmIdentifier& alg_id,
}
#endif

#if defined(BOTAN_HAS_XMSSMT_RFC8391)
if(alg_name == "XMSSMT") {
return std::make_unique<XMSSMT_PrivateKey>(key_bits);
}
#endif

#if defined(BOTAN_HAS_DILITHIUM) || defined(BOTAN_HAS_DILITHIUM_AES)
if(alg_name == "Dilithium" || alg_name.starts_with("Dilithium-")) {
return std::make_unique<Dilithium_PrivateKey>(alg_id, key_bits);
Expand Down Expand Up @@ -647,6 +663,19 @@ std::unique_ptr<Private_Key> create_private_key(std::string_view alg_name,
}
#endif

#if defined(BOTAN_HAS_XMSSMT_RFC8391)
if(alg_name == "XMSSMT") {
const auto xmssmt_oid = [&]() -> XMSSMT_Parameters::xmssmt_algorithm_t {
if(params.empty()) {
return XMSSMT_Parameters::XMSSMT_SHA2_20_2_256;
}
return XMSSMT_Parameters(params).oid();
}();

return std::make_unique<XMSSMT_PrivateKey>(xmssmt_oid, rng);
}
#endif

#if defined(BOTAN_HAS_ED25519)
if(alg_name == "Ed25519") {
return std::make_unique<Ed25519_PrivateKey>(rng);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/pubkey/pkcs8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ std::pair<std::string, std::string> choose_pbe_params(std::string_view pbe_algo,
* SIV or GCM. For others (RSA, ECDSA, ...) default to something widely
* compatible.
*/
const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS");
const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS" || key_algo == "XMSSMT");

if(nonstandard_pk) {
#if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64)
Expand Down
Loading
Loading