diff --git a/doc/api_ref/pubkey.rst b/doc/api_ref/pubkey.rst index 913a1023147..0e8437704de 100644 --- a/doc/api_ref/pubkey.rst +++ b/doc/api_ref/pubkey.rst @@ -123,6 +123,13 @@ signatures can be created. If the same state is ever used to generate two 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+ ~~~~~~~~~ @@ -782,6 +789,7 @@ Botan implements the following signature algorithms: #. SPHINCS+. Takes the optional parameter ``Deterministic`` (default) or ``Randomized``. #. XMSS. Takes no parameter. +#. HSS-LMS. Takes no parameter. .. _ecdsa_example: @@ -1204,10 +1212,12 @@ is based on `RFC 8391 "XMSS: eXtended Merkle Signature Scheme" .. warning:: - XMSS is stateful, meaning the private key must be updated after - each signature. 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 securely. + XMSS 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 securely. XMSS uses the Botan interfaces for public key cryptography. The following algorithms are implemented: @@ -1241,3 +1251,44 @@ signature: .. literalinclude:: /../src/examples/xmss.cpp :language: cpp + + +Hierarchical Signature System with Leighton-Micali Hash-Based Signatures (HSS-LMS) +---------------------------------------------------------------------------------- + +HSS-LMS is a stateful hash-based signature scheme which is defined in `RFC 8554 +"Leighton-Micali Hash-Based Signatures" `_. + +It is a multitree scheme, which is highly configurable. Multitree means, it consists +of multiple layers of Merkle trees, which can be defined individually. Moreover, the +used hash function and the Winternitz Parameter of the underlying one-time signature +can be chosen for each tree layer. For a sensible selection of parameters refer to +`RFC 8554 Section 6.4. `_. + +.. warning:: + + HSS-LMS 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 HSS-LMS securely. + +HSS-LMS uses the Botan interfaces for public key cryptography. The ``params`` +argument of the HSS-LMS private key is used to define the parameter set. +The syntax of this argument must be the following: + +``HSS-LMS(,HW(,),HW(,),...)`` + +e.g. ``HSS-LMS(SHA-256,HW(5,1),HW(5,1))`` to use SHA-256 in a two-layer HSS instance +with LMS tree height 5 and Winternitz parameter 1. This results in a +private key that can be used to create up to 2^(5+5)=1024 signatures. + +The following parameters are allowed (which are specified in +`RFC 8554 `_ and +and `draft-fluhrer-lms-more-parm-sets-11 `_): + +- hash: ``SHA-256``, ``Truncated(SHA-256,192)``, ``SHAKE-256(256)``, ``SHAKE-256(192)`` +- h: ``5``, ``10``, ``15``, ``20``, ``25`` +- w: ``1``, ``2``, ``4``, ``8`` + diff --git a/doc/dev_ref/oids.rst b/doc/dev_ref/oids.rst index 47683e9fc60..62a577ad7e1 100644 --- a/doc/dev_ref/oids.rst +++ b/doc/dev_ref/oids.rst @@ -84,6 +84,8 @@ Values currently assigned are:: SphincsPlus-haraka-256s-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 5 } SphincsPlus-haraka-256f-r3.1 OBJECT IDENTIFIER ::= { SphincsPlus-haraka 6 } + HSS-LMS-Private-Key OBJECT IDENTIFIER ::= { publicKey 13 } + symmetricKey OBJECT IDENTIFIER ::= { randombit 3 } ocbModes OBJECT IDENTIFIER ::= { symmetricKey 2 } diff --git a/src/build-data/oids.txt b/src/build-data/oids.txt index 366fcd4e2f5..781efa92811 100644 --- a/src/build-data/oids.txt +++ b/src/build-data/oids.txt @@ -1,5 +1,5 @@ -# Regenerate with ./src/scripts/dev_tools/oids.py oids > src/lib/asn1/oid_maps.cpp -# AND ./src/scripts/dev_tools/oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp +# Regenerate with ./src/scripts/dev_tools/gen_oids.py oids > src/lib/asn1/oid_maps.cpp +# AND ./src/scripts/dev_tools/gen_oids.py dn_ub > src/lib/x509/x509_dn_ub.cpp # (if you modified something under [dn] # Public key types @@ -47,6 +47,14 @@ 1.3.6.1.4.1.25258.1.10.2 = Dilithium-6x5-AES-r3 1.3.6.1.4.1.25258.1.10.3 = Dilithium-8x7-AES-r3 +# HSS-LMS +# draft-gazdag-x509-hash-sigs-01 +1.2.840.113549.1.9.16.3.17 = HSS-LMS + +# HSS-LMS private key (since the format of an HSS-LMS private key is not specified, +# we define an OID for the one we use) +1.3.6.1.4.1.25258.1.13 = HSS-LMS-Private-Key + # SPHINCS+ OIDs are currently in Botan's private arc 1.3.6.1.4.1.25258.1.12.1.1 = SphincsPlus-shake-128s-r3.1 1.3.6.1.4.1.25258.1.12.1.2 = SphincsPlus-shake-128f-r3.1 diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index 8e4dac4a3f1..d39656b4fd3 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -124,6 +124,10 @@ #include #endif +#if defined(BOTAN_HAS_HSS_LMS) + #include +#endif + #if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE) #include #endif @@ -411,7 +415,8 @@ class Speed final : public Command { "McEliece", "Kyber", "SPHINCS+", - "FrodoKEM" + "FrodoKEM", + "HSS-LMS", }; // clang-format on } @@ -626,6 +631,11 @@ class Speed final : public Command { bench_xmss(provider, msec); } #endif +#if defined(BOTAN_HAS_HSS_LMS) + else if(algo == "HSS-LMS") { + bench_hss_lms(provider, msec); + } +#endif #if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE) else if(algo == "SPHINCS+") { bench_sphincs_plus(provider, msec); @@ -2134,6 +2144,33 @@ class Speed final : public Command { } #endif +#if defined(BOTAN_HAS_HSS_LMS) + void bench_hss_lms(const std::string& provider, std::chrono::milliseconds msec) { + // At first we compare instances with multiple hash functions. LMS trees with + // height 10 are suitable, since they can be used for enough signatures and are + // fast enough for speed testing. + // Afterward, setups with multiple HSS layers are tested + std::vector hss_lms_instances{"SHA-256,HW(10,1)", + "SHAKE-256(256),HW(10,1)", + "SHAKE-256(192),HW(10,1)", + "Truncated(SHA-256,192),HW(10,1)", + "SHA-256,HW(10,1),HW(10,1)", + "SHA-256,HW(10,1),HW(10,1),HW(10,1)"}; + + for(const auto& params : hss_lms_instances) { + auto keygen_timer = make_timer(params, provider, "keygen"); + + std::unique_ptr key( + keygen_timer->run([&] { return Botan::create_private_key("HSS-LMS", rng(), params); })); + + record_result(keygen_timer); + if(bench_pk_sig(*key, params, provider, "", msec) == 1) { + break; + } + } + } +#endif + #if defined(BOTAN_HAS_ZFEC) void bench_zfec(std::chrono::milliseconds msec) { const size_t k = 4; diff --git a/src/lib/asn1/oid_maps.cpp b/src/lib/asn1/oid_maps.cpp index cae0c936de7..bed6e9bda5a 100644 --- a/src/lib/asn1/oid_maps.cpp +++ b/src/lib/asn1/oid_maps.cpp @@ -91,6 +91,7 @@ std::unordered_map OID_Map::load_oid2str_map() { {"1.2.840.113549.1.5.13", "PBE-PKCS5v20"}, {"1.2.840.113549.1.9.1", "PKCS9.EmailAddress"}, {"1.2.840.113549.1.9.14", "PKCS9.ExtensionRequest"}, + {"1.2.840.113549.1.9.16.3.17", "HSS-LMS"}, {"1.2.840.113549.1.9.16.3.18", "ChaCha20Poly1305"}, {"1.2.840.113549.1.9.16.3.6", "KeyWrap.TripleDES"}, {"1.2.840.113549.1.9.16.3.8", "Compression.Zlib"}, @@ -164,6 +165,7 @@ std::unordered_map OID_Map::load_oid2str_map() { {"1.3.6.1.4.1.25258.1.12.3.4", "SphincsPlus-haraka-192f-r3.1"}, {"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"}, + {"1.3.6.1.4.1.25258.1.13", "HSS-LMS-Private-Key"}, {"1.3.6.1.4.1.25258.1.14.1", "FrodoKEM-640-SHAKE"}, {"1.3.6.1.4.1.25258.1.14.2", "FrodoKEM-976-SHAKE"}, {"1.3.6.1.4.1.25258.1.14.3", "FrodoKEM-1344-SHAKE"}, @@ -405,6 +407,8 @@ std::unordered_map OID_Map::load_str2oid_map() { {"HMAC(SHA-384)", OID({1, 2, 840, 113549, 2, 10})}, {"HMAC(SHA-512)", OID({1, 2, 840, 113549, 2, 11})}, {"HMAC(SHA-512-256)", OID({1, 2, 840, 113549, 2, 13})}, + {"HSS-LMS", OID({1, 2, 840, 113549, 1, 9, 16, 3, 17})}, + {"HSS-LMS-Private-Key", OID({1, 3, 6, 1, 4, 1, 25258, 1, 13})}, {"KeyWrap.AES-128", OID({2, 16, 840, 1, 101, 3, 4, 1, 5})}, {"KeyWrap.AES-192", OID({2, 16, 840, 1, 101, 3, 4, 1, 25})}, {"KeyWrap.AES-256", OID({2, 16, 840, 1, 101, 3, 4, 1, 45})}, diff --git a/src/lib/pubkey/hss_lms/hss.cpp b/src/lib/pubkey/hss_lms/hss.cpp new file mode 100644 index 00000000000..23f0f3706cb --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss.cpp @@ -0,0 +1,410 @@ +/** + * HSS - Hierarchical Signatures System (RFC 8554) + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace Botan { + +namespace { + +/** + * @brief The maximum number of levels in a HSS-LMS tree. + * + * RFC 8554 Section 6: + * The number of levels is denoted as L and is between one + * and eight, inclusive. + */ +constexpr HSS_Level HSS_MAX_LEVELS(8); + +/** + * @brief Domain-separation parameter for generation the seed of a child LMS tree. + * + * This comes from https://github.com/cisco/hash-sigs. + */ +constexpr uint16_t SEED_CHILD_SEED = 0xfffe; + +/** + * @brief Domain-separation parameter for generation the identifier of a child LMS tree. + * + * This comes from https://github.com/cisco/hash-sigs. + */ +constexpr uint16_t SEED_CHILD_I = 0xffff; + +/** + * @brief Check that the given @p hash_name is one of the supported hash functions for HSS-LMS. + */ +constexpr bool is_supported_hash_function(std::string_view hash_name) { + return hash_name == "SHA-256" || hash_name == "Truncated(SHA-256,192)" || hash_name == "SHAKE-256(256)" || + hash_name == "SHAKE-256(192)"; +} + +/** + * Given an HSS index, i.e. the number of already created HSS signatures, return the lms leaf indices for + * the different LMS layers from root layer to bottom layer. + */ +std::vector derive_lms_leaf_indices_from_hss_index(HSS_Sig_Idx hss_idx, + const HSS_LMS_Params& hss_params) { + std::vector q(hss_params.L().get()); + for(int32_t layer_ctr = hss_params.L().get() - 1; layer_ctr >= 0; --layer_ctr) { + HSS_Level layer(layer_ctr); + const HSS_LMS_Params::LMS_LMOTS_Params_Pair& layer_params = hss_params.params_at_level(layer); + size_t layer_h = layer_params.lms_params().h(); + q.at(layer.get()) = + checked_cast_to(hss_idx.get() % checked_cast_to(1ULL << layer_h)); + hss_idx = hss_idx >> layer_h; + } + BOTAN_ARG_CHECK(hss_idx == HSS_Sig_Idx(0), "HSS Tree is exhausted"); + + return q; +} + +} // namespace + +HSS_LMS_Params::HSS_LMS_Params(std::vector lm_lmots_params) : + m_lms_lmots_params(std::move(lm_lmots_params)), m_max_sig_count(calc_max_sig_count()) { + BOTAN_ARG_CHECK(!m_lms_lmots_params.empty() && m_lms_lmots_params.size() <= HSS_MAX_LEVELS, + "Invalid number of levels"); +} + +HSS_LMS_Params::HSS_LMS_Params(std::string_view algo_params) { + SCAN_Name scan(fmt("HSS-LMS({})", algo_params)); + + BOTAN_ARG_CHECK(scan.arg_count() >= 2 && scan.arg_count() <= HSS_MAX_LEVELS + 1, "Invalid number of arguments"); + std::string hash = scan.arg(0); + BOTAN_ARG_CHECK(is_supported_hash_function(hash), "Supported HSS-LMS hash function"); + + for(size_t i = 1; i < scan.arg_count(); ++i) { + SCAN_Name scan_layer(scan.arg(i)); + BOTAN_ARG_CHECK(scan_layer.algo_name() == "HW", "Invalid name for layer parameters"); + BOTAN_ARG_CHECK(scan_layer.arg_count() == 2, "Invalid number of layer parameters"); + const auto h = + checked_cast_to_or_throw(scan_layer.arg_as_integer(0), "Invalid tree height"); + const auto w = checked_cast_to_or_throw(scan_layer.arg_as_integer(1), + "Invalid Winternitz parameter"); + m_lms_lmots_params.push_back({LMS_Params::create_or_throw(hash, h), LMOTS_Params::create_or_throw(hash, w)}); + } + m_max_sig_count = calc_max_sig_count(); +} + +HSS_Sig_Idx HSS_LMS_Params::calc_max_sig_count() const { + uint32_t total_height_counter = 0; + for(HSS_Level level(0); level < L(); level++) { + total_height_counter += params_at_level(level).lms_params().h(); + } + if(total_height_counter >= sizeof(HSS_Sig_Idx) * 8) { + return HSS_Sig_Idx(std::numeric_limits::max()); + } + return HSS_Sig_Idx(1) << total_height_counter; +} + +HSS_LMS_PrivateKeyInternal::HSS_LMS_PrivateKeyInternal(const HSS_LMS_Params& hss_params, RandomNumberGenerator& rng) : + m_hss_params(hss_params), m_current_idx(0), m_sig_size(HSS_Signature::size(m_hss_params)) { + m_hss_seed = rng.random_vec(m_hss_params.params_at_level(HSS_Level(0)).lms_params().m()); + m_identifier = rng.random_vec(LMS_IDENTIFIER_LEN); +} + +std::shared_ptr HSS_LMS_PrivateKeyInternal::from_bytes_or_throw( + std::span key_bytes) { + if(key_bytes.size() < sizeof(HSS_Level) + sizeof(HSS_Sig_Idx)) { + throw Decoding_Error("Too few private key bytes."); + } + BufferSlicer slicer(key_bytes); + + const auto L = load_be(slicer.take()); + if(L == 0U || L > HSS_MAX_LEVELS) { + throw Decoding_Error("Invalid number of HSS layers in private HSS-LMS key."); + } + + const auto sig_idx = load_be(slicer.take()); + + std::vector params; + for(size_t layer = 1; layer <= L; ++layer) { + if(slicer.remaining() < sizeof(LMS_Algorithm_Type) + sizeof(LMOTS_Algorithm_Type)) { + throw Decoding_Error("Out of bytes while parsing private HSS-LMS key."); + } + const auto lms_type = load_be(slicer.take()); + const auto lmots_type = load_be(slicer.take()); + params.push_back({LMS_Params::create_or_throw(lms_type), LMOTS_Params::create_or_throw(lmots_type)}); + } + std::string hash_name = params.at(0).lms_params().hash_name(); + if(std::any_of(params.begin(), params.end(), [&hash_name](HSS_LMS_Params::LMS_LMOTS_Params_Pair& lms_lmots_params) { + bool invalid_lmots_hash = lms_lmots_params.lmots_params().hash_name() != hash_name; + bool invalid_lms_hash = lms_lmots_params.lms_params().hash_name() != hash_name; + return invalid_lmots_hash || invalid_lms_hash; + })) { + throw Decoding_Error("Inconsistent hash functions are not allowed."); + } + + if(slicer.remaining() < params.at(0).lms_params().m() + LMS_IDENTIFIER_LEN) { + throw Decoding_Error("Out of bytes while parsing private HSS-LMS key."); + } + auto hss_seed = slicer.copy(params.at(0).lms_params().m()); + auto identifier = slicer.copy(LMS_IDENTIFIER_LEN); + + if(!slicer.empty()) { + throw Decoding_Error("Private HSS-LMS key contains more bytes than expected."); + } + auto sk = std::shared_ptr( + new HSS_LMS_PrivateKeyInternal(HSS_LMS_Params(std::move(params)), std::move(hss_seed), std::move(identifier))); + + sk->set_idx(sig_idx); + return sk; +} + +secure_vector HSS_LMS_PrivateKeyInternal::to_bytes() const { + secure_vector sk_bytes(size()); + BufferStuffer stuffer(sk_bytes); + + stuffer.append(store_be(hss_params().L())); + stuffer.append(store_be(get_idx())); + + for(HSS_Level layer(1); layer <= hss_params().L(); ++layer) { + const auto& params = hss_params().params_at_level(layer - 1); + stuffer.append(store_be(params.lms_params().algorithm_type())); + stuffer.append(store_be(params.lmots_params().algorithm_type())); + } + stuffer.append(m_hss_seed); + stuffer.append(m_identifier); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + return sk_bytes; +} + +void HSS_LMS_PrivateKeyInternal::set_idx(HSS_Sig_Idx idx) { + m_current_idx = idx; +} + +HSS_Sig_Idx HSS_LMS_PrivateKeyInternal::reserve_next_idx() { + HSS_Sig_Idx next_idx = m_current_idx; + if(next_idx >= m_hss_params.max_sig_count()) { + throw Decoding_Error("HSS private key is exhausted"); + } + set_idx(m_current_idx + 1); + return next_idx; +} + +size_t HSS_LMS_PrivateKeyInternal::size() const { + size_t sk_size = sizeof(HSS_Level) + sizeof(HSS_Sig_Idx); + // The concatenated algorithm types for all layers + sk_size += hss_params().L().get() * (sizeof(LMS_Algorithm_Type) + sizeof(LMOTS_Algorithm_Type)); + sk_size += m_hss_seed.size() + m_identifier.size(); + return sk_size; +} + +HSS_LMS_PrivateKeyInternal::HSS_LMS_PrivateKeyInternal(HSS_LMS_Params hss_params, + LMS_Seed hss_seed, + LMS_Identifier identifier) : + m_hss_params(std::move(hss_params)), + m_hss_seed(std::move(hss_seed)), + m_identifier(std::move(identifier)), + m_current_idx(0), + m_sig_size(HSS_Signature::size(m_hss_params)) { + BOTAN_ARG_CHECK(m_hss_seed.size() == m_hss_params.params_at_level(HSS_Level(0)).lms_params().m(), + "Invalid seed size"); + BOTAN_ARG_CHECK(m_identifier.size() == LMS_IDENTIFIER_LEN, "Invalid identifier size"); +} + +secure_vector HSS_LMS_PrivateKeyInternal::sign(std::span msg) { + secure_vector sig(HSS_Signature::size(hss_params())); + BufferStuffer sig_stuffer(sig); + sig_stuffer.append(store_be(hss_params().L() - 1)); + + std::vector q = derive_lms_leaf_indices_from_hss_index(reserve_next_idx(), hss_params()); + + // Derive LMS private keys and compute buffers + std::vector lms_key_at_layer; + std::vector> out_lms_sig_buffer_at_layer; + std::vector> out_child_pk_buffer_at_layer; + for(HSS_Level layer(0); layer < hss_params().L(); ++layer) { + // Generate key for current layer + const HSS_LMS_Params::LMS_LMOTS_Params_Pair& layer_params = hss_params().params_at_level(layer); + if(layer == HSS_Level(0)) { + lms_key_at_layer.push_back(hss_derive_root_lms_private_key()); + } else { + lms_key_at_layer.push_back( + hss_derive_child_lms_private_key(layer_params, lms_key_at_layer.back(), q.at(layer.get() - 1))); + out_child_pk_buffer_at_layer.push_back(sig_stuffer.next(LMS_PublicKey::size(layer_params.lms_params()))); + } + out_lms_sig_buffer_at_layer.push_back(sig_stuffer.next( + LMS_Signature::size(layer_params.lms_params(), layer_params.lmots_params()))); + } + BOTAN_ASSERT_NOMSG(sig_stuffer.full()); + + // Sign and write the signature from bottom layer to root layer + std::vector current_pk; + for(int32_t layer_it = hss_params().L().get() - 1; layer_it >= 0; --layer_it) { + HSS_Level layer(layer_it); + if(layer == hss_params().L() - 1) { + current_pk = + lms_key_at_layer.at(layer.get()) + .sign_and_get_pk(out_lms_sig_buffer_at_layer.at(layer.get()), q.at(layer.get()), LMS_Message(msg)) + .to_bytes(); + } else { + copy_mem(out_child_pk_buffer_at_layer.at(layer.get()), current_pk); + current_pk = + lms_key_at_layer.at(layer.get()) + .sign_and_get_pk(out_lms_sig_buffer_at_layer.at(layer.get()), q.at(layer.get()), LMS_Message(current_pk)) + .to_bytes(); + } + } + + return sig; +} + +LMS_PrivateKey HSS_LMS_PrivateKeyInternal::hss_derive_root_lms_private_key() const { + auto& top_params = hss_params().params_at_level(HSS_Level(0)); + return LMS_PrivateKey(top_params.lms_params(), top_params.lmots_params(), m_identifier, m_hss_seed); +} + +LMS_PrivateKey HSS_LMS_PrivateKeyInternal::hss_derive_child_lms_private_key( + const HSS_LMS_Params::LMS_LMOTS_Params_Pair& child_lms_lmots_params, + const LMS_PrivateKey& parent_sk, + LMS_Tree_Node_Idx parent_q) { + const auto hash = HashFunction::create_or_throw(child_lms_lmots_params.lms_params().hash_name()); + + // CHILD_SEED = H( PARENT_I || PARENT_Q || SEED_CHILD_SEED || 0xff || PARENT_SEED ) + PseudorandomKeyGeneration seed_generator(parent_sk.identifier()); + seed_generator.set_q(parent_q.get()); + seed_generator.set_i(SEED_CHILD_SEED); + seed_generator.set_j(0xff); + auto child_seed = seed_generator.gen(*hash, parent_sk.seed()); + + // CHILD_I = H( PARENT_I || PARENT_Q || SEED_CHILD_I || 0xff || PARENT_SEED ) + seed_generator.set_i(SEED_CHILD_I); + auto child_identifier = seed_generator.gen(*hash, parent_sk.seed()); + child_identifier.resize(LMS_IDENTIFIER_LEN); + + return LMS_PrivateKey(child_lms_lmots_params.lms_params(), + child_lms_lmots_params.lmots_params(), + std::move(child_identifier), + std::move(child_seed)); +} + +HSS_LMS_PublicKeyInternal HSS_LMS_PublicKeyInternal::create(const HSS_LMS_PrivateKeyInternal& hss_sk) { + auto& hss_params = hss_sk.hss_params(); + + const auto root_sk = hss_sk.hss_derive_root_lms_private_key(); + LMS_PublicKey top_pub_key = LMS_PublicKey(root_sk); + + return HSS_LMS_PublicKeyInternal(hss_params.L(), std::move(top_pub_key)); +} + +std::shared_ptr HSS_LMS_PublicKeyInternal::from_bytes_or_throw( + std::span key_bytes) { + if(key_bytes.size() < sizeof(HSS_Level)) { + throw Decoding_Error("Too few public key bytes."); + } + BufferSlicer slicer(key_bytes); + + const auto L = load_be(slicer.take()); + if(L > HSS_MAX_LEVELS) { + throw Decoding_Error("Invalid number of HSS layers in public HSS-LMS key."); + } + + LMS_PublicKey lms_pub_key = LMS_PublicKey::from_bytes_or_throw(slicer); + + if(!slicer.empty()) { + throw Decoding_Error("Public HSS-LMS key contains more bytes than expected."); + } + return std::make_shared(L, std::move(lms_pub_key)); +} + +std::vector HSS_LMS_PublicKeyInternal::to_bytes() const { + return concat>(store_be(m_L), m_top_lms_pub_key.to_bytes()); +} + +AlgorithmIdentifier HSS_LMS_PublicKeyInternal::algorithm_identifier() const { + return AlgorithmIdentifier(object_identifier(), AlgorithmIdentifier::USE_EMPTY_PARAM); +} + +OID HSS_LMS_PublicKeyInternal::object_identifier() const { + return OID::from_string(algo_name()); +} + +size_t HSS_LMS_PublicKeyInternal::size() const { + return sizeof(m_L) + LMS_PublicKey::size(m_top_lms_pub_key.lms_params()); +} + +bool HSS_LMS_PublicKeyInternal::verify_signature(std::span msg, const HSS_Signature& sig) const { + if(checked_cast_to(sig.Nspk()) + 1 != m_L) { + // HSS levels in the public key does not match with the signature's + return false; + } + + const LMS_PublicKey* lms_pk = &lms_pub_key(); + const auto hash_name = lms_pk->lms_params().hash_name(); + + // Verify the signature by the above layer over the LMS public keys for layer 1 to Nspk. + for(HSS_Level layer(0); layer < sig.Nspk(); ++layer) { + const HSS_Signature::Signed_Pub_Key& signed_pub_key = sig.signed_pub_key(layer); + if(signed_pub_key.public_key().lms_params().hash_name() != hash_name || + signed_pub_key.public_key().lmots_params().hash_name() != hash_name) { + // We do not allow HSS-LMS instances with multiple different hash functions. + return false; + } + if(!lms_pk->verify_signature(LMS_Message(signed_pub_key.public_key().to_bytes()), signed_pub_key.signature())) { + return false; + } + lms_pk = &signed_pub_key.public_key(); + } + + // Verify the signature by the bottom layer over the message. + return lms_pk->verify_signature(LMS_Message(msg), sig.bottom_sig()); +} + +HSS_Signature::Signed_Pub_Key::Signed_Pub_Key(LMS_Signature sig, LMS_PublicKey pub) : + m_sig(std::move(sig)), m_pub(std::move(pub)) {} + +HSS_Signature HSS_Signature::from_bytes_or_throw(std::span sig_bytes) { + if(sig_bytes.size() < sizeof(uint32_t)) { + throw Decoding_Error("Too few HSS signature bytes."); + } + BufferSlicer slicer(sig_bytes); + + const auto Nspk = load_be(slicer.take()); + if(Nspk >= HSS_MAX_LEVELS) { + throw Decoding_Error("Invalid number of HSS layers in signature."); + } + + std::vector signed_pub_keys; + for(size_t i = 0; i < Nspk; ++i) { + LMS_Signature sig = LMS_Signature::from_bytes_or_throw(slicer); + LMS_PublicKey pub_key = LMS_PublicKey::from_bytes_or_throw(slicer); + signed_pub_keys.push_back(Signed_Pub_Key(std::move(sig), std::move(pub_key))); + } + + auto sig = LMS_Signature::from_bytes_or_throw(slicer); + + if(!slicer.empty()) { + throw Decoding_Error("HSS-LMS signature contains more bytes than expected."); + } + return HSS_Signature(std::move(signed_pub_keys), std::move(sig)); +} + +size_t HSS_Signature::size(const HSS_LMS_Params& params) { + size_t size = sizeof(uint32_t); + size += LMS_Signature::size(params.params_at_level(HSS_Level(0)).lms_params(), + params.params_at_level(HSS_Level(0)).lmots_params()); + for(HSS_Level layer(1); layer < params.L(); ++layer) { + const auto& param = params.params_at_level(layer); + size += LMS_PublicKey::size(param.lms_params()); + size += LMS_Signature::size(param.lms_params(), param.lmots_params()); + } + return size; +} + +} // namespace Botan diff --git a/src/lib/pubkey/hss_lms/hss.h b/src/lib/pubkey/hss_lms/hss.h new file mode 100644 index 00000000000..e640c769482 --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss.h @@ -0,0 +1,392 @@ +/** + * HSS - Hierarchical Signatures System (RFC 8554) + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_HSS_H_ +#define BOTAN_HSS_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +/** + * @brief The index of a node within a specific LMS tree layer + */ +using HSS_Sig_Idx = Strong; + +/** + * @brief The HSS layer in the HSS multi tree starting at 0 from the root + */ +using HSS_Level = Strong; + +/** + * @brief The HSS-LMS parameters. + * + * See RFC 8554 Section 6. + */ +class BOTAN_TEST_API HSS_LMS_Params final { + public: + /** + * @brief Represents a pair of LMS and LMOTS parameters associated with one LMS tree layer. + */ + class LMS_LMOTS_Params_Pair { + public: + /** + * @brief The LMS parameters. + */ + const LMS_Params& lms_params() const { return m_lms_params; } + + /** + * @brief The LMOTS parameters. + */ + const LMOTS_Params& lmots_params() const { return m_lmots_params; } + + /** + * @brief Construct a new params pair + */ + LMS_LMOTS_Params_Pair(LMS_Params p_lms_params, LMOTS_Params p_lmots_params) : + m_lms_params(std::move(p_lms_params)), m_lmots_params(std::move(p_lmots_params)) {} + + private: + LMS_Params m_lms_params; + LMOTS_Params m_lmots_params; + }; + + /** + * @brief Construct the HSS-LMS parameters from a vector LMS and LM-OTS parameters. + */ + explicit HSS_LMS_Params(std::vector lm_lmots_params); + + /** + * @brief Construct the HSS-LMS parameters form an algorithm parameter string. + * + * The HSS/LMS instance to use for creating new keys is defined using an algorithm parameter string, + * i.e. to define which hash function (hash), LMS tree hights (h) + * and OTS Winternitz coefficient widths (w) to use. The syntax is the following: + * + * HSS-LMS(,HW(,),HW(,),...) + * + * e.g. 'HSS-LMS(SHA-256,HW(5,1),HW(5,1))' to use SHA-256 in a two-layer HSS instance + * with a LMS tree height 5 and w=1. The following parameters are allowed (which are + * specified in RFC 8554 and draft-fluhrer-lms-more-parm-sets-11): + * + * hash: 'SHA-256', 'Truncated(SHA-256,192)', 'SHAKE-256(256)', SHAKE-256(192) + * h: '5', '10', '15', '20', '25' + * w: '1', '2', '4', '8' + * + * Note: The selected hash function is also used for seed derivation. + */ + explicit HSS_LMS_Params(std::string_view algo_params); + + /** + * @brief Returns the LMS an LM-OTS parameters at the specified @p level of the HSS tree. + */ + const LMS_LMOTS_Params_Pair& params_at_level(HSS_Level level) const { return m_lms_lmots_params.at(level.get()); } + + /** + * @brief Returns the number of layers the HSS tree has. + */ + HSS_Level L() const { return checked_cast_to(m_lms_lmots_params.size()); } + + /** + * @brief The maximal number of signatures allowed for these HSS parameters + */ + HSS_Sig_Idx max_sig_count() const { return m_max_sig_count; } + + private: + /** + * @brief Compute the maximal number of signatures + */ + HSS_Sig_Idx calc_max_sig_count() const; + + std::vector m_lms_lmots_params; + HSS_Sig_Idx m_max_sig_count; +}; + +/** + * @brief The internal HSS-LMS private key. + * + * Note that the format is not specified in the RFC 8554, + * and is Botan specific. + */ +class BOTAN_TEST_API HSS_LMS_PrivateKeyInternal final { + public: + /** + * @brief Create an internal HSS-LMS private key. + * + * @param hss_params The HSS-LMS parameters for the key. + * @param rng The rng to use. + */ + HSS_LMS_PrivateKeyInternal(const HSS_LMS_Params& hss_params, RandomNumberGenerator& rng); + + /** + * @brief Parse a private HSS-LMS key. + * + * @param key_bytes The private key bytes to parse. + * @return The internal HSS-LMS private key. + * @throws Decoding_Error If parsing the private key fails. + */ + static std::shared_ptr from_bytes_or_throw(std::span key_bytes); + + /** + * @brief Returns the used HSS-LMS parameters. + */ + const HSS_LMS_Params& hss_params() const { return m_hss_params; } + + /** + * @brief Returns the key in its encoded format. + */ + secure_vector to_bytes() const; + + /** + * @brief Get the idx of the next signature to generate. + */ + HSS_Sig_Idx get_idx() const { return m_current_idx; } + + /** + * @brief Set the idx of the next signature to generate. + * + * Note that creating two signatures with the same index is insecure. + * The index must be lower than hss_params().max_sig_count(). + */ + void set_idx(HSS_Sig_Idx idx); + + /** + * @brief Create a HSS-LMS signature. + * + * See RFC 8554 6.2 - Algorithm 8. + * + * For each signature creation the hypertree is computed once + * again, so no data is stored between multiple signatures. However, + * storing data between multiple signatures could be an optimization + * if applications create multiple signatures in one go. + * + * @param msg The message to sign. + */ + secure_vector sign(std::span msg); + + /** + * @brief Create the HSS root LMS tree's LMS_PrivateKey using the HSS-LMS private key. + * + * We use the same generation as the reference implementation (https://github.com/cisco/hash-sigs) + * with SECRET_METHOD==2. + * + * @return The LMS private key + */ + LMS_PrivateKey hss_derive_root_lms_private_key() const; + + /** + * @brief Returns the size in bytes of a signature created by this key. + */ + size_t signature_size() const { return m_sig_size; } + + private: + HSS_LMS_PrivateKeyInternal(HSS_LMS_Params hss_params, LMS_Seed hss_seed, LMS_Identifier identifier); + + /** + * @brief Get the index of the next signature to generate and + * increase the counter by one. + */ + HSS_Sig_Idx reserve_next_idx(); + + /** + * @brief Returns the size in bytes the key would have in its encoded format. + */ + size_t size() const; + + /** + * @brief Derive the seed and identifier of an LMS tree from its parent LMS tree. + * + * We use the same generation as the reference implementation (https://github.com/cisco/hash-sigs). + * + * @param child_lms_lmots_params The LMS-LMOTS parameter pair of the child tree. + * @param parent_sk The parent's LMS private key + * @param parent_q The LMS leaf number the child tree has in its parent tree. + * @return LMS private key + */ + static LMS_PrivateKey hss_derive_child_lms_private_key( + const HSS_LMS_Params::LMS_LMOTS_Params_Pair& child_lms_lmots_params, + const LMS_PrivateKey& parent_sk, + LMS_Tree_Node_Idx parent_q); + + HSS_LMS_Params m_hss_params; + LMS_Seed m_hss_seed; + LMS_Identifier m_identifier; + HSS_Sig_Idx m_current_idx; + const size_t m_sig_size; +}; + +class HSS_Signature; + +/** + * @brief The internal HSS-LMS public key. + * + * Format according to RFC 8554: + * u32str(L) || pub[0] + */ +class BOTAN_TEST_API HSS_LMS_PublicKeyInternal final { + public: + /** + * @brief Create the public HSS-LMS key from its private key. + * + * @param hss_sk The private HSS-LMS key. + * @return The internal HSS-LMS public key. + */ + static HSS_LMS_PublicKeyInternal create(const HSS_LMS_PrivateKeyInternal& hss_sk); + + /** + * @brief Parse a public HSS-LMS key. + * + * @param key_bytes The public key bytes to parse. + * @return The internal HSS-LMS public key. + * @throws Decoding_Error If parsing the public key fails. + */ + static std::shared_ptr from_bytes_or_throw(std::span key_bytes); + + HSS_LMS_PublicKeyInternal(HSS_Level L, LMS_PublicKey top_lms_pub_key) : + m_L(L), m_top_lms_pub_key(std::move(top_lms_pub_key)) {} + + /** + * @brief Returns the key in its encoded format. + */ + std::vector to_bytes() const; + + /** + * @brief Returns the public LMS key of the top LMS tree. + */ + const LMS_PublicKey& lms_pub_key() const { return m_top_lms_pub_key; } + + /** + * @brief Returns the size in bytes the key would have in its encoded format. + */ + size_t size() const; + + /** + * @brief The algorithm identifier for HSS-LMS + */ + AlgorithmIdentifier algorithm_identifier() const; + + /** + * @brief The object identifier for HSS-LMS + */ + OID object_identifier() const; + + /** + * @brief The algorithm name for HSS-LMS + */ + std::string algo_name() const { return "HSS-LMS"; } + + /** + * @brief Verify a HSS-LMS signature. + * + * See RFC 8554 6.3. + * + * @param msg The signed message. + * @param sig The already parsed HSS-LMS signature. + * @return True iff the signature is valid. + */ + bool verify_signature(std::span msg, const HSS_Signature& sig) const; + + private: + HSS_Level m_L; + LMS_PublicKey m_top_lms_pub_key; +}; + +/** + * @brief A HSS-LMS signature. + * + * Format according to RFC 8554: + * u32str(Nspk) || sig[0] || pub[1] || ... || sig[Nspk-1] || pub[Nspk] || sig[Nspk] + */ +class BOTAN_TEST_API HSS_Signature final { + public: + /** + * @brief A LMS public key signed by the HSS layer above it. + * + * signed_pub_key[i] = sig[i] || pub[i+1], + * for i between 0 and Nspk-1, inclusive. + */ + class Signed_Pub_Key { + public: + /** + * @brief Constructor for a new sig-pubkey-pair + */ + Signed_Pub_Key(LMS_Signature sig, LMS_PublicKey pub); + + /** + * @brief The signature of the public key + */ + const LMS_Signature& signature() const { return m_sig; } + + /** + * @brief The signed public key + */ + const LMS_PublicKey& public_key() const { return m_pub; } + + private: + LMS_Signature m_sig; + LMS_PublicKey m_pub; + }; + + /** + * @brief Parse a HSS-LMS signature. + * + * @param sig_bytes The signature bytes to parse. + * @return The parsed HSS-LMS signature. + * @throws Decoding_Error If parsing the signature fails. + */ + static HSS_Signature from_bytes_or_throw(std::span sig_bytes); + + /** + * @brief Returns the size a signature would have in its encoded format. + * + * @param params The HSS-LMS parameters. + * @return size_t The expected size in bytes. + */ + static size_t size(const HSS_LMS_Params& params); + + /** + * @brief Returns the number of signed public keys (Nspk = L-1). + */ + HSS_Level Nspk() const { return HSS_Level(static_cast(m_signed_pub_keys.size())); } + + /** + * @brief Returns the signed LMS key signed by a specific layer. + * + * @param layer The layer by which the LMS key is signed. + * @return The LMS key and the signature by its parent layer. + */ + const Signed_Pub_Key& signed_pub_key(HSS_Level layer) const { return m_signed_pub_keys.at(layer.get()); } + + /** + * @brief Returns the LMS signature by the bottom layer of the signed message. + */ + const LMS_Signature& bottom_sig() const { return m_sig; } + + private: + /** + * @brief Private constructor using the individual signature fields. + */ + HSS_Signature(std::vector signed_pub_keys, LMS_Signature sig) : + m_signed_pub_keys(std::move(signed_pub_keys)), m_sig(std::move(sig)) {} + + std::vector m_signed_pub_keys; + LMS_Signature m_sig; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/hss_lms/hss_lms.cpp b/src/lib/pubkey/hss_lms/hss_lms.cpp new file mode 100644 index 00000000000..08b8eaf89d5 --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss_lms.cpp @@ -0,0 +1,196 @@ +/** +* HSS-LMS +* (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) +*/ + +#include + +#include +#include +#include + +namespace Botan { + +HSS_LMS_PublicKey::HSS_LMS_PublicKey(std::span pub_key) : + m_public(HSS_LMS_PublicKeyInternal::from_bytes_or_throw(pub_key)) {} + +HSS_LMS_PublicKey::~HSS_LMS_PublicKey() = default; + +size_t HSS_LMS_PublicKey::key_length() const { + return m_public->size(); +} + +size_t HSS_LMS_PublicKey::estimated_strength() const { + // draft-fluhrer-lms-more-parm-sets-11 Section 9. + // As shown in [Katz16], if we assume that the hash function can be + // modeled as a random oracle, then the security of the system is at + // least 8N-1 bits (where N is the size of the hash output in bytes); + return 8 * m_public->lms_pub_key().lms_params().m() - 1; +} + +std::string HSS_LMS_PublicKey::algo_name() const { + return m_public->algo_name(); +} + +AlgorithmIdentifier HSS_LMS_PublicKey::algorithm_identifier() const { + return m_public->algorithm_identifier(); +} + +OID HSS_LMS_PublicKey::object_identifier() const { + return m_public->object_identifier(); +} + +bool HSS_LMS_PublicKey::check_key(RandomNumberGenerator&, bool) const { + // Nothing to check. Only useful checks are already done during parsing. + return true; +} + +std::vector HSS_LMS_PublicKey::public_key_bits() const { + return m_public->to_bytes(); +} + +class HSS_LMS_Verification_Operation final : public PK_Ops::Verification { + public: + HSS_LMS_Verification_Operation(std::shared_ptr pub_key) : + m_public(std::move(pub_key)) {} + + void update(const uint8_t msg[], size_t msg_len) override { + m_msg_buffer.insert(m_msg_buffer.end(), msg, msg + msg_len); + } + + bool is_valid_signature(const uint8_t* sig, size_t sig_len) override { + std::vector message_to_verify = std::exchange(m_msg_buffer, {}); + try { + const auto signature = HSS_Signature::from_bytes_or_throw({sig, sig_len}); + return m_public->verify_signature(message_to_verify, signature); + } catch(const Decoding_Error&) { + // Signature could not be decoded + return false; + } + } + + std::string hash_function() const override { return m_public->lms_pub_key().lms_params().hash_name(); } + + private: + std::shared_ptr m_public; + std::vector m_msg_buffer; +}; + +std::unique_ptr HSS_LMS_PublicKey::create_verification_op(std::string_view /*params*/, + std::string_view provider) const { + if(provider.empty() || provider == "base") { + return std::make_unique(m_public); + } + throw Provider_Not_Found(algo_name(), provider); +} + +std::unique_ptr HSS_LMS_PublicKey::create_x509_verification_op( + const AlgorithmIdentifier& signature_algorithm, std::string_view provider) const { + if(provider.empty() || provider == "base") { + if(signature_algorithm != this->algorithm_identifier()) { + throw Decoding_Error("Unexpected AlgorithmIdentifier for HSS-LMS signature"); + } + return std::make_unique(m_public); + } + throw Provider_Not_Found(algo_name(), provider); +} + +bool HSS_LMS_PublicKey::supports_operation(PublicKeyOperation op) const { + return op == PublicKeyOperation::Signature; +} + +std::unique_ptr HSS_LMS_PublicKey::generate_another(RandomNumberGenerator&) const { + // For this key type we cannot derive all required parameters from just + // the public key. It is however possible to call HSS_LMS_PrivateKey::generate_another(). + throw Not_Implemented("Cannot generate a new HSS/LMS keypair from a public key"); +} + +HSS_LMS_PrivateKey::HSS_LMS_PrivateKey(std::span private_key) { + m_private = HSS_LMS_PrivateKeyInternal::from_bytes_or_throw(private_key); + m_public = std::make_shared(HSS_LMS_PublicKeyInternal::create(*m_private)); +} + +HSS_LMS_PrivateKey::HSS_LMS_PrivateKey(RandomNumberGenerator& rng, std::string_view algo_params) { + HSS_LMS_Params hss_params(algo_params); + m_private = std::make_shared(hss_params, rng); + m_public = std::make_shared(HSS_LMS_PublicKeyInternal::create(*m_private)); +} + +HSS_LMS_PrivateKey::HSS_LMS_PrivateKey(std::shared_ptr sk) : m_private(std::move(sk)) { + m_public = std::make_shared(HSS_LMS_PublicKeyInternal::create(*m_private)); +} + +HSS_LMS_PrivateKey::~HSS_LMS_PrivateKey() = default; + +secure_vector HSS_LMS_PrivateKey::private_key_bits() const { + return m_private->to_bytes(); +} + +secure_vector HSS_LMS_PrivateKey::raw_private_key_bits() const { + return private_key_bits(); +} + +std::unique_ptr HSS_LMS_PrivateKey::public_key() const { + return std::make_unique(*this); +} + +// We use a separate algorithm identifier for the private key since we use a Botan scoped OID for it. +// This is necessary since the private key format is implementation specific, since it is not defined +// in RFC 8554. +AlgorithmIdentifier HSS_LMS_PrivateKey::pkcs8_algorithm_identifier() const { + return AlgorithmIdentifier(OID::from_string("HSS-LMS-Private-Key"), AlgorithmIdentifier::USE_EMPTY_PARAM); +} + +std::optional HSS_LMS_PrivateKey::remaining_operations() const { + return (m_private->hss_params().max_sig_count() - m_private->get_idx()).get(); +} + +std::unique_ptr HSS_LMS_PrivateKey::generate_another(RandomNumberGenerator& rng) const { + // Cannot use std::make_unique because the utilized constructor is private. + return std::unique_ptr( + new HSS_LMS_PrivateKey(std::make_shared(m_private->hss_params(), rng))); +} + +class HSS_LMS_Signature_Operation final : public PK_Ops::Signature { + public: + HSS_LMS_Signature_Operation(std::shared_ptr private_key, + std::shared_ptr public_key) : + m_private(std::move(private_key)), m_public(std::move(public_key)) {} + + void update(const uint8_t msg[], size_t msg_len) override { + m_msg_buffer.insert(m_msg_buffer.end(), msg, msg + msg_len); + } + + secure_vector sign(RandomNumberGenerator&) override { + std::vector message_to_sign = std::exchange(m_msg_buffer, {}); + return m_private->sign(message_to_sign); + } + + size_t signature_length() const override { return m_private->signature_size(); } + + AlgorithmIdentifier algorithm_identifier() const override { return m_public->algorithm_identifier(); } + + std::string hash_function() const override { return m_public->lms_pub_key().lms_params().hash_name(); } + + private: + std::shared_ptr m_private; + std::shared_ptr m_public; + std::vector m_msg_buffer; +}; + +std::unique_ptr HSS_LMS_PrivateKey::create_signature_op(RandomNumberGenerator& rng, + std::string_view params, + std::string_view provider) const { + BOTAN_UNUSED(rng); + BOTAN_ARG_CHECK(params.empty(), "Unexpected parameters for signing with HSS-LMS"); + + if(provider.empty() || provider == "base") { + return std::make_unique(m_private, m_public); + } + throw Provider_Not_Found(algo_name(), provider); +} + +} // namespace Botan diff --git a/src/lib/pubkey/hss_lms/hss_lms.h b/src/lib/pubkey/hss_lms/hss_lms.h new file mode 100644 index 00000000000..a400874b85e --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss_lms.h @@ -0,0 +1,162 @@ +/* + * Hierarchical Signature System (HSS) / Leighton-Micali Signature (LMS) + * hash-based signature algorithm (RFC 8554). + * + * (C) 2023 Jack Lloyd + * 2023 Philippe Lieser, Fabian Albert - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + **/ + +#ifndef BOTAN_HSS_LMS_H_ +#define BOTAN_HSS_LMS_H_ + +#include + +#include +#include + +namespace Botan { + +class HSS_LMS_PublicKeyInternal; +class HSS_LMS_PrivateKeyInternal; + +/** + * @brief An HSS/LMS public key. + * + * Implementation of the Hierarchical Signature System (HSS) of + * Leighton-Micali Hash-Based Signatures (LMS) defined in RFC 8554 + * (https://www.rfc-editor.org/rfc/rfc8554.html). + * + * To derive seeds for single LMS trees in the HSS-multitree, the method (SECRET_METHOD 2) + * of the reference implementation (https://github.com/cisco/hash-sigs) is used. + */ +class BOTAN_PUBLIC_API(3, 5) HSS_LMS_PublicKey : public virtual Public_Key { + public: + /** + * @brief Load an existing public key using its bytes. + */ + HSS_LMS_PublicKey(std::span pub_key_bytes); + + ~HSS_LMS_PublicKey() override; + + size_t key_length() const override; + + std::string algo_name() const override; + + size_t estimated_strength() const override; + AlgorithmIdentifier algorithm_identifier() const override; + OID object_identifier() const override; + bool check_key(RandomNumberGenerator& rng, bool strong) const override; + std::vector public_key_bits() const override; + + std::unique_ptr create_verification_op(std::string_view params, + std::string_view provider) const override; + + std::unique_ptr create_x509_verification_op(const AlgorithmIdentifier& signature_algorithm, + std::string_view provider) const override; + + bool supports_operation(PublicKeyOperation op) const override; + + /** + * @throws Not_Implemented for LMS public keys. + */ + std::unique_ptr generate_another(RandomNumberGenerator& rng) const override; + + protected: + HSS_LMS_PublicKey() = default; + + std::shared_ptr m_public; +}; + +BOTAN_DIAGNOSTIC_PUSH +BOTAN_DIAGNOSTIC_IGNORE_INHERITED_VIA_DOMINANCE + +/** + * @brief An HSS/LMS private key. + * + * HSS/LMS is a statefule hash-based signature scheme. This means the private key must + * be (securely) updated after using it for signing. Also, there is a maximal number + * of signatures that can be created using one HSS/LMS key pair, which depends on + * the number and size of LMS layers of the chosen HSS/LMS instance. For the selection + * of a sensible parameter set, refer to RFC 8554 6.4. + * + * The format of the HSS/LMS private key is not defined in + * RFC 8554. We use the following format (big endian): + * + * PrivateKey = u32str(L) || u64str(idx) || + * u32str(LMS algorithm id (root layer)) || u32str(LMOTS algorithm id (root layer)) || + * ... || + * u32str(LMS algorithm id (bottom layer)) || u32str(LMOTS algorithm id (bottom layer)) || + * HSS_SEED || HSS_Identifier + * + * L: Number of LMS layers + * Idx: Number of signatures already created using this private key + * HSS_SEED: Seed to derive LMS Seeds (see RFC 8554 Appendix A) like in SECRET_METHOD 2 of + * https://github.com/cisco/hash-sigs. As long as the hash functions output length. + * HSS_Identifier: 16 bytes long. + * + * The HSS/LMS instance to use for creating new keys is defined using an algorithm parameter sting, + * i.e. to define which hash function (hash), LMS tree height (h) + * and OTS Winternitz coefficient widths (w) to use. The syntax is the following: + * + * HSS-LMS(,HW(,),HW(,),...) + * + * e.g. 'HSS-LMS(SHA-256,HW(5,1),HW(5,1))' to use SHA-256 in a two-layer HSS instance + * with a LMS tree hights 5 and w=1. The following parameters are allowed (which are + * specified in RFC 8554 and draft-fluhrer-lms-more-parm-sets-11): + * + * hash: 'SHA-256', 'Truncated(SHA-256,192)', 'SHAKE-256(256)', SHAKE-256(192) + * h: '5', '10', '15', '20', '25' + * w: '1', '2', '4', '8' + * + * Note: The selected hash function is also used for seed derivation. + */ +class BOTAN_PUBLIC_API(3, 5) HSS_LMS_PrivateKey final : public virtual HSS_LMS_PublicKey, + public virtual Private_Key { + public: + /** + * @brief Load an existing LMS private key using its bytes + */ + HSS_LMS_PrivateKey(std::span private_key_bytes); + + /** + * @brief Construct a new hss lms privatekey object. + * + * @param rng random number generator + * @param algo_params string is format 'HSS-LMS(,HW(,),HW(,),...)' + */ + HSS_LMS_PrivateKey(RandomNumberGenerator& rng, std::string_view algo_params); + + ~HSS_LMS_PrivateKey() override; + + secure_vector private_key_bits() const override; + secure_vector raw_private_key_bits() const override; + std::unique_ptr public_key() const override; + + AlgorithmIdentifier pkcs8_algorithm_identifier() const override; + + bool stateful_operation() const override { return true; } + + /** + * Retrieves the number of remaining signatures for this private key. + */ + std::optional remaining_operations() const override; + + std::unique_ptr generate_another(RandomNumberGenerator& rng) const override; + + std::unique_ptr create_signature_op(RandomNumberGenerator& rng, + std::string_view params, + std::string_view provider) const override; + + private: + HSS_LMS_PrivateKey(std::shared_ptr sk); + + std::shared_ptr m_private; +}; + +BOTAN_DIAGNOSTIC_POP + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/hss_lms/hss_lms_utils.cpp b/src/lib/pubkey/hss_lms/hss_lms_utils.cpp new file mode 100644 index 00000000000..fa28e3773bd --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss_lms_utils.cpp @@ -0,0 +1,32 @@ +/** + * Utils for HSS/LMS + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include + +namespace Botan { + +// The magic numbers in the initializer list below reflect the structure of the +// m_input_buffer member and must be updated if any of the pre-defined +// std::span<>s are changed. +PseudorandomKeyGeneration::PseudorandomKeyGeneration(std::span identifier) : + m_input_buffer(identifier.size() + 7), + m_q(std::span(m_input_buffer).last<7>().first<4>()), + m_i(std::span(m_input_buffer).last<3>().first<2>()), + m_j(std::span(m_input_buffer).last<1>()) { + copy_mem(std::span(m_input_buffer).first(identifier.size()), identifier); +} + +void PseudorandomKeyGeneration::gen(std::span out, HashFunction& hash, std::span seed) const { + hash.update(m_input_buffer); + hash.update(seed); + hash.final(out); +} + +} // namespace Botan diff --git a/src/lib/pubkey/hss_lms/hss_lms_utils.h b/src/lib/pubkey/hss_lms/hss_lms_utils.h new file mode 100644 index 00000000000..e55adebd5a7 --- /dev/null +++ b/src/lib/pubkey/hss_lms/hss_lms_utils.h @@ -0,0 +1,76 @@ +/** + * Utils for HSS/LMS + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_HSS_LMS_UTILS_H_ +#define BOTAN_HSS_LMS_UTILS_H_ + +#include +#include + +namespace Botan { + +/** + * @brief Helper class used to derive secret values based in the pseudorandom key generation + * described in RFC 8554 Appendix A. + * + * This generation computes the following: + * + * Result = Hash( identifier || u32str(q) || u16str(i) || u8str(j) || SEED ) + * + * This Key Generation procedure is also used for the seed derivation function of + * SECRET_METHOD 2 defined in https://github.com/cisco/hash-sigs, + */ +class PseudorandomKeyGeneration { + public: + /** + * @brief Create a PseudorandomKeyGeneration instance for a fixed @p identifier + */ + PseudorandomKeyGeneration(std::span identifier); + + /** + * @brief Specify the value for the u32str(q) hash input field + */ + void set_q(uint32_t q) { store_be(m_q, q); } + + /** + * @brief Specify the value for the u16str(i) hash input field + */ + void set_i(uint16_t i) { store_be(m_i, i); } + + /** + * @brief Specify the value for the u8str(j) hash input field + */ + void set_j(uint8_t j) { store_be(m_j, j); } + + /** + * @brief Create a hash value using the preconfigured prefix and a @p seed + */ + template > + T gen(HashFunction& hash, std::span seed) const { + T output(hash.output_length()); + gen(output, hash, seed); + return output; + } + + /** + * @brief Create a hash value using the preconfigured prefix and a @p seed + */ + void gen(std::span out, HashFunction& hash, std::span seed) const; + + private: + /// Input buffer containing the prefix: 'identifier || u32str(q) || u16str(i) || u8str(j)' + std::vector m_input_buffer; + + std::span m_q; + std::span m_i; + std::span m_j; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/hss_lms/info.txt b/src/lib/pubkey/hss_lms/info.txt new file mode 100644 index 00000000000..56dc9e04a48 --- /dev/null +++ b/src/lib/pubkey/hss_lms/info.txt @@ -0,0 +1,26 @@ + +HSS_LMS -> 20230925 + + + +name -> "HSS-LMS" + + + +hss_lms.h + + + +hss_lms_utils.h +lms.h +lm_ots.h +hss.h + + + +rng +sha2_32 +shake +trunc_hash +tree_hash + diff --git a/src/lib/pubkey/hss_lms/lm_ots.cpp b/src/lib/pubkey/hss_lms/lm_ots.cpp new file mode 100644 index 00000000000..146d7aa4d3d --- /dev/null +++ b/src/lib/pubkey/hss_lms/lm_ots.cpp @@ -0,0 +1,353 @@ +/** + * LM-OTS - Leighton-Micali One-Time Signatures + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { +constexpr uint16_t D_PBLC = 0x8080; +constexpr uint16_t D_MESG = 0x8181; +/// For derivation of C as in https://github.com/cisco/hash-sigs +constexpr uint16_t C_INDEX = 0xFFFD; + +class Chain_Generator { + public: + Chain_Generator(const LMS_Identifier& identifier, LMS_Tree_Node_Idx q) : m_gen(identifier) { + m_gen.set_q(q.get()); + } + + void process(HashFunction& hash, + uint16_t chain_idx, + uint8_t start, + uint8_t end, + std::span in, + std::span out) { + BOTAN_ARG_CHECK(start <= end, "Start value is bigger than end value"); + + copy_mem(out, in); + m_gen.set_i(chain_idx); + + for(uint8_t j = start; j < end; ++j) { + m_gen.set_j(j); + m_gen.gen(out, hash, out); + } + } + + private: + PseudorandomKeyGeneration m_gen; +}; + +// RFC 8554 3.1.1 +uint8_t byte(std::span S, uint32_t i) { + BOTAN_ARG_CHECK(i < S.size(), "Index out of range"); + return S[i]; +} + +// RFC 8554 3.1.3 +uint8_t coef(std::span S, uint32_t i, const LMOTS_Params& params) { + const uint8_t w_bit_mask = params.coef_max(); + const uint8_t coef_byte = byte(S, (i * params.w()) / 8); + const uint8_t shift = 8 - (params.w() * (i % (8 / params.w())) + params.w()); + + return w_bit_mask & (coef_byte >> shift); +} + +// RFC 8554 4.4 +uint16_t checksum(const LMOTS_Params& params, std::span S) { + size_t sum = 0; + for(uint32_t i = 0; i < (params.n() * 8 / params.w()); ++i) { + sum += params.coef_max() - coef(S, i, params); + } + return checked_cast_to(sum << params.ls()); +} + +std::vector gen_Q_with_cksm(const LMOTS_Params& params, + const LMS_Identifier& identifier, + const LMS_Tree_Node_Idx& q, + std::span C, + const LMS_Message& msg) { + std::vector Q_with_cksm(params.n() + sizeof(uint16_t)); + BufferStuffer qwc_stuffer(Q_with_cksm); + const auto hash = params.hash(); + hash->update(identifier); + hash->update(store_be(q)); + hash->update(store_be(D_MESG)); + hash->update(C); + hash->update(msg); + auto Q_span = qwc_stuffer.next(params.n()); + hash->final(Q_span); + + qwc_stuffer.append(store_be(checksum(params, Q_span))); + + return Q_with_cksm; +} + +} // namespace + +LMOTS_Params LMOTS_Params::create_or_throw(LMOTS_Algorithm_Type type) { + auto [hash_name, w] = [](const LMOTS_Algorithm_Type& lmots_type) -> std::pair { + switch(lmots_type) { + case LMOTS_Algorithm_Type::SHA256_N32_W1: + return {"SHA-256", 1}; + case LMOTS_Algorithm_Type::SHA256_N32_W2: + return {"SHA-256", 2}; + case LMOTS_Algorithm_Type::SHA256_N32_W4: + return {"SHA-256", 4}; + case LMOTS_Algorithm_Type::SHA256_N32_W8: + return {"SHA-256", 8}; + case LMOTS_Algorithm_Type::SHA256_N24_W1: + return {"Truncated(SHA-256,192)", 1}; + case LMOTS_Algorithm_Type::SHA256_N24_W2: + return {"Truncated(SHA-256,192)", 2}; + case LMOTS_Algorithm_Type::SHA256_N24_W4: + return {"Truncated(SHA-256,192)", 4}; + case LMOTS_Algorithm_Type::SHA256_N24_W8: + return {"Truncated(SHA-256,192)", 8}; + case LMOTS_Algorithm_Type::SHAKE_N32_W1: + return {"SHAKE-256(256)", 1}; + case LMOTS_Algorithm_Type::SHAKE_N32_W2: + return {"SHAKE-256(256)", 2}; + case LMOTS_Algorithm_Type::SHAKE_N32_W4: + return {"SHAKE-256(256)", 4}; + case LMOTS_Algorithm_Type::SHAKE_N32_W8: + return {"SHAKE-256(256)", 8}; + case LMOTS_Algorithm_Type::SHAKE_N24_W1: + return {"SHAKE-256(192)", 1}; + case LMOTS_Algorithm_Type::SHAKE_N24_W2: + return {"SHAKE-256(192)", 2}; + case LMOTS_Algorithm_Type::SHAKE_N24_W4: + return {"SHAKE-256(192)", 4}; + case LMOTS_Algorithm_Type::SHAKE_N24_W8: + return {"SHAKE-256(192)", 8}; + case LMOTS_Algorithm_Type::RESERVED: + throw Decoding_Error("Unsupported LMS algorithm type"); + } + throw Decoding_Error("Unsupported LMS algorithm type"); + }(type); + + return LMOTS_Params(type, hash_name, w); +} + +LMOTS_Params LMOTS_Params::create_or_throw(std::string_view hash_name, uint8_t w) { + if(w != 1 && w != 2 && w != 4 && w != 8) { + throw Decoding_Error("Invalid Winternitz parameter"); + } + LMOTS_Algorithm_Type type = [](std::string_view hash, uint8_t w_p) -> LMOTS_Algorithm_Type { + if(hash == "SHA-256") { + switch(w_p) { + case 1: + return LMOTS_Algorithm_Type::SHA256_N32_W1; + case 2: + return LMOTS_Algorithm_Type::SHA256_N32_W2; + case 4: + return LMOTS_Algorithm_Type::SHA256_N32_W4; + case 8: + return LMOTS_Algorithm_Type::SHA256_N32_W8; + default: + throw Decoding_Error("Unsupported Winternitz parameter"); + } + } + if(hash == "Truncated(SHA-256,192)") { + switch(w_p) { + case 1: + return LMOTS_Algorithm_Type::SHA256_N24_W1; + case 2: + return LMOTS_Algorithm_Type::SHA256_N24_W2; + case 4: + return LMOTS_Algorithm_Type::SHA256_N24_W4; + case 8: + return LMOTS_Algorithm_Type::SHA256_N24_W8; + default: + throw Decoding_Error("Unsupported Winternitz parameter"); + } + } + if(hash == "SHAKE-256(256)") { + switch(w_p) { + case 1: + return LMOTS_Algorithm_Type::SHAKE_N32_W1; + case 2: + return LMOTS_Algorithm_Type::SHAKE_N32_W2; + case 4: + return LMOTS_Algorithm_Type::SHAKE_N32_W4; + case 8: + return LMOTS_Algorithm_Type::SHAKE_N32_W8; + default: + throw Decoding_Error("Unsupported Winternitz parameter"); + } + } + if(hash == "SHAKE-256(192)") { + switch(w_p) { + case 1: + return LMOTS_Algorithm_Type::SHAKE_N24_W1; + case 2: + return LMOTS_Algorithm_Type::SHAKE_N24_W2; + case 4: + return LMOTS_Algorithm_Type::SHAKE_N24_W4; + case 8: + return LMOTS_Algorithm_Type::SHAKE_N24_W8; + default: + throw Decoding_Error("Unsupported Winternitz parameter"); + } + } + throw Decoding_Error("Unsupported hash function"); + }(hash_name, w); + + return LMOTS_Params(type, hash_name, w); +} + +LMOTS_Params::LMOTS_Params(LMOTS_Algorithm_Type algorithm_type, std::string_view hash_name, uint8_t w) : + m_algorithm_type(algorithm_type), m_w(w), m_hash_name(hash_name) { + const auto hash = HashFunction::create_or_throw(m_hash_name); + m_n = hash->output_length(); + // RFC 8553 Appendix B - Parameter Computation + auto u = ceil_division(8 * m_n, m_w); // ceil(8*n/w) + auto v = ceil_division(high_bit(((1 << m_w) - 1) * u), m_w); // ceil((floor(lg[(2^w - 1) * u]) + 1) / w) + m_ls = checked_cast_to(16 - (v * w)); + m_p = checked_cast_to(u + v); +} + +LMOTS_Signature::LMOTS_Signature(LMOTS_Algorithm_Type lmots_type, + std::vector C, + std::vector y_buffer) : + m_algorithm_type(lmots_type), m_C(std::move(C)), m_y_buffer(std::move(y_buffer)) { + LMOTS_Params params = LMOTS_Params::create_or_throw(m_algorithm_type); + + BufferSlicer y_slicer(m_y_buffer); + for(uint16_t i = 0; i < params.p(); ++i) { + m_y.push_back(y_slicer.take(params.n())); + } + BOTAN_ASSERT_NOMSG(y_slicer.empty()); +} + +LMOTS_Signature LMOTS_Signature::from_bytes_or_throw(BufferSlicer& slicer) { + size_t total_remaining_bytes = slicer.remaining(); + // Alg. 6a. 1. (last 4 bytes) / Alg. 4b. 1. + if(total_remaining_bytes < sizeof(LMOTS_Algorithm_Type)) { + throw Decoding_Error("Too few signature bytes while parsing LMOTS signature."); + } + // Alg. 6a. 2.b. / Alg. 4b. 2.a. + auto algorithm_type = load_be(slicer.take()); + + // Alg. 6a. 2.d. / Alg. 4b. 2.c. + LMOTS_Params params = LMOTS_Params::create_or_throw(algorithm_type); + + if(total_remaining_bytes < size(params)) { + throw Decoding_Error("Too few signature bytes while parsing LMOTS signature."); + } + + // Alg. 4b. 2.d. + auto C = slicer.copy_as_vector(params.n()); + // Alg. 4b. 2.e. + auto m_y_buffer = slicer.copy_as_vector(params.p() * params.n()); + + return LMOTS_Signature(algorithm_type, std::move(C), std::move(m_y_buffer)); +} + +LMOTS_Private_Key::LMOTS_Private_Key(const LMOTS_Params& params, + const LMS_Identifier& identifier, + LMS_Tree_Node_Idx q, + const LMS_Seed& seed) : + OTS_Instance(params, identifier, q), m_seed(seed) { + PseudorandomKeyGeneration gen(identifier); + const auto hash = params.hash(); + + gen.set_q(q.get()); + gen.set_j(0xff); + + for(uint16_t i = 0; i < params.p(); ++i) { + gen.set_i(i); + m_ots_sk.push_back(gen.gen(*hash, seed)); + } +} + +void LMOTS_Private_Key::sign(StrongSpan out_sig, const LMS_Message& msg) const { + BOTAN_ARG_CHECK(out_sig.size() == LMOTS_Signature::size(params()), "Invalid output buffer size"); + BufferStuffer sig_stuffer(out_sig); + const auto hash = params().hash(); + sig_stuffer.append(store_be(params().algorithm_type())); + const auto C = sig_stuffer.next(params().n()); + + // Since we do not store the signatures of the lms trees in the HSS sk, + // we need deterministic signatures to avoid reusing a OTS key to generate multiple signatures. + // See also: https://github.com/cisco/hash-sigs/blob/b0631b8891295bf2929e68761205337b7c031726/lm_ots_sign.c#L110-L115 + derive_random_C(C, *hash); + + const auto Q_with_cksm = gen_Q_with_cksm(params(), identifier(), q(), C, msg); + + Chain_Generator chain_gen(identifier(), q()); + for(uint16_t i = 0; i < params().p(); ++i) { + const auto y_i = sig_stuffer.next(params().n()); + const uint8_t a = coef(Q_with_cksm, i, params()); + chain_gen.process(*hash, i, 0, a, chain_input(i), y_i); + } + BOTAN_ASSERT_NOMSG(sig_stuffer.full()); +} + +void LMOTS_Private_Key::derive_random_C(std::span out, HashFunction& hash) const { + PseudorandomKeyGeneration gen(identifier()); + + gen.set_q(q().get()); + gen.set_i(C_INDEX); + gen.set_j(0xff); + + gen.gen(out, hash, m_seed); +} + +LMOTS_Public_Key::LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk) : OTS_Instance(lmots_sk) { + const auto pk_hash = lmots_sk.params().hash(); + pk_hash->update(lmots_sk.identifier()); + pk_hash->update(store_be(lmots_sk.q())); + pk_hash->update(store_be(D_PBLC)); + + Chain_Generator chain_gen(lmots_sk.identifier(), lmots_sk.q()); + const auto hash = lmots_sk.params().hash(); + LMOTS_Node tmp(lmots_sk.params().n()); + for(uint16_t i = 0; i < lmots_sk.params().p(); ++i) { + chain_gen.process(*hash, i, 0, lmots_sk.params().coef_max(), lmots_sk.chain_input(i), tmp); + pk_hash->update(tmp); + } + + m_K = pk_hash->final(); +} + +LMOTS_K lmots_compute_pubkey_from_sig(const LMOTS_Signature& sig, + const LMS_Message& msg, + const LMS_Identifier& identifier, + LMS_Tree_Node_Idx q) { + auto params = LMOTS_Params::create_or_throw(sig.algorithm_type()); + + // Alg. 4b 3. + + const auto Q_with_cksm = gen_Q_with_cksm(params, identifier, q, sig.C(), msg); + + // Prefill the final hash object + const auto pk_hash = params.hash(); + pk_hash->update(identifier); + pk_hash->update(store_be(q)); + pk_hash->update(store_be(D_PBLC)); + + Chain_Generator chain_gen(identifier, q); + const auto hash = params.hash(); + LMOTS_Node tmp(params.n()); + for(uint16_t i = 0; i < params.p(); ++i) { + const uint8_t a = coef(Q_with_cksm, i, params); + chain_gen.process(*hash, i, a, params.coef_max(), sig.y(i), tmp); + pk_hash->update(tmp); + } + // Alg. 4b 4. + return pk_hash->final(); +} + +} // namespace Botan diff --git a/src/lib/pubkey/hss_lms/lm_ots.h b/src/lib/pubkey/hss_lms/lm_ots.h new file mode 100644 index 00000000000..596f4ec3d19 --- /dev/null +++ b/src/lib/pubkey/hss_lms/lm_ots.h @@ -0,0 +1,346 @@ +/** + * LM-OTS - Leighton-Micali One-Time Signatures (RFC 8554 Section 4) + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_LM_OTS_H_ +#define BOTAN_LM_OTS_H_ + +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +/** + * @brief Seed of the LMS tree, used to generate the LM-OTS private keys. + */ +using LMS_Seed = Strong, struct LMS_SEED_>; + +/** + * @brief One node within one LM-OTS hash chain. + */ +using LMOTS_Node = Strong, struct LMOTS_Node_>; + +/** + * @brief The K value from the LM-OTS public key. + */ +using LMOTS_K = Strong, struct LMOTS_K_>; + +/** + * @brief Byte vector of an LM-OTS signature. + */ +using LMOTS_Signature_Bytes = Strong, struct LMOTS_Signature_Bytes_>; + +/** + * @brief The index of a node within a specific LMS tree layer + */ +using LMS_Tree_Node_Idx = Strong; + +/** + * @brief The identifier of an LMS tree (I in RFC 8554) + */ +using LMS_Identifier = Strong, struct LMS_Identifier_>; + +/** + * @brief A message that is signed with an LMS tree + */ +using LMS_Message = Strong, struct LMS_Message_>; + +/** + * @brief Enum of available LM-OTS algorithm types. + * + * The supported parameter sets are defined in RFC 8554 Section 4.1. and + * draft-fluhrer-lms-more-parm-sets-11 Section 4. HSS/LMS typecodes are + * introduced in RFC 8554 Section 3.2. and their format specified in + * Section 3.3. + */ +enum class LMOTS_Algorithm_Type : uint32_t { + // --- RFC 8554 --- + RESERVED = 0x00, + + // SHA-256 based + SHA256_N32_W1 = 0x01, + SHA256_N32_W2 = 0x02, + SHA256_N32_W4 = 0x03, + SHA256_N32_W8 = 0x04, + + // --- draft-fluhrer-lms-more-parm-sets-11 --- + // SHA-256/192 based + SHA256_N24_W1 = 0x05, + SHA256_N24_W2 = 0x06, + SHA256_N24_W4 = 0x07, + SHA256_N24_W8 = 0x08, + + // SHAKE-256/256 based + SHAKE_N32_W1 = 0x09, + SHAKE_N32_W2 = 0x0a, + SHAKE_N32_W4 = 0x0b, + SHAKE_N32_W8 = 0x0c, + + // SHAKE-256/192 based + SHAKE_N24_W1 = 0x0d, + SHAKE_N24_W2 = 0x0e, + SHAKE_N24_W4 = 0x0f, + SHAKE_N24_W8 = 0x10, +}; + +/** + * @brief The LM-OTS parameters. + * + * See RFC 8554 Section 4.1. + */ +class BOTAN_TEST_API LMOTS_Params { + public: + /** + * @brief Create the LM-OTS parameters from a known algorithm type. + * @throws Decoding_Error If the algorithm type is unknown + */ + static LMOTS_Params create_or_throw(LMOTS_Algorithm_Type type); + + /** + * @brief Create the LM-OTS parameters from a hash function and width. + * + * @param hash_name tha name of the hash function to use. + * @param w the width (in bits) of the Winternitz coefficients. + * @throws Decoding_Error If the algorithm type is unknown + */ + static LMOTS_Params create_or_throw(std::string_view hash_name, uint8_t w); + + /** + * @brief Returns the LM-OTS algorithm type. + */ + LMOTS_Algorithm_Type algorithm_type() const { return m_algorithm_type; } + + /** + * @brief The number of bytes of the output of the hash function. + */ + size_t n() const { return m_n; } + + /** + * @brief The width (in bits) of the Winternitz coefficients. + */ + uint8_t w() const { return m_w; } + + /** + * @brief The maximum the winternitz coefficients can have. + */ + uint8_t coef_max() const { return (1 << m_w) - 1; } + + /** + * @brief The number of n-byte string elements that make up the LM-OTS signature. + */ + uint16_t p() const { return m_p; } + + /** + * @brief The number of left-shift bits used in the checksum function Cksm. + */ + uint8_t ls() const { return m_ls; } + + /** + * @brief Name of the hash function to use. + */ + const std::string& hash_name() const { return m_hash_name; } + + /** + * @brief Construct a new hash instance for the OTS instance. + */ + std::unique_ptr hash() const { return HashFunction::create_or_throw(hash_name()); } + + private: + /** + * @brief Construct a new LM-OTS parameter object. + * + * @param algorithm_type The algorithm type. + * @param hash_name The name of the hash function to use. + * @param w The width (in bits) of the Winternitz coefficients. + */ + LMOTS_Params(LMOTS_Algorithm_Type algorithm_type, std::string_view hash_name, uint8_t w); + + LMOTS_Algorithm_Type m_algorithm_type; + size_t m_n; + uint8_t m_w; + uint16_t m_p; + uint8_t m_ls; + std::string m_hash_name; +}; + +/** + * @brief Representation of a LM-OTS signature. + */ +class BOTAN_TEST_API LMOTS_Signature { + public: + /** + * @brief Parse a LM-OTS signature. + * + * @param slicer The private key bytes to parse. + * @return The LM-OTS signature. + * @throws Decoding_Error If parsing the signature fails. + */ + static LMOTS_Signature from_bytes_or_throw(BufferSlicer& slicer); + + /** + * @brief Returns the LM-OTS algorithm type. + */ + LMOTS_Algorithm_Type algorithm_type() const { return m_algorithm_type; } + + /** + * @brief The n-byte randomizer of the signature. + */ + std::span C() const { return m_C; } + + /** + * @brief Returns the part of the signature for @p chain_idx. + */ + StrongSpan y(uint16_t chain_idx) const { return m_y.at(chain_idx); } + + /** + * @brief The expected size of the signature. + */ + static size_t size(const LMOTS_Params& params) { return 4 + params.n() * (params.p() + 1); } + + private: + LMOTS_Signature(LMOTS_Algorithm_Type lmots_type, std::vector C, std::vector y_buffer); + + LMOTS_Algorithm_Type m_algorithm_type; + std::vector m_C; + std::vector m_y_buffer; + std::vector> m_y; +}; + +/** + * @brief Base class for LMOTS private and public key. Contains the parameters for + * the specific OTS instance + */ +class BOTAN_TEST_API OTS_Instance { + public: + /** + * @brief Constructor storing the specific OTS parameters + */ + OTS_Instance(const LMOTS_Params& params, const LMS_Identifier& identifier, LMS_Tree_Node_Idx q) : + m_params(params), m_identifier(identifier), m_q(q) {} + + /** + * @brief The LMOTS parameters + */ + const LMOTS_Params& params() const { return m_params; } + + /** + * @brief The LMS identifier of the LMS tree containing this OTS instance ('I' in RFC 8554) + */ + const LMS_Identifier& identifier() const { return m_identifier; } + + /** + * @brief The index of the LMS tree leaf associated with this OTS instance + */ + LMS_Tree_Node_Idx q() const { return m_q; } + + private: + LMOTS_Params m_params; + LMS_Identifier m_identifier; + LMS_Tree_Node_Idx m_q; +}; + +/** + * @brief Representation of an LMOTS private key. + * + * Contains the OTS params, I, q, the secret LMS seed and its derived + * secret chain inputs (x[] in RFC 8554 4.2) + */ +class BOTAN_TEST_API LMOTS_Private_Key : public OTS_Instance { + public: + /** + * @brief Derive a LMOTS private key for a given @p seed. + * + * Implements RFC 8554 4.2 using derivation of Appendix A + */ + LMOTS_Private_Key(const LMOTS_Params& params, + const LMS_Identifier& identifier, + LMS_Tree_Node_Idx q, + const LMS_Seed& seed); + + /** + * @brief The secret chain input at a given chain index. (x[] in RFC 8554 4.2). + */ + const LMOTS_Node& chain_input(uint16_t chain_idx) const { return m_ots_sk.at(chain_idx); } + + /** + * @brief Generate a new LMOTS signature. + * + * Defined in RFC 8554 4.5 + */ + void sign(StrongSpan out_sig, const LMS_Message& msg) const; + + private: + /** + * @brief Derive random value C + * + * Derive the randomized value C as in the reference implementation (cisco): + * C = HASH(I || Q || 0xFFFD || 0xFF || SEED) + * + * Note that this derivation is important if we do not store the signature of root LMS nodes + * in the private key. Otherwise these root nodes are signed twice with different C values, + * resulting in a broken OTS signature. + */ + void derive_random_C(std::span out, HashFunction& hash) const; + + LMS_Seed m_seed; + std::vector m_ots_sk; +}; + +/** + * @brief Representation of an OTS public key. + * + * Contains the public key bytes + * as defined in RFC 8554 4.3: + * + * u32str(type) || I || u32str(q) || K + */ +class BOTAN_TEST_API LMOTS_Public_Key : public OTS_Instance { + public: + /** + * @brief Derivivation of an LMOTS public key using an LMOTS_Private_Key as defined + * in RFC 8554 4.3 + */ + LMOTS_Public_Key(const LMOTS_Private_Key& lmots_sk); + + /** + * @brief Construct a new LMOTS public key object using the bytes. + * + * Note that the passed params, identifier and + * q value should match with the prefix in @p pub_key_bytes. + */ + LMOTS_Public_Key(const LMOTS_Params& params, const LMS_Identifier& identifier, LMS_Tree_Node_Idx q, LMOTS_K K) : + OTS_Instance(params, identifier, q), m_K(std::move(K)) {} + + /** + * @brief The public key final hash value (K in RFC 8554 4.3 ) + * + * @return const LMOTS_K& + */ + const LMOTS_K& K() const { return m_K; } + + private: + LMOTS_K m_K; +}; + +/** + * @brief Compute a public key candidate for an OTS-signature-message pair and the OTS instance parameters. + * + * Defined in RFC 8554 4.6 - Algorithm 4b + */ +BOTAN_TEST_API LMOTS_K lmots_compute_pubkey_from_sig(const LMOTS_Signature& sig, + const LMS_Message& msg, + const LMS_Identifier& identifier, + LMS_Tree_Node_Idx q); + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/hss_lms/lms.cpp b/src/lib/pubkey/hss_lms/lms.cpp new file mode 100644 index 00000000000..ed5e3585773 --- /dev/null +++ b/src/lib/pubkey/hss_lms/lms.cpp @@ -0,0 +1,425 @@ +/** + * LMS - Leighton-Micali Hash-Based Signatures (RFC 8554) + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#include + +#include +#include +#include + +namespace Botan { +namespace { + +/** + * @brief Domain-separation parameter when computing the hash of the leaf of an LMS tree. + */ +constexpr uint16_t D_LEAF = 0x8282; + +/** + * @brief Domain-separation parameter when computing the hash of an interior node of an LMS tree. + */ +constexpr uint16_t D_INTR = 0x8383; + +/// Index of the layer in a tree starting with 0 for the bottom level to the root layer +using LMS_TreeLayerIndex = Strong; + +class TreeAddress final { + public: + explicit TreeAddress(uint32_t total_tree_height) : m_h(total_tree_height), m_r(0) { + BOTAN_ARG_CHECK(total_tree_height > 0 && total_tree_height < 32, "Invalid tree height"); + } + + TreeAddress& set_address(LMS_TreeLayerIndex tree_layer, LMS_Tree_Node_Idx tree_index) { + BOTAN_ARG_CHECK(tree_index.get() < (1 << m_h), "Invalid tree index"); + BOTAN_ARG_CHECK(tree_layer.get() <= m_h, "Invalid tree index"); + m_r = (1 << (m_h - tree_layer)).get() + tree_index.get(); + return *this; + } + + uint32_t r() const { return m_r; } + + bool is_leaf() const { return m_r >= (1 << m_h); } + + LMS_Tree_Node_Idx q() const { + BOTAN_STATE_CHECK(is_leaf()); + return LMS_Tree_Node_Idx(m_r - (1 << m_h.get())); + } + + private: + LMS_TreeLayerIndex m_h; + uint32_t m_r; +}; + +auto get_hash_pair_func_for_identifier(const LMS_Params& lms_params, LMS_Identifier identifier) { + return [hash = lms_params.hash(), I = std::move(identifier)](StrongSpan out, + const TreeAddress& address, + StrongSpan left, + StrongSpan right) { + auto lms_address = dynamic_cast(address); + + hash->update(I); + hash->update(store_be(lms_address.r())); + hash->update(store_be(D_INTR)); + hash->update(left); + hash->update(right); + hash->final(out); + }; +} + +void lms_gen_leaf(StrongSpan out, + const LMOTS_Public_Key& lmots_pk, + const TreeAddress& tree_address, + HashFunction& hash) { + hash.update(lmots_pk.identifier()); + hash.update(store_be(tree_address.r())); + hash.update(store_be(D_LEAF)); + hash.update(lmots_pk.K()); + hash.final(out); +} + +auto lms_gen_leaf_func(const LMS_PrivateKey& lms_sk) { + return [hash = lms_sk.lms_params().hash(), lms_sk](StrongSpan out, const TreeAddress& tree_address) { + auto lmots_sk = LMOTS_Private_Key(lms_sk.lmots_params(), lms_sk.identifier(), tree_address.q(), lms_sk.seed()); + auto lmots_pk = LMOTS_Public_Key(lmots_sk); + lms_gen_leaf(out, lmots_pk, tree_address, *hash); + }; +} + +void lms_treehash(StrongSpan out_root, + std::optional> out_auth_path, + std::optional leaf_idx, + const LMS_PrivateKey& lms_sk) { + auto hash_pair_func = get_hash_pair_func_for_identifier(lms_sk.lms_params(), lms_sk.identifier()); + auto gen_leaf = lms_gen_leaf_func(lms_sk); + TreeAddress lms_tree_address(lms_sk.lms_params().h()); + + treehash(out_root, + out_auth_path, + leaf_idx, + lms_sk.lms_params().m(), + LMS_TreeLayerIndex(lms_sk.lms_params().h()), + 0, + std::move(hash_pair_func), + std::move(gen_leaf), + lms_tree_address); +} + +} // namespace + +LMS_Params LMS_Params::create_or_throw(LMS_Algorithm_Type type) { + auto [hash_name, height] = [](const LMS_Algorithm_Type& lms_type) -> std::pair { + switch(lms_type) { + case LMS_Algorithm_Type::SHA256_M32_H5: + return {"SHA-256", 5}; + case LMS_Algorithm_Type::SHA256_M32_H10: + return {"SHA-256", 10}; + case LMS_Algorithm_Type::SHA256_M32_H15: + return {"SHA-256", 15}; + case LMS_Algorithm_Type::SHA256_M32_H20: + return {"SHA-256", 20}; + case LMS_Algorithm_Type::SHA256_M32_H25: + return {"SHA-256", 25}; + case LMS_Algorithm_Type::SHA256_M24_H5: + return {"Truncated(SHA-256,192)", 5}; + case LMS_Algorithm_Type::SHA256_M24_H10: + return {"Truncated(SHA-256,192)", 10}; + case LMS_Algorithm_Type::SHA256_M24_H15: + return {"Truncated(SHA-256,192)", 15}; + case LMS_Algorithm_Type::SHA256_M24_H20: + return {"Truncated(SHA-256,192)", 20}; + case LMS_Algorithm_Type::SHA256_M24_H25: + return {"Truncated(SHA-256,192)", 25}; + case LMS_Algorithm_Type::SHAKE_M32_H5: + return {"SHAKE-256(256)", 5}; + case LMS_Algorithm_Type::SHAKE_M32_H10: + return {"SHAKE-256(256)", 10}; + case LMS_Algorithm_Type::SHAKE_M32_H15: + return {"SHAKE-256(256)", 15}; + case LMS_Algorithm_Type::SHAKE_M32_H20: + return {"SHAKE-256(256)", 20}; + case LMS_Algorithm_Type::SHAKE_M32_H25: + return {"SHAKE-256(256)", 25}; + case LMS_Algorithm_Type::SHAKE_M24_H5: + return {"SHAKE-256(192)", 5}; + case LMS_Algorithm_Type::SHAKE_M24_H10: + return {"SHAKE-256(192)", 10}; + case LMS_Algorithm_Type::SHAKE_M24_H15: + return {"SHAKE-256(192)", 15}; + case LMS_Algorithm_Type::SHAKE_M24_H20: + return {"SHAKE-256(192)", 20}; + case LMS_Algorithm_Type::SHAKE_M24_H25: + return {"SHAKE-256(192)", 25}; + case LMS_Algorithm_Type::RESERVED: + throw Decoding_Error("Unsupported LMS algorithm type"); + } + BOTAN_ASSERT_UNREACHABLE(); + }(type); + + return LMS_Params(type, hash_name, height); +} + +LMS_Params LMS_Params::create_or_throw(std::string_view hash_name, uint8_t height) { + LMS_Algorithm_Type type = [](std::string_view hash, uint8_t h) -> LMS_Algorithm_Type { + if(hash == "SHA-256") { + switch(h) { + case 5: + return LMS_Algorithm_Type::SHA256_M32_H5; + case 10: + return LMS_Algorithm_Type::SHA256_M32_H10; + case 15: + return LMS_Algorithm_Type::SHA256_M32_H15; + case 20: + return LMS_Algorithm_Type::SHA256_M32_H20; + case 25: + return LMS_Algorithm_Type::SHA256_M32_H25; + default: + throw Decoding_Error("Unsupported height for hash function"); + } + } + if(hash == "Truncated(SHA-256,192)") { + switch(h) { + case 5: + return LMS_Algorithm_Type::SHA256_M24_H5; + case 10: + return LMS_Algorithm_Type::SHA256_M24_H10; + case 15: + return LMS_Algorithm_Type::SHA256_M24_H15; + case 20: + return LMS_Algorithm_Type::SHA256_M24_H20; + case 25: + return LMS_Algorithm_Type::SHA256_M24_H25; + default: + throw Decoding_Error("Unsupported height for hash function"); + } + } + if(hash == "SHAKE-256(256)") { + switch(h) { + case 5: + return LMS_Algorithm_Type::SHAKE_M32_H5; + case 10: + return LMS_Algorithm_Type::SHAKE_M32_H10; + case 15: + return LMS_Algorithm_Type::SHAKE_M32_H15; + case 20: + return LMS_Algorithm_Type::SHAKE_M32_H20; + case 25: + return LMS_Algorithm_Type::SHAKE_M32_H25; + default: + throw Decoding_Error("Unsupported height for hash function"); + } + } + if(hash == "SHAKE-256(192)") { + switch(h) { + case 5: + return LMS_Algorithm_Type::SHAKE_M24_H5; + case 10: + return LMS_Algorithm_Type::SHAKE_M24_H10; + case 15: + return LMS_Algorithm_Type::SHAKE_M24_H15; + case 20: + return LMS_Algorithm_Type::SHAKE_M24_H20; + case 25: + return LMS_Algorithm_Type::SHAKE_M24_H25; + default: + throw Decoding_Error("Unsupported height for hash function"); + } + } + throw Decoding_Error("Unsupported hash function"); + }(hash_name, height); + + return LMS_Params(type, hash_name, height); +} + +LMS_Params::LMS_Params(LMS_Algorithm_Type algorithm_type, std::string_view hash_name, uint8_t h) : + m_algorithm_type(algorithm_type), m_h(h), m_hash_name(hash_name) { + const auto hash = HashFunction::create_or_throw(m_hash_name); + m_m = hash->output_length(); +} + +LMS_PublicKey LMS_PrivateKey::sign_and_get_pk(StrongSpan out_sig, + LMS_Tree_Node_Idx q, + const LMS_Message& msg) const { + // Pre-alloc space for the signature + BOTAN_ARG_CHECK(out_sig.size() == LMS_Signature::size(lms_params(), lmots_params()), "Invalid output buffer size"); + + BufferStuffer sig_stuffer(out_sig); + sig_stuffer.append(store_be(q)); + const LMOTS_Private_Key lmots_sk(lmots_params(), identifier(), q, seed()); + lmots_sk.sign(sig_stuffer.next(LMOTS_Signature::size(lmots_params())), msg); + sig_stuffer.append(store_be(lms_params().algorithm_type())); + const auto auth_path_buffer = sig_stuffer.next(lms_params().m() * lms_params().h()); + + BOTAN_ASSERT_NOMSG(sig_stuffer.full()); + + TreeAddress lms_tree_address(lms_params().h()); + LMS_Tree_Node pk_buffer(lms_params().m()); + lms_treehash(StrongSpan(pk_buffer.get()), auth_path_buffer, q, *this); + + return LMS_PublicKey(lms_params(), lmots_params(), identifier(), std::move(pk_buffer)); +} + +LMS_PublicKey LMS_PublicKey::from_bytes_or_throw(BufferSlicer& slicer) { + size_t total_remaining_bytes = slicer.remaining(); + // Alg. 6. 1. (4 bytes are sufficient until the next check) + if(total_remaining_bytes < sizeof(LMS_Algorithm_Type)) { + throw Decoding_Error("Too few bytes while parsing LMS public key."); + } + // Alg. 6. 2.a. + auto lms_type = load_be(slicer.take()); + // Alg. 6. 2.c. + auto lms_params = LMS_Params::create_or_throw(lms_type); + // Alg. 6. 2.d. + if(total_remaining_bytes < LMS_PublicKey::size(lms_params)) { + throw Decoding_Error("Too few bytes while parsing LMS public key."); + } + // Alg. 6. 2.b. + auto lmots_type = load_be(slicer.take()); + auto lmots_params = LMOTS_Params::create_or_throw(lmots_type); + + if(lms_params.hash_name() != lmots_params.hash_name()) { + throw Decoding_Error("No support for HSS-LMS instances with multiple hash functions."); + } + + // Alg. 6. 2.e. + auto I = slicer.copy(LMS_IDENTIFIER_LEN); + // Alg. 6. 2.f. + auto lms_root = slicer.copy(lms_params.m()); + + return LMS_PublicKey(std::move(lms_params), std::move(lmots_params), std::move(I), std::move(lms_root)); +} + +std::vector LMS_PublicKey::to_bytes() const { + // clang-format off + return concat>( + store_be(lms_params().algorithm_type()), + store_be(lmots_params().algorithm_type()), + identifier(), + m_lms_root); + // clang-format on +} + +LMS_PublicKey::LMS_PublicKey(LMS_Params lms_params, + LMOTS_Params lmots_params, + LMS_Identifier I, + LMS_Tree_Node lms_root) : + LMS_Instance(std::move(lms_params), std::move(lmots_params), std::move(I)), m_lms_root(std::move(lms_root)) { + BOTAN_ARG_CHECK(identifier().size() == LMS_IDENTIFIER_LEN, "Invalid LMS identifier"); + BOTAN_ARG_CHECK(m_lms_root.size() == this->lms_params().m(), "Invalid LMS root"); +} + +size_t LMS_PublicKey::size(const LMS_Params& lms_params) { + return sizeof(LMS_Algorithm_Type) + sizeof(LMOTS_Algorithm_Type) + LMS_IDENTIFIER_LEN + lms_params.m(); +} + +LMS_Signature LMS_Signature::from_bytes_or_throw(BufferSlicer& slicer) { + size_t total_remaining_bytes = slicer.remaining(); + // Alg. 6a 1. (next 4 bytes are checked in LMOTS_Signature::from_bytes_or_throw) + if(total_remaining_bytes < sizeof(LMS_Tree_Node_Idx)) { + throw Decoding_Error("Too few signature bytes while parsing LMS signature."); + } + // Alg. 6a 2.a. + auto q = load_be(slicer.take()); + + // Alg. 6a 2.b.-e. + auto lmots_sig = LMOTS_Signature::from_bytes_or_throw(slicer); + LMOTS_Params lmots_params = LMOTS_Params::create_or_throw(lmots_sig.algorithm_type()); + + if(slicer.remaining() < sizeof(LMS_Algorithm_Type)) { + throw Decoding_Error("Too few signature bytes while parsing LMS signature."); + } + // Alg. 6a 2.f. + auto lms_type = load_be(slicer.take()); + // Alg. 6a 2.h. + LMS_Params lms_params = LMS_Params::create_or_throw(lms_type); + // Alg. 6a 2.i. (signature is not exactly [...] bytes long) + if(total_remaining_bytes < size(lms_params, lmots_params)) { + throw Decoding_Error("Too few signature bytes while parsing LMS signature."); + } + + // Alg. 6a 2.j. + auto auth_path = slicer.copy(lms_params.m() * lms_params.h()); + + return LMS_Signature(q, std::move(lmots_sig), lms_type, std::move(auth_path)); +} + +LMS_PublicKey::LMS_PublicKey(const LMS_PrivateKey& sk) : LMS_Instance(sk), m_lms_root(sk.lms_params().m()) { + lms_treehash(StrongSpan(m_lms_root), std::nullopt, std::nullopt, sk); +} + +bool LMS_PublicKey::verify_signature(const LMS_Message& msg, const LMS_Signature& sig) const { + if(lms_root().size() != lms_params().m()) { + // LMS public key (T[1] part) has unexpected length + return false; + } + if(lms_params().algorithm_type() != sig.lms_type()) { + // LMS algorithm type does not match with the signature's + return false; + } + // Alg. 6a 2.g. + if(lmots_params().algorithm_type() != sig.lmots_sig().algorithm_type()) { + // LMOTS algorithm type does not match with the signature's + return false; + } + // Alg. 6a 2.i. + if(sig.q() >= (1ULL << uint64_t(lms_params().h()))) { + return false; + } + // Alg 6. 3. + std::optional Tc = lms_compute_root_from_sig(msg, sig); + if(!Tc.has_value()) { + return false; + } + // Alg 6. 4. + return Tc.value() == lms_root(); +} + +std::optional LMS_PublicKey::lms_compute_root_from_sig(const LMS_Message& msg, + const LMS_Signature& sig) const { + // Alg. 6a 2.c, 2.g + if(lms_params().algorithm_type() != sig.lms_type() || + lmots_params().algorithm_type() != sig.lmots_sig().algorithm_type()) { + return std::nullopt; + } + try { + const LMS_Params lms_params = LMS_Params::create_or_throw(sig.lms_type()); + const LMOTS_Signature& lmots_sig = sig.lmots_sig(); + const LMOTS_Params lmots_params = LMOTS_Params::create_or_throw(lmots_sig.algorithm_type()); + const LMOTS_K Kc = lmots_compute_pubkey_from_sig(lmots_sig, msg, identifier(), sig.q()); + const auto hash = lms_params.hash(); + + auto hash_pair_func = get_hash_pair_func_for_identifier(lms_params, identifier()); + + auto lms_address = TreeAddress(lms_params.h()); + lms_address.set_address(LMS_TreeLayerIndex(0), LMS_Tree_Node_Idx(sig.q().get())); + + LMOTS_Public_Key pk_candidate(lmots_params, identifier(), sig.q(), Kc); + LMS_Tree_Node tmp(lms_params.m()); + lms_gen_leaf(tmp, pk_candidate, lms_address, *hash); + + LMS_Tree_Node root(lms_params.m()); + + compute_root(StrongSpan(root), + sig.auth_path(), + sig.q(), + StrongSpan(tmp), + lms_params.m(), + LMS_TreeLayerIndex(lms_params.h()), + 0, + std::move(hash_pair_func), + lms_address); + return LMS_Tree_Node(root); + } catch(const Decoding_Error&) { + return std::nullopt; + } +} + +size_t LMS_Signature::size(const LMS_Params& lms_params, const LMOTS_Params& lmots_params) { + return sizeof(uint32_t) + LMOTS_Signature::size(lmots_params) + sizeof(uint32_t) + lms_params.h() * lms_params.m(); +} + +} // namespace Botan diff --git a/src/lib/pubkey/hss_lms/lms.h b/src/lib/pubkey/hss_lms/lms.h new file mode 100644 index 00000000000..52587e46f6a --- /dev/null +++ b/src/lib/pubkey/hss_lms/lms.h @@ -0,0 +1,347 @@ +/** + * LMS - Leighton-Micali Hash-Based Signatures (RFC 8554) + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Botan is released under the Simplified BSD License (see license.txt) + */ + +#ifndef BOTAN_LMS_H_ +#define BOTAN_LMS_H_ + +#include + +#include +#include +#include +#include + +namespace Botan { + +/** + * @brief Enum of available LMS algorithm types. + * + * The supported parameter sets are defined in RFC 8554 Section 5.1. and + * draft-fluhrer-lms-more-parm-sets-11 Section 5. HSS/LMS typecodes are + * introduced in RFC 8554 Section 3.2. and their format specified in + * Section 3.3. + */ +enum class LMS_Algorithm_Type : uint32_t { + // --- RFC 8554 --- + RESERVED = 0x00, + + // SHA-256 based + SHA256_M32_H5 = 0x05, + SHA256_M32_H10 = 0x06, + SHA256_M32_H15 = 0x07, + SHA256_M32_H20 = 0x08, + SHA256_M32_H25 = 0x09, + + // --- draft-fluhrer-lms-more-parm-sets-11 --- + // SHA-256/192 based + SHA256_M24_H5 = 0x0a, + SHA256_M24_H10 = 0x0b, + SHA256_M24_H15 = 0x0c, + SHA256_M24_H20 = 0x0d, + SHA256_M24_H25 = 0x0e, + + // SHAKE-256/256 based + SHAKE_M32_H5 = 0x0f, + SHAKE_M32_H10 = 0x10, + SHAKE_M32_H15 = 0x11, + SHAKE_M32_H20 = 0x12, + SHAKE_M32_H25 = 0x13, + + // SHAKE-256/192 based + SHAKE_M24_H5 = 0x14, + SHAKE_M24_H10 = 0x15, + SHAKE_M24_H15 = 0x16, + SHAKE_M24_H20 = 0x17, + SHAKE_M24_H25 = 0x18 +}; + +/** + * @brief The length in bytes of the LMS identifier (I). + */ +constexpr size_t LMS_IDENTIFIER_LEN = 16; + +/** + * @brief The authentication path of an LMS signature + */ +using LMS_AuthenticationPath = Strong, struct LMS_AuthenticationPath_>; + +/** + * @brief A node with the LMS tree + */ +using LMS_Tree_Node = Strong, struct LMS_Tree_Node_>; + +/** + * @brief Raw bytes of an LMS signature + */ +using LMS_Signature_Bytes = Strong, struct LMS_Signature_Bytes_>; + +/** + * @brief The LMS parameters. + * + * See RFC 8554 Section 5.1. + */ +class BOTAN_TEST_API LMS_Params { + public: + /** + * @brief Create the LMS parameters from a known algorithm type. + * @throws Decoding_Error If the algorithm type is unknown + */ + static LMS_Params create_or_throw(LMS_Algorithm_Type type); + + /** + * @brief Create the LMS parameters from a hash function and tree height. + * + * @param hash_name The name of the hash function to use. + * @param h The height of the tree. + * @throws Decoding_Error If the algorithm type is unknown + */ + static LMS_Params create_or_throw(std::string_view hash_name, uint8_t h); + + /** + * @brief Retuns the LMS algorithm type. + */ + LMS_Algorithm_Type algorithm_type() const { return m_algorithm_type; } + + /** + * @brief Returns the height of the LMS tree. + */ + uint8_t h() const { return m_h; } + + /** + * @brief Returns the number of bytes associated with each node. + */ + size_t m() const { return m_m; } + + /** + * @brief Returns the name of the hash function to use. + */ + const std::string& hash_name() const { return m_hash_name; } + + /** + * @brief Construct a new hash instance for the LMS instance. + */ + std::unique_ptr hash() const { return HashFunction::create_or_throw(hash_name()); } + + private: + /** + * @brief Construct a new LMS parameter object. + * + * @param algorithm_type The algorithm type. + * @param hash_name The name of the hash function to use. + * @param h The height of the tree. + */ + LMS_Params(LMS_Algorithm_Type algorithm_type, std::string_view hash_name, uint8_t h); + + LMS_Algorithm_Type m_algorithm_type; + uint8_t m_h; + size_t m_m; + std::string m_hash_name; +}; + +/** + * @brief Base class for LMS private and public key. Contains public data associated with this + * LMS instance. + */ +class BOTAN_TEST_API LMS_Instance { + public: + /** + * @brief Constructor storing the provided LMS data. + */ + LMS_Instance(LMS_Params lms_params, LMOTS_Params lmots_params, LMS_Identifier identifier) : + m_lms_params(std::move(lms_params)), + m_lmots_params(std::move(lmots_params)), + m_identifier(std::move(identifier)) {} + + /** + * @brief The LMS parameters for this LMS instance. + */ + const LMS_Params& lms_params() const { return m_lms_params; } + + /** + * @brief The LMOTS parameters used for OTS instances of this LMS instance. + */ + const LMOTS_Params& lmots_params() const { return m_lmots_params; } + + /** + * @brief The identifier of this LMS tree ('I' in RFC 8554) + */ + const LMS_Identifier& identifier() const { return m_identifier; } + + private: + LMS_Params m_lms_params; + LMOTS_Params m_lmots_params; + LMS_Identifier m_identifier; +}; + +class LMS_PublicKey; + +/** + * @brief Representation of an LMS Private key + * + * Contains the secret seed used for OTS key derivation + * as described in RFC 8554 Appendix A. + */ +class BOTAN_TEST_API LMS_PrivateKey : public LMS_Instance { + public: + /** + * @brief Construct storing the LMS instance data and the secret seed + */ + LMS_PrivateKey(LMS_Params lms_params, LMOTS_Params lmots_params, LMS_Identifier I, LMS_Seed seed) : + LMS_Instance(std::move(lms_params), std::move(lmots_params), std::move(I)), m_seed(std::move(seed)) {} + + /** + * @brief The secret seed used for LMOTS' WOTS chain input creation (RFC 8554 Appendix A) + */ + const LMS_Seed& seed() const { return m_seed; } + + /** + * @brief Sign a message using an LMS_PrivateKey and the used leaf index (RFC 8554 5.4.1). + * + * The signature is written in the provided buffer. The LMS_PublicKey + * associated with the given private key is returned. + */ + LMS_PublicKey sign_and_get_pk(StrongSpan out_sig, + LMS_Tree_Node_Idx q, + const LMS_Message& msg) const; + + private: + LMS_Seed m_seed; +}; + +class LMS_Signature; + +/** + * @brief The LMS public key. + * + * Format according to RFC 8554: + * u32str(type) || u32str(otstype) || I || T[1] + */ +class BOTAN_TEST_API LMS_PublicKey : public LMS_Instance { + public: + /** + * @brief Parse a public LMS key. + * + * @param slicer The BufferSlicer at the public key bytes' position + * @return The LMS public key. + * @throws Decoding_Error If parsing the public key fails. + */ + static LMS_PublicKey from_bytes_or_throw(BufferSlicer& slicer); + + /** + * @brief Construct a public key for given public key data + */ + LMS_PublicKey(LMS_Params lms_params, LMOTS_Params lmots_params, LMS_Identifier I, LMS_Tree_Node lms_root); + + /** + * @brief Construct a new public key from a given LMS private key (RFC 8554 5.3). + */ + LMS_PublicKey(const LMS_PrivateKey& sk); + + /** + * @brief Bytes of the full lms public key according to 8554 5.3 + * + * pub_key_bytes = u32str(type) || u32str(otstype) || I || T[1] + */ + std::vector to_bytes() const; + + /** + * @brief The expected size of an LMS public key for given @p lms_params + */ + static size_t size(const LMS_Params& lms_params); + + /** + * @brief Verify a LMS signature. + * + * See RFC 8554 5.4.2 - Algorithm 6. + * + * @param msg The signed message. + * @param sig The already parsed LMS signature. + * @return True if the signature is valid, false otherwise. + */ + bool verify_signature(const LMS_Message& msg, const LMS_Signature& sig) const; + + private: + /** + * @brief Compute an lms public key candidate. + * + * Given the LMS public key, a LMS-Signature-LMS_Message pair, compute + * an LMS public key candidate as described in RFC 8554 5.4.2 Algorithm 6a. + */ + std::optional lms_compute_root_from_sig(const LMS_Message& msg, const LMS_Signature& sig) const; + + /** + * @brief Root node of the LMS tree ('T[1]' in RFC 8554 5.3) + */ + const LMS_Tree_Node& lms_root() const { return m_lms_root; } + + LMS_Tree_Node m_lms_root; +}; + +/** + * @brief Container for LMS Signature data. + * + * Contains a method for secure signature parsing. + */ +class BOTAN_TEST_API LMS_Signature { + public: + /** + * @brief Parse the bytes of a lms signature into a LMS Signature object + * + * @param slicer A BufferSlicer object at the position of the LMS_Signature to parse + * @return LMS_Signature object + * @throws Decoding_Error If parsing the signature fails. + */ + static LMS_Signature from_bytes_or_throw(BufferSlicer& slicer); + + /** + * @brief The index of the signing leaf given by the signature + */ + LMS_Tree_Node_Idx q() const { return m_q; } + + /** + * @brief The LMOTS signature object containing the parsed LMOTS signature bytes + * contained in the LMS signature + */ + const LMOTS_Signature& lmots_sig() const { return m_lmots_sig; } + + /** + * @brief The LMS algorithm type given by the signature + */ + LMS_Algorithm_Type lms_type() const { return m_lms_type; } + + /** + * @brief The authentication path bytes given by the signature + * + * ('path[0] || ... || path[h-1]' in RFC 8554 5.4) + */ + StrongSpan auth_path() const { return m_auth_path; } + + /** + * @return size_t The expected size of the signature. + */ + static size_t size(const LMS_Params& lms_params, const LMOTS_Params& lmots_params); + + private: + /** + * @brief Private constructor storing the data fields individually + */ + LMS_Signature(LMS_Tree_Node_Idx q, + LMOTS_Signature lmots_sig, + LMS_Algorithm_Type lms_type, + LMS_AuthenticationPath auth_path) : + m_q(q), m_lmots_sig(std::move(lmots_sig)), m_lms_type(lms_type), m_auth_path(std::move(auth_path)) {} + + LMS_Tree_Node_Idx m_q; + LMOTS_Signature m_lmots_sig; + LMS_Algorithm_Type m_lms_type; + LMS_AuthenticationPath m_auth_path; +}; + +} // namespace Botan + +#endif diff --git a/src/lib/pubkey/pk_algs.cpp b/src/lib/pubkey/pk_algs.cpp index de9be217168..c845f43ebb4 100644 --- a/src/lib/pubkey/pk_algs.cpp +++ b/src/lib/pubkey/pk_algs.cpp @@ -82,6 +82,10 @@ #include #endif +#if defined(BOTAN_HAS_HSS_LMS) + #include +#endif + #if defined(BOTAN_HAS_XMSS_RFC8391) #include #endif @@ -220,6 +224,12 @@ std::unique_ptr load_public_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_HSS_LMS) + if(alg_name == "HSS-LMS") { + return std::make_unique(key_bits); + } +#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-")) { return std::make_unique(alg_id, key_bits); @@ -349,6 +359,12 @@ std::unique_ptr load_private_key(const AlgorithmIdentifier& alg_id, } #endif +#if defined(BOTAN_HAS_HSS_LMS) + if(alg_name == "HSS-LMS-Private-Key") { + return std::make_unique(key_bits); + } +#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-")) { return std::make_unique(alg_id, key_bits); @@ -485,6 +501,12 @@ std::unique_ptr create_private_key(std::string_view alg_name, } #endif +#if defined(BOTAN_HAS_HSS_LMS) + if(alg_name == "HSS-LMS") { + return std::make_unique(rng, params); + } +#endif + #if defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHA2) || defined(BOTAN_HAS_SPHINCS_PLUS_WITH_SHAKE) if(alg_name == "SPHINCS+" || alg_name == "SphincsPlus-") { auto sphincs_params = Sphincs_Parameters::create(params); diff --git a/src/lib/utils/bit_ops.h b/src/lib/utils/bit_ops.h index 504c363ef9e..3cb25367c1d 100644 --- a/src/lib/utils/bit_ops.h +++ b/src/lib/utils/bit_ops.h @@ -137,6 +137,19 @@ constexpr uint8_t ceil_log2(T x) return result; } +/** + * Ceil of an unsigned integer division. @p b must not be zero. + * + * @param a divident + * @param b divisor + * + * @returns ceil(a/b) + */ +template +inline constexpr T ceil_division(T a, T b) { + return (a + b - 1) / b; +} + /** * Return the number of bytes necessary to contain @p bits bits. */ diff --git a/src/lib/utils/concepts.h b/src/lib/utils/concepts.h index 522cc0581c1..4363249a857 100644 --- a/src/lib/utils/concepts.h +++ b/src/lib/utils/concepts.h @@ -198,9 +198,16 @@ concept strong_type = is_strong_type_v; template concept contiguous_strong_type = strong_type && contiguous_container; +template +concept integral_strong_type = strong_type && std::integral; + template concept unsigned_integral_strong_type = strong_type && std::unsigned_integral; +template +concept strong_type_with_capability = T::template +has_capability(); + } // namespace concepts } // namespace Botan diff --git a/src/lib/utils/int_utils.h b/src/lib/utils/int_utils.h index 76e42a88190..1639954e444 100644 --- a/src/lib/utils/int_utils.h +++ b/src/lib/utils/int_utils.h @@ -8,6 +8,7 @@ #define BOTAN_INT_UTILS_H_ #include +#include #include #include #include @@ -47,14 +48,71 @@ constexpr inline std::optional checked_mul(T a, T b) { return r; } -template -constexpr inline std::optional checked_cast(std::unsigned_integral auto input) { - if(std::numeric_limits::max() < input) { - return {}; +namespace detail { + +template +concept int_or_strong_type = std::integral || concepts::integral_strong_type; + +template +struct unwrap_type {}; + +template + requires std::integral +struct unwrap_type { + using type = T; +}; + +template + requires concepts::integral_strong_type +struct unwrap_type { + using type = typename T::wrapped_type; +}; + +template +using unwrap_type_t = typename unwrap_type::type; + +template +constexpr auto unwrap(T t) -> unwrap_type_t { + if constexpr(std::integral) { + return t; + } else { + return t.get(); } - return static_cast(input); } +template +constexpr auto wrap(unwrap_type_t t) -> T { + if constexpr(std::integral) { + return t; + } else { + return T(t); + } +} + +} // namespace detail + +template +constexpr RT checked_cast_to_or_throw(AT i, std::string_view error_msg_on_fail) { + const auto unwrapped_input = detail::unwrap(i); + using unwrapped_input_type = detail::unwrap_type_t; + using unwrapped_result_type = detail::unwrap_type_t; + + const auto unwrapped_result = static_cast(unwrapped_input); + if(unwrapped_input != static_cast(unwrapped_result)) [[unlikely]] { + throw ExceptionType(error_msg_on_fail); + } + + return detail::wrap(unwrapped_result); +} + +template +constexpr RT checked_cast_to(AT i) { + return checked_cast_to_or_throw(i, "Error during integer conversion"); +} + +#define BOTAN_CHECKED_ADD(x, y) checked_add(x, y, __FILE__, __LINE__) +#define BOTAN_CHECKED_MUL(x, y) checked_mul(x, y) + } // namespace Botan #endif diff --git a/src/lib/utils/strong_type.h b/src/lib/utils/strong_type.h index 031693ef657..15f6872c733 100644 --- a/src/lib/utils/strong_type.h +++ b/src/lib/utils/strong_type.h @@ -169,6 +169,11 @@ class Strong : public detail::Strong_Adapter { public: using detail::Strong_Adapter::Strong_Adapter; + template + constexpr static bool has_capability() { + return (std::is_same_v || ...); + } + private: using Tag = TagTypeT; }; @@ -603,6 +608,15 @@ class StrongSpan { underlying_span m_span; }; +template +struct is_strong_span : std::false_type {}; + +template +struct is_strong_span> : std::true_type {}; + +template +constexpr bool is_strong_span_v = is_strong_span::value; + } // namespace Botan #endif diff --git a/src/lib/utils/tree_hash/info.txt b/src/lib/utils/tree_hash/info.txt new file mode 100644 index 00000000000..3b20bbe7699 --- /dev/null +++ b/src/lib/utils/tree_hash/info.txt @@ -0,0 +1,14 @@ + +TREE_HASH -> 20231006 + + + +name -> "Tree Hash" +brief -> "Generic implementation of Merkle Tree Hashing" +type -> "Internal" + + + +tree_hash.h + + diff --git a/src/lib/utils/tree_hash/tree_hash.h b/src/lib/utils/tree_hash/tree_hash.h new file mode 100644 index 00000000000..52644705ed7 --- /dev/null +++ b/src/lib/utils/tree_hash/tree_hash.h @@ -0,0 +1,264 @@ +/** + * Treehash logic used for hash-based signatures + * (C) 2023 Jack Lloyd + * 2023 Fabian Albert, René Meusel, Amos Treiber, Philippe Lieser - Rohde & Schwarz Cybersecurity GmbH + * + * Parts of this file have been adapted from https://github.com/sphincs/sphincsplus + * + * Botan is released under the Simplified BSD License (see license.txt) + */ +#ifndef BOTAN_TREE_HASH_H_ +#define BOTAN_TREE_HASH_H_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace concepts { + +template +concept tree_node = contiguous_container; + +/** + * @brief An index of a node in a layer. + * + * This is a separate index for each layer. + * The left most node of a layer has the index 0. + */ +template +concept tree_node_index = strong_type_with_capability; + +/** + * @brief A layer in a Tree. + * + * The bottom layer is the layer 0. + */ +template +concept tree_layer_index = strong_type_with_capability; + +template +concept strong_span = is_strong_span_v; + +/** + * @brief An adress in a Tree. + */ +template +concept tree_address = requires(T a, TreeLayerIndex tree_layer, TreeNodeIndex tree_index) { + requires tree_layer_index; + requires tree_node_index; + { a.set_address(tree_layer, tree_index) }; + }; + +template +concept tree_hash_node_pair = concepts::tree_node_index && concepts::tree_layer_index && + concepts::tree_address && concepts::strong_span && + requires(T func, NodeSS out, const Address& address, NodeSS a, NodeSS b) { + { func(out, address, a, b) }; + }; + +template +concept tree_gen_leaf = concepts::tree_node_index && concepts::tree_layer_index && + concepts::tree_address && concepts::strong_span && + requires(T func, NodeSS out, const Address& address) { + { func(out, address) }; + }; + +} // namespace concepts + +/** + * @brief Treehash logic to build up a merkle hash tree. + * + * Computes the root of the merkle tree. + * Can also output an authentication path necessary for a hash based signature. + * + * Given the following tree: + * Layer: + * 2 7R + * / \ + * 1 3X 6A + * / \ / \ + * 0 1X 2A 4 5 + * + * The treehash logic traverses the tree (Post-order traversal), i.e., the nodes are + * discovered in order 1,2,3,...,7. If we want to create a signature using leaf node 1, + * the authentication path is (Node 2, Node 6), since we need those to compute the + * root. + * + * @param out_root An output buffer to store the root node in (size: node_size ). + * @param out_auth_path Optional buffer to store the authentication path in (size: node_size * total_tree_height). + * @param leaf_idx The optional index of the leaf used to sign in the bottom tree layer beginning with index 0. + * nullopt if no node is signed, so we need no auth path. + * @param node_size The size of each node in the tree. + * @param total_tree_height The hight of the merkle tree to construct. + * @param idx_offset If we compute a subtree this marks the index of the leftmost leaf node in the bottom layer + * @param node_pair_hash The function to process two child nodes to compute their parent node. + * @param gen_leaf The logic to create a leaf node given the address in the tree. Probably this function + * creates a one-time/few-time-signature's public key which is hashed to be the leaf node. + * @param tree_address The address that is passed to gen_leaf or node_pair hash. This function will update the + * address accordings to the currently processed node. This object may contain further + * algorithm specific information, like the position of this merkle tree in a hypertree. + */ +template + requires concepts::tree_address +inline void treehash( + StrongSpan out_root, + std::optional out_auth_path, + std::optional leaf_idx, + size_t node_size, + TreeLayerIndex total_tree_height, + uint32_t idx_offset, + concepts::tree_hash_node_pair> auto node_pair_hash, + concepts::tree_gen_leaf> auto gen_leaf, + Address& tree_address) { + BOTAN_ASSERT_NOMSG(out_root.size() == node_size); + BOTAN_ASSERT(out_auth_path.has_value() == leaf_idx.has_value(), + "Both leaf index and auth path buffer is given or neither."); + const bool is_signing = leaf_idx.has_value(); + BOTAN_ASSERT_NOMSG(!is_signing || out_auth_path.value().size() == node_size * total_tree_height.get()); + + const TreeNodeIndex max_idx(uint32_t((1 << total_tree_height.get()) - 1)); + + std::vector last_visited_left_child_at_layer(total_tree_height.get(), TreeNode(node_size)); + + TreeNode current_node(node_size); // Current logical node + + // Traverse the tree from the left-most leaf, matching siblings and up until + // the root (Post-order traversal). Collect the adjacent nodes to build + // the authentication path along the way. + for(TreeNodeIndex idx(0); true; ++idx) { + tree_address.set_address(TreeLayerIndex(0), idx + idx_offset); + gen_leaf(StrongSpan(current_node), tree_address); + + // Now combine the freshly generated right node with previously generated + // left ones + uint32_t internal_idx_offset = idx_offset; + TreeNodeIndex internal_idx = idx; + auto internal_leaf = leaf_idx; + + for(TreeLayerIndex h(0); true; ++h) { + // Check if we hit the top of the tree + if(h == total_tree_height) { + copy_mem(out_root, current_node); + return; + } + + // Check if the node we have is a part of the authentication path; if + // it is, write it out. The XOR sum of both nodes (at internal_idx and internal_leaf) + // is 1 iff they have the same parent node in the FORS tree + if(is_signing && (internal_idx ^ internal_leaf.value()) == 0x01U) { + auto auth_path_location = out_auth_path.value().get().subspan(h.get() * node_size, node_size); + copy_mem(auth_path_location, current_node); + } + + // Check if we're at a left child; if so, stop going up the tree + // Exception: if we've reached the end of the tree, keep on going (so + // we combine the last 4 nodes into the one root node in two more + // iterations) + if((internal_idx & 1) == 0U && idx < max_idx) { + // We've hit a left child; save the current for when we get the + // corresponding right child. + copy_mem(last_visited_left_child_at_layer.at(h.get()), current_node); + break; + } + + // Ok, we're at a right node. Now combine the left and right logical + // nodes together. + + // Set the address of the node we're creating. + internal_idx_offset /= 2; + tree_address.set_address(h + 1, internal_idx / 2 + internal_idx_offset); + + node_pair_hash(current_node, tree_address, last_visited_left_child_at_layer.at(h.get()), current_node); + + internal_idx /= 2; + if(internal_leaf.has_value()) { + internal_leaf.value() /= 2; + } + } + } +} + +/** + * @brief Uses an authentication path and a leaf node to reconstruct the root node + * of a merkle tree. + * + * @param out_root A output buffer for the root node of the merkle tree. + * @param authentication_path The authentication path in one buffer (concatenated nodes). + * @param leaf_idx The index of the leaf used to sig in the bottom layer beginning with 0. + * @param leaf The leaf node used to sig. + * @param node_size The size of each node in the tree. + * @param total_tree_height The hight of the merkle tree to construct. + * @param idx_offset If we compute a subtree this marks the index of the leftmost leaf node in the bottom layer. + * @param node_pair_hash The function to process two child nodes to compute their parent node. + * @param tree_address The address that is passed to node_pair hash. This function will update the + * address accordings to the currently processed node. This object may contain further + * algorithm specific information, like the position of this merkle tree in a hypertree. + */ +template + requires concepts::tree_address +inline void compute_root( + StrongSpan out_root, + AuthPathSS authentication_path, + TreeNodeIndex leaf_idx, + StrongSpan leaf, + size_t node_size, + TreeLayerIndex total_tree_height, + uint32_t idx_offset, + concepts::tree_hash_node_pair> auto node_pair_hash, + Address& tree_address) { + BOTAN_ASSERT_NOMSG(out_root.size() == node_size); + BOTAN_ASSERT_NOMSG(authentication_path.size() == node_size * static_cast(total_tree_height.get())); + BOTAN_ASSERT_NOMSG(leaf.size() == node_size); + + // Use the `out` parameter as intermediate buffer for left/right nodes + // while traversing the tree. + copy_mem(out_root, leaf); + + // Views into either `auth_path` or `out` depending on the tree location. + StrongSpan left; + StrongSpan right; + + BufferSlicer auth_path(authentication_path); + + // The leaf is put in the left or right buffer, depending on its indexes parity. + // Same for the first node of the authentication path + + for(TreeLayerIndex i(0); i < total_tree_height; i++) { + // The input of the hash function takes the current node and the node + // given in the authentication path. If the current node is a right node + // in the tree (i.e. its leaf index is uneven) the hash function inputs + // must be swapped. + left = out_root; + right = auth_path.take(node_size); + + if((leaf_idx & 1) == 1U) { + std::swap(left, right); + } + + leaf_idx /= 2; + idx_offset /= 2; + tree_address.set_address(i + 1, leaf_idx + idx_offset); + + node_pair_hash(out_root, tree_address, left, right); + } + + BOTAN_ASSERT_NOMSG(auth_path.empty()); +} +} // namespace Botan + +#endif // BOTAN_TREE_HASH_H_ diff --git a/src/lib/x509/x509_obj.cpp b/src/lib/x509/x509_obj.cpp index ae9be85f640..dad2d3983ce 100644 --- a/src/lib/x509/x509_obj.cpp +++ b/src/lib/x509/x509_obj.cpp @@ -173,6 +173,9 @@ std::string x509_signature_padding_for(const std::string& algo_name, } else if(algo_name == "XMSS") { // XMSS does not take any padding, but if the user insists, we pass it along return std::string(user_specified_padding); + } else if(algo_name == "HSS-LMS") { + // HSS-LMS does not take any padding, but if the user insists, we pass it along + return std::string(user_specified_padding); } else { throw Invalid_Argument("Unknown X.509 signing key type: " + algo_name); } diff --git a/src/tests/data/pubkey/api_sign.vec b/src/tests/data/pubkey/api_sign.vec index 3a142027fc7..a524e11e043 100644 --- a/src/tests/data/pubkey/api_sign.vec +++ b/src/tests/data/pubkey/api_sign.vec @@ -36,6 +36,11 @@ SigParams = Ed25519ph AlgoParams = gost_256A SigParams = Raw +[HSS-LMS] + +AlgoParams = SHA-256,HW(5,8) +SigParams = + [RSA] AlgoParams = 2048 diff --git a/src/tests/data/pubkey/hss_lms_invalid.vec b/src/tests/data/pubkey/hss_lms_invalid.vec new file mode 100644 index 00000000000..2e9dd088305 --- /dev/null +++ b/src/tests/data/pubkey/hss_lms_invalid.vec @@ -0,0 +1,24 @@ +# Test cases created using the reference implementation https://github.com/cisco/hash-sigs + +# HSS with 2 levels: +# Root Level: LMS_SHA256_N32_H10 with LMOTS_SHA256_N32_W4 +# 2. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# Signature signs only root level +PublicKey = 00000002000000060000000324fa7601ab93e539a3509136aca588aff0b8840fa459c3c1fd00e4212f85a280a1e3f35e9f42fa2fd14394706426eb90 +Msg = 000000050000000433d2fe4dcc7de5604114b68cb77e1daa6573aa17df94f516109ae9743c1a9300052dfc3b6b33fe27d0db3064c23daa21 +InvalidSignature = 000000000000000400000003a6391ea95c206bb1f17e1444f4557580b8d7ce580773c159c4eca8f12dc5aba7080f2e4f0af402ff44195ec2cc9ef338cf072bcb792cf8f97fd60f4c1fd171aaf741665a1924942122988c5ee7280b74c2160fb4004d957b75299838bfe3d9daa1848f3277220ad1f97578f0b44efa9e88627b3e700af1475c311245a898d3bbb708f065c3e1aa85979f5b57f94dddfe3444f1841b44438c526eb9d3456f617cec51fbad86aa39a06bfb3464c557e46d4b4e8fb1a1dd310b23466bacc9cb583f3099eb3a9bd6bb32562b3c152df94fd67e402fcb777ce8e71388fba1ed84b5f738ff9439e7e15829879e76dc36f0b9ecf3c8b4861851db88aa553025bb6bf0b6ee7445343dd575b8c8c73f5f43abe4dc543e13e59c2105d17ff12228cb03cf87a28f453cd8450fdcc107372ced5dbc4d9fde3b5bc92ee833ac475ae090cfaffda6db9e7a8d9ae795cab9a4b1bdb94dc4fc362d16bd31344a9d689301daec2041be493977f7fd8d73f1281b4d176ce0cfae46308686216793d429a71fbb9badb6aa1df8ca4020bda55f9dab9799fec922ee898e8f7dc7271653b396893abeef79217401e5bc70243b35c3bd4a9bcb4d0e83a31fb7caf3de2f547dafdd50cb82d93dfed1625f5926fec3c695885e021bd28391774b9d8310186ec5179b43a965ec84daaea6ab35c95cba4f153da37657d9c347ff5e8e67633f68b0c05a9c400e116091a9370fbc8f0736a4b85fe5e3b1b6bcd2d3729050b157019dc27bd02fd52d89959cdf74d3a0d1bf4d598e64f49bead0e3b980c0bef524aae4af1417377e2142b31518c4cceee6ba4d46b9973fa9705cfc0cc7ae41e97e66857c0b985a427bf2b20194f35f08e662c0b0ef151b89b1750c73a1ae786fdc0e03c3099fe41efe365993bb9bf13f1faf6349825d477d9c799e861704ea08337224332dde0012cd9fcbc7971eb7c10b1251431c079285dec4d3b957ccd8ebee7cad591fdc86eed8a82432b1ff52df1217008e62e614c0bda18f3fb06d247063c8ff570044f56c5b7e03693f7095babacfa81ce56f2746397a89c752cd91c532a9e468340e1557c4167316ae2eed4a483c34703fa7acf64aa700f7ef3f1480c2ffe0283d41b7b4085f4ef7210919cbc8d36c6871856a04be79a5e9893a2874efa6e5f1d9739ecac5e615b6d2f48bb3bf7c299371711021e614ea8b5253ec2f0655723e4165157a8543473d848c050e83568a6cd4f179b698b63e8fd16d50cde116cbd38b0048dc975d49afa6d6749d8efb467c5ce5bcbcf8fd9c31c6666bfab5ae2c7becde50a0f6dd7c40b054933f811c0793548675026437211b998b1befcf251648e25c0c67e1440bbb7c8e8766b2b7d994bbf8602be2acaf8869dc98cf1461e78f451d2c810216b405a38fac33051fbf7ef0239c3c0325504de5e73c5caecb9a7a8423b3174acf1d9770523d107b5379122a23439654bfe2f443a765875bdc893f00681e1ce7d127668b54e854d5194dd0968b1f7ed65f5e8908efa90928098b8a84970f6e7174f28a635850d7fa7e01d3b5f387a15237fcde359f56fe08a652e75652eba9a4bc000d3cd94176f2a71fe2ff8f63c4645eb6a406bdf95011ee9ad218b4b016875e592eeb8af6aa75806988ee984b2b0a946d220d1ebbe5ba07132c318981a30fa9e77c9f67a213f605155f0cf482c8330c214ccb4adcccf79ac3fa1f56b567b0ca1d26d9157837a040095cff4ead264b9b0a41164faa05fbeb3394bff15909f2f3a72d17eb194907622c5a350d4958df63c969bf4f8150697ec25755b39df9addc22fb057255d4e306572f3b30ec3be401101b8ba45e1e64867d85bc183786f334cebf0ad7ce7e1e5adf79e01fffae1b2eb5e5ebfef8611f1c72f6cb310cff200b6150450a396f6ad31f72f72da0498b88eb805cbd9f8e48229fab6208e3c545845880f5d7c2ce82ef53027596feab8828a85021e3d46271d1cba191e9b3b0e1773e0c9359e4e7d5ad82d60ee26b0dc5ea4676d3afecc1a7130bd4ed2150784748128e7879c04f088f9740ccde1cf286394a249d7a1867859e0f8c78e439b2b0f96a174c4346485dc9b4f27541c2bf6166e684f546b60deca9f2126705a4a4453c500c2bc4cb6002371d89f34247642b6a4d8c4bff98c63e6bcdf78dcedd0b27dd2bc0f9a2c6dcd945a56e0a2b6db5761150b6989e6f69543455ac22053068020fa6e1a84f05516ea5743fcd7b65995047e8274dadcdd4ba7708d26637855bfc2c282ac86113fa0ba5234d802b2c4f4f4b49c8ae2800d8e036573ec8e6ba509f9592d1e5f569307c266ccae492a738f084b5e19d33ae0a83687c8970326e8423baca398e3e0da4b7d1dc5e29011ca528b39e7387f676574e9e7252768f7f6e398f31233d248ef757ace08df1da275b3f463b1081cc80c2bf252dac6170940491d2024c979869be6c44f81ddde792abc728f9752c6fbe6a434d176dd6fcb5ddda6edb25c8c1342b44c5db5a63115d0c1a553e83d1468b2effa13965b30ecd36b048412ee4e94b8884b3962f70c4c90717a61285e03f839f5332f128578b99d5a5ef349370e6aca168e549afa5542a222ff7da757a179491904e27f3f7875d1b74ecb13b007ba12f8250a2a5d3c8771cb28c65d5f08b6b71ee5ec5a1caaff432e7084935efd216fc1aefbea9357a0c2169a7379a7752830823af65a69640a12099c757e5a4a36cfe16ad03d7ec8073d8c53e75ffc87f68ce41df27cb07897df1c0efc7be4b36b8e115645deaebc6da5fa2012d6e306049d7b48e2d1ed61ccc67c953177c3f6673127eb33f13983b8a5c686c16360ebbc116034f9cfd53698f6bbe4183ca5ba510301cc109058efb9ece9a497f0568bc39c6fa3408b8fa5d509346f813573312a31d47a16dd5c376f67b8c5a81c9cae72a205ca5bc53f3cac5b7983bd300b5e909eba6418c8c4b38246259bcec7f56bfdeb89395fee0673c84017201dac2f0c6e48a810e202c445715e76f6b008df3a43aa06b0ac1b9410ead0f52a68a3af94fa517f65381d7363126ca793a901cbc7fae2c3689c25eccd2b63fb0000000068400814de2eb6f52766beeb27d3cefd7246cbb6734fbd7f55f40648fb4ed8bb0ab2feddcea6451cba949f9a63e644b0a5793a49a3c3d7b1f89fcbfbec62b84e6b0ed1018d99aaf3884634d5a41ebc577991f0ed496b98e44ae25ff3df635098944abdc9e77353e61541aaafce88f05378787f8d6a4dd2cf3dee37045eb86b12114a821a12a4ad8159dd276fe85aa09a94de1b2bb7e559ca6896e19ae53eca7539d33ff6f58bf1cf097e313ad44bc6d6c5a796ff77f98c642ed82587413e162b58f8a23634fab60bf261da8058d9d2d8731f28f907cd5b8efdbeec5d83d59621daf220114dbc06a7a83be557e4c3535bce0ca9c7aa121f291daab84f361474d6a6a7e1c8be7dbce8969a6a5aeddc23fcb3b096d41ea0c523079b63c53eeab1ccb811326d7186cdf89c2f3706fdfa6d310242091cdb0c7d98de70ee5a2a03af5cf + +# Unknown OTS type +PublicKey = 00000002000000060000000324fa7601ab93e539a3509136aca588aff0b8840fa459c3c1fd00e4212f85a280a1e3f35e9f42fa2fd14394706426eb90 +Msg = deadbeef +InvalidSignature = 000000010000000408150015a6391ea95c206bb1f17e1444f4557580b8d7ce580773c159c4eca8f12dc5aba7080f2e4f0af402ff44195ec2cc9ef338cf072bcb792cf8f97fd60f4c1fd171aaf741665a1924942122988c5ee7280b74c2160fb4004d957b75299838bfe3d9daa1848f3277220ad1f97578f0b44efa9e88627b3e700af1475c311245a898d3bbb708f065c3e1aa85979f5b57f94dddfe3444f1841b44438c526eb9d3456f617cec51fbad86aa39a06bfb3464c557e46d4b4e8fb1a1dd310b23466bacc9cb583f3099eb3a9bd6bb32562b3c152df94fd67e402fcb777ce8e71388fba1ed84b5f738ff9439e7e15829879e76dc36f0b9ecf3c8b4861851db88aa553025bb6bf0b6ee7445343dd575b8c8c73f5f43abe4dc543e13e59c2105d17ff12228cb03cf87a28f453cd8450fdcc107372ced5dbc4d9fde3b5bc92ee833ac475ae090cfaffda6db9e7a8d9ae795cab9a4b1bdb94dc4fc362d16bd31344a9d689301daec2041be493977f7fd8d73f1281b4d176ce0cfae46308686216793d429a71fbb9badb6aa1df8ca4020bda55f9dab9799fec922ee898e8f7dc7271653b396893abeef79217401e5bc70243b35c3bd4a9bcb4d0e83a31fb7caf3de2f547dafdd50cb82d93dfed1625f5926fec3c695885e021bd28391774b9d8310186ec5179b43a965ec84daaea6ab35c95cba4f153da37657d9c347ff5e8e67633f68b0c05a9c400e116091a9370fbc8f0736a4b85fe5e3b1b6bcd2d3729050b157019dc27bd02fd52d89959cdf74d3a0d1bf4d598e64f49bead0e3b980c0bef524aae4af1417377e2142b31518c4cceee6ba4d46b9973fa9705cfc0cc7ae41e97e66857c0b985a427bf2b20194f35f08e662c0b0ef151b89b1750c73a1ae786fdc0e03c3099fe41efe365993bb9bf13f1faf6349825d477d9c799e861704ea08337224332dde0012cd9fcbc7971eb7c10b1251431c079285dec4d3b957ccd8ebee7cad591fdc86eed8a82432b1ff52df1217008e62e614c0bda18f3fb06d247063c8ff570044f56c5b7e03693f7095babacfa81ce56f2746397a89c752cd91c532a9e468340e1557c4167316ae2eed4a483c34703fa7acf64aa700f7ef3f1480c2ffe0283d41b7b4085f4ef7210919cbc8d36c6871856a04be79a5e9893a2874efa6e5f1d9739ecac5e615b6d2f48bb3bf7c299371711021e614ea8b5253ec2f0655723e4165157a8543473d848c050e83568a6cd4f179b698b63e8fd16d50cde116cbd38b0048dc975d49afa6d6749d8efb467c5ce5bcbcf8fd9c31c6666bfab5ae2c7becde50a0f6dd7c40b054933f811c0793548675026437211b998b1befcf251648e25c0c67e1440bbb7c8e8766b2b7d994bbf8602be2acaf8869dc98cf1461e78f451d2c810216b405a38fac33051fbf7ef0239c3c0325504de5e73c5caecb9a7a8423b3174acf1d9770523d107b5379122a23439654bfe2f443a765875bdc893f00681e1ce7d127668b54e854d5194dd0968b1f7ed65f5e8908efa90928098b8a84970f6e7174f28a635850d7fa7e01d3b5f387a15237fcde359f56fe08a652e75652eba9a4bc000d3cd94176f2a71fe2ff8f63c4645eb6a406bdf95011ee9ad218b4b016875e592eeb8af6aa75806988ee984b2b0a946d220d1ebbe5ba07132c318981a30fa9e77c9f67a213f605155f0cf482c8330c214ccb4adcccf79ac3fa1f56b567b0ca1d26d9157837a040095cff4ead264b9b0a41164faa05fbeb3394bff15909f2f3a72d17eb194907622c5a350d4958df63c969bf4f8150697ec25755b39df9addc22fb057255d4e306572f3b30ec3be401101b8ba45e1e64867d85bc183786f334cebf0ad7ce7e1e5adf79e01fffae1b2eb5e5ebfef8611f1c72f6cb310cff200b6150450a396f6ad31f72f72da0498b88eb805cbd9f8e48229fab6208e3c545845880f5d7c2ce82ef53027596feab8828a85021e3d46271d1cba191e9b3b0e1773e0c9359e4e7d5ad82d60ee26b0dc5ea4676d3afecc1a7130bd4ed2150784748128e7879c04f088f9740ccde1cf286394a249d7a1867859e0f8c78e439b2b0f96a174c4346485dc9b4f27541c2bf6166e684f546b60deca9f2126705a4a4453c500c2bc4cb6002371d89f34247642b6a4d8c4bff98c63e6bcdf78dcedd0b27dd2bc0f9a2c6dcd945a56e0a2b6db5761150b6989e6f69543455ac22053068020fa6e1a84f05516ea5743fcd7b65995047e8274dadcdd4ba7708d26637855bfc2c282ac86113fa0ba5234d802b2c4f4f4b49c8ae2800d8e036573ec8e6ba509f9592d1e5f569307c266ccae492a738f084b5e19d33ae0a83687c8970326e8423baca398e3e0da4b7d1dc5e29011ca528b39e7387f676574e9e7252768f7f6e398f31233d248ef757ace08df1da275b3f463b1081cc80c2bf252dac6170940491d2024c979869be6c44f81ddde792abc728f9752c6fbe6a434d176dd6fcb5ddda6edb25c8c1342b44c5db5a63115d0c1a553e83d1468b2effa13965b30ecd36b048412ee4e94b8884b3962f70c4c90717a61285e03f839f5332f128578b99d5a5ef349370e6aca168e549afa5542a222ff7da757a179491904e27f3f7875d1b74ecb13b007ba12f8250a2a5d3c8771cb28c65d5f08b6b71ee5ec5a1caaff432e7084935efd216fc1aefbea9357a0c2169a7379a7752830823af65a69640a12099c757e5a4a36cfe16ad03d7ec8073d8c53e75ffc87f68ce41df27cb07897df1c0efc7be4b36b8e115645deaebc6da5fa2012d6e306049d7b48e2d1ed61ccc67c953177c3f6673127eb33f13983b8a5c686c16360ebbc116034f9cfd53698f6bbe4183ca5ba510301cc109058efb9ece9a497f0568bc39c6fa3408b8fa5d509346f813573312a31d47a16dd5c376f67b8c5a81c9cae72a205ca5bc53f3cac5b7983bd300b5e909eba6418c8c4b38246259bcec7f56bfdeb89395fee0673c84017201dac2f0c6e48a810e202c445715e76f6b008df3a43aa06b0ac1b9410ead0f52a68a3af94fa517f65381d7363126ca793a901cbc7fae2c3689c25eccd2b63fb0000000068400814de2eb6f52766beeb27d3cefd7246cbb6734fbd7f55f40648fb4ed8bb0ab2feddcea6451cba949f9a63e644b0a5793a49a3c3d7b1f89fcbfbec62b84e6b0ed1018d99aaf3884634d5a41ebc577991f0ed496b98e44ae25ff3df635098944abdc9e77353e61541aaafce88f05378787f8d6a4dd2cf3dee37045eb86b12114a821a12a4ad8159dd276fe85aa09a94de1b2bb7e559ca6896e19ae53eca7539d33ff6f58bf1cf097e313ad44bc6d6c5a796ff77f98c642ed82587413e162b58f8a23634fab60bf261da8058d9d2d8731f28f907cd5b8efdbeec5d83d59621daf220114dbc06a7a83be557e4c3535bce0ca9c7aa121f291daab84f361474d6a6a7e1c8be7dbce8969a6a5aeddc23fcb3b096d41ea0c523079b63c53eeab1ccb811326d7186cdf89c2f3706fdfa6d310242091cdb0c7d98de70ee5a2a03af5cf000000050000000433d2fe4dcc7de5604114b68cb77e1daa6573aa17df94f516109ae9743c1a9300052dfc3b6b33fe27d0db3064c23daa210000000300000004e59c87e946e6fefcf4bcbbed83d7b124f907a0d383a7c8b85aafe870272afe93663038a509b97210339530250ec470aa465fcde0290622c433d97190c09b16423c4d16fad30c4352387972a189aebdd28cc145b57c93da2121d4b75a31988dbe172243b1127fbfa5cf0a8079df0837d2b31ccb9092c66a69fe629b5f9e006c19123611a288c9455c469758992de721c402f00b5b1d7af0eaf1bc635360371e3ad18eda72a58241ef0195c43da9dfc2f425d042bff122b43615174de735e69c1aee9c38ca9cfdc6da8d5e25493d5f4eaca85496fdc601b1c6c866da4c038787097390974964de80b055b573cea1b9f4e528285eb4bce23015198e3e3b26a6a7c46cbd1d3dd4ebca33165ddb03de8f12ae90e7930f28ab7787bb56c0b24dbe0d3f4b452eb0957b9dfe7d06296afdad8efafe1a2ba4ef22bf00f61aeff94183b5494d7274b05e935dbdf648b72eff38be31c238192598dbfa3ddc1f4cc320e5bdad5243c6b7ca99c6d4dfce6d61e04cc04bb83ec188ff6ddca4051999651c6f184b58f3bbeb1823c21a751d2e8b8b39b2a24e583338133a78694b56583ec326cf4f5c26cc6bdcefff450342da9a9c064875b5f5a66ac80c1faa5056eca2bfc1466a0d01484117a0fbe807fe44d0efd3d15e8f139c67fbdcc3d30ab0963ecd9f2587100deb256d59d029ed7fccbee80efca0a6621ab7875dd98285cf2303a7b1fee2899f166b7e95c66147af1c9bad3eeb80c8acff8de18848e8c050e0b61a7561ddf0274f0a0ddf9ce2659b1ff2924e20fb0765c91c811b3421c1fe42bb8b983e514e3682257b6d5a83b2ec140bfd7c7932ab2d8326670528ec0e86c12dea14cb04add94def350ac605396471b607d44d227787db956b33e3f64aec805387be0038aa5630266c3265860594bd0275a2f029431485cb98f3f40992639a71d51317c23cd58b5e4fdbc718a628a15a71dd4fd133e9e526f8a18bf9645ba45f2f0c0f0ad5b9e4e7cf1a32d78e463599d0c706a62b145769c257e5eb84492b23966857c8297602322225036a8db0dc4195ee56ff2e5895505230b5234524d1d48b4a6ee6ebeccf578300888b2b0134b6be8cbf52502de22bfbc93afc6ca43df95b25899209dda7915ba13b81ab07fc2ef79875423d551b7592c13a0173df398f920ccbeefa05264a1cb6ad755c74c4a29e827b91fb3f4629cdd4f1eabb76ac33eae31e2dd19c773666a9b2f6f154e773c2ee0c01a256cadbf46865a2d34888726b2b19da250214c62ef956d6437d6e9bb2630764c1ac002985d84a583e458bedac766fd9034379af56015da59cfd014bd38c007ae4c6600fb958ad6e60696720bfb7d6668fc787d8c550678b21636f9e43ecf4a21bd706ecd082fbc654d46bb2f26ab6891c9668a17b96469f68f1318740cbcf7e9b6fb1328ea02912e1f1aa939ee6e6777b7281e4bb7f2bb723e3c3cd46cf7fa5cf0488150e342e4a25935c443d17a249d87cf4be0488f839f9dafba77d4eeac7818db6cf9288d41f8ce6cfc6d2acc8fde24feb41810a6ac8062c4c5a19706ec58bde7b0372273238499893563cbdde0f000000057db7cde50fbf0d556f6561be52b4ad591f48c6c147e5ae8ae8c7f979344435664871517c9b54c7e0582f0bea2103038b8de8dda699a520abb877c67aaea7b595712fe986fb30de9a455ae7792922351c22a38988c36cf8a85e1f4f38bbb4b766c744dd67ee2ee37f8fdf4fc34cfcddcbef6ad9db57dd9819ce922fbd164c583a7ce62bdbfce7e730544ba22e3b17927d85643018a2e6aa981dff9fcdae43bee0 + +# Empty Sig +PublicKey = 00000002000000060000000324fa7601ab93e539a3509136aca588aff0b8840fa459c3c1fd00e4212f85a280a1e3f35e9f42fa2fd14394706426eb90 +Msg = deadbeef +InvalidSignature = + +# Too long signature +PublicKey = 00000002000000050000000461a5d57d37f5e46bfb7520806b07a1b850650e3b31fe4a773ea29a07f09cf2ea30e579f0df58ef8e298da0434cb2b878 +Msg = 54686520706f77657273206e6f742064656c65676174656420746f2074686520556e69746564205374617465732062792074686520436f6e737469747574696f6e2c206e6f722070726f6869626974656420627920697420746f20746865205374617465732c2061726520726573657276656420746f207468652053746174657320726573706563746976656c792c206f7220746f207468652070656f706c652e0a +InvalidSignature = 000000010000000500000004d32b56671d7eb98833c49b433c272586bc4a1c8a8970528ffa04b966f9426eb9965a25bfd37f196b9073f3d4a232feb69128ec45146f86292f9dff9610a7bf95a64c7f60f6261a62043f86c70324b7707f5b4a8a6e19c114c7be866d488778a0e05fd5c6509a6e61d559cf1a77a970de927d60c70d3de31a7fa0100994e162a2582e8ff1b10cd99d4e8e413ef469559f7d7ed12c838342f9b9c96b83a4943d1681d84b15357ff48ca579f19f5e71f18466f2bbef4bf660c2518eb20de2f66e3b14784269d7d876f5d35d3fbfc7039a462c716bb9f6891a7f41ad133e9e1f6d9560b960e7777c52f060492f2d7c660e1471e07e72655562035abc9a701b473ecbc3943c6b9c4f2405a3cb8bf8a691ca51d3f6ad2f428bab6f3a30f55dd9625563f0a75ee390e385e3ae0b906961ecf41ae073a0590c2eb6204f44831c26dd768c35b167b28ce8dc988a3748255230cef99ebf14e730632f27414489808afab1d1e783ed04516de012498682212b07810579b250365941bcc98142da13609e9768aaf65de7620dabec29eb82a17fde35af15ad238c73f81bdb8dec2fc0e7f932701099762b37f43c4a3c20010a3d72e2f606be108d310e639f09ce7286800d9ef8a1a40281cc5a7ea98d2adc7c7400c2fe5a101552df4e3cccfd0cbf2ddf5dc6779cbbc68fee0c3efe4ec22b83a2caa3e48e0809a0a750b73ccdcf3c79e6580c154f8a58f7f24335eec5c5eb5e0cf01dcf4439424095fceb077f66ded5bec73b27c5b9f64a2a9af2f07c05e99e5cf80f00252e39db32f6c19674f190c9fbc506d826857713afd2ca6bb85cd8c107347552f30575a5417816ab4db3f603f2df56fbc413e7d0acd8bdd81352b2471fc1bc4f1ef296fea1220403466b1afe78b94f7ecf7cc62fb92be14f18c2192384ebceaf8801afdf947f698ce9c6ceb696ed70e9e87b0144417e8d7baf25eb5f70f09f016fc925b4db048ab8d8cb2a661ce3b57ada67571f5dd546fc22cb1f97e0ebd1a65926b1234fd04f171cf469c76b884cf3115cce6f792cc84e36da58960c5f1d760f32c12faef477e94c92eb75625b6a371efc72d60ca5e908b3a7dd69fef0249150e3eebdfed39cbdc3ce9704882a2072c75e13527b7a581a556168783dc1e97545e31865ddc46b3c957835da252bb7328d3ee2062445dfb85ef8c35f8e1f3371af34023cef626e0af1e0bc017351aae2ab8f5c612ead0b729a1d059d02bfe18efa971b7300e882360a93b025ff97e9e0eec0f3f3f13039a17f88b0cf808f488431606cb13f9241f40f44e537d302c64a4f1f4ab949b9feefadcb71ab50ef27d6d6ca8510f150c85fb525bf25703df7209b6066f09c37280d59128d2f0f637c7d7d7fad4ed1c1ea04e628d221e3d8db77b7c878c9411cafc5071a34a00f4cf07738912753dfce48f07576f0d4f94f42c6d76f7ce973e9367095ba7e9a3649b7f461d9f9ac1332a4d1044c96aefee67676401b64457c54d65fef6500c59cdfb69af7b6dddfcb0f086278dd8ad0686078dfb0f3f79cd893d314168648499898fbc0ced5f95b74e8ff14d735cdea968bee7400000005d8b8112f9200a5e50c4a262165bd342cd800b8496810bc716277435ac376728d129ac6eda839a6f357b5a04387c5ce97382a78f2a4372917eefcbf93f63bb59112f5dbe400bd49e4501e859f885bf0736e90a509b30a26bfac8c17b5991c157eb5971115aa39efd8d564a6b90282c3168af2d30ef89d51bf14654510a12b8a144cca1848cf7da59cc2b3d9d0692dd2a20ba3863480e25b1b85ee860c62bf51360000000500000004d2f14ff6346af964569f7d6cb880a1b66c5004917da6eafe4d9ef6c6407b3db0e5485b122d9ebe15cda93cfec582d7ab0000000a000000040703c491e7558b35011ece3592eaa5da4d918786771233e8353bc4f62323185c95cae05b899e35dffd717054706209988ebfdf6e37960bb5c38d7657e8bffeef9bc042da4b4525650485c66d0ce19b317587c6ba4bffcc428e25d08931e72dfb6a120c5612344258b85efdb7db1db9e1865a73caf96557eb39ed3e3f426933ac9eeddb03a1d2374af7bf77185577456237f9de2d60113c23f846df26fa942008a698994c0827d90e86d43e0df7f4bfcdb09b86a373b98288b7094ad81a0185ac100e4f2c5fc38c003c1ab6fea479eb2f5ebe48f584d7159b8ada03586e65ad9c969f6aecbfe44cf356888a7b15a3ff074f771760b26f9c04884ee1faa329fbf4e61af23aee7fa5d4d9a5dfcf43c4c26ce8aea2ce8a2990d7ba7b57108b47dabfbeadb2b25b3cacc1ac0cef346cbb90fb044beee4fac2603a442bdf7e507243b7319c9944b1586e899d431c7f91bcccc8690dbf59b28386b2315f3d36ef2eaa3cf30b2b51f48b71b003dfb08249484201043f65f5a3ef6bbd61ddfee81aca9ce60081262a00000480dcbc9a3da6fbef5c1c0a55e48a0e729f9184fcb1407c31529db268f6fe50032a363c9801306837fafabdf957fd97eafc80dbd165e435d0e2dfd836a28b354023924b6fb7e48bc0b3ed95eea64c2d402f4d734c8dc26f3ac591825daef01eae3c38e3328d00a77dc657034f287ccb0f0e1c9a7cbdc828f627205e4737b84b58376551d44c12c3c215c812a0970789c83de51d6ad787271963327f0a5fbb6b5907dec02c9a90934af5a1c63b72c82653605d1dcce51596b3c2b45696689f2eb382007497557692caac4d57b5de9f5569bc2ad0137fd47fb47e664fcb6db4971f5b3e07aceda9ac130e9f38182de994cff192ec0e82fd6d4cb7f3fe00812589b7a7ce515440456433016b84a59bec6619a1c6c0b37dd1450ed4f2d8b584410ceda8025f5d2d8dd0d2176fc1cf2cc06fa8c82bed4d944e71339ece780fd025bd41ec34ebff9d4270a3224e019fcb444474d482fd2dbe75efb20389cc10cd600abb54c47ede93e08c114edb04117d714dc1d525e11bed8756192f929d15462b939ff3f52f2252da2ed64d8fae88818b1efa2c7b08c8794fb1b214aa233db3162833141ea4383f1a6f120be1db82ce3630b3429114463157a64e91234d475e2f79cbf05e4db6a9407d72c6bff7d1198b5c4d6aad2831db61274993715a0182c7dc8089e32c8531deed4f7431c07c02195eba2ef91efb5613c37af7ae0c066babc69369700e1dd26eddc0d216c781d56e4ce47e3303fa73007ff7b949ef23be2aa4dbf25206fe45c20dd888395b2526391a724996a44156beac808212858792bf8e74cba49dee5e8812e019da87454bff9e847ed83db07af313743082f880a278f682c2bd0ad6887cb59f652e155987d61bbf6a88d36ee93b6072e6656d9ccbaae3d655852e38deb3a2dcf8058dc9fb6f2ab3d3b3539eb77b248a661091d05eb6e2f297774fe6053598457cc61908318de4b826f0fc86d4bb117d33e865aa805009cc2918d9c2f840c4da43a703ad9f5b5806163d7161696b5a0adc00000005d5c0d1bebb06048ed6fe2ef2c6cef305b3ed633941ebc8b3bec9738754cddd60e1920ada52f43d055b5031cee6192520d6a5115514851ce7fd448d4a39fae2ab2335b525f484e9b40d6a4a969394843bdcf6d14c48e8015e08ab92662c05c6e9f90b65a7a6201689999f32bfd368e5e3ec9cb70ac7b8399003f175c40885081a09ab3034911fe125631051df0408b3946b0bde790911e8978ba07dd56c73e7eeaddadd diff --git a/src/tests/data/pubkey/hss_lms_sig.vec b/src/tests/data/pubkey/hss_lms_sig.vec new file mode 100644 index 00000000000..6e23eb52432 --- /dev/null +++ b/src/tests/data/pubkey/hss_lms_sig.vec @@ -0,0 +1,22 @@ +# Test cases created using the reference implementation https://github.com/cisco/hash-sigs +# with seed derivation logic Secret Method 2. + +# HSS with 2 levels: +# Root Level: LMS_SHA256_N32_H10 with LMOTS_SHA256_N32_W4 +# 2. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +PrivateKey = 0000000200000000000000830000000600000003000000050000000467c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4 +Msg = deadbeef +Signature = 0000000100000004000000033b4c17fbfa9a6ae2c471d7f26992849c4d956a81a65ee52e757f1df90b8c031d56a47a2d953108c6958b9e26da66abf4f52b8856bc93e3305f61512a04d2c7a77239318432f1bb858fd2b8af19f2ffa1713eabe3d5c7bd4eec1ae075041e87a8dec5ec762772dcda6e1a19be5ae4745ddcd8f954d8570c866a62a0ef1d5fc449c93b1dd3151fc75596b1be6b98ead724d45f9879525483101e11105aa8d94d2c42d24921f148095cd71be805bb40f319b1632a2b0a2d2f5b3cb5911531eddc36540b622d9bbf232cba12fd1508b59e47cce1d6756ff5c0a76f8f11aa09f0e156514ddde065481c527b3cd20b644c450fca3fadf589ed57c1a893cc134eea4b4c2a0668b741b707040ddc495ce60046b76547d0a2f48952126701060acf931c8bfd4edb60f0e90af071e0aa5bd54a26cd10964f4b978600e586b012546c17e2d3955219229c279c2ba05bd61be9f117dbfd512ba9ede726c0b2216fb5d972e440a75e5e9a3f72e42e986ff4ce03c5182bef6bd8b7aac80e91d9e72a1c9ef857de713cce2916f7e723954e177f93425df98a79e461d1c5c890138fd05f5721c63663877dc5cf72cb3ac1209a9282e843d1097e58bd577b5b611606f054f8c3238e315c40aef191de389c7978ce56d21f01336f346ddc6203076dfd11f2b9abe06823bd4b9331123a7774a1a8b29a89f9122891b9f3ce13fcfddf5758c77bff267f7f22215831061e5209c46149e541f108960c918fc8a9bdff2d78987cf782d5dc4a8de064e921d433c3a8f4362d340e19156564072d3ac925fe884c67434b7cc3760c203cfb2f34c21e881b4ddb26214b711ad96bfcaadab534799af504762bcfd367e34efe693a7da6e828e68979585aeab758378bd779ca8e379dcd2e2dc38c0d7f7211f58bad8bb0a72461af81712c35c9825f8b23e26625b7aa46ea71847ab67c5770e732cfc9a3948ec0772f67382bb2b9a863166693329a50f80c2a87b8547b6c11a5de36f9b10f28ed67813770fd1ae160c4b33e3a96b0dbec09a24f5a1852383b1fb066b778acb0a86302297fe67f99a488d984c2875ffa2bae331d961bcb726b818662402c3e5c9ce3ce8b0e7ae87e221e18ae35072d3dbf313f58b5d399c7e783d5be4234234391b5b9676ad6e7417aab4716ce1a7aa24db9254f325c6e4632e62ee8bbabd5885ebd19f703c6aa0199469519ec68fbfca30c7549911afa846735dde5c69865475f13ddc09b240a6d3bc50ea0940e89778e369cece235de4b1ed23433d305d7da612eec60b066e519724ad75ec00b2da20551b08723d9779015f2df6b30d1529ef52eb40383f37190c72be0c9d045b3d382eb5e56ddd3eb2028a074cb4a4b6105dfcd14ab226cc1240c681d3805e6f54260ab8aee8b8acafb483d37f7c87a4674b8043c40dad09baf64f2aacf915bea02fe01b6581b1a4d1e37d76ae7e4014cce79f7c817007f1d4dbb30057450efe38e2a843f44cc6a99e190e3199d8a1f65e120bddb0ec1df8475339b9d0fe3653a927bd915f6447ac495a0f76fc9336a77ba34e646d20fb0b63535683da47bd53d8bc3c846e4c27e199e7be6a6c2821c1a0311fa55699e88c100a0e1520cb775bf4d998915ba93f654bac743640e8c01ea15389840c49cec190420fa99080aac1d82cd917c15f454a1d3869387ccf169a5b015e3c918edb251130d773354485cda5666d8500907dc315c93467044413829526b89f567ac882fb0b868fa29687950b3c1a8a1eaafeb164df0ae598fbb204540e4cfe2ad121e28c305e8d8eb50822a345b24efa536ad999fb824a11a1574c3c80676120eab3f68552ba940df7bab21534ffd17491d7d804103671aee935c925d57c14519cfe3afa4be8870d5abaa0c26da9586136ddcbcc3818d8e9f611f6c8fde36fbe81b0b21095a9906abece667c7c15f9efbbdd30329b963d2a32b4d3230f56b762cf86bbc84c1fd50e4a65b2eb4c3ecd273b4815dc6bfb10972fa1b3bc13c7c1902448cd4873895fca5c452d2fbffd0313bb280bef50583a322f1266e86bc26debcb53ca98935a170c0f21a729f4e3996f2318b9d13d6b1099689c8c520b7cde5b1938f54172b21e8cc2703433c454a26b26f6ea33744e3991e99377b2eb3e67c7a3e83f73206293685e16b406b5117111c56113a1f703cc3bd7309e23057278354074cb127c12a918f1fa22444c972b66d04e0065740a64b9c0e190653e4845ab0ce568a18edd204e4b673093f33eb0b45be36d2240312b2e9f5dee6255a689e86167c8d059fd06a2e15e2d437b542e06a915c0858db75034a61de2ae85689c34f6a4398ea1c8d64e87ddfaafca41454548afe6c50b0645a0317b83e14f6bca49cf9fa9a0152887cdd7311faf728c2ce0f9bfd48d35ba75bdacbd77180ad0f7d0ba9e8422fa8349296a3dc6dec74cb76515211fb31d1ed51279ea004f7c529151f576da91047080f6c86dfa9d75e22b71a66331610c75af8bcb865f0d17b89fb3e2ff9c262423d00843924547b9f8d9ee7bf7cc49519028785a8b912813fb4a2524a23d211d75e2421d09f7d77d1a8cb00d637225a3509571ae50e7496de26d04ecebfc07c8c2add9d34f73d6ac593e5fe03707aadcef5f362e422ec285bebad8abec92a77b56df920a68aff30433bb2a2beca8662eec7bb053def251d42f59e97fabe4ee304f952b073fc64b04661d401f19d61c61a1594716d986bdd1749fe6244701e33916561e9d7be473c866bb4ee616d985f10d07405f82e0bdbf91499b07ab3866a21a52b8d89522acb9b410c0784ce4033acbfceed097cc7c11e0216934eef324c272374f3a947a5ba501e8db1416d54881bb0efaa2794f0d0e2592a2ed0526fb56e286e2beaf2f22a32028ba75c2a4e3690d0b7ce6e4fbeb944756b9e68db4b8cc873313484063dcbcc3cb842507e09ae5cbacb5761a9eaff5fdf8a271dea35192d4e4086760fd3d363d6a1bd7254e1f330cbd69589e61ce0fe9debe70f39cf1989979d82f4563a55aa8e609126d8d11a90db5ff0d39975781fd81adfe6b628b2ff1537eaeef692f6748068d6b300000006f0b054a497da7ad540d28c4cabc6fb47d0b99287073378d2ef81da4c970839f0676cfc7c443e335f06259e4342497e824b9a2630ac28124582e9fa8cd0dcf0ddf81b07478dd5ec5c84c7fb43158202814767556027f40fefa69ee48dcb865422346afe98f627c0b1e6c7ad2de6de92594a4acbfea6dcaad85c5f066f57cc010de83cba33ea6cd9975e1f7b32ea8184e69f1f4bc82c0c96f59e194932c133a1be2d24f1c945046e32b2e0480c70de41152603f8cc322b67cff334546d9083be11de638e7448d32cccfefd5bad504bfd59764c797f91c61400f3eed0b8c92c20fbfce05ae61377a499c7a2d364dfa4d3d5fc54f1070364e08364d71cbf3bfd1b6561553169277d221d4fe50867d1e0223a22169b78927a131b7ee42f55fe9e3eff55fb241e0006d38ff42fb4bc929f32378b17ad5de3dea117dda0eaff570466b8000000050000000470fa96c7207fd718fbbc14f206be924232934612284f800c38fe9ea986386a321704fbaecfa1996db792a8d14123e3440000000300000004d1324626d19a4f048634ec2b98dc95ce0ce192986cbed9a7faf605a7a9da476688c28e49e9e676ef0640709d23015073dab879ba93d64f71837b1b6628a43c80a3f06032c4318e03b18c252e16797afde4cc8db79abab009272475fb7e909a78742d467bfe3820f37ba4f0575abfff0a32970a246d54d794e5757ee82185ec993e3aafb598eb0d634fd0070467d60d4619a8fb5c49c82fc5df0d39ff40811ff6ec9c3ad0c87a95254566a1065099c0174317943d21a2dc9eb8c81d8c9363a686406c46caf8683f16b9f865f5063627bed345b7514e06abd2e28543b182a0cc36a09a1399788b04575d79b478dd86ce2870d8202bda4ca79d9afdaefe32398a2521e605f4adb6ed8612a1430a25f6ebe264ad37d4f401284d16924fff9d68630323c4338f2252f5780f44be76b9a97d5010e2abf846c17e811114a245424796c3c97b5992cbdb8014928723088a5f421ba067482b19c2f4890cdb0c7c15ec6eccbd8e133dc2a2c10e544ba855b2b07a2ccd982ce8c6ddf191c09a0705e73a9a492099cec2731623dc551a7425255d95e17eefe2ec160bd96c488e9f3c9985454241f4429b15f4193680dfbd30c7ccfd6920b0f5f7f1ae0a563b0fc93690807d5c6d784b8ddf24b1f6c9bce3692061de54e057e897c10323657c624294fdce227fee7c50ff9be5b9bd56022adc6515b7fb44964e9c07969c2c325b2083977f07342bb503448621a51f9f9e48b2e5bd9756371f900e1a1aca92331c1f0173e25a211c9a7083dc2135324420b4472919c6e9c6d2b18e60ed9fda9f9de23869cc428547d1030b0f27fcc0814d4a91967fb94d2b0c04873486e50a5875e21508c25f4679e04879484ffbf0a7c65c40937a246a4eceb959d8e0cf6cc6c29e95019c3d69558640a51ce187c6df658947fea5b358680ecc568902442fc159c1479bc43393683019ad7b8ceaf094ea165ea4fab3ebcdb57337e5e28c5f23e3bf7de2fc7fa9ac3a19a7eba424bfccf4d5c1614869c6085d3cbf1f77af36e7a6bbdf60dd8e3792974c7712f33107dc553dbf3f1cf4a7423b7cb1624aa40d01f4450e245530bf20056adf34b7ae3e8b17c059c9073931e46eece7b8b6a725c3aa84ef43dfc25bd23e441953aa40d8689cd34e9ec0ce7a9dfb37454b8d152877f45592eda49df265c4a6586e8de427fcbc01cadc240df08fa3fb3a36572ef8758185ee748ee063e08c6b47c031e75b799861e877019265515267f65497e3307c50d0680043fd36c9e13a83bd7b3e0892dbe41211ea50f795ab2408dac8b5e1eb7c23673b72e4c076faf93a8c71d3ad9f8570091d16667f1fde53eb5380804a5c5c78c6bea53acfff3fe673599351d9988acf0435a8c692e8d46de87af8d72389deadf9a0cca2312ab35684d06c0bd1cb362077fa53805c4d7823914656904094ffc6a1e155e52974b07ff2055fa4c14454f2dd46fa16fda77a11e131df27ad93f5c007d2354d344c1fc79a67eef198083480d62be9c8294d7f01763089c0580f74ad18471848d758e4b225d8972e20261ff3753e60630199bd43f6d41693bffbfd7921719dd7b60000000598a22fbbeb0bfc68202e2e9d480f6f1df0e4e6b75e3566ca6cd61efa6347d12bcb3448adc2c04c4de6528270236158998fd4569299325dbbc648a99c3f0d511c23006ebb7a865a20de81dd118b374aeee6494fa9169dead8d7b0d7c7b07416f259846e45169c5a83dfdcdfe6b145eb0626baf570eff8b8e6f995d2498ac74c331faf60b72418234316df461327e4ac14619b565ebcfd7e5a4b388930198b9d0d + +# HSS with 8 levels: +# Root Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 2. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 3. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 4. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 5. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 6. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 7. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +# 8. Level: LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W8 +PrivateKey = 00000008000000000000002a0000000500000004000000050000000400000005000000040000000500000004000000050000000400000005000000040000000500000004000000050000000467c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a66320db73158a35a255d051758e95ed4 +Msg = 1234f006ba89f00111213baf0016171819f0 +Signature = 00000007000000000000000405d6540e1e239a4c12025ce32634d2fa91bdcb3610e756bfacf24100206cda393968e22f252ce3ebbd698ada87c64722729b47ea30b8ea2280b0a343a3291c226463d3a2bdc4f3fd3943cd41188ae66e124413cc44be8b7be7f0be1e7e2410b09d956331d2adfe21f5c9edb29ea288a51d051a57fb5063a25c9d7b5d7da96bda2e6b92e0a18322e4840ee07ac17d0cfe2423dfc8f618f09b18be76d65eb9acbad0d9b97a1c8e56f17c3394a39859f10868e7e7ab0e12ba21c08c17aabb8a06df6b81b9d88dc0b7c434f9155f770cabe3d207c4d69334c5c8964b09a58ef812f2cef94c1a4edcb24089849302527465e2d49192378a4eeb3b5b9b7bd397cd4a7afddd6e7a8678bb6184ee9262f1b3de632b4824d8ef84d4c1c84096ea1e19a5f8c335fef770df4eff1b71380bc756b365a181f8e6b2283370f0ebd3c67dc7ddd93a827344cf29713362e583d83f686b212c61444a2a7f68ae52d2ed26773a2300af3e8994f9c6be9da89df58d3e74a7d7e5a043030818dcdb1e1a7f4d99bedc296d40898f3bbaf738db43bf48029a63149f241a9b0a4f819aa90103347809ec39bec35bbae153d8534a019e218c07e3af40a05f5a434566c829181e5a34af65030960a60f6e44d18a0968cec07462f97e17b0e22cc40790dd55b157c0fc2afe50ba462cd5498065f1956c3590c7645f1fc57d4b084bfc0fbb37815b5579f7da4f1cdf37b5917e28ff9c2bd36a014581db1f37209be1765aca712edfcddd5aa73f35018817d84406203f52ac2a9a934adc5504f9b390c24b3bee7fbf8e3e14d2a69bfeb64f11fe3efa576a2f75a21652805a0c5427c4e38753f0cf249d65557e595c638175d1fa9646ccf9e337532122456a1062c6eea4a5e96a8c5ebf52712829d322b057135a77f2b271c049bdee1d468caf7b67f3dcf6d8f86a6b8e4e52a2216d887a96cc5e8108ed8b76eee7689acdd582cebe2932dfa334f0b22ffd409dbb051b69c113b6816cfe01945305712e68f898636cafdd7a5beac8ea4312e676e4d6ec08c653e497453711d68fa5222e9e6901338554fa0bb30a095cd0c81702d04bf180c1a438897bedb5a253298863074d0c44ec53d36366f9d7f8eb499e7139bf521e0ea7283d727f29a4de2f8b4f8d88990bca0b0228ef00e0fcc75554c78b2b36f84120228b13cad748b6dced7be47c3315b8841cded1039899a800b9f67443c0d541c8a7b08ed9a33edc837aa7aaca9c777c3b2e1d84041e1e5e42d40a02a43203c0be6197f44fb79c5ea1b5d30e597d5a5f2786b19ccf65f10aab43ce7e2e560a3eda27df1e621792a4c3a98f0dd7ec190d0d45cfcf54459b0add4695f8d2c2d8f4d6430c71e4c2743ead7174ccbb64ce78b98479cdaa5e579b4441273b2bd3e9e692e17356660f063e0e231911a45bcdcaeb21ae5cc1ab4fea49aaa75095f1c23804603c490a6ebfd3b831698736a3d3ee6db206548cb3ea23c7ab6503ac9964c73d4b92f4733df66b64fb6d2c6994324d14f198f2a135dcf9b58105cf3464c3bc96dbb2a563b2c37f37a9b9c046fb3887ca9a7b670325fc92d12a35b0000000054a970a2543c843b3714d001881ee29f8a2b57f2771cd395b65ef564256c1a699e43b8805dc7fd899b7cbad019c8f763dccd3199b1604fd1fd615e0b17bdcbcafeaeddf3ff41a6287db6d578914fd4f490442b5224a213e39a447e303fe773447b75b2a348a22add5309b677ff3cf401cd02feb39bd590b3848b1842dd0b40d5a4619ac8dd06c1ddaffa01c25a31cfe37b7b0e1de2631bfd93670784a7894abc20000000500000004b9e64d2247edcfeb98973062bf0fbc69b15e5cb290f5ef1ca11e972cfba495c9c7e7e48ce4fb18d69f36bbc7e2d6ef370000000000000004f1d08604e954027b6754e4983743ca61f9c2428df683144570de246e3539564a7be6b8f6b0c1b97f125cce5961ab203004cfcd6d92be8cc1b94f3adbb8e5bb4391f5a7336b69445623f8fcda6f0fbd2e671669f24d5928d466e63633b5c526fe0b5cac1d62341d2595d0294e44abc3a38470a590a258b18aafca160f688df4e98cd8c88e94086ec4115397f90554f0551d97c256a071b57ae571cd569b1ac8248ef174c29e8e97c058795bc45e7e93e4dfc40cf8c0388d59c0bfa1519e142134782663a14d40144c43444a80cd479f50f706ff7bb884236f97696931aad382f07d130cc1bb597de8c49041a44d65ce78c61dd70701d08a014a0e8f2939912f736f84016d3bf486fa3fd36ef35623cead9a2fbe2ef58ede86eb631ae2be4dc087225169bc4f04fc4b6cfbbccfe2655849e1b00ef2eed5be1136e32ac2542355dc750b6c8e6ca5082a0226139ec020c23ebde8be59971fceaa569a992520b5ff92501a72ded50694288341fb98de445f0f193e31380a66ce1d12846097f207fe30adee435a48b1c0dac3174419394719d43be334f5533f7e92ea2a1dd4541848c8e30c25bc727ddfde6eb4f32c78298bd778e7d11bb6d4c26c09519ce51421df0074d7c6cf7461b38eea57ede4584cd2d5e433bcecd70491a2323c0c5d7993eabea1de12eb6b47246506559b26c14deb84347b6986f70ae6550c7a57bacaf09ba0740df66aed4700c1a016512ce10fc4370b7d0d8c90d90188569f502f27f4499e78a2e924e3b8b2aabf3db342fb0403b7878dab41576e0539a41e94c40751ed8cb6afb35704ea8584e533bf5f4f002821dc243884b4c82e3511f57cfcb9bea544ae969b37d6c81186bf2a778c6ec4b70666ae1b38ca9d9892d5ef04880f0c749e60dc8f7a05b3db1ac8e8714090b14e5d7405265f9631b65596e8a754d4a860bde4b71350d66952410a4a2cfa5cae79fe35b9e8372076a651796f605c3427164d74567a996be2d313d4f4f22d921a3e38dbb25d8fe89cbc9488d989e31561920b552f26895bb7dbe42a2ba7ccaaad08f1463808749723fcc87cd6fbb93ed0088fd4ffd2ad775e64d748e574090ba69c861eecaf778ef591d806444327183b6948d931bf15029adde57f59340acf24023fe6ef667e239f048e198782895057f9d7517f348f60b314c2237b4259a6c7320c03c71d4d29fb0e13857f68f77dee5d1c536b44febd242fc7e2928f9e449a44b742825ec3ac3505456f2ee47696cf16dd5f4424935d9dfdcfb36b0b251e7367288cea74af922c7bd586fa5abb2ecf1a2398fc0d7810f0089bacbd32f1eb30eaaa14e90481043dfbef4c8872f4bb292cabed42e6c0895988db3a5bac8fa86e079400ab358e152d3f2b5505a980d569defd48e3d14279397e6889b3db2d6dcfe073787264019370c3d8fb5fecac4316288197cf6bf45e007a2c3241c7e043a6c78aafa3f7808b4fffca82de752807e6dfc18981d39bb1e45515090db04afde329273d7e71be0c90bff437ec907610d9315a44a8e4feeb788eca167fe51950e011da211d523332e4033506b128fa1db6c64a000000050b180d4bee3f157a6ffe348e507d39ea68b8a5744b86213cc2ef01f4d5c300611aff4114afb1c7f4aa622251da30a10b326a6855c7de57c09bfa553ce5be8e40a54a477bb21dd23634b9232f5215381fce7b8c2c611ea64e5dc2fc004746adc1e801f2a8819ad0dce9d846998a49f0df5da5a491b2c79502d66b898bdcea3bf2cd798a3fa16144abfd217798d156dd483e4dfd4ab0ba8cc27bbb5deb269de1b20000000500000004fd49b7ac89c4b8839802e7972e13167ef167a7a86042696dca85eef5ea7faeb96a7fdefd23df5ffe4f389e5ce7e7a9b30000000000000004ce8c0974ae894c9f1540a8115afac5a90505e579cf2200c1786fa4e418da82f1ef4877dbc21a69a6e7cb255ffd947851cd24072d8953c0c465de07a2b1d9efd47aba87ef02104bfefc5958b9818280732a56d6fabb8df486128d71c72733d4cc615876f3eadf450ffd5ce85439838ba01215addb18e801c75a6456c09c5224ea627e2c41f63206439d62c547faa2c2364060db8f4d20bedef5c2b048320b133057154150ef0f9bcc07f10419fc49628f4a29c856f8cae268811e52ff7a6eeb8e01960ee054d5c3f46f07faf99d62c8faf653a0bf01b3b3203cc9228d3647fea12f88528c2414d0b1c11f4b14ebf696242e74646df2317aeea1f3564a32805c3056e156071a3ba25a675dda45f99438c3a954f9e8aa87106db304f5d311bb3606e47110a50403866d1ad450e6527a13df5a4e0722173b7d6b331a9cc85683b3346ee3837030ca546ededb8a96fe7f92a5615e0503147374ef1400f483776a2de8c4e03649b5f04c426e8ea436b1d528dc916d6e6bc7fcfdaaf0d3d74f7385faf41d0b9ae6a2fbedc26f4a4b36235b18abfe287a4f194cc314c370a07eb55ad4158fd500320f4b97cfa51d8ce83e435b34de937333b81c7af2f53be8792fa0116a3242dcbd9db53ea8513bd288331dd507aea922eba376387cc52e219fc0358c0b219112fc3c8f7346d09a062f62219af3ffcc142b78cd635f6d39b96a4f81bae6750248b4dc1bb34787f71f9a8056b5111fcb90e17345a4ecb0a9c4962293c37efdb9252a2039e8211a521b3e28bd733a392477dc11f99d40e00512563e60ed0497ab99f5a08e2e0f140a279f30d799aa3eab3557df4236a4df2d8e61541f80713254564252c2e03d22ae7cfac3ee330d76afd815bb03ba103cbeacecf42575601fa11d97ef6e5f17ff7eae2b2a67bae7418bb1d9adbe8f61fcc2f9fd6af7db4e68f688599aabcfcdad97ae3214a78844302b3d3418bf5caac744dc6a7db9256d45627ff33cff2854fe5e05d82466efa389d503b07c3c99f569ae4f3a3eef8426340d404d32f89f5c8bed9edda2bc824bec18eeeaf5b065f09536acbf483b409998ed316d321ac0140baaa394bbd5c6e0ed9302dc1dcc5cb1fc1738dd220b03749cae079b4850237f4250debc2b76e37922bc0d259d8c73e92b3738b59d1b6a8d59db7d85de95c255803dadc7b60e5e799c414443c32a07aa1f6392ae819c9c7f1d59aab1370064a3c3aed346f273b4e6e690769e3087fc75a840e89d4dd939d617fa99a641c96efb1735835290e366e52a7a2f5366c6483aa59cd87b379c8b25fba3128418157fd2dc8dab35fae8855139c7b3ccb08e022fe78362b94dac9b44ad3a73d52d1833b25fbed9d2713a7e7162164b293d95c1ff2b1ac17165380d648c8d925d43bc5594ec8e3ac323245dce749ce2eff8ea586ac13ee91de2adba6cc5c3ef270373247cad05e18afed6ec0f69b3f688a6f27c04e0e3fe5fb1795bf1dfdbcb75d6bc71ecf95f4df54dd710fe5076c5636bfe9e942c05f2feea6361f1bdf7e97d45c5710064989bd3292bf745f69f823616c4a9335eb92252afec5ca7000000052fe436bb96be4b74c39a2519f21461bc40db9266d532adce3e82aec6b964d061051678af6bd0063f03f937fc382e8a0ea472b30cbbe02840ead6d3c0a1a140f6b937d519689cafd82c67ac5c294e8d82628440892d74f18c30b8bf7737573b0d812a72cae4635218766fd73578f1b8ae6201935e9d7fb121dbe8e15163e76363dbac7bf1d06204a7da2d3c09e74a648c128cf83a6f3a453e457d157624f6954a000000050000000403a98d91f5fed62e9b9c3d907c8df63fe29d053f9476167d814d36ba31bba86bbe4fb8addb172512540157ddd36c1d190000000000000004288ca511323d353ce1ec31144503e475b648e934c3f15fe9051cebbfab186a81391722b32c471fbda6f6c0b0f9c8e063f0d343bc174ed2f292730ad353cb2d9966221b6e24e8c36f287dfa65c3237111dde4897a455b950831dd38e7d7d3b2458eb528160adfb8789569008399353c1d5d63b17aee102eeb22e516d749710bb28aaaab242bb579e7ecfd5097c44b8be71edf2b5c59bed6a86a27d88605d6b7c1bf25590d3a84b6e6437fba8c7479a84252221c2e0fc6ed26a8e94a75838958e52f5aea0442eefaa67d1b183fab268c3ee26c51704962b85ee2476f46906af3a8feb5e44a07bd8f11160ba9c4b5b42c1fa02bcb94b51e15f3591784fba19ab3f8741eb1ae8c1156a21e642779dd2ebadc37cdb247131537012c2bcb45a3828ced39834c4beee1b74e884c64453e0925dc22fde064ac74d4a3283577c0327993fd862d9a1ad1adbdde2c74fb4e2d3955cb6a8044248ebd2f31fe88536d005d3ba900d017088f5b40ad8dfd64b90587ca561e4ced1dc13f4f77b253fd7db71eead5f4d9d91a31dab44c3664790554ca731898dd06a1f6b14ec1735077afd3ca12ff620c84dbb1cb27c39228d0a993dc92b7420516e0bfc410a92a182783bc6fa41c4005d75696c507c8447ca35d81e9b75d11d231bf476fe37106afcb43b347bd2086829f9569bba03aa6aa58c8e5c2176f8da4d69c9cfbe90cdb01cf961ab1b1424d53ed6f5245c147a21e9805a9709311f3b8dd8131c842133ef9afc8fbfcc909d71a3fac020830d9b92aee2de144ab52b6a833c5b1b3d2aabf021414ab3d1f1bff4453e62aacc4a3ec86b351b3ebdb06b1d8aebeb19be8398a6f8fe674886a41eef72dd72a6a99078d0b2912555f791631fd555839ebd91402094b8ee268d91dc5468d0fd9cd1ea5260cdfd08a20bd14b672e3bce567a425e503722f76c3ca0a080a6cf6307c609111b72002a60b293a3d9367b35f6faabbfee547d7337bdd75c4d90848da596d01c0cce633fbe45736d520640e8a4bf8eb91f64126f3b6e0dfd56d104101ffbe195c7602d7b1e63f9c77945cd53b5c1d6d19c4e59ddd8334643f15fa8b358857f6088152c3c2f1d9e21a56706c0bcc30f87d3900470216819a46c12bc16f3ac917ee74e651f20d8bf1e856d1ddb729ac5f7fda4488f50cdb32c2f58988fd09e3be17d058b3c09f4e61eeed0b52618f4b23d090931c8632d7d2aef40768135fd963edb736acf89679c7be29aaef385ae919a8836d5b60ab2f85334b600a9b49c27a12b5f3e09717653263ec258517672bf28bc58f3251e876dd0890ddd837361f13d176776d7db28a44992b1eefd69b02184b9b6c12cc73af90835522a76c46bcbf4aef0051bba88f94c730b071b095af2ae689a2a3afd4a2b1ad94ee7ab509724d49a9f8626455253aac13d9067b7876cb6727b97bebd2d479555b67351ee59efccb31ca217883ac230943b55a7a94a89cc579379986befaf478faa998a50eb1deb548723da7f018aa9642c5e996b20dc572e1b35de2104d757ad02f08392fc7a0f96e0a75671a001d80eddf08a4d2c998dd51e3ac1ae92a7200000005071200db237436cd7c542ff1ad5e8973f60a2f9547f24140bce52b22dba7559437b84bd023661fcad2f8aa6bb5c52340a04c6a1ad72ba2327b621a1f64833bdd3ff778b8deb63d933599a9d9659d097ecce2d0ac76d36c7f636edab0a0d57097d7ba39878dc356fe9f0736d4ac01de9f0691fb35055a7881c39728bd332febef41da6c758ed1dc545ca858b1b0fe8c46267224aa45ef5a7a019bf98aa35ff99e00000005000000044a85c34f1d5e3d5f6186b5fa093ca9819fe4743ca2db8ae3d316157de54f1c5f72babaed7497624ff7fea4f24cfd7fe30000000000000004e73fc7b3b38ce2198321c0f05704e8ca9a07dd2fd008bf32a417fe8fdefcc6f8b118b31fc93533076e29159660e4e5bab2d943a3bd29fc5589aec445c2d44eab4670cab7b78f2b7e813f8172d329d573af0868d56a3081bd22f7fa575b366b2729e3af9da748c4efbe4db8866d435674202537c82097433e4a6333dd9acf7a85844f6e36859d547d0c3f505a7952ce5aebce5d20e0a34d20a6741bdee394f119fe21405683309dd2086828d9fc86d370ced71fac710c73b5e7abaa930a82303eb8616d6fa572a0734e9bbe4a3630a61fca4ac9ebef438f66a4978050b189d65cb966d2ae978fd9dae85960dfbe589c8fe97e79fbe2cd0eda320e7507db44a5abc20511a12de217f37b6be38bde75cd8fbd6e3c7ceb95fecc58be7c41ebed985d5b9c83b67fe7dff9b62318bc8a1e6e2149727e972ca47bc63f3d446b57a6d7295dfbc19eb440ab3ec888027b0f54a585d87e1f3461be46808869382e0d3cb647eca7b29f42cdc5446006fbd58153580c42437b6d20bdd8fbe5ce02154e618df750f368d31b23f839396ea8fa2842fdbbe7aaf8dfa8df00d22d13fd1f2eff2daa7e0a193fc683b86985e0a33da498c5151d613f28d33cbbf20a0d2f8dd668f9ca667be11bf2ec4aa1848e1a9f66b3069e63d89d0c3c421bc566dd71e13157e72e4b28a489bf35c6464ca6b98ba6dc0a5e236fd1d6afeffaebcf8bb443c7a50d6cf38035a55d1f18f7b1378fcb510bf41800117085c5f276a78f853ca79f8e9121f23e2b3e4a514a78839caaae42204431068655a9e74c33caa82b5e6a677db7f71943e3321eeca123f4bd90e4e17a4abbe025684afe7fc49057fa86a64393d5ee109e6aade800c64414c4b6024ba9ea1431cfd23414f708ef15811e0d14b57fa9f06a6092996472cb55752d8f900984bf9bf9fd816ca5953491c91ad174d817d7ced49a0c2380eecc5039822617afad7b5244a451f21e76d8bd8d449e720904587e76b4e8fc771f29a4038e5bb112ef9eae477677333f1718c12472dde0a18204efe10adb117165825fd5f76fd161f027850979b4304ef64b906b8c5dbd51df63c0027d0b61820c89dd3b8d747d285e918aac6daea72c68929cb63edbb4181af2b6f50a73404415d11baee572af2b1d600f5fe63ef2a0e021e0c41a5a23333461a074a81ef92abc97b46320086a341dec3bb47ca477a01c64e7f749c6c19fae0fede2f480310916f273f2bdb2755a34a8f2fb367a6c2af642789cc17d52b29b1d8fe5b9921f06f9a0d74f871c8cca49829ca65c61cdce17b46a4dc927f95be281bc1ff72b9d4391e2fc8626c1953f284a3a310e09bdab525faa78b0b7e26859aaec194e88aaf4fe1f792924bcf8b51622e852d34d847c3d099e74145fbec1f6d8b77756c863835d7f7b82b3aef190f57233e97807ef8a4d20858e121f40c845edf12e5cb481d83d9ce50ac4be81acf5c57deece23f4c138cad107797d5756fe40ddd7bda76b4d9e5f744cf649b76d5724b97f8e6ea2c349393bf8e314b334c63f578d47ffea7dc3faec8d2e657faf8566367d66b1602975f36a82181f837e545d00000005c89ca9bb2d7db697f367fdddfd5d8bfdccbbc5b1d9f5f33c0f0d938f9aa4028760240001d43c7991dd66f9ea20183f1f292934c064d5f8df527568b995f206a86580f098fc3f9fd33d082e77232fcdba14b5d0813710ec4a5a60c16b9e2ac82df3446ade7c76f4ff4a46beff4ff30f960826a2fec9af85a842ad6076e02e259687d977d1d63506b00fde5afd9addd100b15db34b151f41a05a154296a7f1a1e3000000050000000490bfe15d1f05d95c8d253f6912ac96f14cb60f4e64fc90998ae6d74bb0b4bce0a6f3f285618e2d55db46424561b9e84f000000000000000416214e38e67788bb4cf4a76d7b93355c0538b5b1662ff60e7585952e3112f21aa9286a7706b621b63e8b88524600e101f4e56c5fea5980c2a1ddca9db1b551852f65956e852ea422835ae5c788714b5fc8296c0c4c06fdbf199251fc1cb17ee4b4937abb22e35465f88020ba330e5f7411633e6204a30709e40414b755bfb0d84986cc2bb58b8c80d630ae379a8c4adc6df29f85a52fa70ead5073fde470c9666137ebd63f7f13637da99fb978b1f16dd50df2c213a22d9a828ec060c4c90437ba3593ee61439fbcd1920dcf74d6433177881ef1e2bea125fdd625584dab5960129210d01be44d4cc5d80f0930a58f6f210db866e68b63794c1ea602a7ad5520d3888aae9f652fe6445aeeb6c9c2cad8806e3e1e3b073448753447215cff453317d58f3003068af281356a5f448dde54f7d352cd62d5da058ecb792f56e9da395acec4d0236e3c80e3b449f31f892bbf171365236289b491e0dcd67cd86302d0583a4c54ad5e15366dfc02ec74b1cd197ddd07176d588e2c9e16d159518a9e6306a2eb7a4f353759be460b1e1aeaad7880ed82010820b34f1ac46b5a05a4dcc146458b3eef5e0ec19ec9c087604271692d2697f8c04b6529063dec7b53dde54ebe42a435ce2f3f3b94a7f78aa6f0aeaee0356b5a622bc540fd81df8a5a0f9cc7baedcf6231f50f707fd39ce1e0101fb4cd79d576104f735c647f555dcc756eb868b793f961c5689107d9d8a6c628ab52cf6d552c1962e4decfb224fa1b32afe6e837ce2c584e848724d5165f4cbbe94e16482435498b787a7b16136e9bfab14275c30decb873821b7ead9cdd3304383f9c9027eed62d871d127e8fc99094430639a87cda5097bed99d6613115d9a4415dfb618889bbd9e44c34dbebd06da3e167993d388a6f106b0948d4cee011830970ee8e5719bb3cfeab6ce5a9f6415b89770b39615e98aa6348e2a0526112dffd31efc22bfe8e6f233e45ff70ed9039a0624fa290211dbe9bf965993a57f24b658879a4464164c245c6ae11476ba05fe6c7821bc5e42375556dda06f1bb3e8728b88f77b8a0a13325269eaf4c176bc822abf8b5159dcb8a4675e4eddc51827a34547e754cef9addbd2116cbae7b70c3324dd17dee271390ba6a09014930297b3abdfea220bc5dc17974e88c2992dee1ee2c1ae1e4802fed5b94120db3ce063c3acb7e625c4b4de2546aab6441b9bc84d63d43fa33557ed1e28de5076e2e3c2440b502406d2cd019a55c2f0ca457510e36cf32d09b5c92f6e600c7246567122dc2f2cb6a5bbcf6aed8ef5686523ba11a7379830d3ab78081e239cbabaa9ee320b0d3074c0306dcaa35ec63962dba95b9d861ab19ea2f5682f505e964f47ff9fcbee437dbf8689ca01b580adff8a2e35f8c4bd3004889e74b3e7ec1c7a5bc381d577fad6be1bf34ea8dbaa761b2e96ad618cdd0153ad7e6e3a5f4f855e0889aa1d321e244b2f1a5f603a456e69cc321abcc9353413f5f6a2ffeefdebe156c3b6739dcd93acb95113e5abe10435a561e25a7e016d03a18ccaf00f9c774fc1c0497c4a478be37e9abf60f221c206e47257a76d000000052c887acb0b01fec3d954155fb6c05c1cfd49a7d08f47b922ff0a037dca04512d09ebb3f25c134b43782c2bb22d4635564d7d443b84848ed576a9c6c0d41f529788e76a899c34b361795c891b2ef03cac5dd5f85df25f2d4705226e60617619a5aa86e9474a0b5fa6544dafb998af97ce7f99082fbd4296920af3dd3879b1887b058004d091cee10d0796ea20bc0683d7230169fef922d6903e9f946410b7e54700000005000000040e0d0f3221d2ac730300088e7360b2b9c57764c964e440d7e2f291c8d5d9e2ac225f58adaec5332a98b9c596e3e4e6650000000100000004a755f4c5ba2c97b47448ef2baa8adc9b4e47070af99a3d9abf25ef3b062e7b3fa3fbc54d239babb16ae961ae8e440b6df5d2440cce3fa87e19b516ffa6a022a528ab89921dc9ea6c4955d0d0b4a5068272dba7ea21e6d3629d8af2c5a902ece63f5599d9b7bc4a431dfbe553d949e571a99b5a4603038d3c03a9a6556da85c1d898b0190fd4577b3aa90478693bda15b42d07de0f90f98ee8fbd2a86ef768aa393b5bbfc7984654239b9e4f40a8bcddf34b422933566eeff3d21f88ea8dd2a3c865b9dcffb856f36016378ef08d3b53dffff17b9a1a04b1bd6b29d74d3ee0def2c231176d0d9acf93c7f61c83e569ad935266cf0d2641e0b90fbf91b04cdc635a2ea378fabd9ae6f03ca8e68c36dad3cd69293fe7c871c4fdef014ceed7483018cc2da6d29f6317f504b0490529a4776ee96048f2ba88973c6f21a6e5786be1927b53c881f79e9bfb62f0dbdd90e62ebd16bf18dbc8b062b046cb04f50efecc7d1712cf169ac2987f7eafa1d33ca3b653275b3cf8f5d5a6248e02245ec87854dc2b8da097726a26179c5960d7bd7bf62b59f89bcfba31211d04f18874470ceb8c8cff42237b15e38587342cf33fc9b54a47988e5f7897722d20f3713e80e8d75806ae7eb82b459072f28420ab2a6cc1e603f39dd0aec59a1be963aa72bb174e4330ee61ed79e1337515f6c38d4738c8c17e499aa4613469e4cc0aacf345bc634a3a3c5057bd802b252a6bfd626275039993e11889c2fd6c1e5b454aa34333cc4bfb4b24aef6dad7843781c9e81bd5d9ab528f880f25370a9f9da186b0acabec8ecaf405cab5f75f850958eacc21ae5138c80ead4c580e3a0f8ca4701ad6dcde39a9963544744de529fd1d6d67ab6674accbab6636042e90e24cb3d2eb2e017def1ad7be905d29d51d1376ded655da81cb4d04d59082fcd70fe43430a959fb01f02722fa6ed39c6c612f1bd5d4f2ce3f462efa2d07bd4b3bb20b1418e36088369d289b5e6a62ab5c71a831f635edcf4d3a112bb44ad216d8bc48062e50925ce1f260d1ae4c709c16f3c531f0e7b6da1dc9d362291f9c58b56bc6b9b16ecb4988755d29fe9f2fbffbee9f0ee0d5b431426db9c72e633b10bc6b9b51117877323e00e72b0b60c78f53fcee4851c2a89bbc3ce62704415b0ee1a78b7fb981eaf8490b284f47e488075f05d32a7630d7b6bf12adb495c853e245ae856922a764ebcb6ec194710d8b3e1145c1b573548cd76a9a567413cd5b3527797ce6feeecee8de885240a7913ac5c121ebbde05691124dce512d9c19df63f908b1d23536a93ded8316802da8348e1bf46cf80f3a58954a6acbd56efe44ee6a7e977a14fd4889b9ec57f6ba99d47eb75f197a1d234f8ed8861dc34a7376d5b8a66d3562fe92df1482979f4d8cb7f01ea4978d5071a6f2513efc6dc65ed4bbe9382b59a2bf5f86004244193e483d200d4c413f28247ff74951c9a6ffdafd71b6c6e581fa2255da48fd837b0757603f316bf95e95a1752ea0d3fb559ba090a7f306ec3ebdc55365aedb352050936298442a88e7ab27bf91a86a5a2b1e2692c469d3edacaf1d361976700000005ac67c55d81b59a4f49a3373c41941394f2df2a29bd154718cb7bf01b59f6c0d3293dfc6653da2687a2acd7b1209201d8eb54ec86e0999f9cd9911346d6bb4b5038729269c3f09560792d44602af220bba33f78757e0b1a625783d83e080a312d36b274205192b04c4945e5e541514933d1eb52724d39a2c72dfcb3d2e665250a22f411c4f9b224d38e73d63a1ac6ff2dd225846a51a691215663b0cbab31c28e000000050000000475769664d9c2a018a311b3181afeb6dc5644e3c96f758148a862eb0b9acbd9454daaba156a0dc07880367649a62f058d0000000a000000042e71818137e7a0de0913d3dbe669d8ed052eb11463e0e27944d8321827eb9ca10f6333c8f46cf967ed981ba096943ba9bb200c349c3c861e44d69e4892da1cff00c1f3f761384c17500be09af903705f6fdd58f5b6191744361565e453a0ad4db84fd6d9b05b29eecf53d42c279b449436ef411d9a3a7644093871dcf4191350611289215624c0bbfae167bd2ba5fea16b5bd267a16ce73b42ed7b7cdbc9ec90e9eda87a264346877871b136a0e15f284e953a01ce47243f4baa84b209da7097a29449c0e1dd5de4909676071e822b2cdf4020d048090d62f139efbe257cf22f5f6e260ca398e822b78ab6f369fda984e0eac0e1c406a10c2500d816e189da26c79da4f20d8d1c70b9d2a2b7cd1b35531b83f5109aea65dc33c52950190c5e91ed06e7ce01fe22d3ea8277df3b18b973e1bb29aa9385e9623c7d75d0d137257e13207b63660e88a145acc65d1c14faf9459836b1343d55b7d5a2d0e953e5a0d2f6e54321cbe661b0360372941eecd5b2d5dcc5a9fb7cc846f277260b0ddb92eb15086d7e44d43eed12e05a84c5de08bb924d9695687c63958a186bc0a4f7b284b0cf40540a3749f019565ec9facb1d5602d8485f3ff5b9e6dfa0bb96428b3f9abdec3d830b6893c682a767eff95afd5e5486bb924373ae42824d8bd3914bbe37fa2f39f5c4569e0962163af6a9621dec44d9c2c21f02d9126439caa62299d569426648ede23af52fe8f3ef62962778e12dcf6f50c82a77fc7b5b601f8304a800ad3cd17f57ce757baf8db0f12994831c204dc55b47f3aa650b096a506118aa213721d4a5ec26951d5b489d623d2341382359aaa447895b7b8b7ccffe5473f3bd4e4a4feee427307c155c34b857fc7ab7c7b4dac15d0b1bf0201e5c32702b8bcab101f5c3bb7d357f1ba37ef329210bbd95ab073b6028f3e0f5410d07cb7860d24f9431cb32ef567eefd542831a8539b321fa23f8c8c2e5c96699cac8931299672d3451d67882c91df6cbd2dcd18b8eb6d0d42d9601ea60c0469774aaa8001e36e2512bb8488315350d02650b2a7b8d614d18140d32958be9f0ff626005a0362632e9797770506e29794d78a441220d7af4f2393cd48dc3dd60dbb4ede627939e5db71aacb5a7bd66355ec4ef7afef1b6b10e84f4d46f3951f136194e2ed02e6a501f74b3933e14ffee106a814e45fde1112dbdffd75925724b48d35cbf8ca958535910bf7dd22008c2f1c929602a55e975cd394e8faabdc5b1a7d1f423d46e3544f38be84c30b786e4aa3ae3a258c76b76e439a59fc014be73d155bb616fa6743d95d6fb10ce7b6e47c825b050a61661e1e121749edca4a788957a2591dd9a4227341ae9a15ac21aa160a7d317c7a6e5473087f74c8af6559f94bef1b2ef386bff40a7dfc6db067970d161abc22068a6d16989ab890c3c71492addd96b9fff4a5d05633432f975574cc41624745fc78b732f1b0111d5c69b17847d22d3731ff262028db786829c282f8b050c5250491e87f13ad387a2523fa5255ad0af22ce6a63df5d51237b48323f5de1b7ebe8f1fb244a2e1f1a447a8f68e0225bc694b203000000053e73cf2115d7d605601febff7a660097aaccf4705112b7fa133990a734fdc6de8436508c95c94243184cb4a272e1748c983dbb0e9d6e8023ccfa07e4b1b5b0109d72ca5b6b83fb49c7b76fa9c3e88beaaba6e46364c5ed122df80ef06838ee05480ef5d99c10a2c704ae827e2c7529b1c4c7053449698987d74d0704de2d94c5c75bef86b09aa5e3633315c7c7522f98efdb912779018acd75d59392185ac4fa diff --git a/src/tests/data/pubkey/hss_lms_verify.vec b/src/tests/data/pubkey/hss_lms_verify.vec new file mode 100644 index 00000000000..49399411e80 --- /dev/null +++ b/src/tests/data/pubkey/hss_lms_verify.vec @@ -0,0 +1,24 @@ +# RFC 8554 - Test Case 1 +PublicKey = 00000002000000050000000461a5d57d37f5e46bfb7520806b07a1b850650e3b31fe4a773ea29a07f09cf2ea30e579f0df58ef8e298da0434cb2b878 +Msg = 54686520706f77657273206e6f742064656c65676174656420746f2074686520556e69746564205374617465732062792074686520436f6e737469747574696f6e2c206e6f722070726f6869626974656420627920697420746f20746865205374617465732c2061726520726573657276656420746f207468652053746174657320726573706563746976656c792c206f7220746f207468652070656f706c652e0a +Signature = 000000010000000500000004d32b56671d7eb98833c49b433c272586bc4a1c8a8970528ffa04b966f9426eb9965a25bfd37f196b9073f3d4a232feb69128ec45146f86292f9dff9610a7bf95a64c7f60f6261a62043f86c70324b7707f5b4a8a6e19c114c7be866d488778a0e05fd5c6509a6e61d559cf1a77a970de927d60c70d3de31a7fa0100994e162a2582e8ff1b10cd99d4e8e413ef469559f7d7ed12c838342f9b9c96b83a4943d1681d84b15357ff48ca579f19f5e71f18466f2bbef4bf660c2518eb20de2f66e3b14784269d7d876f5d35d3fbfc7039a462c716bb9f6891a7f41ad133e9e1f6d9560b960e7777c52f060492f2d7c660e1471e07e72655562035abc9a701b473ecbc3943c6b9c4f2405a3cb8bf8a691ca51d3f6ad2f428bab6f3a30f55dd9625563f0a75ee390e385e3ae0b906961ecf41ae073a0590c2eb6204f44831c26dd768c35b167b28ce8dc988a3748255230cef99ebf14e730632f27414489808afab1d1e783ed04516de012498682212b07810579b250365941bcc98142da13609e9768aaf65de7620dabec29eb82a17fde35af15ad238c73f81bdb8dec2fc0e7f932701099762b37f43c4a3c20010a3d72e2f606be108d310e639f09ce7286800d9ef8a1a40281cc5a7ea98d2adc7c7400c2fe5a101552df4e3cccfd0cbf2ddf5dc6779cbbc68fee0c3efe4ec22b83a2caa3e48e0809a0a750b73ccdcf3c79e6580c154f8a58f7f24335eec5c5eb5e0cf01dcf4439424095fceb077f66ded5bec73b27c5b9f64a2a9af2f07c05e99e5cf80f00252e39db32f6c19674f190c9fbc506d826857713afd2ca6bb85cd8c107347552f30575a5417816ab4db3f603f2df56fbc413e7d0acd8bdd81352b2471fc1bc4f1ef296fea1220403466b1afe78b94f7ecf7cc62fb92be14f18c2192384ebceaf8801afdf947f698ce9c6ceb696ed70e9e87b0144417e8d7baf25eb5f70f09f016fc925b4db048ab8d8cb2a661ce3b57ada67571f5dd546fc22cb1f97e0ebd1a65926b1234fd04f171cf469c76b884cf3115cce6f792cc84e36da58960c5f1d760f32c12faef477e94c92eb75625b6a371efc72d60ca5e908b3a7dd69fef0249150e3eebdfed39cbdc3ce9704882a2072c75e13527b7a581a556168783dc1e97545e31865ddc46b3c957835da252bb7328d3ee2062445dfb85ef8c35f8e1f3371af34023cef626e0af1e0bc017351aae2ab8f5c612ead0b729a1d059d02bfe18efa971b7300e882360a93b025ff97e9e0eec0f3f3f13039a17f88b0cf808f488431606cb13f9241f40f44e537d302c64a4f1f4ab949b9feefadcb71ab50ef27d6d6ca8510f150c85fb525bf25703df7209b6066f09c37280d59128d2f0f637c7d7d7fad4ed1c1ea04e628d221e3d8db77b7c878c9411cafc5071a34a00f4cf07738912753dfce48f07576f0d4f94f42c6d76f7ce973e9367095ba7e9a3649b7f461d9f9ac1332a4d1044c96aefee67676401b64457c54d65fef6500c59cdfb69af7b6dddfcb0f086278dd8ad0686078dfb0f3f79cd893d314168648499898fbc0ced5f95b74e8ff14d735cdea968bee7400000005d8b8112f9200a5e50c4a262165bd342cd800b8496810bc716277435ac376728d129ac6eda839a6f357b5a04387c5ce97382a78f2a4372917eefcbf93f63bb59112f5dbe400bd49e4501e859f885bf0736e90a509b30a26bfac8c17b5991c157eb5971115aa39efd8d564a6b90282c3168af2d30ef89d51bf14654510a12b8a144cca1848cf7da59cc2b3d9d0692dd2a20ba3863480e25b1b85ee860c62bf51360000000500000004d2f14ff6346af964569f7d6cb880a1b66c5004917da6eafe4d9ef6c6407b3db0e5485b122d9ebe15cda93cfec582d7ab0000000a000000040703c491e7558b35011ece3592eaa5da4d918786771233e8353bc4f62323185c95cae05b899e35dffd717054706209988ebfdf6e37960bb5c38d7657e8bffeef9bc042da4b4525650485c66d0ce19b317587c6ba4bffcc428e25d08931e72dfb6a120c5612344258b85efdb7db1db9e1865a73caf96557eb39ed3e3f426933ac9eeddb03a1d2374af7bf77185577456237f9de2d60113c23f846df26fa942008a698994c0827d90e86d43e0df7f4bfcdb09b86a373b98288b7094ad81a0185ac100e4f2c5fc38c003c1ab6fea479eb2f5ebe48f584d7159b8ada03586e65ad9c969f6aecbfe44cf356888a7b15a3ff074f771760b26f9c04884ee1faa329fbf4e61af23aee7fa5d4d9a5dfcf43c4c26ce8aea2ce8a2990d7ba7b57108b47dabfbeadb2b25b3cacc1ac0cef346cbb90fb044beee4fac2603a442bdf7e507243b7319c9944b1586e899d431c7f91bcccc8690dbf59b28386b2315f3d36ef2eaa3cf30b2b51f48b71b003dfb08249484201043f65f5a3ef6bbd61ddfee81aca9ce60081262a00000480dcbc9a3da6fbef5c1c0a55e48a0e729f9184fcb1407c31529db268f6fe50032a363c9801306837fafabdf957fd97eafc80dbd165e435d0e2dfd836a28b354023924b6fb7e48bc0b3ed95eea64c2d402f4d734c8dc26f3ac591825daef01eae3c38e3328d00a77dc657034f287ccb0f0e1c9a7cbdc828f627205e4737b84b58376551d44c12c3c215c812a0970789c83de51d6ad787271963327f0a5fbb6b5907dec02c9a90934af5a1c63b72c82653605d1dcce51596b3c2b45696689f2eb382007497557692caac4d57b5de9f5569bc2ad0137fd47fb47e664fcb6db4971f5b3e07aceda9ac130e9f38182de994cff192ec0e82fd6d4cb7f3fe00812589b7a7ce515440456433016b84a59bec6619a1c6c0b37dd1450ed4f2d8b584410ceda8025f5d2d8dd0d2176fc1cf2cc06fa8c82bed4d944e71339ece780fd025bd41ec34ebff9d4270a3224e019fcb444474d482fd2dbe75efb20389cc10cd600abb54c47ede93e08c114edb04117d714dc1d525e11bed8756192f929d15462b939ff3f52f2252da2ed64d8fae88818b1efa2c7b08c8794fb1b214aa233db3162833141ea4383f1a6f120be1db82ce3630b3429114463157a64e91234d475e2f79cbf05e4db6a9407d72c6bff7d1198b5c4d6aad2831db61274993715a0182c7dc8089e32c8531deed4f7431c07c02195eba2ef91efb5613c37af7ae0c066babc69369700e1dd26eddc0d216c781d56e4ce47e3303fa73007ff7b949ef23be2aa4dbf25206fe45c20dd888395b2526391a724996a44156beac808212858792bf8e74cba49dee5e8812e019da87454bff9e847ed83db07af313743082f880a278f682c2bd0ad6887cb59f652e155987d61bbf6a88d36ee93b6072e6656d9ccbaae3d655852e38deb3a2dcf8058dc9fb6f2ab3d3b3539eb77b248a661091d05eb6e2f297774fe6053598457cc61908318de4b826f0fc86d4bb117d33e865aa805009cc2918d9c2f840c4da43a703ad9f5b5806163d7161696b5a0adc00000005d5c0d1bebb06048ed6fe2ef2c6cef305b3ed633941ebc8b3bec9738754cddd60e1920ada52f43d055b5031cee6192520d6a5115514851ce7fd448d4a39fae2ab2335b525f484e9b40d6a4a969394843bdcf6d14c48e8015e08ab92662c05c6e9f90b65a7a6201689999f32bfd368e5e3ec9cb70ac7b8399003f175c40885081a09ab3034911fe125631051df0408b3946b0bde790911e8978ba07dd56c73e7ee + +# RFC 8554 - Test Case 2 +PublicKey = 000000020000000600000003d08fabd4a2091ff0a8cb4ed834e7453432a58885cd9ba0431235466bff9651c6c92124404d45fa53cf161c28f1ad5a8e +Msg = 54686520656e756d65726174696f6e20696e2074686520436f6e737469747574696f6e2c206f66206365727461696e207269676874732c207368616c6c206e6f7420626520636f6e73747275656420746f2064656e79206f7220646973706172616765206f74686572732072657461696e6564206279207468652070656f706c652e0a +Signature = 0000000100000003000000033d46bee8660f8f215d3f96408a7a64cf1c4da02b63a55f62c666ef5707a914ce0674e8cb7a55f0c48d484f31f3aa4af9719a74f22cf823b94431d01c926e2a76bb71226d279700ec81c9e95fb11a0d10d065279a5796e265ae17737c44eb8c594508e126a9a7870bf4360820bdeb9a01d9693779e416828e75bddd7d8c70d50a0ac8ba39810909d445f44cb5bb58de737e60cb4345302786ef2c6b14af212ca19edeaa3bfcfe8baa6621ce88480df2371dd37add732c9de4ea2ce0dffa53c92649a18d39a50788f4652987f226a1d48168205df6ae7c58e049a25d4907edc1aa90da8aa5e5f7671773e941d8055360215c6b60dd35463cf2240a9c06d694e9cb54e7b1e1bf494d0d1a28c0d31acc75161f4f485dfd3cb9578e836ec2dc722f37ed30872e07f2b8bd0374eb57d22c614e09150f6c0d8774a39a6e168211035dc52988ab46eaca9ec597fb18b4936e66ef2f0df26e8d1e34da28cbb3af752313720c7b345434f72d65314328bbb030d0f0f6d5e47b28ea91008fb11b05017705a8be3b2adb83c60a54f9d1d1b2f476f9e393eb5695203d2ba6ad815e6a111ea293dcc21033f9453d49c8e5a6387f588b1ea4f706217c151e05f55a6eb7997be09d56a326a32f9cba1fbe1c07bb49fa04cecf9df1a1b815483c75d7a27cc88ad1b1238e5ea986b53e087045723ce16187eda22e33b2c70709e53251025abde8939645fc8c0693e97763928f00b2e3c75af3942d8ddaee81b59a6f1f67efda0ef81d11873b59137f67800b35e81b01563d187c4a1575a1acb92d087b517a8833383f05d357ef4678de0c57ff9f1b2da61dfde5d88318bcdde4d9061cc75c2de3cd4740dd7739ca3ef66f1930026f47d9ebaa713b07176f76f953e1c2e7f8f271a6ca375dbfb83d719b1635a7d8a13891957944b1c29bb101913e166e11bd5f34186fa6c0a555c9026b256a6860f4866bd6d0b5bf90627086c6149133f8282ce6c9b3622442443d5eca959d6c14ca8389d12c4068b503e4e3c39b635bea245d9d05a2558f249c9661c0427d2e489ca5b5dde220a90333f4862aec793223c781997da98266c12c50ea28b2c438e7a379eb106eca0c7fd6006e9bf612f3ea0a454ba3bdb76e8027992e60de01e9094fddeb3349883914fb17a9621ab929d970d101e45f8278c14b032bcab02bd15692d21b6c5c204abbf077d465553bd6eda645e6c3065d33b10d518a61e15ed0f092c32226281a29c8a0f50cde0a8c66236e29c2f310a375cebda1dc6bb9a1a01dae6c7aba8ebedc6371a7d52aacb955f83bd6e4f84d2949dcc198fb77c7e5cdf6040b0f84faf82808bf985577f0a2acf2ec7ed7c0b0ae8a270e951743ff23e0b2dd12e9c3c828fb5598a22461af94d568f29240ba2820c4591f71c088f96e095dd98beae456579ebbba36f6d9ca2613d1c26eee4d8c73217ac5962b5f3147b492e8831597fd89b64aa7fde82e1974d2f6779504dc21435eb3109350756b9fdabe1c6f368081bd40b27ebcb9819a75d7df8bb07bb05db1bab705a4b7e37125186339464ad8faaa4f052cc1272919fde3e025bb64aa8e0eb1fcbfcc25acb5f718ce4f7c2182fb393a1814b0e942490e52d3bca817b2b26e90d4c9b0cc38608a6cef5eb153af0858acc867c9922aed43bb67d7b33acc519313d28d41a5c6fe6cf3595dd5ee63f0a4c4065a083590b275788bee7ad875a7f88dd73720708c6c6c0ecf1f43bbaadae6f208557fdc07bd4ed91f88ce4c0de842761c70c186bfdafafc444834bd3418be4253a71eaf41d718753ad07754ca3effd5960b0336981795721426803599ed5b2b7516920efcbe32ada4bcf6c73bd29e3fa152d9adeca36020fdeeee1b739521d3ea8c0da497003df1513897b0f54794a873670b8d93bcca2ae47e64424b7423e1f078d9554bb5232cc6de8aae9b83fa5b9510beb39ccf4b4e1d9c0f19d5e17f58e5b8705d9a6837a7d9bf99cd13387af256a8491671f1f2f22af253bcff54b673199bdb7d05d81064ef05f80f0153d0be7919684b23da8d42ff3effdb7ca0985033f389181f47659138003d712b5ec0a614d31cc7487f52de8664916af79c98456b2c94a8038083db55391e3475862250274a1de2584fec975fb09536792cfbfcf6192856cc76eb5b13dc4709e2f7301ddff26ec1b23de2d188c999166c74e1e14bbc15f457cf4e471ae13dcbdd9c50f4d646fc6278e8fe7eb6cb5c94100fa870187380b777ed19d7868fd8ca7ceb7fa7d5cc861c5bdac98e7495eb0a2ceec1924ae979f44c5390ebedddc65d6ec11287d978b8df064219bc5679f7d7b264a76ff272b2ac9f2f7cfc9fdcfb6a51428240027afd9d52a79b647c90c2709e060ed70f87299dd798d68f4fadd3da6c51d839f851f98f67840b964ebe73f8cec41572538ec6bc131034ca2894eb736b3bda93d9f5f6fa6f6c0f03ce43362b8414940355fb54d3dfdd03633ae108f3de3ebc85a3ff51efeea3bc2cf27e1658f1789ee612c83d0f5fd56f7cd071930e2946beeecaa04dccea9f97786001475e0294bc2852f62eb5d39bb9fbeef75916efe44a662ecae37ede27e9d6eadfdeb8f8b2b2dbccbf96fa6dbaf7321fb0e701f4d429c2f4dcd153a2742574126e5eaccc77686acf6e3ee48f423766e0fc466810a905ff5453ec99897b56bc55dd49b991142f65043f2d744eeb935ba7f4ef23cf80cc5a8a335d3619d781e7454826df720eec82e06034c44699b5f0c44a8787752e057fa3419b5bb0e25d30981e41cb1361322dba8f69931cf42fad3f3bce6ded5b8bfc3d20a2148861b2afc14562ddd27f12897abf0685288dcc5c4982f826026846a24bf77e383c7aacab1ab692b29ed8c018a65f3dc2b87ff619a633c41b4fadb1c78725c1f8f922f6009787b1964247df0136b1bc614ab575c59a16d089917bd4a8b6f04d95c581279a139be09fcf6e98a470a0bceca191fce476f9370021cbc05518a7efd35d89d8577c990a5e19961ba16203c959c91829ba7497cffcbb4b294546454fa5388a23a22e805a5ca35f956598848bda678615fec28afd5da61a00000006b326493313053ced3876db9d237148181b7173bc7d042cefb4dbe94d2e58cd21a769db4657a103279ba8ef3a629ca84ee836172a9c50e51f45581741cf8083150b491cb4ecbbabec128e7c81a46e62a67b57640a0a78be1cbf7dd9d419a10cd8686d16621a80816bfdb5bdc56211d72ca70b81f1117d129529a7570cf79cf52a7028a48538ecdd3b38d3d5d62d26246595c4fb73a525a5ed2c30524ebb1d8cc82e0c19bc4977c6898ff95fd3d310b0bae71696cef93c6a552456bf96e9d075e383bb7543c675842bafbfc7cdb88483b3276c29d4f0a341c2d406e40d4653b7e4d045851acf6a0a0ea9c710b805cced4635ee8c107362f0fc8d80c14d0ac49c516703d26d14752f34c1c0d2c4247581c18c2cf4de48e9ce949be7c888e9caebe4a415e291fd107d21dc1f084b1158208249f28f4f7c7e931ba7b3bd0d824a45700000000500000004215f83b7ccb9acbcd08db97b0d04dc2ba1cd035833e0e90059603f26e07ad2aad152338e7a5e5984bcd5f7bb4eba40b700000004000000040eb1ed54a2460d512388cad533138d240534e97b1e82d33bd927d201dfc24ebb11b3649023696f85150b189e50c00e98850ac343a77b3638319c347d7310269d3b7714fa406b8c35b021d54d4fdada7b9ce5d4ba5b06719e72aaf58c5aae7aca057aa0e2e74e7dcfd17a0823429db62965b7d563c57b4cec942cc865e29c1dad83cac8b4d61aacc457f336e6a10b66323f5887bf3523dfcadee158503bfaa89dc6bf59daa82afd2b5ebb2a9ca6572a6067cee7c327e9039b3b6ea6a1edc7fdc3df927aade10c1c9f2d5ff446450d2a3998d0f9f6202b5e07c3f97d2458c69d3c8190643978d7a7f4d64e97e3f1c4a08a7c5bc03fd55682c017e2907eab07e5bb2f190143475a6043d5e6d5263471f4eecf6e2575fbc6ff37edfa249d6cda1a09f797fd5a3cd53a066700f45863f04b6c8a58cfd341241e002d0d2c0217472bf18b636ae547c1771368d9f317835c9b0ef430b3df4034f6af00d0da44f4af7800bc7a5cf8a5abdb12dc718b559b74cab9090e33cc58a955300981c420c4da8ffd67df540890a062fe40dba8b2c1c548ced22473219c534911d48ccaabfb71bc71862f4a24ebd376d288fd4e6fb06ed8705787c5fedc813cd2697e5b1aac1ced45767b14ce88409eaebb601a93559aae893e143d1c395bc326da821d79a9ed41dcfbe549147f71c092f4f3ac522b5cc57290706650487bae9bb5671ecc9ccc2ce51ead87ac01985268521222fb9057df7ed41810b5ef0d4f7cc67368c90f573b1ac2ce956c365ed38e893ce7b2fae15d3685a3df2fa3d4cc098fa57dd60d2c9754a8ade980ad0f93f6787075c3f680a2ba1936a8c61d1af52ab7e21f416be09d2a8d64c3d3d8582968c2839902229f85aee297e717c094c8df4a23bb5db658dd377bf0f4ff3ffd8fba5e383a48574802ed545bbe7a6b4753533353d73706067640135a7ce517279cd683039747d218647c86e097b0daa2872d54b8f3e5085987629547b830d8118161b65079fe7bc59a99e9c3c7380e3e70b7138fe5d9be2551502b698d09ae193972f27d40f38dea264a0126e637d74ae4c92a6249fa103436d3eb0d4029ac712bfc7a5eacbdd7518d6d4fe903a5ae65527cd65bb0d4e9925ca24fd7214dc617c150544e423f450c99ce51ac8005d33acd74f1bed3b17b7266a4a3bb86da7eba80b101e15cb79de9a207852cf91249ef480619ff2af8cabca83125d1faa94cbb0a03a906f683b3f47a97c871fd513e510a7a25f283b196075778496152a91c2bf9da76ebe089f4654877f2d586ae7149c406e663eadeb2b5c7e82429b9e8cb4834c83464f079995332e4b3c8f5a72bb4b8c6f74b0d45dc6c1f79952c0b7420df525e37c15377b5f0984319c3993921e5ccd97e097592064530d33de3afad5733cbe7703c5296263f77342efbf5a04755b0b3c997c4328463e84caa2de3ffdcd297baaaacd7ae646e44b5c0f16044df38fabd296a47b3a838a913982fb2e370c078edb042c84db34ce36b46ccb76460a690cc86c302457dd1cde197ec8075e82b393d542075134e2a17ee70a5e187075d03ae3c853cff60729ba4000000054de1f6965bdabc676c5a4dc7c35f97f82cb0e31c68d04f1dad96314ff09e6b3de96aeee300d1f68bf1bca9fc58e4032336cd819aaf578744e50d1357a0e4286704d341aa0a337b19fe4bc43c2e79964d4f351089f2e0e41c7c43ae0d49e7f404b0f75be80ea3af098c9752420a8ac0ea2bbb1f4eeba05238aef0d8ce63f0c6e5e4041d95398a6f7f3e0ee97cc1591849d4ed236338b147abde9f51ef9fd4e1c1 + +# Internet-Draft draft-fluhrer-lms-more-parm-sets-11 - Test Case 1 (SHA-256/192) +PublicKey = 000000010000000a00000008202122232425262728292a2b2c2d2e2f2c571450aed99cfb4f4ac285da14882796618314508b12d2 +Msg = 54657374206d65737361676520666f72205348413235362d3139320a +Signature = 0000000000000005000000080b5040a18c1b5cabcbc85b047402ec6294a30dd8da8fc3dae13b9f0875f09361dc77fcc4481ea463c073716249719193614b835b4694c059f12d3aedd34f3db93f3580fb88743b8b3d0648c0537b7a50e433d7ea9d6672fffc5f42770feab4f98eb3f3b23fd2061e4d0b38f832860ae76673ad1a1a52a9005dcf1bfb56fe16ff723627612f9a48f790f3c47a67f870b81e919d99919c8db48168838cece0abfb683da48b9209868be8ec10c63d8bf80d36498dfc205dc45d0dd870572d6d8f1d90177cf5137b8bbf7bcb67a46f86f26cfa5a44cbcaa4e18da099a98b0b3f96d5ac8ac375d8da2a7c248004ba11d7ac775b9218359cddab4cf8ccc6d54cb7e1b35a36ddc9265c087063d2fc6742a7177876476a324b03295bfed99f2eaf1f38970583c1b2b616aad0f31cd7a4b1bb0a51e477e94a01bbb4d6f8866e2528a159df3d6ce244d2b6518d1f0212285a3c2d4a927054a1e1620b5b02aab0c8c10ed48ae518ea73cba81fcfff88bff461dac51e7ab4ca75f47a6259d24820b9995792d139f61ae2a8186ae4e3c9bfe0af2cc717f424f41aa67f03faedb0665115f2067a46843a4cbbd297d5e83bc1aafc18d1d03b3d894e8595a6526073f02ab0f08b99fd9eb208b59ff6317e5545e6f9ad5f9c183abd043d5acd6eb2dd4da3f02dbc3167b468720a4b8b92ddfe7960998bb7a0ecf2a26a37598299413f7b2aecd39a30cec527b4d9710c4473639022451f50d01c0457125da0fa4429c07dad859c846cbbd93ab5b91b01bc770b089cfede6f651e86dd7c15989c8b5321dea9ca608c71fd862323072b827cee7a7e28e4e2b999647233c3456944bb7aef9187c96b3f5b79fb98bc76c3574dd06f0e95685e5b3aef3a54c4155fe3ad817749629c30adbe897c4f4454c86c490000000ae9ca10eaa811b22ae07fb195e3590a334ea64209942fbae338d19f152182c807d3c40b189d3fcbea942f44682439b191332d33ae0b761a2a8f984b56b2ac2fd4ab08223a69ed1f7719c7aa7e9eee96504b0e60c6bb5c942d695f0493eb25f80a5871cffd131d0e04ffe5065bc7875e82d34b40b69dd9f3c1 + +# Internet-Draft draft-fluhrer-lms-more-parm-sets-11 - Test Case 2 (SHAKE256/192) +PublicKey = 000000010000001400000010505152535455565758595a5b5c5d5e5fdb54a4509901051c01e26d9990e550347986da87924ff0b1 +Msg = 54657374206d65737361676520666f72205348414b453235362d3139320a +Signature = 00000000000000060000001084219da9ce9fffb16edb94527c6d10565587db28062deac4208e62fc4fbe9d85deb3c6bd2c01640accb387d8a6093d68511234a6a1a50108091c034cb1777e02b5df466149a66969a498e4200c0a0c1bf5d100cdb97d2dd40efd3cada278acc5a570071a043956112c6deebd1eb3a7b56f5f6791515a7b5ffddb0ec2d9094bfbc889ea15c3c7b9bea953efb75ed648f535b9acab66a2e9631e426e4e99b733caa6c55963929b77fec54a7e703d8162e736875cb6a455d4a9015c7a6d8fd5fe75e402b47036dc3770f4a1dd0a559cb478c7fb1726005321be9d1ac2de94d731ee4ca79cff454c811f46d11980909f047b2005e84b6e15378446b1ca691efe491ea98acc9d3c0f785caba5e2eb3c306811c240ba22802923827d582639304a1e9783ba5bc9d69d999a7db8f749770c3c04a152856dc726d8067921465b61b3f847b13b2635a45379e5adc6ff58a99b00e60ac767f7f30175f9f7a140257e218be307954b1250c9b41902c4fa7c90d8a592945c66e86a76defcb84500b55598a1990faaa10077c74c94895731585c8f900de1a1c675bd8b0c180ebe2b5eb3ef8019ece3e1ea7223eb7906a2042b6262b4aa25c4b8a05f205c8befeef11ceff1282508d71bc2a8cfa0a99f73f3e3a74bb4b3c0d8ca2abd0e1c2c17dafe18b4ee2298e87bcfb1305b3c069e6d385569a4067ed547486dd1a50d6f4a58aab96e2fa883a9a39e1bd45541eee94efc32faa9a94be66dc8538b2dab05aee5efa6b3b2efb3fd020fe789477a93afff9a3e636dbba864a5bffa3e28d13d49bb597d94865bde88c4627f206ab2b465084d6b780666e952f8710efd748bd0f1ae8f1035087f5028f14affcc5fffe332121ae4f87ac5f1eac9062608c7d87708f1723f38b23237a4edf4b49a5cd3d700000014dd4bdc8f928fb526f6fb7cdb944a7ebaa7fb05d995b5721a27096a5007d82f79d063acd434a04e97f61552f7f81a9317b4ec7c87a5ed10c881928fc6ebce6dfce9daae9cc9dba6907ca9a9dd5f9f573704d5e6cf22a43b04e64c1ffc7e1c442ecb495ba265f465c56291a902e62a461f6dfda232457fad14 + +# Internet-Draft draft-fluhrer-lms-more-parm-sets-11 - Test Case 3 (SHAKE256/256) +PublicKey = 000000010000000f0000000c808182838485868788898a8b8c8d8e8f9bb7faee411cae806c16a466c3191a8b65d0ac31932bbf0c2d07c7a4a36379fe +Msg = 54657374206d657361676520666f72205348414b453235362d3235360a +Signature = 00000000000000070000000cb82709f0f00e83759190996233d1ee4f4ec50534473c02ffa145e8ca2874e32b16b228118c62b96c9c77678b33183730debaade8fe607f05c6697bc971519a341d69c00129680b67e75b3bd7d8aa5c8b71f02669d177a2a0eea896dcd1660f16864b302ff321f9c4b8354408d06760504f768ebd4e545a9b0ac058c575078e6c1403160fb45450d61a9c8c81f6bd69bdfa26a16e12a265baf79e9e233eb71af634ecc66dc88e10c6e0142942d4843f70a0242727bc5a2aabf7b0ec12a99090d8caeef21303f8ac58b9f200371dc9e41ab956e1a3efed9d4bbb38975b46c28d5f5b3ed19d847bd0a737177263cbc1a2262d40e80815ee149b6cce2714384c9b7fceb3bbcbd25228dda8306536376f8793ecadd6020265dab9075f64c773ef97d07352919995b74404cc69a6f3b469445c9286a6b2c9f6dc839be76618f053de763da3571ef70f805c9cc54b8e501a98b98c70785eeb61737eced78b0e380ded4f769a9d422786def59700eef3278017babbe5f9063b468ae0dd61d94f9f99d5cc36fbec4178d2bda3ad31e1644a2bcce208d72d50a7637851aa908b94dc4376120d5beab0fb805e1945c41834dd6085e6db1a3aa78fcb59f62bde68236a10618cff123abe64dae8dabb2e84ca705309c2ab986d4f8326ba0642272cb3904eb96f6f5e3bb8813997881b6a33cac0714e4b5e7a882ad87e141931f97d612b84e903e773139ae377f5ba19ac86198d485fca97742568f6ff758120a89bf19059b8a6bfe2d86b12778164436ab2659ba866767fcc435584125fb7924201ee67b535daf72c5cb31f5a0b1d926324c26e67d4c3836e301aa09bae8fb3f91f1622b1818ccf440f52ca9b5b9b99aba8a6754aae2b967c4954fa85298ad9b1e74f27a46127c36131c8991f0cc2ba57a15d35c91cf8bc48e8e20d625af4e85d8f9402ec44afbd4792b924b839332a64788a7701a30094b9ec4b9f4b648f168bf457fbb3c9594fa87920b645e42aa2fecc9e21e000ca7d3ff914e15c40a8bc533129a7fd39529376430f355aaf96a0a13d13f2419141b3cc25843e8c90d0e551a355dd90ad770ea7255214ce11238605de2f000d200104d0c3a3e35ae64ea10a3eff37ac7e9549217cdf52f307172e2f6c7a2a4543e14314036525b1ad53eeaddf0e24b1f36914ed22483f2889f61e62b6fb78f5645bdbb02c9e5bf97db7a0004e87c2a55399b61958786c97bd52fa199c27f6bb4d68c4907933562755bfec5d4fb52f06c289d6e852cf6bc773ffd4c07ee2d6cc55f57edcfbc8e8692a49ad47a121fe3c1b16cab1cc285faf6793ffad7a8c341a49c5d2dce7069e464cb90a00b2903648b23c81a68e21d748a7e7b1df8a593f3894b2477e8316947ca725d141135202a9442e1db33bbd390d2c04401c39b253b78ce297b0e14755e46ec08a146d279c67af70de256890804d83d6ec5ca3286f1fca9c72abf6ef868e7f6eb0fddda1b040ecec9bbc69e2fd8618e9db3bdb0af13dda06c6617e95afa522d6a2552de15324d99119f55e9af11ae3d5614b564c642dbfec6c644198ce80d2433ac8ee738f9d825e0000000f71d585a35c3a908379f4072d070311db5d65b242b714bc5a756ba5e228abfa0d1329978a05d5e815cf4d74c1e547ec4aa3ca956ae927df8b29fb9fab3917a7a4ae61ba57e5342e9db12caf6f6dbc5253de5268d4b0c4ce4ebe6852f012b162fc1c12b9ffc3bcb1d3ac8589777655e22cd9b99ff1e4346fd0efeaa1da044692e7ad6bfc337db69849e54411df8920c228a2b7762c11e4b1c49efb74486d3931ea \ No newline at end of file diff --git a/src/tests/data/pubkey/lmots.vec b/src/tests/data/pubkey/lmots.vec new file mode 100644 index 00000000000..e4c5fc1612b --- /dev/null +++ b/src/tests/data/pubkey/lmots.vec @@ -0,0 +1,36 @@ +### Test Cases created using the implementation: https://github.com/cisco/hash-sigs +# LMOTS_SHA256_N32_W1 +TypeId = 1 +Seed = 020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b +I = 010a010a010a010a010a010a010a010a +q = 12 +Msg = 0a0b0c0d +PublicKey = b27b082f57aa5155a33936c14541f864366b5e7eef6925cf571fb4c9ddf4d0a4 +HashSig = 3f1d62631d36dc7eae50544f096ffa2bb290d206a8a09ce8d32035ddf796b183 + +# LMOTS_SHA256_N32_W2 +TypeId = 2 +Seed = 020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b +I = 010a010a010a010a010a010a010a010a +q = 12 +Msg = 0a0b0c0d +PublicKey = 78ec03decc4eaed2d31db9df5ce0f14b7614d11e9d230f651291d71d2a1e4a27 +HashSig = c3832a45e6cddf24b95dfe4e6e878fc7cfa076804e3d7ad2bbb997898c36e3a5 + +# LMOTS_SHA256_N32_W4 +TypeId = 3 +Seed = 020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b +I = 010a010a010a010a010a010a010a010a +q = 12 +Msg = 0a0b0c0d +PublicKey = a2cc303e27f17c386f939d90bb93a7799dce6835c9f3197ae88746695ab288d8 +HashSig = 3b763e78712dca8d2b129b6a80cd1476f22e925418915f62315baa0de6edd66e + +# LMOTS_SHA256_N32_W8 +TypeId = 4 +Seed = 020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b020b +I = 010a010a010a010a010a010a010a010a +q = 12 +Msg = 0a0b0c0d +PublicKey = 51f5845f2b0d9f2614d1e4af250e397d889e5634803d71b3e4f956ae06e63b2b +HashSig = 8a35f5d72b77851fab44c62714b1fb483d4ebb58d85fe0f08ee678febce10928 diff --git a/src/tests/data/pubkey/lms.vec b/src/tests/data/pubkey/lms.vec new file mode 100644 index 00000000000..1ef713ba0f2 --- /dev/null +++ b/src/tests/data/pubkey/lms.vec @@ -0,0 +1,23 @@ +### Test Case 2 of RFC 8554 Appendix F. +Seed = a1c4696e2608035a886100d05cd99945eb3370731884a8235e2fb3d4d71f2547 +Msg = 54686520656e756d65726174696f6e20696e2074686520436f6e737469747574696f6e2c206f66206365727461696e207269676874732c207368616c6c206e6f7420626520636f6e73747275656420746f2064656e79206f7220646973706172616765206f74686572732072657461696e6564206279207468652070656f706c652e0a +q = 4 +PublicKey = 0000000500000004215f83b7ccb9acbcd08db97b0d04dc2ba1cd035833e0e90059603f26e07ad2aad152338e7a5e5984bcd5f7bb4eba40b7 +HashSig = 987a83f7670a93837c484888fde579ca3653db8b66c9339b3c03b1e9b949d771 + + +### Test Cases created using the implementation: https://github.com/cisco/hash-sigs +# LMS_SHA256_N32_H5 with LMOTS_SHA256_N32_W1 +Seed = 67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a +Msg = deadbeef +q = 5 +PublicKey = 000000050000000166320db73158a35a255d051758e95ed4a5c8d702b44ff9b23bcae0963e9f757cb974bfaf29bbf07fa68191b53a85c264 +HashSig = 355e718eeea6fa765c395273c86b628752887e8d8737edc8bafa82ca231b2628 + +# # LMS_SHA256_N32_H10 with LMOTS_SHA256_N32_W2 +Seed = 67c6697351ff4aec29cdbaabf2fbe3467cc254f81be8e78d765a2e63339fc99a +Msg = caca010bb1acce55e5b100d1e55f055115 +q = 5 +PublicKey = 000000060000000266320db73158a35a255d051758e95ed4547c0449d8b3531f210b7e34fea7301771b6160e7cef454f4b83374d68cd1a76 +HashSig = d805cea3a3b3e4c983105da576900d189c0c2c849875f03811b0034c784f9bd9 + diff --git a/src/tests/test_hss_lms.cpp b/src/tests/test_hss_lms.cpp new file mode 100644 index 00000000000..984f9b41846 --- /dev/null +++ b/src/tests/test_hss_lms.cpp @@ -0,0 +1,303 @@ +/* +* (C) 2023 Jack Lloyd +* 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "test_pubkey.h" +#include "tests.h" + +#if defined(BOTAN_HAS_HSS_LMS) + + #include + #include + #include + #include + +namespace Botan_Tests { + +namespace { + +/** + * @brief Test the correct parsing of HSS-LMS parameters + */ +std::vector test_hss_lms_params_parsing() { + return { + Botan_Tests::CHECK("HSS Parameter Parsing", + [&](Test::Result& result) { + result.test_no_throw("no throw", [&] { + Botan::HSS_LMS_Params hss_params("SHA-256,HW(5,1),HW(25,8)"); + + result.test_is_eq("hss levels", hss_params.L(), Botan::HSS_Level(2)); + auto& top_lms_params = hss_params.params_at_level(Botan::HSS_Level(0)); + result.test_is_eq( + "hash name", top_lms_params.lms_params().hash_name(), std::string("SHA-256")); + result.test_is_eq("top level - lms type", + top_lms_params.lms_params().algorithm_type(), + Botan::LMS_Algorithm_Type::SHA256_M32_H5); + result.test_is_eq("top level - ots type", + top_lms_params.lmots_params().algorithm_type(), + Botan::LMOTS_Algorithm_Type::SHA256_N32_W1); + + auto& second_lms_params = hss_params.params_at_level(Botan::HSS_Level(1)); + result.test_is_eq("2nd level - lms type", + second_lms_params.lms_params().algorithm_type(), + Botan::LMS_Algorithm_Type::SHA256_M32_H25); + result.test_is_eq("2nd level - ots type", + second_lms_params.lmots_params().algorithm_type(), + Botan::LMOTS_Algorithm_Type::SHA256_N32_W8); + }); + }), + + }; +} + +/** + * @brief Test signature generation using the raw private key bytes + */ +class HSS_LMS_Signature_Generation_Test final : public PK_Signature_Generation_Test { + public: + HSS_LMS_Signature_Generation_Test() : + PK_Signature_Generation_Test("HSS-LMS", "pubkey/hss_lms_sig.vec", "Msg,PrivateKey,Signature") {} + + std::string default_padding(const VarMap&) const final { return ""; } + + std::unique_ptr load_private_key(const VarMap& vars) final { + const auto sk_bytes = Botan::lock(vars.get_req_bin("PrivateKey")); + return std::make_unique(sk_bytes); + } +}; + +/** + * @brief Test signature verification using the raw public key bytes + */ +class HSS_LMS_Signature_Verify_Tests final : public PK_Signature_Verification_Test { + public: + HSS_LMS_Signature_Verify_Tests() : + PK_Signature_Verification_Test("HSS-LMS", "pubkey/hss_lms_verify.vec", "Msg,PublicKey,Signature") {} + + std::string default_padding(const VarMap&) const final { return ""; } + + std::unique_ptr load_public_key(const VarMap& vars) override { + const std::vector pk_bytes = vars.get_req_bin("PublicKey"); + return std::make_unique(pk_bytes); + } +}; + +/** + * @brief Test the correct revocation of invalid signatures + */ +class HSS_LMS_Signature_Verify_Invalid_Tests final : public PK_Signature_NonVerification_Test { + public: + HSS_LMS_Signature_Verify_Invalid_Tests() : + PK_Signature_NonVerification_Test( + "HSS_LMS", "pubkey/hss_lms_invalid.vec", "Msg,PublicKey,InvalidSignature") {} + + std::string default_padding(const VarMap&) const override { return ""; } + + std::unique_ptr load_public_key(const VarMap& vars) override { + const std::vector raw_key = vars.get_req_bin("PublicKey"); + return std::make_unique(raw_key); + } +}; + +/** + * @brief Test HSS-LMS public key creation + */ +class HSS_LMS_Key_Generation_Test final : public PK_Key_Generation_Test { + public: + std::vector keygen_params() const final { return {"SHA-256,HW(10,4),HW(5,8)"}; } + + std::string algo_name() const final { return "HSS-LMS"; } +}; + +/** + * @brief Test that for too short HSS-LMS signatures, private keys, and public keys a DecodeError occurs. + */ +class HSS_LMS_Too_Short_Test final : public Test { + Test::Result test_too_short_signature() { + Test::Result result("HSS-LMS"); + + auto sk = Botan::create_private_key("HSS-LMS", Test::rng(), "Truncated(SHA-256,192),HW(5,8)"); + + Botan::PK_Signer signer(*sk, Test::rng(), ""); + Botan::PK_Verifier verifier(*sk, ""); + + std::vector mes = {0xde, 0xad, 0xbe, 0xef}; + + signer.update(mes); + auto valid_sig = signer.signature(Test::rng()); + verifier.update(mes); + result.confirm("Entire signature is valid", verifier.check_signature(valid_sig.data(), valid_sig.size())); + for(size_t n = 0; n < valid_sig.size(); ++n) { + result.test_no_throw("Verification does not throw", [&]() { + verifier.update(mes); + bool valid = verifier.check_signature(valid_sig.data(), n); + result.confirm("Too short signature is invalid", !valid); + }); + } + + return result; + } + + Test::Result test_too_short_private_key() { + Test::Result result("HSS-LMS"); + + // HSS_LMS_PublicKey::key_length() + auto sk = Botan::create_private_key("HSS-LMS", Test::rng(), "Truncated(SHA-256,192),HW(5,8)"); + + auto sk_bytes = sk->private_key_bits(); + result.test_no_throw("Entire private key valid", [&]() { + Botan::HSS_LMS_PrivateKey key(sk_bytes); + BOTAN_UNUSED(key); + }); + for(size_t n = 0; n < sk_bytes.size(); ++n) { + result.test_throws("Partial private key invalid", [&]() { + std::span partial_key = {sk_bytes.data(), n}; + Botan::HSS_LMS_PrivateKey key(partial_key); + BOTAN_UNUSED(key); + }); + } + return result; + } + + Test::Result test_too_short_public_key() { + Test::Result result("HSS-LMS"); + + // HSS_LMS_PublicKey::key_length() + auto sk = Botan::create_private_key("HSS-LMS", Test::rng(), "Truncated(SHA-256,192),HW(5,8)"); + + auto sk_bytes = sk->public_key_bits(); + result.test_no_throw("Entire public key valid", [&]() { + Botan::HSS_LMS_PublicKey key(sk_bytes); + BOTAN_UNUSED(key); + }); + for(size_t n = 0; n < sk_bytes.size(); ++n) { + result.test_throws("Partial public key invalid", [&]() { + std::span partial_key = {sk_bytes.data(), n}; + Botan::HSS_LMS_PublicKey key(partial_key); + BOTAN_UNUSED(key); + }); + } + return result; + } + + std::vector run() final { + return {test_too_short_signature(), test_too_short_private_key(), test_too_short_public_key()}; + } +}; + +/** + * @brief Test the correct handling of the HSS-LMS private key's state. + */ +class HSS_LMS_Statefulness_Test final : public Test { + Botan::HSS_LMS_PrivateKey create_private_key_with_idx(uint64_t idx) { + auto sk = Botan::HSS_LMS_PrivateKey(Test::rng(), "Truncated(SHA-256,192),HW(5,8)"); + auto bytes = sk.private_key_bits(); + // The index is store after the level (uint32_t) + Botan::store_be(idx, bytes.data() + sizeof(uint32_t)); + return Botan::HSS_LMS_PrivateKey(bytes); + } + + Test::Result test_sig_changes_state() { + Test::Result result("HSS-LMS"); + + auto sk = Botan::HSS_LMS_PrivateKey(Test::rng(), "Truncated(SHA-256,192),HW(5,8),HW(5,8)"); + Botan::PK_Signer signer(sk, Test::rng(), ""); + std::vector mes = {0xde, 0xad, 0xbe, 0xef}; + auto sk_bytes_begin = sk.private_key_bits(); + + // Tree hights: 5,5 => 2^(5+5) = 1024 signatures available + const uint64_t expected_total = 1024; + result.confirm("Fresh key starts with total number of remaining signatures.", + sk.remaining_operations() == expected_total); + + // Creating a signature should update the private key's state + auto sig_0 = signer.sign_message(mes, Test::rng()); + result.confirm( + "First signature uses index 0.", + Botan::HSS_Signature::from_bytes_or_throw(sig_0).bottom_sig().q() == Botan::LMS_Tree_Node_Idx(0)); + + auto sk_bytes_after_sig = sk.private_key_bits(); + + result.confirm("Signature decreases number of remaining signatures.", + sk.remaining_operations() == expected_total - 1); + result.test_ne("Signature updates private key.", sk_bytes_after_sig, sk_bytes_begin); + + auto sig_1 = signer.sign_message(mes, Test::rng()); + result.confirm( + "Next signature uses the new index.", + Botan::HSS_Signature::from_bytes_or_throw(sig_1).bottom_sig().q() == Botan::LMS_Tree_Node_Idx(1)); + + return result; + } + + Test::Result test_max_sig_count() { + Test::Result result("HSS-LMS"); + + uint64_t total_sig_count = 32; + auto sk = create_private_key_with_idx(total_sig_count - 1); + + Botan::PK_Signer signer(sk, Test::rng(), ""); + std::vector mes = {0xde, 0xad, 0xbe, 0xef}; + auto sk_bytes_begin = sk.private_key_bits(); + + result.confirm("One remaining signature.", sk.remaining_operations() == uint64_t(1)); + result.test_no_throw("Use last signature index.", [&]() { signer.sign_message(mes, Test::rng()); }); + result.confirm("No remaining signatures.", sk.remaining_operations() == uint64_t(0)); + result.test_throws("Cannot sign with exhausted key.", [&]() { signer.sign_message(mes, Test::rng()); }); + result.confirm("Still zero remaining signatures.", sk.remaining_operations() == uint64_t(0)); + + return result; + } + + std::vector run() final { return {test_sig_changes_state(), test_max_sig_count()}; } +}; + +/** + * @brief Test APIs not covered by other tests. + */ +class HSS_LMS_Missing_API_Test final : public Test { + std::vector run() final { + Test::Result result("HSS-LMS"); + + // HSS_LMS_PublicKey::key_length() + auto sk = Botan::create_private_key("HSS-LMS", Test::rng(), "SHA-256,HW(10,4)"); + sk->key_length(); + result.test_gt("Public key length must be greater than the simply type information plus I", + sk->key_length(), + 3 * sizeof(uint32_t) + Botan::LMS_IDENTIFIER_LEN); + + // HSS_LMS_Verification_Operation::hash_function() + Botan::PK_Verifier verifier(*sk, ""); + result.test_eq("PK_Verifier should report the hash of the key", verifier.hash_function(), "SHA-256"); + + // HSS_LMS_PrivateKey::raw_private_key_bits() + result.test_eq("Our BER and raw encoding is the same", sk->raw_private_key_bits(), sk->private_key_bits()); + + // HSS_LMS_Signature_Operation::algorithm_identifier() + Botan::PK_Signer signer(*sk, Test::rng(), ""); + result.test_is_eq(signer.algorithm_identifier(), sk->algorithm_identifier()); + + // HSS_LMS_Signature_Operation::hash_function() + result.test_eq("PK_Signer should report the hash of the key", signer.hash_function(), "SHA-256"); + + return {result}; + } +}; + +BOTAN_REGISTER_TEST_FN("pubkey", "hss_lms_params_parsing", test_hss_lms_params_parsing); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_sign", HSS_LMS_Signature_Generation_Test); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_verify", HSS_LMS_Signature_Verify_Tests); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_verify_invalid", HSS_LMS_Signature_Verify_Invalid_Tests); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_keygen", HSS_LMS_Key_Generation_Test); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_short", HSS_LMS_Too_Short_Test); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_state", HSS_LMS_Statefulness_Test); +BOTAN_REGISTER_TEST("pubkey", "hss_lms_api", HSS_LMS_Missing_API_Test); + +} // namespace + +} // namespace Botan_Tests + +#endif // BOTAN_HAS_HSS_LMS diff --git a/src/tests/test_lmots.cpp b/src/tests/test_lmots.cpp new file mode 100644 index 00000000000..327352fa5ec --- /dev/null +++ b/src/tests/test_lmots.cpp @@ -0,0 +1,72 @@ +/* +* (C) 2023 Jack Lloyd +* 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "tests.h" + +#if defined(BOTAN_HAS_HSS_LMS) + + #include + #include + +namespace Botan_Tests { + +namespace { + +/** + * @brief Test the LMOTS logic of HSS-LMS + */ +class LMOTS_Test final : public Text_Based_Test { + public: + LMOTS_Test() : Text_Based_Test("pubkey/lmots.vec", "TypeId,Seed,I,q,Msg,PublicKey,HashSig") {} + + bool skip_this_test(const std::string&, const VarMap& vars) override { + BOTAN_UNUSED(vars); + return false; + } + + Test::Result run_one_test(const std::string&, const VarMap& vars) final { + Test::Result result("LMOTS"); + + const auto lmots_type_id = vars.get_req_u32("TypeId"); + const auto seed = Botan::LMS_Seed(vars.get_req_bin("Seed")); + const auto identifier = Botan::LMS_Identifier(vars.get_req_bin("I")); + const auto q = Botan::LMS_Tree_Node_Idx(vars.get_req_u32("q")); + const auto msg = Botan::LMS_Message(vars.get_req_bin("Msg")); + const auto pk_ref = Botan::LMOTS_K(vars.get_req_bin("PublicKey")); + // To safe file space the signature is only stored in hashed form + const auto sig_ref = vars.get_req_bin("HashSig"); + + auto hash = Botan::HashFunction::create("SHA-256"); + + auto type = static_cast(lmots_type_id); + auto params = Botan::LMOTS_Params::create_or_throw(type); + + // Test private/public OTS key creation + auto sk = Botan::LMOTS_Private_Key(params, identifier, q, seed); + const auto pk = Botan::LMOTS_Public_Key(sk); + result.test_is_eq("Public key generation", pk.K(), pk_ref); + + // Test signature creation + Botan::LMOTS_Signature_Bytes sig(Botan::LMOTS_Signature::size(params)); + sk.sign(sig, msg); + result.test_is_eq("Signature generation", hash->process>(sig), sig_ref); + + // Test create pubkey from signature + auto sig_slicer = Botan::BufferSlicer(sig); + auto sig_obj = Botan::LMOTS_Signature::from_bytes_or_throw(sig_slicer); + Botan::LMOTS_K pk_from_sig = Botan::lmots_compute_pubkey_from_sig(sig_obj, msg, identifier, q); + result.test_is_eq("Public key from signature", pk_from_sig, pk_ref); + + return result; + } +}; + +BOTAN_REGISTER_TEST("pubkey", "lmots", LMOTS_Test); + +} // namespace +} // namespace Botan_Tests + +#endif // BOTAN_HAS_HSS_LMS diff --git a/src/tests/test_lms.cpp b/src/tests/test_lms.cpp new file mode 100644 index 00000000000..bda77d47c9f --- /dev/null +++ b/src/tests/test_lms.cpp @@ -0,0 +1,72 @@ +/* +* (C) 2023 Jack Lloyd +* 2023 Fabian Albert, Philippe Lieser - Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include "tests.h" + +#if defined(BOTAN_HAS_HSS_LMS) + + #include + #include + +namespace Botan_Tests { + +namespace { + +/** + * @brief Test the LMS logic of HSS-LMS + */ +class LMS_Test final : public Text_Based_Test { + public: + LMS_Test() : Text_Based_Test("pubkey/lms.vec", "Seed,Msg,q,PublicKey,HashSig") {} + + bool skip_this_test(const std::string&, const VarMap& vars) override { + BOTAN_UNUSED(vars); + return false; + } + + Test::Result run_one_test(const std::string&, const VarMap& vars) final { + Test::Result result("LMS"); + + const auto seed = Botan::LMS_Seed(vars.get_req_bin("Seed")); + const auto msg = Botan::LMS_Message(vars.get_req_bin("Msg")); + const auto q = Botan::LMS_Tree_Node_Idx(vars.get_req_u32("q")); + const auto pk_ref = vars.get_req_bin("PublicKey"); + // To safe file space the signature is only stored in hashed form + const auto hashed_sig_ref = vars.get_req_bin("HashSig"); + + auto hash = Botan::HashFunction::create("SHA-256"); + + auto lms_pk_ref_slicer = Botan::BufferSlicer(pk_ref); + Botan::LMS_PublicKey lms_pk_ref = Botan::LMS_PublicKey::from_bytes_or_throw(lms_pk_ref_slicer); + + // Test public key creation + auto lms_sk = + Botan::LMS_PrivateKey(lms_pk_ref.lms_params(), lms_pk_ref.lmots_params(), lms_pk_ref.identifier(), seed); + auto pub_key = Botan::LMS_PublicKey(lms_sk); + + result.test_is_eq("Public key generation", pub_key.to_bytes(), pk_ref); + + // Test signature creation and verification + auto sk = + Botan::LMS_PrivateKey(lms_pk_ref.lms_params(), lms_pk_ref.lmots_params(), lms_pk_ref.identifier(), seed); + Botan::LMS_Signature_Bytes sig(Botan::LMS_Signature::size(lms_pk_ref.lms_params(), lms_pk_ref.lmots_params())); + auto pk_from_sig = sk.sign_and_get_pk(sig, q, msg); + result.test_is_eq("Signature creation", hash->process>(sig), hashed_sig_ref); + + auto sig_slicer = Botan::BufferSlicer(sig); + auto sig_obj = Botan::LMS_Signature::from_bytes_or_throw(sig_slicer); + result.confirm("Signature verification", pub_key.verify_signature(msg, sig_obj)); + + return result; + } +}; + +BOTAN_REGISTER_TEST("pubkey", "lms", LMS_Test); + +} // namespace +} // namespace Botan_Tests + +#endif // BOTAN_HAS_HSS_LMS diff --git a/src/tests/test_utils.cpp b/src/tests/test_utils.cpp index 1c8f1034a46..af2e8757cee 100644 --- a/src/tests/test_utils.cpp +++ b/src/tests/test_utils.cpp @@ -117,12 +117,12 @@ class Utility_Function_Tests final : public Test { const uint32_t is_16_bits = 0x8123; const uint32_t is_8_bits = 0x89; - result.confirm("checked_cast checks", !Botan::checked_cast(large).has_value()); - result.confirm("checked_cast checks", !Botan::checked_cast(large).has_value()); + result.test_throws("checked_cast checks", [&] { Botan::checked_cast_to(large); }); + result.test_throws("checked_cast checks", [&] { Botan::checked_cast_to(large); }); - result.test_int_eq("checked_cast converts", Botan::checked_cast(large).value(), large); - result.test_int_eq("checked_cast converts", Botan::checked_cast(is_16_bits).value(), 0x8123); - result.test_int_eq("checked_cast converts", Botan::checked_cast(is_8_bits).value(), 0x89); + result.test_int_eq("checked_cast converts", Botan::checked_cast_to(large), large); + result.test_int_eq("checked_cast converts", Botan::checked_cast_to(is_16_bits), 0x8123); + result.test_int_eq("checked_cast converts", Botan::checked_cast_to(is_8_bits), 0x89); return result; } diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index 33e04bd6144..525e2d42719 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -109,6 +109,9 @@ std::unique_ptr make_a_private_key(const std::string& algo, if(algo == "ECKCDSA" || algo == "ECGDSA") { return "brainpool256r1"; } + if(algo == "HSS-LMS") { + return "SHA-256,HW(5,4),HW(5,4)"; + } return ""; // default "" means choose acceptable algo-specific params }(); @@ -1228,7 +1231,7 @@ Test::Result test_valid_constraints(const Botan::Private_Key& key, const std::st result.test_eq("crl sign not permitted", crl_sign.compatible_with(key), false); result.test_eq("sign", sign_everything.compatible_with(key), false); } else if(pk_algo == "DSA" || pk_algo == "ECDSA" || pk_algo == "ECGDSA" || pk_algo == "ECKCDSA" || - pk_algo == "GOST-34.10" || pk_algo == "Dilithium") { + pk_algo == "GOST-34.10" || pk_algo == "Dilithium" || pk_algo == "HSS-LMS") { // these are signature algorithms only result.test_eq("all constraints not permitted", all.compatible_with(key), false); @@ -1521,6 +1524,8 @@ std::vector get_sig_paddings(const std::string& sig_algo, const std return {"Pure"}; } else if(sig_algo == "Dilithium") { return {"Randomized"}; + } else if(sig_algo == "HSS-LMS") { + return {""}; } else { return {}; } @@ -1534,7 +1539,7 @@ class X509_Cert_Unit_Tests final : public Test { auto& rng = this->rng(); const std::string sig_algos[]{ - "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA", "GOST-34.10", "Ed25519", "Ed448", "Dilithium"}; + "RSA", "DSA", "ECDSA", "ECGDSA", "ECKCDSA", "GOST-34.10", "Ed25519", "Ed448", "Dilithium", "HSS-LMS"}; for(const std::string& algo : sig_algos) { #if !defined(BOTAN_HAS_EMSA_PKCS1)