From 032b1d86d87b0d43540175a4e850267f440bdd34 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Wed, 21 Aug 2024 13:53:57 -0400 Subject: [PATCH] Add SPAKE2+ password authenticated key exchange (RFC 9383) --- doc/api_ref/contents.rst | 1 + doc/api_ref/ffi.rst | 169 ++++++++ doc/api_ref/python.rst | 17 + doc/api_ref/spake2p.rst | 237 ++++++++++++ doc/roadmap.rst | 1 - src/examples/spake2p.cpp | 57 +++ src/lib/ffi/ffi.h | 337 ++++++++++++++++ src/lib/ffi/ffi_spake2p.cpp | 363 +++++++++++++++++ src/lib/pake/spake2p/info.txt | 21 + src/lib/pake/spake2p/spake2p.cpp | 466 ++++++++++++++++++++++ src/lib/pake/spake2p/spake2p.h | 424 ++++++++++++++++++++ src/python/botan3.py | 220 ++++++++++- src/scripts/test_python.py | 77 ++++ src/tests/data/pake/spake2p.vec | 90 +++++ src/tests/data/pake/spake2p_custom.vec | 516 +++++++++++++++++++++++++ src/tests/test_ffi.cpp | 302 +++++++++++++++ src/tests/test_spake2p.cpp | 455 ++++++++++++++++++++++ 17 files changed, 3750 insertions(+), 3 deletions(-) create mode 100644 doc/api_ref/spake2p.rst create mode 100644 src/examples/spake2p.cpp create mode 100644 src/lib/ffi/ffi_spake2p.cpp create mode 100644 src/lib/pake/spake2p/info.txt create mode 100644 src/lib/pake/spake2p/spake2p.cpp create mode 100644 src/lib/pake/spake2p/spake2p.h create mode 100644 src/tests/data/pake/spake2p.vec create mode 100644 src/tests/data/pake/spake2p_custom.vec create mode 100644 src/tests/test_spake2p.cpp diff --git a/doc/api_ref/contents.rst b/doc/api_ref/contents.rst index a75bd8f46c3..8de90e78d1e 100644 --- a/doc/api_ref/contents.rst +++ b/doc/api_ref/contents.rst @@ -24,6 +24,7 @@ API Reference keywrap passhash cryptobox + spake2p srp psk_db filters diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index 7e13f399300..f089d7965a9 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -2429,6 +2429,175 @@ All available value types of the generic X.509 object getters are: The URLs of the issuing CA certificate of a certificate as a character array. There might be more than one such URL defined in a certificate. +SPAKE2+ Password Authenticated Key Exchange +-------------------------------------------- + +.. versionadded:: 3.13.0 + +An implementation of the SPAKE2+ password authenticated key exchange +(RFC 9383). The *prover* knows the password itself, while the *verifier* +stores only a registration record derived from the password. See +:doc:`spake2p` for a description of the protocol and the expected +message flow. + +The identity, salt, and context parameters of these functions may be null, +if the corresponding length is zero. Since the lengths of the outputs vary +with the system parameters, all outputs are produced using view callbacks. + +.. cpp:type:: opaque* botan_spake2p_params_t + + An opaque data type for SPAKE2+ system parameters, which select the + elliptic curve group, the SPAKE2+ M/N group elements, and the hash + function. Objects created from the system parameters hold their own + copy, so the parameters may be destroyed at any time. + +.. cpp:function:: int botan_spake2p_params_init(botan_spake2p_params_t* params, const char* ciphersuite) + + Create system parameters from an RFC 9383 ciphersuite name, one of + "P256-SHA256", "P256-SHA512", "P384-SHA256", "P384-SHA512", or + "P521-SHA512", all using HMAC key confirmation. + +.. cpp:function:: int botan_spake2p_params_init_custom(botan_spake2p_params_t* params, \ + botan_ec_group_t group, const uint8_t seed[], size_t seed_len, \ + const char* hash_fn) + + Create custom system parameters for an arbitrary group, deriving the + M/N group elements from the seed using hash to curve; returns + ``BOTAN_FFI_ERROR_NOT_IMPLEMENTED`` if the group does not support hash + to curve. Both peers must use the same group, seed, and hash. + +.. cpp:function:: int botan_spake2p_params_destroy(botan_spake2p_params_t params) + + Destroy an object. + +.. cpp:function:: int botan_spake2p_params_share_size(botan_spake2p_params_t params, size_t* share_size) + + Return the size in bytes of a key share (shareP or shareV). + +.. cpp:function:: int botan_spake2p_params_confirmation_size(botan_spake2p_params_t params, size_t* confirmation_size) + + Return the size in bytes of a key confirmation message (confirmP or confirmV). + +.. cpp:function:: int botan_spake2p_derive_secret(botan_spake2p_params_t params, \ + const char* password, \ + const uint8_t prover_id[], size_t prover_id_len, \ + const uint8_t verifier_id[], size_t verifier_id_len, \ + const uint8_t salt[], size_t salt_len, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Derive a prover secret (w0 and w1) from a password, using Argon2id. + The view callback is invoked with the serialized prover secret, which + is password equivalent and must be protected accordingly. It is used + with ``botan_spake2p_registration_record`` and + ``botan_spake2p_prover_init``. + +.. cpp:function:: int botan_spake2p_registration_record(botan_spake2p_params_t params, \ + botan_rng_t rng, const uint8_t secret[], size_t secret_len, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Compute a registration record (w0 and L) from a serialized prover + secret. The record is provided to the verifier during registration. + While it does not allow directly impersonating the prover, it does + allow offline password guessing attacks, so it should be protected. + +.. cpp:type:: opaque* botan_spake2p_prover_t + + An opaque data type for a SPAKE2+ prover. + +.. cpp:function:: int botan_spake2p_prover_init(botan_spake2p_prover_t* prover, \ + botan_spake2p_params_t params, \ + const uint8_t secret[], size_t secret_len, \ + const uint8_t prover_id[], size_t prover_id_len, \ + const uint8_t verifier_id[], size_t verifier_id_len, \ + const uint8_t context[], size_t context_len) + + Initialize a prover from a serialized prover secret. The identities + and context must be agreed upon by both parties; the identities must + additionally match the values used when deriving the prover secret. + +.. cpp:function:: int botan_spake2p_prover_destroy(botan_spake2p_prover_t prover) + + Destroy an object. + +.. cpp:function:: int botan_spake2p_prover_generate_message(botan_spake2p_prover_t prover, \ + botan_rng_t rng, botan_view_ctx ctx, botan_view_bin_fn view) + + Generate the prover's key share (shareP), which is sent to the + verifier. This can be called only once per prover object. + +.. cpp:function:: int botan_spake2p_prover_process_message(botan_spake2p_prover_t prover, \ + botan_rng_t rng, const uint8_t peer_message[], size_t peer_message_len, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Consume the verifier's response (shareV followed by confirmV) and + produce the prover's key confirmation (confirmP), which is sent to the + verifier. Returns ``BOTAN_FFI_ERROR_BAD_MAC`` if the verifier's key + confirmation is wrong, typically meaning the passwords do not match. + +.. cpp:function:: int botan_spake2p_prover_shared_secret(botan_spake2p_prover_t prover, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Return the shared secret (K_shared). This may be called only after + ``botan_spake2p_prover_process_message`` has succeeded. + +.. cpp:type:: opaque* botan_spake2p_verifier_t + + An opaque data type for a SPAKE2+ verifier. + +.. cpp:function:: int botan_spake2p_verifier_init(botan_spake2p_verifier_t* verifier, \ + botan_spake2p_params_t params, \ + const uint8_t record[], size_t record_len, \ + const uint8_t prover_id[], size_t prover_id_len, \ + const uint8_t verifier_id[], size_t verifier_id_len, \ + const uint8_t context[], size_t context_len) + + Initialize a verifier from a serialized registration record. See + ``botan_spake2p_prover_init`` for the requirements on the identities + and context. + +.. cpp:function:: int botan_spake2p_verifier_destroy(botan_spake2p_verifier_t verifier) + + Destroy an object. + +.. cpp:function:: int botan_spake2p_verifier_process_message(botan_spake2p_verifier_t verifier, \ + botan_rng_t rng, const uint8_t peer_message[], size_t peer_message_len, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Consume the prover's key share (shareP) and produce the verifier's + response (shareV followed by confirmV), which is sent to the prover. + This can be called only once per verifier object. + +.. cpp:function:: int botan_spake2p_verifier_verify_confirmation(botan_spake2p_verifier_t verifier, \ + const uint8_t confirmation[], size_t confirmation_len) + + Check the prover's key confirmation (confirmP). Returns + ``BOTAN_FFI_ERROR_BAD_MAC`` if the confirmation is wrong, meaning the + prover does not know the password. + +.. cpp:function:: int botan_spake2p_verifier_skip_confirmation(botan_spake2p_verifier_t verifier) + + Can be called after ``botan_spake2p_verifier_process_message``, in + place of ``botan_spake2p_verifier_verify_confirmation``, to allow + extracting the shared secret without having checked the prover's key + confirmation. + + .. warning:: + + After calling this, nothing is known about the peer; only a prover + which knows the password can compute the same shared secret, but no + evidence of this has been received. It is intended solely for + protocols which embed SPAKE2+ and perform the prover's key + confirmation themselves, such as the proposed PAKE extension for + TLS 1.3, where the TLS handshake takes the place of confirmP. + Anywhere else, use ``botan_spake2p_verifier_verify_confirmation``. + +.. cpp:function:: int botan_spake2p_verifier_shared_secret(botan_spake2p_verifier_t verifier, \ + botan_view_ctx ctx, botan_view_bin_fn view) + + Return the shared secret (K_shared). This may be called only after + ``botan_spake2p_verifier_verify_confirmation`` has succeeded, or after + ``botan_spake2p_verifier_skip_confirmation``. + ZFEC (Forward Error Correction) ---------------------------------------- diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst index 243269adbca..30f11b9dfd0 100644 --- a/doc/api_ref/python.rst +++ b/doc/api_ref/python.rst @@ -225,6 +225,23 @@ ZFEC .. autofunction:: zfec_decode +SPAKE2+ +----------------------------------------- +.. versionadded:: 3.13.0 + +.. autoclass:: Spake2pParams + :members: + +.. autofunction:: spake2p_derive_secret + +.. autofunction:: spake2p_registration_record + +.. autoclass:: Spake2pProver + :members: + +.. autoclass:: Spake2pVerifier + :members: + X509Cert ----------------------------------------- diff --git a/doc/api_ref/spake2p.rst b/doc/api_ref/spake2p.rst new file mode 100644 index 00000000000..31f25421eb0 --- /dev/null +++ b/doc/api_ref/spake2p.rst @@ -0,0 +1,237 @@ +SPAKE2+ Password Authenticated Key Exchange +============================================= + +.. versionadded:: 3.13.0 + +An implementation of the SPAKE2+ password authenticated key exchange, compatible with +RFC 9383, is included. + +SPAKE2+ allows two peers who share a (possibly low entropy) secret such as a password to +agree on a strong shared secret key. An attacker who observes or modifies the exchange +learns nothing about the password (beyond excluding a single guess per protocol +execution) or the session key. + +SPAKE2+ is an *augmented* PAKE, meaning the two sides are asymmetric. The *prover* +(typically a client) knows the password itself, while the *verifier* (typically a +server) stores only a *registration record* derived from the password. An attacker who +steals the registration record cannot impersonate the prover without first performing a +successful dictionary attack against the record. + +The protocol consists of two phases: + +* *Registration* (performed once, over some trusted channel): the prover derives its + secret values from the password, and provides the resulting registration record to the + verifier. + +* *Online authentication* (performed per session): the two sides exchange key shares and + key confirmation messages, resulting in a mutually authenticated shared secret. + +The online message flow is:: + + Prover Verifier + + ProverContext::generate_message + --- shareP --> + VerifierContext::process_message + <-- shareV || confirmV --- + ProverContext::process_message + --- confirmP --> + VerifierContext::verify_confirmation + +After the final step both sides call ``shared_secret`` to obtain the session key +(``K_shared`` in RFC 9383). + +Some protocols which embed SPAKE2+ perform the prover's key confirmation themselves +rather than exchanging ``confirmP``. For example in the proposed PAKE extension for TLS +1.3 (draft-bmw-tls-pake13), the server acts as the verifier, feeds the shared secret +into the TLS key schedule immediately after processing the prover's key share, and the +TLS handshake takes the place of ``confirmP``. To support such protocols, the verifier +may call ``skip_confirmation`` in place of ``verify_confirmation``; see below. + +System Parameters +------------------- + +.. cpp:class:: SPAKE2p::SystemParameters + + Specifies the ciphersuite in use, namely the elliptic curve group, the SPAKE2+ + ``M``/``N`` group elements, and the hash function. + + SPAKE2+ parameters include a KDF and an authentication code. The KDF is always HKDF + using the specified hash, and the MAC is always HMAC using the specified hash. In + particular the CMAC-based SPAKE2+ suites described in RFC 9383 Table 1 are not supported. + + Static factory functions ``rfc9383_p256_sha256``, ``rfc9383_p256_sha512``, + ``rfc9383_p384_sha256``, ``rfc9383_p384_sha512``, and ``rfc9383_p521_sha512`` return + the standard ciphersuites from RFC 9383. + + .. cpp:function:: static SystemParameters custom(const EC_Group& group, \ + std::span seed, \ + std::string_view hash_fn) + + Creates custom system parameters for an arbitrary group, deriving the ``M``/``N`` + elements from the seed using hash to curve (which not all groups support). Both + peers must use the same seed. + + If the seed includes the identities of the participants, this additionally makes + the scheme "quantum annoying": an attacker with a discrete logarithm oracle must + compute a new discrete logarithm for each (prover, verifier) pair they wish to + attack, rather than being able to attack any user after computing the discrete + logarithms of the fixed ``M``/``N`` elements once. + +Registration +-------------- + +.. cpp:class:: SPAKE2p::ProverSecret + + The secret values (``w0`` and ``w1`` in RFC 9383) which the prover derives + from the password. + + .. cpp:function:: static ProverSecret from_password(const SystemParameters& params, \ + std::string_view password, \ + std::span prover_id, \ + std::span verifier_id, \ + std::span salt) + + Derives the prover secret from a password using Argon2id, with the + memory-constrained parameters recommended in RFC 9106 (m=64 MiB, t=3, p=4). + + Following RFC 9383, the Argon2id passphrase input is the concatenation + ``len(pw) || pw || len(idProver) || idProver || len(idVerifier) || idVerifier``, where + each length is an 8-byte little-endian count of bytes; the salt is provided to + Argon2id directly. The output is split into two halves, each of which is reduced + modulo the group order. + + The identities and the salt may be empty; if a salt is available it should be + used, since it prevents precomputed dictionary attacks. + + .. cpp:function:: static ProverSecret from_prehashed(EC_Scalar w0, EC_Scalar w1) + + Creates a prover secret from already derived scalars, for applications which + require a password hashing scheme other than the default one. The scalars must be + derived from the password in a way that produces uniformly random values modulo + the group order; see RFC 9383 section 3.2 for the requirements. + + .. cpp:function:: RegistrationRecord registration_record(RandomNumberGenerator& rng) const + + Computes the registration record (``w0`` and ``L = w1*P``) which is provided to + the verifier during registration. + + .. cpp:function:: static ProverSecret deserialize(const SystemParameters& params, \ + std::span secret) + + .. cpp:function:: secure_vector serialize() const + + Serialization, if the prover wishes to store the derived secret rather than + rederiving it from the password each time. The serialized secret is password + equivalent, so it should be encrypted if stored persistently. + +.. cpp:class:: SPAKE2p::RegistrationRecord + + The information (``w0`` and ``L`` in RFC 9383) which the verifier stores in order to + later authenticate the prover. + + .. cpp:function:: static RegistrationRecord from_password(const SystemParameters& params, \ + std::string_view password, \ + std::span prover_id, \ + std::span verifier_id, \ + std::span salt, \ + RandomNumberGenerator& rng) + + Performs password registration in a single step, equivalent to + ``ProverSecret::from_password`` followed by ``registration_record``. + + .. cpp:function:: static RegistrationRecord deserialize(const SystemParameters& params, \ + std::span record) + + .. cpp:function:: secure_vector serialize() const + + Serialization, for storage by the verifier. While the record does not allow direct + impersonation of the prover, it does allow offline password guessing attacks, so + it should be encrypted if possible. + +Online Authentication +----------------------- + +.. cpp:class:: SPAKE2p::ProverContext + + .. cpp:function:: ProverContext(const SystemParameters& params, \ + const ProverSecret& secret, \ + std::span prover_id, \ + std::span verifier_id, \ + std::span context = {}) + + Prepares an execution of the protocol. The identities and the context must be + agreed upon by both peers, and the identities must match the values used during + registration. Even if there is no natural identity available, using fixed labels + such as "client" and "server" is preferable to leaving the identities empty. The + context should identify the application and protocol version; it may be empty. + + .. cpp:function:: std::vector generate_message(RandomNumberGenerator& rng) + + Returns the prover's key share (``shareP``), which is sent to the verifier. Can be + called only once. + + .. cpp:function:: std::vector process_message(std::span peer_message, \ + RandomNumberGenerator& rng) + + Consumes the verifier's response (``shareV || confirmV``) and returns the prover's + key confirmation (``confirmP``), which is sent to the verifier. Throws + ``Decoding_Error`` if the message is malformed, or ``Invalid_Authentication_Tag`` + if the key confirmation is wrong (typically meaning the passwords do not match). + + .. cpp:function:: secure_vector shared_secret() const + + Returns the shared secret. May be called only after ``process_message`` has + succeeded. + +.. cpp:class:: SPAKE2p::VerifierContext + + .. cpp:function:: VerifierContext(const SystemParameters& params, \ + const RegistrationRecord& record, \ + std::span prover_id, \ + std::span verifier_id, \ + std::span context = {}) + + Prepares an execution of the protocol; see ``ProverContext`` above for the + requirements on the identities and context. + + .. cpp:function:: std::vector process_message(std::span peer_message, \ + RandomNumberGenerator& rng) + + Consumes the prover's key share (``shareP``) and returns the verifier's response + (``shareV || confirmV``), which is sent to the prover. Can be called only + once. Throws ``Decoding_Error`` if the key share is malformed. + + .. cpp:function:: void verify_confirmation(std::span confirmation) + + Checks the prover's key confirmation (``confirmP``). Throws ``Invalid_Authentication_Tag`` + if the confirmation is wrong, meaning the prover does not know the password. + + .. cpp:function:: void skip_confirmation() + + Can be called after ``process_message``, in place of ``verify_confirmation``, to + allow extracting the shared secret without having checked the prover's key + confirmation. + + .. warning:: + + After calling this, nothing is known about the peer; only a prover which knows + the password can compute the same shared secret, but no evidence of this has + been received. It is intended solely for protocols which embed SPAKE2+ and + perform the prover's key confirmation themselves, such as the proposed PAKE + extension for TLS 1.3, where the TLS handshake takes the place of + ``confirmP``. Anywhere else, use ``verify_confirmation``. + + .. cpp:function:: secure_vector shared_secret() const + + Returns the shared secret. May be called only after ``verify_confirmation`` has + succeeded, or after ``skip_confirmation``. + +Code Example: SPAKE2+ PAKE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The example below demonstrates using SPAKE2+ to perform a password authenticated key +exchange. + +.. literalinclude:: /../src/examples/spake2p.cpp + :language: cpp diff --git a/doc/roadmap.rst b/doc/roadmap.rst index b7c0e87f366..7119eb025ed 100644 --- a/doc/roadmap.rst +++ b/doc/roadmap.rst @@ -18,7 +18,6 @@ Botan3 The following major feature work is currently planned for Botan3: -* SPAKE2+ password authenticated key exchange * BLS12-381 * HPKE (RFC 9180) * XMSS^MT diff --git a/src/examples/spake2p.cpp b/src/examples/spake2p.cpp new file mode 100644 index 00000000000..1fbad17c9cf --- /dev/null +++ b/src/examples/spake2p.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +int main() { + const auto as_span = [](std::string_view s) -> std::span { + return {reinterpret_cast(s.data()), s.size()}; + }; + + // Both peers must agree on the system parameters, the identities, and + // the context string + const auto params = Botan::SPAKE2p::SystemParameters::rfc9383_p256_sha256(); + const auto prover_id = as_span("client"); + const auto verifier_id = as_span("server"); + const auto context = as_span("botan spake2+ example"); + + const std::string_view password = "top!secret"; + + Botan::AutoSeeded_RNG rng; + + // Registration, performed once: the prover derives its secret from the + // password, and gives the registration record (along with the salt) to + // the verifier. The verifier stores the record; it never sees the + // password itself. + const auto salt = rng.random_vec(16); + const auto secret = Botan::SPAKE2p::ProverSecret::from_password(params, password, prover_id, verifier_id, salt); + const auto record = secret.registration_record(rng); + + // The online phase, performed for each session: + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, context); + + // First the prover generates its key share and sends it to the verifier + const auto prover_share = prover.generate_message(rng); + + // The verifier consumes the prover's share and responds with its own key + // share plus a key confirmation message + const auto verifier_msg = verifier.process_message(prover_share, rng); + + // The prover consumes the verifier's message, checking the verifier's key + // confirmation (an exception is thrown if it is invalid), and responds + // with its own key confirmation + const auto prover_confirm = prover.process_message(verifier_msg, rng); + + // Finally the verifier checks the prover's key confirmation + verifier.verify_confirmation(prover_confirm); + + // Now both sides share a secret key + if(prover.shared_secret() == verifier.shared_secret()) { + std::cout << "Key exchange worked\n"; + return 0; + } else { + // This should never happen, as long as verify_confirmation succeeded + std::cout << "Something went wrong\n"; + return 1; + } +} diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 910b108762b..f72869637dc 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -4664,6 +4664,343 @@ int botan_srp6_client_agree(const char* username, BOTAN_FFI_EXPORT(3, 0) int botan_srp6_group_size(const char* group_id, size_t* group_p_bytes); +/** +* SPAKE2+ (RFC 9383) password authenticated key exchange +* +* All operations are relative to a set of system parameters, which select +* the elliptic curve group, the SPAKE2+ M/N group elements, and the hash +* function. The parameters are created either from the name of one of the +* RFC 9383 ciphersuites using HMAC key confirmation ("P256-SHA256", +* "P256-SHA512", "P384-SHA256", "P384-SHA512", or "P521-SHA512"), or for +* an application specific group using botan_spake2p_params_init_custom. +* +* The identity, salt, and context parameters may be null, if the +* corresponding length is zero. +* +* Since the lengths of the outputs vary with the system parameters, all +* outputs are produced using view callbacks. The expected lengths of the +* messages received from the peer can be obtained from +* botan_spake2p_params_share_size and +* botan_spake2p_params_confirmation_size. +*/ + +/** +* SPAKE2+ system parameters +*/ +typedef struct botan_spake2p_params_struct* botan_spake2p_params_t; + +/** +* Create SPAKE2+ system parameters from an RFC 9383 ciphersuite name +* +* Objects created from the system parameters hold their own copy, so the +* parameters may be destroyed at any time. +* +* @param params output parameter for the created system parameters +* @param ciphersuite the SPAKE2+ ciphersuite name +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_params_init(botan_spake2p_params_t* params, const char* ciphersuite); + +/** +* Create custom SPAKE2+ system parameters for an arbitrary group +* +* The M/N group elements are derived from the seed using hash to curve; +* returns BOTAN_FFI_ERROR_NOT_IMPLEMENTED if the group does not support +* hash to curve. Both peers must use the same group, seed, and hash. +* +* If the seed includes the identities of the participants, this +* additionally makes the scheme "quantum annoying", in that an attacker +* with a discrete logarithm oracle must compute a new discrete log for +* each (prover, verifier) pair they wish to attack. +* +* @param params output parameter for the created system parameters +* @param group the elliptic curve group to use +* @param seed the seed bytes used to derive the M/N group elements +* @param seed_len length of seed in bytes +* @param hash_fn the hash function to use (eg "SHA-256") +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_params_init_custom( + botan_spake2p_params_t* params, botan_ec_group_t group, const uint8_t seed[], size_t seed_len, const char* hash_fn); + +/** +* Frees all resources of SPAKE2+ system parameters +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_params_destroy(botan_spake2p_params_t params); + +/** +* Return the size in bytes of a SPAKE2+ key share (shareP or shareV) +* +* @param params the SPAKE2+ system parameters +* @param share_size output parameter for the key share size +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_params_share_size(botan_spake2p_params_t params, size_t* share_size); + +/** +* Return the size in bytes of a SPAKE2+ key confirmation message (confirmP or confirmV) +* +* @param params the SPAKE2+ system parameters +* @param confirmation_size output parameter for the key confirmation size +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_params_confirmation_size(botan_spake2p_params_t params, size_t* confirmation_size); + +/** +* Derive a SPAKE2+ prover secret (w0 and w1) from a password, using Argon2id +* +* The view callback is invoked with the serialized prover secret, which is +* password equivalent and must be protected accordingly. It is used with +* botan_spake2p_registration_record and botan_spake2p_prover_init. +* +* @param params the SPAKE2+ system parameters +* @param password the (null terminated) password +* @param prover_id the identity of the prover +* @param prover_id_len length of prover_id in bytes +* @param verifier_id the identity of the verifier +* @param verifier_id_len length of verifier_id in bytes +* @param salt a salt value, ideally random and stored with the registration record +* @param salt_len length of salt in bytes +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the serialized prover secret +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_derive_secret(botan_spake2p_params_t params, + const char* password, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t salt[], + size_t salt_len, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** +* Compute a SPAKE2+ registration record (w0 and L) from a prover secret +* +* The registration record is provided to the verifier during registration. +* While it does not allow directly impersonating the prover, it does allow +* offline password guessing attacks, so it should be protected. +* +* @param params the SPAKE2+ system parameters +* @param rng a random number generator +* @param secret the serialized prover secret +* @param secret_len length of secret in bytes +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the serialized registration record +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_registration_record(botan_spake2p_params_t params, + botan_rng_t rng, + const uint8_t secret[], + size_t secret_len, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** +* SPAKE2+ prover +*/ +typedef struct botan_spake2p_prover_struct* botan_spake2p_prover_t; + +/** +* SPAKE2+ verifier +*/ +typedef struct botan_spake2p_verifier_struct* botan_spake2p_verifier_t; + +/** +* Initialize a SPAKE2+ prover +* +* The identities and context must be agreed upon by both parties; the +* identities must additionally match the values used when deriving the +* prover secret. +* +* @param prover output parameter for the created prover object +* @param params the SPAKE2+ system parameters +* @param secret the serialized prover secret +* @param secret_len length of secret in bytes +* @param prover_id the identity of the prover +* @param prover_id_len length of prover_id in bytes +* @param verifier_id the identity of the verifier +* @param verifier_id_len length of verifier_id in bytes +* @param context an application specific context string +* @param context_len length of context in bytes +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_prover_init(botan_spake2p_prover_t* prover, + botan_spake2p_params_t params, + const uint8_t secret[], + size_t secret_len, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t context[], + size_t context_len); + +/** +* Frees all resources of a SPAKE2+ prover +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_prover_destroy(botan_spake2p_prover_t prover); + +/** +* Generate the prover's key share (shareP), which is sent to the verifier +* +* This can be called only once per prover object. +* +* @param prover the prover object +* @param rng a random number generator +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the key share +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_prover_generate_message(botan_spake2p_prover_t prover, + botan_rng_t rng, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** +* Consume the verifier's response (shareV followed by confirmV) and produce +* the prover's key confirmation (confirmP), which is sent to the verifier. +* +* Returns BOTAN_FFI_ERROR_BAD_MAC if the verifier's key confirmation is +* wrong, typically meaning the passwords do not match. +* +* @param prover the prover object +* @param rng a random number generator +* @param peer_message the verifier's response +* @param peer_message_len length of peer_message in bytes +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the prover's key confirmation +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_prover_process_message(botan_spake2p_prover_t prover, + botan_rng_t rng, + const uint8_t peer_message[], + size_t peer_message_len, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** +* Return the prover's shared secret (K_shared) +* +* This may be called only after botan_spake2p_prover_process_message +* has succeeded. +* +* @param prover the prover object +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the shared secret +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_prover_shared_secret(botan_spake2p_prover_t prover, botan_view_ctx ctx, botan_view_bin_fn view); + +/** +* Initialize a SPAKE2+ verifier +* +* The identities and context must be agreed upon by both parties; the +* identities must additionally match the values used when deriving the +* prover secret. +* +* @param verifier output parameter for the created verifier object +* @param params the SPAKE2+ system parameters +* @param record the serialized registration record +* @param record_len length of record in bytes +* @param prover_id the identity of the prover +* @param prover_id_len length of prover_id in bytes +* @param verifier_id the identity of the verifier +* @param verifier_id_len length of verifier_id in bytes +* @param context an application specific context string +* @param context_len length of context in bytes +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_init(botan_spake2p_verifier_t* verifier, + botan_spake2p_params_t params, + const uint8_t record[], + size_t record_len, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t context[], + size_t context_len); + +/** +* Frees all resources of a SPAKE2+ verifier +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_destroy(botan_spake2p_verifier_t verifier); + +/** +* Consume the prover's key share (shareP) and produce the verifier's +* response (shareV followed by confirmV), which is sent to the prover. +* +* This can be called only once per verifier object. +* +* @param verifier the verifier object +* @param rng a random number generator +* @param peer_message the prover's key share +* @param peer_message_len length of peer_message in bytes +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the verifier's response +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_process_message(botan_spake2p_verifier_t verifier, + botan_rng_t rng, + const uint8_t peer_message[], + size_t peer_message_len, + botan_view_ctx ctx, + botan_view_bin_fn view); + +/** +* Check the prover's key confirmation (confirmP) +* +* Returns BOTAN_FFI_ERROR_BAD_MAC if the confirmation is wrong, meaning +* the prover does not know the password. +* +* @param verifier the verifier object +* @param confirmation the prover's key confirmation +* @param confirmation_len length of confirmation in bytes +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_verify_confirmation(botan_spake2p_verifier_t verifier, + const uint8_t confirmation[], + size_t confirmation_len); + +/** +* Skip checking the prover's key confirmation (confirmP) +* +* This can be called after botan_spake2p_verifier_process_message, in +* place of botan_spake2p_verifier_verify_confirmation, to allow extracting +* the shared secret without having checked the prover's key confirmation. +* +* Warning: after calling this, nothing is known about the peer; only a +* prover which knows the password can compute the same shared secret, but +* no evidence of this has been received. It is intended solely for +* protocols which embed SPAKE2+ and perform the prover's key confirmation +* themselves, for example the proposed TLS PAKE extension, where the TLS +* handshake takes the place of confirmP. Anywhere else, use +* botan_spake2p_verifier_verify_confirmation. +* +* @param verifier the verifier object +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_skip_confirmation(botan_spake2p_verifier_t verifier); + +/** +* Return the verifier's shared secret (K_shared) +* +* This may be called only after botan_spake2p_verifier_verify_confirmation +* has succeeded, or after botan_spake2p_verifier_skip_confirmation. +* +* @param verifier the verifier object +* @param ctx a context pointer passed to the view callback +* @param view a view callback which is invoked with the shared secret +*/ +BOTAN_FFI_EXPORT(3, 13) +int botan_spake2p_verifier_shared_secret(botan_spake2p_verifier_t verifier, botan_view_ctx ctx, botan_view_bin_fn view); + /** * ZFEC */ diff --git a/src/lib/ffi/ffi_spake2p.cpp b/src/lib/ffi/ffi_spake2p.cpp new file mode 100644 index 00000000000..b41305bf43f --- /dev/null +++ b/src/lib/ffi/ffi_spake2p.cpp @@ -0,0 +1,363 @@ +/* +* (C) 2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + #include + #include + #include +#endif + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + +namespace { + +Botan::SPAKE2p::SystemParameters spake2p_params_from_name(const char* ciphersuite) { + using Botan::SPAKE2p::SystemParameters; + + const std::string_view name(ciphersuite); + + if(name == "P256-SHA256") { + return SystemParameters::rfc9383_p256_sha256(); + } else if(name == "P256-SHA512") { + return SystemParameters::rfc9383_p256_sha512(); + } else if(name == "P384-SHA256") { + return SystemParameters::rfc9383_p384_sha256(); + } else if(name == "P384-SHA512") { + return SystemParameters::rfc9383_p384_sha512(); + } else if(name == "P521-SHA512") { + return SystemParameters::rfc9383_p521_sha512(); + } else { + throw Botan::Invalid_Argument("Unknown SPAKE2+ ciphersuite"); + } +} + +std::span spake2p_opt_span(const uint8_t* ptr, size_t len) { + if(ptr == nullptr && len > 0) { + throw Botan_FFI::FFI_Error("Null pointer with non-zero length", BOTAN_FFI_ERROR_NULL_POINTER); + } + return {ptr, len}; +} + +} // namespace + +#endif + +extern "C" { + +using namespace Botan_FFI; + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) +BOTAN_FFI_DECLARE_STRUCT(botan_spake2p_params_struct, Botan::SPAKE2p::SystemParameters, 0x2E1B4A96); +BOTAN_FFI_DECLARE_STRUCT(botan_spake2p_prover_struct, Botan::SPAKE2p::ProverContext, 0x9F337C29); +BOTAN_FFI_DECLARE_STRUCT(botan_spake2p_verifier_struct, Botan::SPAKE2p::VerifierContext, 0xD70A9E13); +#else +BOTAN_FFI_DECLARE_DUMMY_STRUCT(botan_spake2p_params_struct, 0x2E1B4A96); +BOTAN_FFI_DECLARE_DUMMY_STRUCT(botan_spake2p_prover_struct, 0x9F337C29); +BOTAN_FFI_DECLARE_DUMMY_STRUCT(botan_spake2p_verifier_struct, 0xD70A9E13); +#endif + +int botan_spake2p_params_init(botan_spake2p_params_t* params, const char* ciphersuite) { + if(any_null_pointers(params, ciphersuite)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return ffi_guard_thunk(__func__, [=]() -> int { + *params = nullptr; + auto p = std::make_unique(spake2p_params_from_name(ciphersuite)); + return ffi_new_object(params, std::move(p)); + }); +#else + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_params_init_custom( + botan_spake2p_params_t* params, botan_ec_group_t group, const uint8_t seed[], size_t seed_len, const char* hash_fn) { + if(any_null_pointers(params, group, hash_fn)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return ffi_guard_thunk(__func__, [=]() -> int { + auto p = std::make_unique( + Botan::SPAKE2p::SystemParameters::custom(safe_get(group), spake2p_opt_span(seed, seed_len), hash_fn)); + return ffi_new_object(params, std::move(p)); + }); +#else + BOTAN_UNUSED(params, group, seed, seed_len, hash_fn); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_params_destroy(botan_spake2p_params_t params) { + return BOTAN_FFI_CHECKED_DELETE(params); +} + +int botan_spake2p_params_share_size(botan_spake2p_params_t params, size_t* share_size) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(params, [=](const auto& p) -> int { + if(share_size == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + *share_size = p.share_size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(params, share_size); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_params_confirmation_size(botan_spake2p_params_t params, size_t* confirmation_size) { + if(any_null_pointers(params, confirmation_size)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(params, [=](const auto& p) -> int { + *confirmation_size = p.confirmation_size(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(params, confirmation_size); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_derive_secret(botan_spake2p_params_t params, + const char* password, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t salt[], + size_t salt_len, + botan_view_ctx ctx, + botan_view_bin_fn view) { + if(any_null_pointers(params, password)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return ffi_guard_thunk(__func__, [=]() -> int { + const auto sec = Botan::SPAKE2p::ProverSecret::from_password(safe_get(params), + password, + spake2p_opt_span(prover_id, prover_id_len), + spake2p_opt_span(verifier_id, verifier_id_len), + spake2p_opt_span(salt, salt_len)); + return invoke_view_callback(view, ctx, sec.serialize()); + }); +#else + BOTAN_UNUSED(params, password, prover_id, prover_id_len, verifier_id, verifier_id_len); + BOTAN_UNUSED(salt, salt_len, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_registration_record(botan_spake2p_params_t params, + botan_rng_t rng, + const uint8_t secret[], + size_t secret_len, + botan_view_ctx ctx, + botan_view_bin_fn view) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + if(any_null_pointers(params, rng, secret)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return ffi_guard_thunk(__func__, [=]() -> int { + const auto sec = Botan::SPAKE2p::ProverSecret::deserialize(safe_get(params), {secret, secret_len}); + return invoke_view_callback(view, ctx, sec.registration_record(safe_get(rng)).serialize()); + }); +#else + BOTAN_UNUSED(params, rng, secret, secret_len, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_prover_init(botan_spake2p_prover_t* prover, + botan_spake2p_params_t params, + const uint8_t secret[], + size_t secret_len, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t context[], + size_t context_len) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + if(any_null_pointers(prover, params, secret)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + return ffi_guard_thunk(__func__, [=]() -> int { + *prover = nullptr; + const auto& p = safe_get(params); + const auto sec = Botan::SPAKE2p::ProverSecret::deserialize(p, {secret, secret_len}); + auto ctx = std::make_unique(p, + sec, + spake2p_opt_span(prover_id, prover_id_len), + spake2p_opt_span(verifier_id, verifier_id_len), + spake2p_opt_span(context, context_len)); + return ffi_new_object(prover, std::move(ctx)); + }); +#else + BOTAN_UNUSED(prover, params, secret, secret_len, prover_id, prover_id_len); + BOTAN_UNUSED(verifier_id, verifier_id_len, context, context_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_prover_destroy(botan_spake2p_prover_t prover) { + return BOTAN_FFI_CHECKED_DELETE(prover); +} + +int botan_spake2p_prover_generate_message(botan_spake2p_prover_t prover, + botan_rng_t rng, + botan_view_ctx ctx, + botan_view_bin_fn view) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT( + prover, [=](auto& p) -> int { return invoke_view_callback(view, ctx, p.generate_message(safe_get(rng))); }); +#else + BOTAN_UNUSED(prover, rng, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_prover_process_message(botan_spake2p_prover_t prover, + botan_rng_t rng, + const uint8_t peer_message[], + size_t peer_message_len, + botan_view_ctx ctx, + botan_view_bin_fn view) { + if(any_null_pointers(peer_message)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(prover, [=](auto& p) -> int { + return invoke_view_callback(view, ctx, p.process_message({peer_message, peer_message_len}, safe_get(rng))); + }); +#else + BOTAN_UNUSED(prover, rng, peer_message, peer_message_len, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_prover_shared_secret(botan_spake2p_prover_t prover, botan_view_ctx ctx, botan_view_bin_fn view) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(prover, [=](auto& p) -> int { return invoke_view_callback(view, ctx, p.shared_secret()); }); +#else + BOTAN_UNUSED(prover, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_verifier_init(botan_spake2p_verifier_t* verifier, + botan_spake2p_params_t params, + const uint8_t record[], + size_t record_len, + const uint8_t prover_id[], + size_t prover_id_len, + const uint8_t verifier_id[], + size_t verifier_id_len, + const uint8_t context[], + size_t context_len) { + if(any_null_pointers(record)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return ffi_guard_thunk(__func__, [=]() -> int { + *verifier = nullptr; + const auto& p = safe_get(params); + const auto rec = Botan::SPAKE2p::RegistrationRecord::deserialize(p, {record, record_len}); + auto ctx = std::make_unique(p, + rec, + spake2p_opt_span(prover_id, prover_id_len), + spake2p_opt_span(verifier_id, verifier_id_len), + spake2p_opt_span(context, context_len)); + return ffi_new_object(verifier, std::move(ctx)); + }); +#else + BOTAN_UNUSED(verifier, params, record, record_len, prover_id, prover_id_len); + BOTAN_UNUSED(verifier_id, verifier_id_len, context, context_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_verifier_destroy(botan_spake2p_verifier_t verifier) { + return BOTAN_FFI_CHECKED_DELETE(verifier); +} + +int botan_spake2p_verifier_process_message(botan_spake2p_verifier_t verifier, + botan_rng_t rng, + const uint8_t peer_message[], + size_t peer_message_len, + botan_view_ctx ctx, + botan_view_bin_fn view) { + if(any_null_pointers(peer_message)) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(verifier, [=](auto& v) -> int { + return invoke_view_callback(view, ctx, v.process_message({peer_message, peer_message_len}, safe_get(rng))); + }); +#else + BOTAN_UNUSED(verifier, rng, peer_message, peer_message_len, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_verifier_verify_confirmation(botan_spake2p_verifier_t verifier, + const uint8_t confirmation[], + size_t confirmation_len) { + if(confirmation == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(verifier, [=](auto& v) -> int { + v.verify_confirmation({confirmation, confirmation_len}); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(verifier, confirmation, confirmation_len); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_verifier_skip_confirmation(botan_spake2p_verifier_t verifier) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(verifier, [](auto& v) -> int { + v.skip_confirmation(); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(verifier); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_spake2p_verifier_shared_secret(botan_spake2p_verifier_t verifier, + botan_view_ctx ctx, + botan_view_bin_fn view) { +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return BOTAN_FFI_VISIT(verifier, [=](auto& v) -> int { return invoke_view_callback(view, ctx, v.shared_secret()); }); +#else + BOTAN_UNUSED(verifier, ctx, view); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} +} diff --git a/src/lib/pake/spake2p/info.txt b/src/lib/pake/spake2p/info.txt new file mode 100644 index 00000000000..aeadcc5d9c3 --- /dev/null +++ b/src/lib/pake/spake2p/info.txt @@ -0,0 +1,21 @@ + +PAKE_SPAKE2PLUS -> 20260702 + + + +name -> "SPAKE2+" +brief -> "SPAKE2+ Password Authenticated Key Exchange (RFC 9383)" + + + +spake2p.h + + + +argon2 +ec_group +hkdf +hmac +sha2_32 +sha2_64 + diff --git a/src/lib/pake/spake2p/spake2p.cpp b/src/lib/pake/spake2p/spake2p.cpp new file mode 100644 index 00000000000..a9fbba2d74d --- /dev/null +++ b/src/lib/pake/spake2p/spake2p.cpp @@ -0,0 +1,466 @@ +/* +* (C) 2024,2025,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::SPAKE2p { + +namespace { + +std::array le64_length(std::span data) { + return store_le(static_cast(data.size())); +} + +std::pair derive_w0_w1(const SystemParameters& params, + std::string_view password, + std::span prover_id, + std::span verifier_id, + std::span salt) { + /* + * RFC 9383 Section 3.2 + * + * w0s || w1s = PBKDF(len(pw) || pw || + * len(idProver) || idProver || + * len(idVerifier) || idVerifier) + * w0 = w0s mod p + * w1 = w1s mod p + */ + secure_vector pbkdf_input(3 * 8 + password.size() + prover_id.size() + verifier_id.size()); + BufferStuffer stuffer(pbkdf_input); + + auto append_with_le64_length = [&](std::span data) { + stuffer.append(le64_length(data)); + stuffer.append(data); + }; + + append_with_le64_length(as_span_of_bytes(password)); + append_with_le64_length(prover_id); + append_with_le64_length(verifier_id); + BOTAN_ASSERT_NOMSG(stuffer.full()); + + /* + * RFC 9106 Section 4 + * + * If much less memory is available, a uniformly safe option is + * Argon2id with t=3 iterations, p=4 lanes, m=2^(16) (64 MiB of RAM) + */ + auto pwhash = PasswordHashFamily::create_or_throw("Argon2id")->from_params(64 * 1024, 3, 4); + + /* + * RFC 9383 Section 3.2 + * + * To control bias, each half must be of length at least + * ceil(log2(p)) + k bits, with k >= 64 + */ + const size_t half_len = params.group().get_order_bytes() + 16; + + secure_vector w0s_w1s(2 * half_len); + const std::string_view pbkdf_input_sv(cast_uint8_ptr_to_char(pbkdf_input.data()), pbkdf_input.size()); + pwhash->hash(w0s_w1s, pbkdf_input_sv, salt); + + auto w0 = EC_Scalar::from_bytes_mod_order(params.group(), std::span{w0s_w1s}.first(half_len)); + auto w1 = EC_Scalar::from_bytes_mod_order(params.group(), std::span{w0s_w1s}.last(half_len)); + + return {std::move(w0), std::move(w1)}; +} + +struct SessionKeys { + secure_vector shared_key; + std::vector confirm_p; + std::vector confirm_v; +}; + +SessionKeys spake2p_key_schedule(const SystemParameters& params, + std::span context, + std::span prover_id, + std::span verifier_id, + std::span share_p, + std::span share_v, + const EC_AffinePoint& z, + const EC_AffinePoint& v, + const EC_Scalar& w0) { + auto hash = HashFunction::create_or_throw(params.hash_function()); + + auto hash_with_le64_length = [&](std::span data) { + hash->update(le64_length(data)); + hash->update(data); + }; + + /* + * RFC 9383 Section 3.3 + * + * TT = len(Context) || Context + * || len(idProver) || idProver + * || len(idVerifier) || idVerifier + * || len(M) || M + * || len(N) || N + * || len(shareP) || shareP + * || len(shareV) || shareV + * || len(Z) || Z + * || len(V) || V + * || len(w0) || w0 + */ + hash_with_le64_length(context); + hash_with_le64_length(prover_id); + hash_with_le64_length(verifier_id); + hash_with_le64_length(params.spake2p_m().serialize_uncompressed()); + hash_with_le64_length(params.spake2p_n().serialize_uncompressed()); + hash_with_le64_length(share_p); + hash_with_le64_length(share_v); + hash_with_le64_length(z.serialize_uncompressed()); + hash_with_le64_length(v.serialize_uncompressed()); + hash_with_le64_length(w0.serialize()); + + const auto k_main = hash->final(); + + /* + * RFC 9383 Section 3.4 + * + * K_main = Hash(TT) + * K_confirmP || K_confirmV = KDF(nil, K_main, "ConfirmationKeys") + * K_shared = KDF(nil, K_main, "SharedKey") + * + * confirmP = MAC(K_confirmP, shareV) + * confirmV = MAC(K_confirmV, shareP) + */ + auto kdf = KDF::create_or_throw(fmt("HKDF({})", params.hash_function())); + auto mac = MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", params.hash_function())); + + const size_t mac_key_len = hash->output_length(); + const auto confirm_keys = kdf->derive_key>(2 * mac_key_len, k_main, "", "ConfirmationKeys"); + + SessionKeys keys; + keys.shared_key = kdf->derive_key>(hash->output_length(), k_main, "", "SharedKey"); + + mac->set_key(std::span{confirm_keys}.first(mac_key_len)); + mac->update(share_v); + keys.confirm_p = mac->final_stdvec(); + + mac->set_key(std::span{confirm_keys}.last(mac_key_len)); + mac->update(share_p); + keys.confirm_v = mac->final_stdvec(); + + return keys; +} + +std::tuple spake2p_group_params(std::string_view group_name, + std::string_view m_hex, + std::string_view n_hex) { + auto group = EC_Group::from_name(group_name); + EC_AffinePoint m(group, hex_decode(m_hex)); + EC_AffinePoint n(group, hex_decode(n_hex)); + return {std::move(group), std::move(m), std::move(n)}; +} + +// The M/N constants from RFC 9383 Section 4 + +constexpr std::string_view SPAKE2P_P256_M = "02886e2f97ace46e55ba9dd7242579f2993b64e16ef3dcab95afd497333d8fa12f"; + +constexpr std::string_view SPAKE2P_P256_N = "03d8bbd6c639c62937b04d997f38c3770719c629d7014d49a24b4f98baa1292b49"; + +constexpr std::string_view SPAKE2P_P384_M = + "030ff0895ae5ebf6187080a82d82b42e2765e3b2f8749c7e05eba366434b363d3dc36f15314739074d2eb8613fceec2853"; + +constexpr std::string_view SPAKE2P_P384_N = + "02c72cf2e390853a1c1c4ad816a62fd15824f56078918f43f922ca21518f9c543bb252c5490214cf9aa3f0baab4b665c10"; + +constexpr std::string_view SPAKE2P_P521_M = + "02003f06f38131b2ba2600791e82488e8d20ab889af753a41806c5db18d37d85608cfae06b82e4a72cd744c719193562a653ea1f119ee" + "f9356907edc9b56979962d7aa"; + +constexpr std::string_view SPAKE2P_P521_N = + "0200c7924b9ec017f3094562894336a53c50167ba8c5963876880542bc669e494b2532d76c5b53dfb349fdf69154b9e0048c58a42e8ed" + "04cef052a3bc349d95575cd25"; + +} // namespace + +SystemParameters::SystemParameters(EC_Group group, EC_AffinePoint m, EC_AffinePoint n, std::string_view hash_fn) : + m_group(std::move(group)), m_spake2p_m(std::move(m)), m_spake2p_n(std::move(n)), m_hash_fn(hash_fn) {} + +SystemParameters SystemParameters::rfc9383_p256_sha256() { + auto [group, m, n] = spake2p_group_params("secp256r1", SPAKE2P_P256_M, SPAKE2P_P256_N); + return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-256"); +} + +SystemParameters SystemParameters::rfc9383_p256_sha512() { + auto [group, m, n] = spake2p_group_params("secp256r1", SPAKE2P_P256_M, SPAKE2P_P256_N); + return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512"); +} + +SystemParameters SystemParameters::rfc9383_p384_sha256() { + auto [group, m, n] = spake2p_group_params("secp384r1", SPAKE2P_P384_M, SPAKE2P_P384_N); + return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-256"); +} + +SystemParameters SystemParameters::rfc9383_p384_sha512() { + auto [group, m, n] = spake2p_group_params("secp384r1", SPAKE2P_P384_M, SPAKE2P_P384_N); + return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512"); +} + +SystemParameters SystemParameters::rfc9383_p521_sha512() { + auto [group, m, n] = spake2p_group_params("secp521r1", SPAKE2P_P521_M, SPAKE2P_P521_N); + return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512"); +} + +SystemParameters SystemParameters::custom(const EC_Group& group, + std::span seed, + std::string_view hash_fn) { + BOTAN_ARG_CHECK(group.has_cofactor() == false, "SPAKE2+ is not supported for groups with a cofactor"); + + if(!group.hash_to_curve_supported(hash_fn)) { + throw Not_Implemented("SPAKE2+ custom params require hash2curve support which is not available for this curve"); + } + + auto m = EC_AffinePoint::hash_to_curve_ro(group, hash_fn, seed, "SPAKE2+ M"); + auto n = EC_AffinePoint::hash_to_curve_ro(group, hash_fn, seed, "SPAKE2+ N"); + + return SystemParameters(group, std::move(m), std::move(n), hash_fn); +} + +size_t SystemParameters::share_size() const { + return 1 + 2 * m_group.get_p_bytes(); +} + +size_t SystemParameters::confirmation_size() const { + if(m_hash_fn == "SHA-256") { + return 32; + } else if(m_hash_fn == "SHA-384") { + return 48; + } else if(m_hash_fn == "SHA-512") { + return 64; + } else { + return HashFunction::create_or_throw(m_hash_fn)->output_length(); + } +} + +RegistrationRecord RegistrationRecord::from_password(const SystemParameters& params, + std::string_view password, + std::span prover_id, + std::span verifier_id, + std::span salt, + RandomNumberGenerator& rng) { + return ProverSecret::from_password(params, password, prover_id, verifier_id, salt).registration_record(rng); +} + +RegistrationRecord RegistrationRecord::deserialize(const SystemParameters& params, std::span record) { + const size_t scalar_len = params.group().get_order_bytes(); + const size_t point_len = params.share_size(); + + if(record.size() != scalar_len + point_len) { + throw Decoding_Error("Invalid length for SPAKE2+ registration record"); + } + + auto w0 = EC_Scalar::deserialize(params.group(), record.first(scalar_len)); + auto l = EC_AffinePoint::deserialize_uncompressed(params.group(), record.subspan(scalar_len)); + + if(!w0 || !l) { + throw Decoding_Error("Invalid SPAKE2+ registration record"); + } + + return RegistrationRecord(std::move(*w0), std::move(*l)); +} + +secure_vector RegistrationRecord::serialize() const { + return concat>(m_w0.serialize(), m_l.serialize_uncompressed()); +} + +ProverSecret ProverSecret::from_password(const SystemParameters& params, + std::string_view password, + std::span prover_id, + std::span verifier_id, + std::span salt) { + auto [w0, w1] = derive_w0_w1(params, password, prover_id, verifier_id, salt); + return ProverSecret(std::move(w0), std::move(w1)); +} + +ProverSecret ProverSecret::from_prehashed(EC_Scalar w0, EC_Scalar w1) { + return ProverSecret(std::move(w0), std::move(w1)); +} + +ProverSecret ProverSecret::deserialize(const SystemParameters& params, std::span secret) { + if(auto w0_w1 = EC_Scalar::deserialize_pair(params.group(), secret)) { + return ProverSecret(std::move(w0_w1->first), std::move(w0_w1->second)); + } else { + throw Decoding_Error("Invalid SPAKE2+ prover secret"); + } +} + +secure_vector ProverSecret::serialize() const { + return EC_Scalar::serialize_pair>(m_w0, m_w1); +} + +RegistrationRecord ProverSecret::registration_record(RandomNumberGenerator& rng) const { + // RFC 9383 Section 3.2: "the registration record L=w1*P" + return RegistrationRecord(m_w0, EC_AffinePoint::g_mul(m_w1, rng)); +} + +ProverContext::ProverContext(const SystemParameters& params, + const ProverSecret& secret, + std::span prover_id, + std::span verifier_id, + std::span context) : + m_params(params), + m_secret(secret), + m_prover_id(prover_id.begin(), prover_id.end()), + m_verifier_id(verifier_id.begin(), verifier_id.end()), + m_context(context.begin(), context.end()) {} + +std::vector ProverContext::generate_message(RandomNumberGenerator& rng) { + BOTAN_STATE_CHECK(m_state == State::Initial); + + const auto x = EC_Scalar::random(m_params.group(), rng); + const auto g = EC_AffinePoint::generator(m_params.group()); + + // RFC 9383 Section 3.3: X = x*P + w0*M + if(auto share_p = EC_AffinePoint::mul_px_qy(g, x, m_params.spake2p_m(), m_secret.m_w0, rng)) { + m_our_message = std::make_pair(share_p->serialize_uncompressed(), x); + m_state = State::ShareGenerated; + return m_our_message->first; + } else { + throw Internal_Error("Computed the identity element during SPAKE2+ key exchange"); + } +} + +std::vector ProverContext::process_message(std::span peer_message, RandomNumberGenerator& rng) { + BOTAN_STATE_CHECK(m_state == State::ShareGenerated); + + const size_t share_size = m_params.share_size(); + const size_t confirm_size = m_params.confirmation_size(); + + if(peer_message.size() != share_size + confirm_size) { + throw Decoding_Error("Invalid length for SPAKE2+ verifier message"); + } + + const auto share_v = peer_message.first(share_size); + const auto confirm_v = peer_message.subspan(share_size); + + const auto y = EC_AffinePoint::deserialize_uncompressed(m_params.group(), share_v); + if(!y) { + throw Decoding_Error("Invalid SPAKE2+ key share"); + } + + const auto& w0 = m_secret.m_w0; + const auto& w1 = m_secret.m_w1; + const auto& n = m_params.spake2p_n(); + const auto& x = m_our_message->second; + + // RFC 9383 Section 3.3: Z = h*x*(Y - w0*N), V = h*w1*(Y - w0*N) + const auto z = EC_AffinePoint::mul_px_qy(*y, x, n, (x * w0).negate(), rng); + const auto v = EC_AffinePoint::mul_px_qy(*y, w1, n, (w1 * w0).negate(), rng); + + if(!z || !v) { + throw Decoding_Error("Invalid SPAKE2+ key share"); + } + + auto keys = + spake2p_key_schedule(m_params, m_context, m_prover_id, m_verifier_id, m_our_message->first, share_v, *z, *v, w0); + + if(!constant_time_compare(keys.confirm_v, confirm_v)) { + m_our_message.reset(); + m_state = State::Failed; + throw Invalid_Authentication_Tag("SPAKE2+ key confirmation failed"); + } + + m_shared_secret = std::move(keys.shared_key); + m_our_message.reset(); + m_state = State::Complete; + + return keys.confirm_p; +} + +secure_vector ProverContext::shared_secret() const { + BOTAN_STATE_CHECK(m_state == State::Complete); + return m_shared_secret; +} + +VerifierContext::VerifierContext(const SystemParameters& params, + const RegistrationRecord& record, + std::span prover_id, + std::span verifier_id, + std::span context) : + m_params(params), + m_record(record), + m_prover_id(prover_id.begin(), prover_id.end()), + m_verifier_id(verifier_id.begin(), verifier_id.end()), + m_context(context.begin(), context.end()) {} + +std::vector VerifierContext::process_message(std::span peer_message, + RandomNumberGenerator& rng) { + BOTAN_STATE_CHECK(m_state == State::Initial); + + const auto x = EC_AffinePoint::deserialize_uncompressed(m_params.group(), peer_message); + if(!x) { + throw Decoding_Error("Invalid SPAKE2+ key share"); + } + + const auto& w0 = m_record.m_w0; + + const auto y = EC_Scalar::random(m_params.group(), rng); + const auto g = EC_AffinePoint::generator(m_params.group()); + + // RFC 9383 Section 3.3: Y = y*P + w0*N + const auto share_v_pt = EC_AffinePoint::mul_px_qy(g, y, m_params.spake2p_n(), w0, rng); + if(!share_v_pt) { + throw Internal_Error("Computed the identity element during SPAKE2+ key exchange"); + } + const auto share_v = share_v_pt->serialize_uncompressed(); + + // RFC 9383 Section 3.3: Z = h*y*(X - w0*M), V = h*y*L + const auto z = EC_AffinePoint::mul_px_qy(*x, y, m_params.spake2p_m(), (y * w0).negate(), rng); + if(!z) { + throw Decoding_Error("Invalid SPAKE2+ key share"); + } + const auto v = m_record.m_l.mul(y, rng); + + auto keys = spake2p_key_schedule(m_params, m_context, m_prover_id, m_verifier_id, peer_message, share_v, *z, v, w0); + + m_shared_secret = std::move(keys.shared_key); + m_expected_confirmation = std::move(keys.confirm_p); + m_state = State::Responded; + + return concat>(share_v, keys.confirm_v); +} + +void VerifierContext::verify_confirmation(std::span confirmation) { + BOTAN_STATE_CHECK(m_state == State::Responded); + + if(!constant_time_compare(m_expected_confirmation, confirmation)) { + m_expected_confirmation.clear(); + m_shared_secret.clear(); + m_state = State::Failed; + throw Invalid_Authentication_Tag("SPAKE2+ key confirmation failed"); + } + + m_expected_confirmation.clear(); + m_state = State::Complete; +} + +void VerifierContext::skip_confirmation() { + BOTAN_STATE_CHECK(m_state == State::Responded); + + m_expected_confirmation.clear(); + m_state = State::Complete; +} + +secure_vector VerifierContext::shared_secret() const { + BOTAN_STATE_CHECK(m_state == State::Complete); + return m_shared_secret; +} + +} // namespace Botan::SPAKE2p diff --git a/src/lib/pake/spake2p/spake2p.h b/src/lib/pake/spake2p/spake2p.h new file mode 100644 index 00000000000..7b97dd7be05 --- /dev/null +++ b/src/lib/pake/spake2p/spake2p.h @@ -0,0 +1,424 @@ +/* +* (C) 2024,2025,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PAKE_SPAKE2PLUS_H_ +#define BOTAN_PAKE_SPAKE2PLUS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan { + +class RandomNumberGenerator; + +} + +/** +* SPAKE2+ (RFC 9383) password authenticated key exchange +* +* SPAKE2+ is an augmented PAKE; the two sides are asymmetric. The prover +* knows the password itself, while the verifier stores only a registration +* record derived from the password. An attacker who steals the registration +* record cannot impersonate the prover without first performing a dictionary +* attack on the record. +* +* The expected message flow is +* +* - The prover calls ProverContext::generate_message and sends the result +* (shareP) to the verifier. +* - The verifier calls VerifierContext::process_message and sends the +* result (shareV followed by confirmV) to the prover. +* - The prover calls ProverContext::process_message, which checks the +* verifier's key confirmation, and sends the result (confirmP) to the +* verifier. +* - The verifier calls VerifierContext::verify_confirmation +* +* After the final confirmation step both sides can call shared_secret to +* obtain the session key (K_shared in RFC 9383) +* +* Protocols which embed SPAKE2+ and perform the prover's key confirmation +* themselves (such as the proposed TLS PAKE extension) may instead call +* VerifierContext::skip_confirmation in place of the final step. +*/ +namespace Botan::SPAKE2p { + +/** +* SPAKE2+ (RFC 9383) System Parameters +* +* This selects the elliptic curve group, the M/N group elements, and the +* hash function; the hash also fixes the KDF (HKDF) and the MAC (HMAC) +*/ +class BOTAN_PUBLIC_API(3, 13) SystemParameters final { + public: + /** + * The RFC 9383 ciphersuite P256-SHA256-HKDF-HMAC-SHA256 + */ + static SystemParameters rfc9383_p256_sha256(); + + /** + * The RFC 9383 ciphersuite P256-SHA512-HKDF-HMAC-SHA512 + */ + static SystemParameters rfc9383_p256_sha512(); + + /** + * The RFC 9383 ciphersuite P384-SHA256-HKDF-HMAC-SHA256 + */ + static SystemParameters rfc9383_p384_sha256(); + + /** + * The RFC 9383 ciphersuite P384-SHA512-HKDF-HMAC-SHA512 + */ + static SystemParameters rfc9383_p384_sha512(); + + /** + * The RFC 9383 ciphersuite P521-SHA512-HKDF-HMAC-SHA512 + */ + static SystemParameters rfc9383_p521_sha512(); + + /** + * SPAKE2+ custom system parameters for an arbitrary group + * + * The M/N values will be derived from the seed using hash2curve; + * note that not all groups support hash2curve. + * + * RFC 9383 Section 3.2: "Applications MAY use different M and N + * values, provided they are computed, e.g., using different input + * seeds to the algorithm in Appendix B, as random elements for + * which the discrete log is unknown." + * + * If the seed includes the identities of the participants, this + * additionally makes the scheme "quantum annoying", in that an attacker + * with a discrete logarithm oracle must compute a new discrete log for + * each (user, verifier) pair they wish to attack. + * + * @param group the elliptic curve group to use + * @param seed the seed bytes used to derive M and N + * @param hash_fn the hash function to use (eg "SHA-256") + */ + static SystemParameters custom(const EC_Group& group, std::span seed, std::string_view hash_fn); + + /** + * Return the elliptic curve group + */ + const EC_Group& group() const { return m_group; } + + /** + * Return the SPAKE2+ M group element + */ + const EC_AffinePoint& spake2p_m() const { return m_spake2p_m; } + + /** + * Return the SPAKE2+ N group element + */ + const EC_AffinePoint& spake2p_n() const { return m_spake2p_n; } + + /** + * Return the name of the hash function + */ + const std::string& hash_function() const { return m_hash_fn; } + + /** + * Return the size in bytes of a key share (shareP or shareV) + */ + size_t share_size() const; + + /** + * Return the size in bytes of a key confirmation message (confirmP or confirmV) + */ + size_t confirmation_size() const; + + private: + SystemParameters(EC_Group group, EC_AffinePoint m, EC_AffinePoint n, std::string_view hash_fn); + + EC_Group m_group; + EC_AffinePoint m_spake2p_m; + EC_AffinePoint m_spake2p_n; + std::string m_hash_fn; +}; + +class ProverSecret; + +/** +* SPAKE2+ Registration Record +* +* This is the information (w0 and L in RFC 9383) which the verifier +* stores in order to authenticate the prover. +*/ +class BOTAN_PUBLIC_API(3, 13) RegistrationRecord final { + public: + /** + * Perform password registration, returning the registration record + * + * This derives the record from the password using Argon2id; see + * ProverSecret::from_password for the details. The same password, + * identities, and salt must later be used to create the prover's secret. + * + * The identities and salt may be empty. + */ + static RegistrationRecord from_password(const SystemParameters& params, + std::string_view password, + std::span prover_id, + std::span verifier_id, + std::span salt, + RandomNumberGenerator& rng); + + /** + * Deserialize a RegistrationRecord previously serialized by serialize + */ + static RegistrationRecord deserialize(const SystemParameters& params, std::span record); + + /** + * Serialize the registration record + * + * @warning the return value is the unencrypted registration record, which + * is a sensitive value allowing password guessing attacks. Encrypt it for + * persistent storage if possible. + */ + secure_vector serialize() const; + + private: + friend class ProverSecret; + friend class VerifierContext; + + RegistrationRecord(EC_Scalar w0, EC_AffinePoint l) : m_w0(std::move(w0)), m_l(std::move(l)) {} + + EC_Scalar m_w0; + EC_AffinePoint m_l; +}; + +/** +* SPAKE2+ Prover Secret +* +* This is the information (w0 and w1 in RFC 9383) which the prover +* derives from the password in order to authenticate itself. +*/ +class BOTAN_PUBLIC_API(3, 13) ProverSecret final { + public: + /** + * Derive the prover secret from a password + * + * The derivation uses Argon2id with the memory-constrained parameters + * from RFC 9106, namely m=64 MiB, t=3, p=4. Following RFC 9383, the + * Argon2id passphrase input is the concatenation + * + * len(pw) || pw || len(idProver) || idProver || len(idVerifier) || idVerifier + * + * with each length an 8-byte little-endian count of bytes, and the salt + * is provided to Argon2id directly. The Argon2id output is split in two + * halves, each of which is reduced modulo the group order. + * + * The identities and salt may be empty; if a salt is available it + * should be used, as this prevents precomputed dictionary attacks. + */ + static ProverSecret from_password(const SystemParameters& params, + std::string_view password, + std::span prover_id, + std::span verifier_id, + std::span salt); + + /** + * Create a prover secret from already derived scalars + * + * @warning This interface is potentially unsafe, depending upon how the + * scalars are derived from the password. They must be uniformly random, + * and preferably computed in a way such that testing password guesses is + * expensive for an attacker. It exists to support testing, as well as + * applications which require using a different password hashing scheme + * than the default one implemented by from_password. + */ + static ProverSecret from_prehashed(EC_Scalar w0, EC_Scalar w1); + + /** + * Deserialize a ProverSecret previously serialized by serialize + */ + static ProverSecret deserialize(const SystemParameters& params, std::span secret); + + /** + * Serialize the prover secret + * + * @warning the return value is password equivalent; encrypt it for + * persistent storage if possible. + */ + secure_vector serialize() const; + + /** + * Compute the registration record (w0 and L=w1*P) for this secret + * + * This would typically be done once, when the password is first + * registered with the verifier. + */ + RegistrationRecord registration_record(RandomNumberGenerator& rng) const; + + private: + friend class ProverContext; + + ProverSecret(EC_Scalar w0, EC_Scalar w1) : m_w0(std::move(w0)), m_w1(std::move(w1)) {} + + EC_Scalar m_w0; + EC_Scalar m_w1; +}; + +/** +* SPAKE2+ (RFC 9383) Prover +* +* The prover knows the password secret (w0 and w1) and authenticates +* itself to a verifier which knows the matching registration record. +*/ +class BOTAN_PUBLIC_API(3, 13) ProverContext final { + public: + /** + * Set up for an execution of the protocol + * + * The identities and context must be agreed upon by both parties; the + * identities must additionally match the values used during password + * registration. Both the identities and the context may be empty. + */ + ProverContext(const SystemParameters& params, + const ProverSecret& secret, + std::span prover_id, + std::span verifier_id, + std::span context = {}); + + /** + * Generate the prover's key share (shareP), which is sent to the verifier. + * + * This can be called only once. + */ + std::vector generate_message(RandomNumberGenerator& rng); + + /** + * Consume the message from the verifier (shareV followed by confirmV) + * and return the prover's key confirmation (confirmP), which is sent + * to the verifier. + * + * Throws Decoding_Error if the message is malformed, and + * Invalid_Authentication_Tag if the verifier's key confirmation is + * wrong (typically due to a password mismatch). + */ + std::vector process_message(std::span peer_message, RandomNumberGenerator& rng); + + /** + * Return the shared secret (K_shared) + * + * This may be called only after process_message has succeeded. + */ + secure_vector shared_secret() const; + + /** + * Return the system parameters + */ + const SystemParameters& parameters() const { return m_params; } + + private: + enum class State : uint8_t { Initial, ShareGenerated, Complete, Failed }; + + SystemParameters m_params; + ProverSecret m_secret; + std::vector m_prover_id; + std::vector m_verifier_id; + std::vector m_context; + std::optional, EC_Scalar>> m_our_message; + secure_vector m_shared_secret; + State m_state = State::Initial; +}; + +/** +* SPAKE2+ (RFC 9383) Verifier +* +* The verifier does not know the password itself; it stores only the +* registration record. +*/ +class BOTAN_PUBLIC_API(3, 13) VerifierContext final { + public: + /** + * Set up for an execution of the protocol + * + * The identities and context must be agreed upon by both parties; the + * identities must additionally match the values used during password + * registration. Both the identities and the context may be empty. + */ + VerifierContext(const SystemParameters& params, + const RegistrationRecord& record, + std::span prover_id, + std::span verifier_id, + std::span context = {}); + + /** + * Consume the prover's key share (shareP) and return the verifier's + * response (shareV followed by confirmV), which is sent to the prover. + * + * This can be called only once. Throws Decoding_Error if the key + * share is malformed. + */ + std::vector process_message(std::span peer_message, RandomNumberGenerator& rng); + + /** + * Check the prover's key confirmation (confirmP) + * + * Throws Invalid_Authentication_Tag if the confirmation is wrong, + * meaning the prover does not know the password. + */ + void verify_confirmation(std::span confirmation); + + /** + * Skip checking the prover's key confirmation (confirmP) + * + * This can be called after process_message, in place of + * verify_confirmation, to allow extracting the shared secret without + * having checked the prover's key confirmation. + * + * @warning After calling this, nothing is known about the peer; only + * a prover which knows the password can compute the same shared + * secret, but no evidence of this has been received. It is intended + * solely for protocols which embed SPAKE2+ and perform the prover's + * key confirmation themselves, for example the proposed TLS PAKE + * extension, where the TLS handshake takes the place of confirmP. + * Anywhere else, use verify_confirmation. + */ + void skip_confirmation(); + + /** + * Return the shared secret (K_shared) + * + * This may be called only after verify_confirmation has succeeded, + * or after skip_confirmation. + * + * RFC 9383 Section 3.3: "The Verifier MUST NOT send application data + * to the Prover until it has received and verified the confirmation + * message." + */ + secure_vector shared_secret() const; + + /** + * Return the system parameters + */ + const SystemParameters& parameters() const { return m_params; } + + private: + enum class State : uint8_t { Initial, Responded, Complete, Failed }; + + SystemParameters m_params; + RegistrationRecord m_record; + std::vector m_prover_id; + std::vector m_verifier_id; + std::vector m_context; + std::vector m_expected_confirmation; + secure_vector m_shared_secret; + State m_state = State::Initial; +}; + +} // namespace Botan::SPAKE2p + +#endif diff --git a/src/python/botan3.py b/src/python/botan3.py index 0c73716980a..293b804befc 100755 --- a/src/python/botan3.py +++ b/src/python/botan3.py @@ -29,9 +29,9 @@ from collections.abc import Iterable from enum import IntEnum -# This Python module requires the FFI API version introduced in Botan 3.11.0 +# This Python module requires the FFI API version introduced in Botan 3.13.0 # -# 3.13.0 - botan_hash_security_level +# 3.13.0 - botan_hash_security_level, SPAKE2+ # 3.12.0 - EcScalar/EcPoint, DRBG # 3.11.0 - XOF API # 3.10.0 - introduced botan_pubkey_load_ec*_sec1() @@ -633,6 +633,32 @@ def ffi_api(fn, args, allowed_errors=None): c_char_p, POINTER(c_size_t), c_char_p, POINTER(c_size_t)]) ffi_api(dll.botan_srp6_group_size, [c_char_p, POINTER(c_size_t)]) + # SPAKE2+ + ffi_api(dll.botan_spake2p_params_init, [c_void_p, c_char_p]) + ffi_api(dll.botan_spake2p_params_init_custom, [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p]) + ffi_api(dll.botan_spake2p_params_destroy, [c_void_p]) + ffi_api(dll.botan_spake2p_params_share_size, [c_void_p, POINTER(c_size_t)]) + ffi_api(dll.botan_spake2p_params_confirmation_size, [c_void_p, POINTER(c_size_t)]) + ffi_api(dll.botan_spake2p_derive_secret, + [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_registration_record, + [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_prover_init, + [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t]) + ffi_api(dll.botan_spake2p_prover_destroy, [c_void_p]) + ffi_api(dll.botan_spake2p_prover_generate_message, [c_void_p, c_void_p, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_prover_process_message, + [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_prover_shared_secret, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_verifier_init, + [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t]) + ffi_api(dll.botan_spake2p_verifier_destroy, [c_void_p]) + ffi_api(dll.botan_spake2p_verifier_process_message, + [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK]) + ffi_api(dll.botan_spake2p_verifier_verify_confirmation, [c_void_p, c_char_p, c_size_t]) + ffi_api(dll.botan_spake2p_verifier_skip_confirmation, [c_void_p]) + ffi_api(dll.botan_spake2p_verifier_shared_secret, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK]) + # ZFEC ffi_api(dll.botan_zfec_encode, [c_size_t, c_size_t, c_char_p, c_size_t, POINTER(c_char_p)]) @@ -3690,6 +3716,196 @@ def srp6_client_agree(username: str, password: str, group: str, hsh: str, salt: a, al, k, kl)) +class Spake2pParams: + """ + SPAKE2+ (RFC 9383) system parameters, selecting the elliptic curve + group, the SPAKE2+ M/N group elements, and the hash function. + + The ciphersuite is one of "P256-SHA256", "P256-SHA512", "P384-SHA256", + "P384-SHA512", or "P521-SHA512". + """ + def __init__(self, ciphersuite: str | None = None): + """Create system parameters for one of the named ciphersuites""" + self.__obj = c_void_p(0) + if ciphersuite is not None: + _DLL.botan_spake2p_params_init(byref(self.__obj), _ctype_str(ciphersuite)) + + @classmethod + def custom(cls, group: ECGroup, seed: bytes, hash_fn: str) -> Spake2pParams: + """ + Create custom system parameters for an arbitrary group, deriving the + M/N group elements from the seed using hash to curve (which not all + groups support). Both peers must use the same group, seed, and hash. + """ + params = Spake2pParams() + _DLL.botan_spake2p_params_init_custom(byref(params._handle()), + group._handle(), + seed, len(seed), + _ctype_str(hash_fn)) + return params + + def __del__(self): + _DLL.botan_spake2p_params_destroy(self.__obj) + + def _handle(self): + return self.__obj + + def share_size(self) -> int: + """ + Return the size in bytes of a key share (shareP or shareV) + """ + return _call_fn_returning_sz(lambda sz: _DLL.botan_spake2p_params_share_size(self.__obj, sz)) + + def confirmation_size(self) -> int: + """ + Return the size in bytes of a key confirmation message (confirmP or confirmV) + """ + return _call_fn_returning_sz(lambda sz: _DLL.botan_spake2p_params_confirmation_size(self.__obj, sz)) + +def spake2p_derive_secret(params: Spake2pParams, password: str, prover_id: bytes = b'', verifier_id: bytes = b'', salt: bytes = b'') -> bytes: + """ + Derive a SPAKE2+ (RFC 9383) prover secret from a password, using Argon2id. + + The returned secret is password equivalent, and must be protected + accordingly. It is used with `spake2p_registration_record` and + `Spake2pProver` + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_derive_secret(params._handle(), + _ctype_str(password), + prover_id, len(prover_id), + verifier_id, len(verifier_id), + salt, len(salt), + vc, vf)) + +def spake2p_registration_record(params: Spake2pParams, secret: bytes, rng: RandomNumberGenerator) -> bytes: + """ + Compute a SPAKE2+ registration record from a prover secret. + + The registration record is provided to the verifier during registration. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_registration_record(params._handle(), + rng._handle(), + secret, len(secret), + vc, vf)) + +class Spake2pProver: + """ + SPAKE2+ (RFC 9383) prover: the side which knows the password secret. + + The expected message flow is + + * The prover calls generate_message and sends the result to the verifier + * The verifier calls process_message on it and sends the result to the prover + * The prover calls process_message on it, verifying the verifier's key + confirmation, and sends the resulting confirmation to the verifier + * The verifier calls verify_confirmation on it + + After the final step both sides can call shared_secret. + """ + def __init__(self, params: Spake2pParams, secret: bytes, prover_id: bytes = b'', verifier_id: bytes = b'', context: bytes = b''): + """ + The identities and context must be agreed upon by both parties; + the identities must additionally match the values used when + deriving the prover secret. + """ + self.__obj = c_void_p(0) + _DLL.botan_spake2p_prover_init(byref(self.__obj), + params._handle(), + secret, len(secret), + prover_id, len(prover_id), + verifier_id, len(verifier_id), + context, len(context)) + + def __del__(self): + _DLL.botan_spake2p_prover_destroy(self.__obj) + + def generate_message(self, rng: RandomNumberGenerator) -> bytes: + """ + Generate the prover's key share, which is sent to the verifier. + Can be called only once. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_prover_generate_message(self.__obj, rng._handle(), vc, vf)) + + def process_message(self, peer_message: bytes, rng: RandomNumberGenerator) -> bytes: + """ + Consume the verifier's response and return the prover's key + confirmation, which is sent to the verifier. Raises an exception + if the verifier's key confirmation is wrong, typically meaning + the passwords do not match. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_prover_process_message(self.__obj, rng._handle(), + peer_message, len(peer_message), + vc, vf)) + + def shared_secret(self) -> bytes: + """ + Return the shared secret. Only valid after process_message succeeded. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_prover_shared_secret(self.__obj, vc, vf)) + +class Spake2pVerifier: + """ + SPAKE2+ (RFC 9383) verifier: the side which stores only the registration + record derived from the password. See Spake2pProver for the message flow. + """ + def __init__(self, params: Spake2pParams, record: bytes, prover_id: bytes = b'', verifier_id: bytes = b'', context: bytes = b''): + """ + The identities and context must be agreed upon by both parties; + the identities must additionally match the values used when + deriving the prover secret. + """ + self.__obj = c_void_p(0) + _DLL.botan_spake2p_verifier_init(byref(self.__obj), + params._handle(), + record, len(record), + prover_id, len(prover_id), + verifier_id, len(verifier_id), + context, len(context)) + + def __del__(self): + _DLL.botan_spake2p_verifier_destroy(self.__obj) + + def process_message(self, peer_message: bytes, rng: RandomNumberGenerator) -> bytes: + """ + Consume the prover's key share and return the verifier's response + (its own key share followed by a key confirmation), which is sent + to the prover. Can be called only once. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_verifier_process_message(self.__obj, rng._handle(), + peer_message, len(peer_message), + vc, vf)) + + def verify_confirmation(self, confirmation: bytes) -> None: + """ + Check the prover's key confirmation. Raises an exception if the + confirmation is wrong, meaning the prover does not know the password. + """ + _DLL.botan_spake2p_verifier_verify_confirmation(self.__obj, confirmation, len(confirmation)) + + def skip_confirmation(self) -> None: + """ + Skip checking the prover's key confirmation, allowing shared_secret + to be called without verify_confirmation. After calling this, no + evidence has been received that the peer knows the password; it is + intended solely for protocols which embed SPAKE2+ and perform the + prover's key confirmation themselves. + """ + _DLL.botan_spake2p_verifier_skip_confirmation(self.__obj) + + def shared_secret(self) -> bytes: + """ + Return the shared secret. Only valid after verify_confirmation + succeeded, or after skip_confirmation. + """ + return _call_fn_viewing_vec(lambda vc, vf: + _DLL.botan_spake2p_verifier_shared_secret(self.__obj, vc, vf)) + def zfec_encode(k: int, n: int, input_bytes: bytes) -> list[bytes]: """ ZFEC-encode an input message according to the given parameters diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py index 2670c482e75..4f86d9757db 100644 --- a/src/scripts/test_python.py +++ b/src/scripts/test_python.py @@ -1287,6 +1287,83 @@ def test_srp6(self): key_s = server.step2(a) self.assertEqual(key_c, key_s) + def test_spake2p(self): + prover_id = b'client' + verifier_id = b'server' + context = b'test_python.py' + rng = botan.RandomNumberGenerator() + + with self.assertRaises(botan.BotanException): + botan.Spake2pParams('P37-MD5') + params = botan.Spake2pParams('P256-SHA256') + + share_size = params.share_size() + self.assertEqual(share_size, 65) + confirmation_size = params.confirmation_size() + self.assertEqual(confirmation_size, 32) + + # Registration + salt = rng.get(16) + secret = botan.spake2p_derive_secret(params, 'tarnished rosebud', prover_id, verifier_id, salt) + self.assertEqual(len(secret), 64) + record = botan.spake2p_registration_record(params, secret, rng) + self.assertEqual(len(record), 32 + share_size) + + # A successful exchange + prover = botan.Spake2pProver(params, secret, prover_id, verifier_id, context) + verifier = botan.Spake2pVerifier(params, record, prover_id, verifier_id, context) + + share_p = prover.generate_message(rng) + self.assertEqual(len(share_p), share_size) + verifier_msg = verifier.process_message(share_p, rng) + self.assertEqual(len(verifier_msg), share_size + confirmation_size) + confirmation = prover.process_message(verifier_msg, rng) + self.assertEqual(len(confirmation), confirmation_size) + verifier.verify_confirmation(confirmation) + + self.assertEqual(prover.shared_secret(), verifier.shared_secret()) + + # An exchange where the verifier explicitly skips the prover's confirmation + prover = botan.Spake2pProver(params, secret, prover_id, verifier_id, context) + verifier = botan.Spake2pVerifier(params, record, prover_id, verifier_id, context) + + with self.assertRaises(botan.BotanException): + verifier.skip_confirmation() + + verifier_msg = verifier.process_message(prover.generate_message(rng), rng) + prover.process_message(verifier_msg, rng) + verifier.skip_confirmation() + + self.assertEqual(prover.shared_secret(), verifier.shared_secret()) + + # A prover with the wrong password fails during confirmation + wrong_secret = botan.spake2p_derive_secret(params, 'tarnished rosebush', prover_id, verifier_id, salt) + prover = botan.Spake2pProver(params, wrong_secret, prover_id, verifier_id, context) + verifier = botan.Spake2pVerifier(params, record, prover_id, verifier_id, context) + + verifier_msg = verifier.process_message(prover.generate_message(rng), rng) + with self.assertRaises(botan.BotanException): + prover.process_message(verifier_msg, rng) + + # Custom system parameters over the same group, so the secret can be reused + group = botan.ECGroup.from_name('secp256r1') + try: + cparams = botan.Spake2pParams.custom(group, b'test_python.py seed', 'SHA-256') + except botan.BotanException as e: + if e.error_code() == -40: # NotImplemented: no hash to curve support + return + raise + + crecord = botan.spake2p_registration_record(cparams, secret, rng) + prover = botan.Spake2pProver(cparams, secret, prover_id, verifier_id, context) + verifier = botan.Spake2pVerifier(cparams, crecord, prover_id, verifier_id, context) + + verifier_msg = verifier.process_message(prover.generate_message(rng), rng) + confirmation = prover.process_message(verifier_msg, rng) + verifier.verify_confirmation(confirmation) + + self.assertEqual(prover.shared_secret(), verifier.shared_secret()) + def test_kyber_raw_keys(self): a_pub_bits = hex_decode("38d4851e5c010da39a7470bc1c80916f78c7bd5891dcd3b1ea84b6f051b346b803c1b97c94604020b7279b27836c3049ea0b9a3758510b7559593237ade72587462206b709f365848c96559326a5fe6c6f4cf531fd1a18477267f66ba14af20163c41f1138a01995147f271ddfc2be5361b281e6029d210584b2859b7383667284f767bb32782baad10933da0138a3a0660a149531c03f9c8ffccb33e3c3a5a7984e21fab26aa72a8a6b942f265e52551a9c800e5a44805f0c0141a05554213387f105df56458496bd8f469051886da223cb9fe78e7b390bf94b0a937691af9550082b76d045cb4d29c23c67942608d078a1c80f24767a945d19f077d82c9b9b197073464abe69cf7c5626177308f384672d5263b0c4826db4470e1a70e4751e3918abe8fcbc3bc0531ae89e5512214b5cc94a16a014bcb3826c79fbf4add0825eeefbab88cb7cff37bb8d491f8de902578a1e961655565b7718782a23504fdc13c783f130e177925e305d1fbc63cc8c15c2c67f85500cca785de9f480490558ef71aaf0fb5b513914401269b309c4c59c64d2a757d8855f58465615925f1ea6812cb143fff383e1048e285118bf932944b86fbdf4b1b9e65685664a07775c46952aaada1168f54b47c7a231e7355c64637467b5a3c09cab67bb35f58640c2726283bb63530a15f66eca48a840c00ca8862e283c73bfbb413a2915b8d1159a043f12c59bfa828248249b76106faa61a127a0280c586350e7a42cb74ca49cabd606891ec7cb8e84affe4b2e14c71658332b755611bab7977fa76ce736b21ed34a17ac0ec3561ca9b282d4a2bc407697924b1cf918ba83d3a4fdc82564c95bd904bdeee91ed6ccb36baa88a05c80712901bf280aee6538ec2078c2a84ee5862fc137cd92e97968d69fc3453a1e1cb161c50c9f2473a0d09037b188a0fa01efc344c2ac8fe8592b0a58456662a95033659a158a2d90a6e50c253a87975785ce29c4570000a154d4b3b2c642205c8c7cf9ac6b1071fbb368ab950a744b88c95ba5243017831120a9048338d29847830d12a933a09abd21a46b828cb14e808cd35129c9dc6e5b931d4a126fefe07909618e2b4586e7b6b424963b7323ba505ba112bb9b834a7d1b78ad0df53d556a1c69369f09148b1dc9938df59223f087fd6833be5b2bc2651fe58911ac01467f9297dfdc22b41a0f1702718710b78cf35b1865813a896d45214d338155b6c043c532330c002d520739467a504a866637fb3451c849f8f83e6a94147f168da53acdf9d8affd968a84124a9abc09af960cd3b29f2344831bb41e67605eebf00df202857117399dd748b6514aed61bb2f6cb841d168d5f35e20054573a331cd4882a04b072c179158825bcf471266da0dcceab1a021c73254751d5a161c1a92062c220a217a69d9823314b4de996fe8d45f6db5af16c1561495a4c43090bc394c94e1b0ec738eb56267201c2ecd1c7b4993c0efc0284bdc9a091c294f95703a7178822c8a95b79b1e4591e0998d893875c1a879c08a073cc67df426bba792c18ae6c1feba879bec54812c2affa012973b700ad48e271078280864268600a7aa309eaa1098750a0f8a522eb929577b412f7855613688b72f9bc85c0a13b9d041586fd583feb12afd5a402dd33b43543f5fa4eb436c8d") b_priv_bits = hex_decode("9bc4986eec853ac90896c7300f914216773baa687f57f81f94e6b5a26515c0a74c73b19dc6b9888755a1d1db6a1128b02c5144ed9ba75e874a68a14dee3a9bf770b767121fbbdb292e097444757fe2f324a875b0a07a4fbdcc8fd6137fce80c69f412be103b01f8b3bed392600f830d1b20d73ca0b386666d16c5d96db10e309041890bfd4ab7bdec27191979abe7637e76153918cc2af1545724abfd95ca496281a0cc6ca63a87819b75aa86452e5027d429cad64a9c029112a3a7b9fb993c6a6d0671f6c9e24986b83f67cf1236d94c051559616826a947eaa15074025d1ab810c80220e8a8c2c222d4e8594bf35811b444a57e9bf94fb2479d174d6b38c8c3b4730437c244b2513232ec798adec4a8e597bca83bca5948468f93c022ba65311a91e3d95c299c70c3f1a43cd0c83dd500bd2211b9b385b82f2c003b10d295765b3e367563a5f19033fc1231db9620f835c95f5a261190a5da1c24ed528316f0a1520d223e208a8c99b24d28598ade74fc88370e7f45102c5a6411891c661b356b9a32e1cc0fafaa085d7670e8bcb5e768eb255204f2445b5b73b04079881903a553c865744343a925c7899777b1c9dd5579a481512f8157061606a9a67c041d38bc179048be17dd9e19dc0a572bce595afa3b68ff21bf78a63a7560636b6bb01ec3362e2aaabc8965818b7f2417ca8f66a5a2a67f72a3931e125d638a872862a7b680a54aa1f25d90dbd567635ec6664919e29517325a5c5048cc8d1c31af5e4866e85025b9184a7b75ed7f2c9c8d88259fa2ec5b05ed3751584f88308c67ff1a72fa2453305160baf404db7d4be56f1547b20bb7dec23f02b10db584b28ca40d8b39c1c3ab9f3d7bbda0822604ca48f26694d69810aa888ae3c0983c5ba4cb74211f7a5361ccdee694f4202aebe13a75b1b2dda1b3232376be213582afc4fde3474766671fe865e2fd98384eb65b2f349f1e24269b91bd9d08c80849735a9951304afd130b5c2211314630aed4b6ac3b1252a0999ff5a3ec26a283342389d219fd243706128b389eb2334fb2a6184a4eab6735d7428df5633ce030b8f853ee47c4401fc5d06b43c9b66b7aeeb23b5f000a30a6f88f027ee3994fe8b63e51b83bc24bb733a3773a35cbe138f6d9c91a3a3898bca6776030d740ac355176547d624719656a9a44e91c63faf7699dc6c2c45575718d48828828b39043c2fda2af416837efc38d17c56d4b63c63a5ab43434647d029f7b236b288958f06910763610f8b2f027a8dcd780039ab34a6871427476ff6500240e83b87c95dcfa45ac5315ef34b343fb609eb296e915c849bb8c57f57c69b177eaa8456377403fe8c6627a3282d45308f675d67085a15f0b1b55aa2a8f21afd6c05c3c00e9eb8c32418cb41963ce427b43e7545c58325c7b9368db2333de424dbeb3430f007d18a68d73b7dc67960b28206a68a1be400a770b5cd9d45a72824ca00345ac56491c1414fe5287a2eb2ad61f3bdcd0c84c335b04a703425d79dbd02b0a0e90de5b331c3c29f6562969e04cdf7095b2a7646b3d006b0b83cb68580b5ccb71de1b4d9f131bac133d6088e10613a00599d81d4818403a4bea83905304cc45ca645a9b2c6484cf9490f1755c744d9988ed60475e6ae44355ed15c7b549366f29581ec2721fd6704e0ca3f878812805675141c0a15a7b7ac35a9e3f8b2a010bc184981c57852895b2695d56131e32326717f6b101df1bc82b3ba0222d52656d118538c4ca3416be1c76ba37a9901a36e4883be6c541f2bbb561818cd2f136b98f658250545c1fa5bcfdc04374016db1c5132447fd6d568866451c25412f72967de868eaeb9c546fb40ea88cd84a1a586ca51c74bc9c3e56e104323afb658d1ac003151bdc35879a4b6762648bff0caa682f1b3319805d2326d5a46af832aacefbaa1ea820568ea3925870e9b6577eb93898e1b0cfbd1c995cb4cd6cabb979813819749b40a9952f50e97c4365e777dcfe9084219294c205acb350e07db9c98f53444546460962e2bf5aa1cc12273d882fcc215a7397b3f9b307c56b9a0429b30f88453a2376669b28b4bc4b84b51714b6652b7a1a0b53e9ec61b55ca51cdb38243239ee5f18243e515f178768a888f475b3d9060136bb22ee355b3da02c16ad83bdbb4aaa13809cb4bcd5bb53710737d3883632f9254e3336af61621a376720572450e3937c0d930e349adbfa7642ff822b9135e18f943e0178617604d10e0c09ffb3e09783c09d12cbe93311757af9857b77d1488ed39321ca97c3745c5bc176ca81274c8321bcb2029938b32bbd01aec137032f9760849701649120050b50d8353c36b8bb7724a67e7660fc93324065d63912c35a86d8fe60683067bbd2685c552ad8c65c77c57c937676fd61595c453174bf996e9d3a9ccb837b25464115c1ba3343ac097b80735aed3225091167cd8f841ffe49c5e698ef542124253084623179394433a4b61547b9ee09c98c2736ea086bd69d1bc7ae68a6ae5ce682a215860006b4604dee45a3e212f97643acb77a79a880382e483537c5198d4483a176d25aac9c3670a3f30956f18ad441904776bcd48131c7d6465bce0c133010f3176c92ec962f5c6b84d4c3ae949619cf48172997ca3b1ccf5c8cc7a67923e1295801048a3d40ac4f2c6467c750fc71314a0c1fc22637dd52e7ea50907ea973d765a6a9bb2b11aa405b9187f72026696710e61af3e41c33da1a05eb65e6523704f078e74e32f10e00247967aee3a8c6546889ef67cd613ad7236583f2104122ac6c6a40a84dc96d81c569e76a952c0a25f396e48337a4fe029a4c91cc7406872706a55573b75f160a4facad7c85fab141c63454bf48990729096ce9965604c7cc1e60ae6868dcc41bc3df71c3e5593f0488b0c6a3063e817f9f4bacb17599c8666ff3591126b4891fb7f5d29660bab60cf5007043a4311d41ab3b29787184f3d3c9ab7cc247f635145b67e970505ba44ad0e06b11ec5cda4175295199d19d660204cbdc17947cc66442d3a2cd408a20fe98174f31ee4e5bb3fa8bcd102bedc26527e9bae836442978a6ccbe510f93ab77569ab1f09d0e6312dd0cc0bcaf095fa8a52a7212d14714a7bf852416c9b026301bd965c30a43d24d97298346a46b2c4bc814ba4059653358b03c9456c60bf0193932eaa2f24ea8e4b010a5a4425ce4540fbab90d8e55c97ac2687f15ff5299278824a08d4743e1a62e1c6619cd3278cd75a97a5b4e3a38668b26c99bf8461495793bbb1b12ca369c825cb31d68731326bf4764b416bb3339ae9c9ce46d9da0e714c0bae8712a670d0e5dcfdd1dd0d045932c79c559b2ab3c7300e2d894b0eaa40a6ab254506d8c1176a33c4a1b2879604b1b80df48d31dd") diff --git a/src/tests/data/pake/spake2p.vec b/src/tests/data/pake/spake2p.vec new file mode 100644 index 00000000000..25851ea751f --- /dev/null +++ b/src/tests/data/pake/spake2p.vec @@ -0,0 +1,90 @@ +# Test vectors for SPAKE2+ from RFC 9383 Appendix C +# +# Only the HMAC ciphersuites are included; the values labeled ConfirmP and +# ConfirmV are the RFC values HMAC(K_confirmP, shareV) and +# HMAC(K_confirmV, shareP) respectively, and Shared is K_shared + +# SPAKE2+-P256-SHA256-HKDF-SHA256-HMAC-SHA256 +Group = secp256r1 +Hash = SHA-256 +Context = 5350414b45322b2d503235362d5348413235362d484b44462d5348413235362d484d41432d534841323536205465737420566563746f7273 +ProverId = 636c69656e74 +VerifierId = 736572766572 +W0 = bb8e1bbcf3c48f62c08db243652ae55d3e5586053fca77102994f23ad95491b3 +W1 = 7e945f34d78785b8a3ef44d0df5a1a97d6b3b460409a345ca7830387a74b1dba +L = 04eb7c9db3d9a9eb1f8adab81b5794c1f13ae3e225efbe91ea487425854c7fc00f00bfedcbd09b2400142d40a14f2064ef31dfaa903b91d1faea7093d835966efd +X = d1232c8e8693d02368976c174e2088851b8365d0d79a9eee709c6a05a2fad539 +ShareP = 04ef3bd051bf78a2234ec0df197f7828060fe9856503579bb1733009042c15c0c1de127727f418b5966afadfdd95a6e4591d171056b333dab97a79c7193e341727 +Y = 717a72348a182085109c8d3917d6c43d59b224dc6a7fc4f0483232fa6516d8b3 +ShareV = 04c0f65da0d11927bdf5d560c69e1d7d939a05b0e88291887d679fcadea75810fb5cc1ca7494db39e82ff2f50665255d76173e09986ab46742c798a9a68437b048 +ConfirmP = 926cc713504b9b4d76c9162ded04b5493e89109f6d89462cd33adc46fda27527 +ConfirmV = 9747bcc4f8fe9f63defee53ac9b07876d907d55047e6ff2def2e7529089d3e68 +Shared = 0c5f8ccd1413423a54f6c1fb26ff01534a87f893779c6e68666d772bfd91f3e7 + +# SPAKE2+-P256-SHA512-HKDF-SHA512-HMAC-SHA512 +Group = secp256r1 +Hash = SHA-512 +Context = 5350414b45322b2d503235362d5348413531322d484b44462d5348413531322d484d41432d534841353132205465737420566563746f7273 +ProverId = 636c69656e74 +VerifierId = 736572766572 +W0 = 1cc5207d6e34b8f7828206fb64b86aa9c712bc952abf251bb9f5856b24d8c8cc +W1 = 4279649e62532b01dc27d2ed39100ba350518fb969672061a01edce752d0e672 +L = 043a348ad475d2200d46df876f1eb2e136056da31dafff52cc7762bf3be84de0097c4e69b0b9321326af1f0af4a14561a9c7b640cb5afd6822d14cb34830fc4511 +X = b586ab83f175c1a2b56b6a1b6a283523f88a9befcf11e22efb48e2ee1fe69a23 +ShareP = 04a7928c4b47f6b8657a5b8ebcb6f1bd266192e152fb9745a4180c94657a2f323b4d50d536c0325cdb0ec42c9bd8db8d7af3ff6dc85edb4b5365375c62e09def4a +Y = ac1fb828f041782d452ea9cc00c3fa34a55fa8f7f98c04be45a3d607b092d441 +ShareV = 04498c29e37dbd53ebf8db76679901d90c6be3af57f46ac3025b32420839f0489c6c3b6bf5ddc8ecbc3d7c83d0891ad814a00ad23eba13197c9d96a5b10275e35d +ConfirmP = 6b2469b56cf8ac3f94a8d0b533380ea6b3d0f46b3e12ee82550d49e129c2412728c9437a64ee5f80c8cdc5e8a30faa0a6deb8a5251346ba81bb6fc955b2304fc +ConfirmV = 154174fc278a935e290b3352ba877e179fa9281c0a76928faea703c72d383b267511a5cf084cb07147efece94e3cfd91944e7baab856858fbebc087167b0f409 +Shared = 11887659d9e002f34fa6cc270d33570f001b2a3fc0522b643c07327d09a4a9f47aab85813d13c585b53adf5ac9de5707114848f3dc31a4045f69a2cc1972b098 + +# SPAKE2+-P384-SHA256-HKDF-SHA256-HMAC-SHA256 +Group = secp384r1 +Hash = SHA-256 +Context = 5350414b45322b2d503338342d5348413235362d484b44462d5348413235362d484d41432d534841323536205465737420566563746f7273 +ProverId = 636c69656e74 +VerifierId = 736572766572 +W0 = 097a61cbb1cee72bb654be96d80f46e0e3531151003903b572fc193f233772c23c22228884a0d5447d0ab49a656ce1d2 +W1 = 18772816140e6c3c3938a693c600b2191118a34c7956e1f1cd5b0d519b56ea5858060966cfaf27679c9182129949e74f +L = 04f27dd5384d6b9beb4c5022c94b1978d632779e1d3abe458611e734a529d004e25053398e5dc9eeaa4ffa59743ca7ddbc0e7ce69155295cb2b846da83ee6a44490dd8e96bb0b0f6645281bfd978dd5f6836561ea0d8b2c045ff04cef2e5873d2c +X = 2f1bdbeda162ff2beba0293d3cd3ae95f663c53663378c7e18ee8f56a4a48b00d31ce0ef43606548da485058f12e8e73 +ShareP = 049fb0404ca7ce71fb85d3aaa8fd05fa054affac996135bc245149be09571e43e2bf76e00d6d52ac452b8224f6b9da31420a4f5e214b377546daad4d61da5ca0cfdea59a5a92ebdb6b42da5d14663b8d1f9eb97050139ab89788e0ada27b048fcf +Y = bbcaf02404a16ed4fa73b183f703a8d969386f3d34f5e98b3a904e760512f11757f07dfcf87a2ada8fc6d028445bd53e +ShareV = 0493b1c1f6a30eac4ac4a15711e44640bae3576787627ee25411042981e94b2e9604b9374f66bb247bc431759212ef3fa0a20c087863b89efb32219e1337ce94be2175f8cb9fd50cf0b84772717fd063c52b69de1229a01ab840b55993287f32ed +ConfirmP = 7ae825e242a5a1f86ad7db172c2c12fcb458b6a2b1ddfc96b2b7cfd2eed5f7ab +ConfirmV = 1581062167d6a3d14493447cd170d408f6fdc58e31225438db86214167426a7a +Shared = 99758e838ae1a856589689fb55b6befe4e2382e6ebbeca1a6232a68f9dc04c1a + +# SPAKE2+-P384-SHA512-HKDF-SHA512-HMAC-SHA512 +Group = secp384r1 +Hash = SHA-512 +Context = 5350414b45322b2d503338342d5348413531322d484b44462d5348413531322d484d41432d534841353132205465737420566563746f7273 +ProverId = 636c69656e74 +VerifierId = 736572766572 +W0 = b8d44a0982b88abe19b724d4bdafba8c90dc93130e0bf4f8062810992326da126fd01db53e40250ca33a3ff302044cb0 +W1 = 2373e2071c3bb2a6d53ece57830d56f8080189816803c22375d6a4a514f9d161b64d0f05b97735b98b348f9b33cc2e30 +L = 049ca7217ff6456bb2e2bcf71b31d9b1e5ed6e0c9700936ae617e990cee87ee1ce3a03629dd5532948c39b89f38b39f13c7f513c5b1ada00f6533a4a8b02b9cd04e1b2a5db1f24ec5fe959198a19666037e04b768cc02e75ac9da0048736db6e5b +X = 5a835d52714f30d2ef539268b89df9558628400063dfa0e41eb979066f4caf409bbf7aab3ddddea13f1b070a1827d3d4 +ShareP = 042f382eef464a2c9aecfdf4b81d25c4de2de113ba67405ce336c762c69217ae7e27bda875144140d7536c4cc08b9b4dace5f872a6a2ed57f34042688ad3c5d446c187dc0caf9cea812df3a4dd6fdbc64b9d7d7d7ff4bf6965abb06eeb108d55ee +Y = c883ee5b08cf7ba122038c279459ab1a730f85f2d624a02732d519faab56a498e773a8dec6c447ed02d5c00303a18bc4 +ShareV = 04d72e11eee332305062454c0a058b8103a3304785d445510cd8d101e9cb44cfb159cb7b72123abaf719ab1c42e0558c84c14b0886e8b446e4c880bff2f4b291fafafc748cb4115824e66732bdeba7fae176388e228ab9d7546255994ca3fb5a52 +ConfirmP = 7f806ae56ea3e49a8b16ffee528086489418913641f529d50ff92aa456ad4648e522f9540b403bff6bd94ee1adc95c7d1b2666f7ba6f9c10748bc7bfb4181d27 +ConfirmV = 8daa262decb79cceda4421f4f8dacf22ec027c08e036f071beea563c8e00813a29807963ff9d7d6bbff48dd5bdcdd9ca9fd7ffc272b162258d981913f7253dcb +Shared = 31e0075a823b9269af5769d71ef3b2f5001cbfe044584fe8551124a217dad078415630bf3eda16b5a38341d418a6d72b3960f818a0926f0de88784b59d6a694b + +# SPAKE2+-P521-SHA512-HKDF-SHA512-HMAC-SHA512 +Group = secp521r1 +Hash = SHA-512 +Context = 5350414b45322b2d503532312d5348413531322d484b44462d5348413531322d484d41432d534841353132205465737420566563746f7273 +ProverId = 636c69656e74 +VerifierId = 736572766572 +W0 = 009c79bcd7656716314fca5a6e2c5cda7ef86131399438e012a043051e863f60b5aeb3c101731e1505e721580f48535a9b0456b231b9266ae6fff49ee90d25f72f5f +W1 = 01632c15f51fcd916cd79e19075f8a69b72b0099922ad62ff8d540b469569f0aa027047aed2b3f242ea0ac4288b4e4db6a4e5946d8ad32b42192c5aa66d9ef8e1b33 +L = 040135072d0fa36f9e80031294cef5c3c35b882a0efa2c66570d64a49f8bec6c66435bf65bb7c7b2a3e7dece491e02b4d567e7087dbc32fe0fae8af417dcb50be6d704012a194588b690e6d3db492656f72ddea01fc1c7fcec0f5d34a5af0102939f6fdeae39c20cff74fcdb7f09855f0fc9520d20b0520b0b096b8d42c7c3d68b4a66f751 +X = 00b69e3bb15df82c9fa0057461334e2c66ab92fc9b8d3662eec81216ef5ddc4a43f19e90dedaa2d72502f69673a115984ffcf88e03a9364b07102114c5602cd93c69 +ShareP = 0400a14431edf6852ff5fe868f8683e16e9e0a45d9e27f9a96442285ac6b161fc0bf267362a5ffb06f9cbd14b7a37e492146d77cae4c77812df00a91dbae09e27e1fac00ae019317ef9768548325bca35ce258e6206fe03c6338b2eb889d09d9f11400a36cf6328a7e1f81c6c7a2af7ff1d9b5210768318f27e57b75b39b9fbfc7b37a60ab +Y = 0056d01c5246fbde964c0493934b7ece89eafd943eb27d357880a2a22022499e249528c5707b1afe8794c8a1d60ceedaeed96dd0dd904ea075f096c9fec5da7de496 +ShareV = 0401aa5af0f3027f63b7170572db5ff06dd1f3d6ea8ea771b26b434fbbc6c9de7d80975131c9c2e94d30c0ed2d62449c4c1b7e95037a85ed7598e415a259126365e89500d0f2156b551b70416d719944736990f346f6f9ba4fbaf2f63e09873690bcf730582e0a7b03ffede50f5787b631d5021a94287f0a29a081b62b9f5a3bf393b001b3 +ConfirmP = f0f5c903dfa42fe367659656a26058cd984b76a8e91ae4d0fa4c13db149008e2ae57713fb230a627761174fefd263b9c10e9a4b6a3746cde59c5943040c17133 +ConfirmV = a8f7ab43f3a800171d3a3fb26d742e1ed236c2d5804ecd328f220a7d245cd2e3bfb6c0526983bff9229c94f70fe64ba9bb5a4d0dc10afcda64a4c96d4c3d81ad +Shared = d1c170e4e55efacb9db8abad286293ebd1dcf24f13973427b9632bbc323e42e447afca2aa7f74f2af3fb5f51684ec543db854b7002cde6799c330b032ba8820a diff --git a/src/tests/data/pake/spake2p_custom.vec b/src/tests/data/pake/spake2p_custom.vec new file mode 100644 index 00000000000..dbeb04f0221 --- /dev/null +++ b/src/tests/data/pake/spake2p_custom.vec @@ -0,0 +1,516 @@ +# SPAKE2+ custom system parameter KATs +# +# Test the behavior of SPAKE2p::SystemParameters::custom + +Group = secp256r1 +Hash = SHA-256 +Seed = +Context = +ProverId = +VerifierId = +M = 04147be092af72e8855c64d8b60fa967e6033877627097b072e3978a72e463748e5d1e8c8f2806ffec275b2e7d727ba6b5a1b694a514f07fa8c6e491eaf9b7e44a +N = 04a5aefe3607082f685f02cf8d49b2380cce0245fa2ff50755bcbf84e24eb739d44c9b8831e8da1d209b89a6d47030b4296efaf0cfbf72829b1ce99cd73ccaed86 +W0 = 2e8056d89e92c18926bd65691fb5518e1b5f301b8d6b3e92ff0e612345adcba7 +W1 = c029c8efa8d6eae4d9474ac48aa89789655151fb200e85dd425a1ad2927619ac +L = 04fd4b379d8762ebcd763371810c4f144d481b92758ce197eb646472fcd72fb8e4786830a785344632f0ba183b28104db3b22f207e7d2f07488610135e95885bb3 +X = 5db0361994c0e72f2e72c299022472fc368b719ae4c8961a563f21ba52443702 +ShareP = 04377162b2961a3d68647cc034be7d0e3df17d2aac5ac427caea4e1a42b2f6b7c0fba14bb336f0e7a10a24aef4d3de265c383d6d70f0cf66ef2c0469ca4a8b5733 +Y = 8c9ca577eb13020db751d81ad7182bdd12ee8532be2520eb5687cf56c7bf4ec8 +ShareV = 046f5be0cc9048d31187bf72f4fba060ea64fdacdcaa07c1536493648d5299bad160ff4e5d7aab117c2a658e419cf7f01b6d91414c92d9fd34dc399c8198f7389d +ConfirmP = 8d5212ccdc6ad8d598536d2710bafd3cffdfbf05155a0c2cb425d129312bb732 +ConfirmV = ff20e43411e0fd641f861d3ca8e558600296a7a11664930d799b21bb24e3e6c2 +Shared = 3242917a559251452a23c4279e768dd2c6c5e8f4537efdec9809d7ef87f38ccc + +Group = secp256r1 +Hash = SHA-256 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04a1481d6e28e75b954ef3e900439ae4921f65f8465f777d8811226400ddb1bc436c2808c05c7fa72d71973dde21d9e2c5230ec920d54dd58cc6341233a69746d1 +N = 04b92f920cb210f4a60ca01eed44282757d4be4e51438c58b1c1005bed7fbbfe968a2b2a0c108e4557d09af90655ae141940625b7d37564213315aef38ef673760 +W0 = 5ca4dda9f9291f9441310f2818934d4d35a606aa6ab9aed3c072dd2a1f440a6d +W1 = 788f4ae78567fb52885543740c382083f17b41d5c841a8a44c866a2a0c74d578 +L = 04e1ceeab2ef8ed8df553482c929146dc2a3892418b827a35bb16d0ed102cc259120cda135fc59271f4892552cf215145e696709b1e472066a74849f5190341877 +X = 7adff905be051903a0b22ba6d00aebf389d6afa25541b8903d27d6fa331f8097 +ShareP = 04b7fc5ac83f1ed36a9466f9f6141d40d8e69a3da1ba2cbec23357d29be5ba67adf181e1ba974475f7a074d1dd18f76001278d9a15b943ba559081f394b7a71d37 +Y = 531ed2d71730518b0c5c25693147e52549b450e456bc3c5ddbc610f38aa3fc56 +ShareV = 049f23aad99435b6948031a094d3aeba2d315b66dca865e442f60ec9cb14e9860b802112215a74543c4e848b5c66a1a76453197da685cb2d0f1e8339ef6a131157 +ConfirmP = d3a055678a3d0ef669f05c07cf9b45445ddd07ab138c4a0835df27e8a82d3df1 +ConfirmV = 65f8d89a54044105f2a9a3b6b825261f462ade34b17ddf37af3c17d5b07b8cb2 +Shared = 6adbaa937c59e549331c3c498675ce85cf387c825df8e1128551151abdebc93e + +Group = secp256r1 +Hash = SHA-256 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 047e2dc730fadedc855ab5453298c7c1df7a7fec34ede9ed9895301355566f05a5c309f26ff3820ed9845c6291429d2ef54a1ce8600fb2165babe2ed727fcce468 +N = 04f0566b21feb7c102d665dae7f18a17018b63fc6912e0e7b3da9a0fba37669290fb30d73d297cd4dfbc71bfebf35dfbf7e4c842882d9267d1300fc3216e0ce871 +W0 = c507944c4549e0aa744b9d07aa79a1ef581cabe8053f05bb2f4fd833c73b389e +W1 = 015c4db3eb89b900d404d24db02edf0f1a6e8d6d2bf0c0beda722f3df3b065ee +L = 04cad3990975b5f96d2b46e85cfcec2d2881a97b85f0a6af4807df56df31b97e828d57cce05597df459a623d958d51ab8a9617495fc18922a6f6b89a6fa10de12c +X = bcb58efa8c98b6517b0faf2c7fce26e9ea9c6d57df4ace100f4dd42474b85496 +ShareP = 049b71bb8d4afe862822a7c688ae399af99780b243671b749de22519fe94e7de149e139ccfd278506fc003fd393d46092cf91de9cdd5415e8747f1acc5f946ad03 +Y = c89fda2be099a39d4e415a3498dbca4cd29cc69f0719d9e41db4b3aad740ffa8 +ShareV = 04e7ef6975e6cb3b156249e3435eded4854854b360d2dcb9614ff3ea91ede0d705eb5b95620c6e07e27514366f9ac0d5120654495b3224b72121db8fbd7ba0504a +ConfirmP = 8c1e1db0a312f375a85f32352a3ab9e0c2b42fcc87ae25d436154855f07bb171 +ConfirmV = 938a80daa859fe7bb578c3307c76977885d53606b4f0f2ce16a447fe05bbc5a4 +Shared = a29ffd83be97b0996e7805bc75499cfdb11199d9d1b72cbdec9917eb32d504bd + +Group = secp256r1 +Hash = SHA-512 +Seed = +Context = +ProverId = +VerifierId = +M = 048ca55571b95e8011825baafbdef0e14af8ea84ea45476e6c11e9c54aaa8af999a98dd39e3c76abf44480a5d616d84a2f8e1eafb1c7f0e22ae9f735ccf6727d66 +N = 04bb27096b60a660e50b803d457bd02d86805cee0f998cc821f5ec210cfeab3954206e65e3c237a56b21cdbb30ca87e7888b99955e20f99ced724e4c3ec0e92549 +W0 = c04c0376bee0006bacb2e015f3af66754b5939902e319e5bc743b8907b287534 +W1 = 704ea60149acc1b9cbdb499358a7184558d17c4b31350ccbe76daf28151406c9 +L = 04e0d023627fff22f2e3379428fc540817ad85f2dbfadfda9783623074986cc3957d6c00b2037eada9b52f41d75eac08d51594df9e28ae83aef01cb7d51dd19215 +X = 7cc9ab66bb2ad291da0f44ad9e78e138f3d287bb5740b87aa085848c3c276289 +ShareP = 0411cf396fe76a436b40b7aa1fd66552bd0f12deee0e0b959438b078496b2c0098fd8e2b3256cebe7096c39bf87872ea18cad12fd37ae6f8c330780f8af6d062e2 +Y = 11f23f01c2cf3a298e4b293556aa0b184e8def5d0d60fc1e4be1322cde2dd964 +ShareV = 040b046b778b9e4c5312aab6444383107619ecc0575116d12919596c947e2c1a1a5604b22d656b29a42dc204a24d799ebe312373f1c4cba7741ddc6c89a54eb9e0 +ConfirmP = 14f494091ea726442ca95d799a771914d4ac51530034c4a10cdbeb8332717e7fb77aa7d89c7a0b2ef3fe94fe2d4bf6b739427ad028c04ba7471a228f7e4cd257 +ConfirmV = d9fbf28a048389555e4c6f0ac7fbb7fd35a9930641953d606ba04bf8b7344c4b6e98ea2b0f9818f2af80ed07e13468a7020b1dcaf764a57eb5173a04590bdf41 +Shared = d8b8edc02b7881539ef6dbf3ab18e9df6a67603c19fb7e731ac66ceea61d69480d77fb586d04b4bfc0d59b8bfaa4f45da458c01def4ad37ab69b9d2d3eb75a6d + +Group = secp256r1 +Hash = SHA-512 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04cf8111d43d59b8c4219c035dd53a11d737f52c97db6066d8eff7e348d0f4c7a05bf175bbf210cbd71c928dffe38c676a891ed1509990d228d71303efc1c1e393 +N = 045d28c401d78d58e65cf483ed6647bb8ae750648ec461f57d059a8e43bf496e037c255ef7e3f9cd6f68d641d13947c504544531f4346a7bc2d960fb3b6530f132 +W0 = 715eb8e57eaff989c90660722b4bde24ef0967545a104a1f844dfe236c7a66f7 +W1 = 802f74a7841c190a4245ae037ff142e570e743078e29e81edc39ba65e9891445 +L = 044bf028b6dd0e8c6e2ac6b553ede259b1a52883acca002e0eb9f75e9bdd2a29d28f42f94e47920d9cf31953ee6f052fe086536957b94c5aae641835152814d094 +X = a3667bac9bb94867276cbf477692cc418610b16d5f8c708f7434672db888bf17 +ShareP = 04ad07a6c73f75fbe0bfd198a5907f1cd33c8734e8c7a7953f654e5a9c599aebc46c6914e102769a7072c987a14a9c4658b45c9c778bd3090414d3661a52cd36b2 +Y = 61298f97ec0f0a25e9e0086f7c0bdf0e11e587f5a47e3371ff0d9106a836c242 +ShareV = 04043d345b90b24c2073157267896934b4eb10e6348cd2a682f8ac96467bc6c37eff72d926543ad29395dbb0494ca10f209dbb357f9886c9b92d7190a42fd7a8b4 +ConfirmP = b3596b9eb1f20a77abe511faecf532c3937ba7a154f4ae05e78172501463094f85693999d20f19da09412bad20249c9baf2116a2ac832d0d2b64f89af05a2c00 +ConfirmV = 4fd0d84a807aa3c9957ab06df5da34328e091c88e4776c9f5bb779b15a19f5380457c4fd09fc24d051e4c0b353a9ffca68e45c3b1ae02d9fca4d2360246f3abc +Shared = 1bc15166691c0e3d3ae08402fedc31d4c53c20e6ae15150bdf6b6fab73e8f08306ab5a7aed170ec69880b907d21a2c4699ab4497eb5ac51cb22e39f13bb27928 + +Group = secp256r1 +Hash = SHA-512 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0457d3e2e85eb242b0caff417c9cb758018dedd8bbcd54cd1cb08cbf38b742b3ce345d1d5fe1f579f45bcea403ba68d37e697a8aa859d5fc18806c947841bd952e +N = 046d4b23da92ab69627f7735905b1223c6cb2ae2cba9009d614c50589f6a411274f3cded5c2be79176b255e5d0b62f99fc7916aa77ff54cd6732d2e29f904191be +W0 = d4c53656f5c48748843127ce7df7176d6f80dd5f13563ee1aac7278689b0d753 +W1 = d56083525b5331d7090c30ef158a991c975465a9310e01d9cc7a9c0423e4707a +L = 044b988ed8bedc4c5ec87dbc1db5dbb84b099de324c571f923e7c0a18bbe7157b3a726c91ba9fd8c21842ba4c701480d641250c6c10c5118a8fe9340db58db28b2 +X = 0c32862a80c4966a4c04ad8d48ba32dd4020032d1c2113263fbadfca84248222 +ShareP = 042610d77822a4e0bd3bb38fb6ff8ca531c50a3b9ee76854fa17282ed6d26c90cbc0633ed21833ef59a5213baf63397b715d0c4ed81c55fc016df85d992324999b +Y = 0f978ee1dda73a3ad942a858091f76f2224dfd02395b5bb32f418060608c2fe1 +ShareV = 042e0959526ec056e0eea411a2692cddd501df6aa2e2fc571b05f0abed5804d0d1b17d76ffe8d8465ba4cce1d24de6894c6a2abbea69cde8fb93a468674f27d756 +ConfirmP = 05b1d74f4d1b7afb1c1f8769830cbaab27b2c76a019d42a1b42188f1bb662dd1eb306805ee54c27251eefceb2f25eabe95c9524f2f958679dbc8429a3b0eaaea +ConfirmV = 0f3fa0be76941c616ae0a7757680f777e2050e79fb390a1a32cb2be17253d8179b5418ed4f9cf7cf6d8d315ef0998369450b886b4c50dcf1824d7fb41e0c8a26 +Shared = 01fa14685b3e9e836f3ba9552f41766e702d68f9a5fc6b6e802551953743a05619a70d791eceeebe0d4c9f28b4a59fa5107b9d22f55377a27f24e6d914256a0b + +Group = secp384r1 +Hash = SHA-384 +Seed = +Context = +ProverId = +VerifierId = +M = 04b4ac2c38824ddb8c70968aa91f2189ac7d6a80762a98f5c2cee1db3a6e01b80e3b47f7837fac61f746b6b479eabec3664fe2c4dc14949b82895f3843d1386e53d144adb6d3d6a91dab915cb30ad54b631f1016768b172913d9626ad77b461565 +N = 04cb08709c519945375caa47195de65d4c9020b49a48f97d48ae08e94e55f40910dca980992048658015c1ff9df7c21a1d0db7763eb57cb56987daacaf33403772e2bcef96bedb86e2d3081f5a1d332fb05584c24427f96ad7f202da0ebb219697 +W0 = 9f0fb61cff48ed42555fe14994d849169567b1572bcf39a52d35e51a15abfbcfbbb32cded1b09158606e7a7ab15d4737 +W1 = 69a3f3a824968021383e4f37a0c0a993e1da384fbb35f5c3f13a7672b2a3bc50059626a961348cc35f1754c4c1312e14 +L = 04f3aaa5127f57b017a5b03d0584f95bb520dcc378568d716c7f2e2a103faf6610a4133c3f385ec2927606c0ae28451ec0f87d05b0e051b1c16d0b68d2c0ccad6aa030946fd341fd67d60e9c4b5eb129ac5f0f9f3ddb000d77f4844c18b020e613 +X = cc6a28c8ab7f32ce8b65b3f088156c32e1339568f91772e7a9849dac5f00662f584c71ca34539cdcdbc0b3d1c3f8f095 +ShareP = 04f936b4d22edb145db8d0a4cef665ecf95de3f2e2007bae42327abf67cef973f5e6206f7c15193c734db65fb523ad289375625d3fa79de08818559e3e3bfc38641eefef495b11ef19420026c192c74ad70ced5378b7b59251b4f76dd98f77534b +Y = 9ee66ad4ffd88736bd239e5360c2da803f63b0a05f22354c08ef6d3326134859a000bd4f1908beef7190ee219cdb3be1 +ShareV = 043ea0abbbeeb4c82b846ec9b38e42be8fdd9db10836da24f965dbfc05eec64ab8d3c99e1f51674a92b7b532759416a75c1a56e4126ae7b85246787125c64b7c138e81dd015671d6a6f123128ce712c72ba250f9c1bd73fb57525d99885f633db1 +ConfirmP = cdf093eb25befbd1d9414ee3d772feb5e492e84b14defaa33c41e1a41ab44dfbe70a08973d14b1be84e4ebf137aa5e29 +ConfirmV = 94e869fa42e5e1c805b008117dc9b10ec8b90ed39477e1f65a65df4bd7e1fdb401f7e0a1d81a392f5d6e258265ed1f71 +Shared = baea04cdc6f751771cd9c1399a6fdf98169f96f4e94577311498196b951ef207ea2c4cd047343273925a1dbba85b02fe + +Group = secp384r1 +Hash = SHA-384 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04153e82eccf929d7fc856111a7c5993189caf36b45d30ddde08f6f63f69ce3e6954dc229e860154be6f0fdacd1cd6cb060c02666e87a5439b78df3ce8ef636b4b0cb9098e3a6ac8e515a7ff0f9a9e526d1ef5aa11447245fd6baed4e3894c455b +N = 04cc0609f0eb050c391e45e05cb3afe7fdaab87ff7de9e1a2190997a4da251cda9f1514d6849aab634c8f79271788956b8a9eaaf5b19898196265b3444088c03b5f66a0e373808ff4c7956cf06f90d97dcba311ccf8b316c057d5e7e4b878aed44 +W0 = f9bda203ad3233b578ffcda7bdb974797965d1584dd0e0f3f5420ca25de7eab997e9fa2c621a574fd7eb5b75b1732959 +W1 = 8f9b8d535d8d803f2e2672b09f41c546a83d25d6f28c1d64d8b7057638b0711c2b15ae0d0451d7b8822c7c25b4f3e3e9 +L = 04e51fa2a8cedfc53d5bb0737cbc88b6e605c5cc607e901064c778e5390dafc8abb9e9776af773c305279b5e18bda5eae9614584601ae9e1d7e9ebc9a5fb8f3d014b19a73b221804a3e908161101680cf00c1e10233e3ebe26894ee59f3b2ab8ae +X = e2d7f4d3bd1cb9e54b2800448812244ae3fbd8d3607350313bb998578c34abe847a040381e25525c20d26b64c1b8ecb8 +ShareP = 04222fc9c9bb44804053386d7b48439d693f0d27c3f5e22be6c5daf8818dc5985fe95662c3a18641efd61af56a9fd33188bf4013f0e2cf94f366b874698fe1572d50b5d856c7a34bcee1d8d934cdf040511c074214fafae87ed531b46ce6d13616 +Y = 5385e035035de874e63e3f10364d30ff0454990073cb37e229719f68455fe253f7cd008395e5adece9a76be2f13ea941 +ShareV = 04ae5528b6d12899b013ecec65c82598956acd7295559cceac09ba8322f4e31c579b0094911152817963e7f05d5ec6c58dfa8b9b2cc4674debd6dcd75c7fb80398e125caff63581d930a0f9d235296097f864b60e9f178449a38cdc6a5a332fc3e +ConfirmP = ec61e51f04725ca46c67257131d356c217c4711ca804ba06b22bd76ff887149c810202132b04c47dbd569be6898e42a0 +ConfirmV = b2737e97f27d2f8b24e7076fa8713755b28415bbbfe99ce343c480a5959b8b3a7a56a081f7656e4e8a78bb507328edbb +Shared = b18f0a64e30cc84de5c16f3e3bd0480afafaa089e61648eafe12c3ec00dae4fd8c7e753461988e3ea0b2ba59a85321b3 + +Group = secp384r1 +Hash = SHA-384 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 040578cd3a592a04a0eb6bc4e89ebbc167a0a49a2e8d58c727d31cfe9baca7bca03f9762228387557c72bbbd5c5812e7f6a069702185e45ad2eac3da7838610058bca9b381b53cb190e286925d03bb32afdb3ed49f6774f48f611830e22e647a5b +N = 04dfef083b23559bdf1748501530fb0533692600d72f2dd55080d636b8a2fd9aedb37b5accba0895c855ab5ce0ef94b2f8ec0e60ae41e5e7c4b2c0b30938608e2594be600eae2b612ea68e24f6018a978ed051b1b8ec503395a99dd0582ec3a643 +W0 = 721c623a1c3b9a9a809cce1b8a8f00537067209b2f6ac334d351d0645a3c282ba51975b5a0db0cddc83ccdb5a5a04d72 +W1 = 40fed4cfeccc41104658386286252a212ac12338913f643db308848f5605b05a1789084bf2dc38d5498e72e4aabb12db +L = 044ae09a992c29a3dd0ddbc4317df3b91ae1649c67480546d67b5bb081923b92c29845bd6c26b14656e0df849fb6e002e33f5b31ff00c2e75c98689496c550ca2f88318cbbd3b5c1dffc23fd664d97a2080978c9cf13ba53e39d83c13a397f50a4 +X = 17fb1c88ceca7a11c5036900d78761875a6f0b6dd23a3c2c0615c24884d711f5e81a6141bf2b49d2d28889cf805a7504 +ShareP = 041e4b1249e44e1e230007656a1cb4cbf849452128764b143e348bde6ebbe9c9d585fae65d9d3bb4801f8143805137df3953bfbb213bb67dc9eb882dc47ed66fe5f6d91e7434d4b3dac94967790b13fc7206804e0c30250b0cc91e356b7f30f348 +Y = 3ef061edeff0f4024d5f78e462e1ad76706f1fa3161f484039ce1e68de9196e49c0fdb627be8afe0f1b0791d2c4e1d8b +ShareV = 042ac2234365ead2bdbc74c3da86ddd272c812a3db92d1e5e9cbb9903c0304cd9c939adaaad42b68aa6fc3cef51bf4b7e28f56a368d2aea2fd64db784011c5dc97b4fab229450bdcaa90f45b0d8c4f1898808432303e826c52de54298bcdd02579 +ConfirmP = 56691ac2c7a528e11372c5f82f2a9b301d239241b403f513475472d0be846436cd856c7e551448c055f81878bf28542d +ConfirmV = 3928b0a31340d383f45980669e896b208f6efcea1b45ac3dccee33b64df62867f22e31ee85848d83a5262462bbd80a87 +Shared = e76a3b8a15e7ed7a2422c448b3b6faac3104d18de7db6b06241cce65b116463bcebdaca719db9c5dc6a1bd03c580077e + +Group = secp384r1 +Hash = SHA-512 +Seed = +Context = +ProverId = +VerifierId = +M = 040f9c209439afa2744f5e80469bc2705f4766d98ed3116f5ed5b9fcb616ce951c18f96d9d54bcc503cf8715877c34ae3279abd6b2e01abe287e451d5ed6bf63ea8913b04efe44156215691b8d0c08b5740bb85f09a9b66812962f1ce4148b6811 +N = 047d6e2aac7a9c8355ede5508fde880936a685aab5fd32d33658aef2f4ec31df4ff6251e4ad285c24afc6fe7ce1c18f8b953be40dba1000c75243700debb692f1391cf5f032a8df999bee0854beb7455875aff00f467083f30660c4fc883f75222 +W0 = a903a8e6c70876b63e42c9ff5a991136d9ee9e6c119c7d9090302d5d650eed1da0a7db8a4809a2214760be13e02abccb +W1 = f7b6bbcd5dfe250beef2e0484919ef89608458ed8835fcba05a9cafa8cee0e6c2d9c81bfb7aca03122b0283943664e75 +L = 0456235ce50cf2b555dd6e2ca33e5b62480bee1031f3bb8c82cf534886ef18d71221a7203bb4dd2ffb378fc4502f9def7664a9b5afe5a777774ee4c6adc78e40f3a616330748c72fe960b1e516464cac9ff350ceb7d2a51963072b5d627981d180 +X = 8b62e5d53dd5567f6ca5f2b8941d544ba00cd2f07f3b2aa305f8c99ccb3876a358f335315b288545f92c7f04038c8614 +ShareP = 04e25c8147d1ff0f01ff86f75a4457f00ff44d39af16b37f6561b478890e75a1f552408b4300ad28326e2afea7dda456a31def3b43213304dc6d73ce43306278d319b7286deaf0a89a47dee864ff34b89c8441f025dca1965c564f376dad9868ab +Y = 20c85fd9dd291350bf132de26ca52c519c9bd6e93f1f8a73774e852f21e131a8eb30fd5ca4fd3df683a8f8b4650cfdd5 +ShareV = 04a887535ee3987f6794a44f923efe7276d6066671a8fd41c20d046ebe83f406cedc374e682f041828e649230093cdc4b19b6f4271cfa24fad1d333206c668602f1f7bcb7e8b20bc95edf0acfa3c804292ae26e50bfb2bc871c4b0bdd8acd1e162 +ConfirmP = a87b648c7129835514ef0a731950204a0ccd83ba58d5a96f74fa6fdcab1e635d6b8e1a34151a642aa3783a71647cd04663043add6beb7c778b695e83376c0b30 +ConfirmV = 1167e1995d965498f37d4ec68d7f90c554702ee3472efafc0f3d35d974e9c6b11676e6fcd310107cda9bdc0a739929ffccbfe015464b58c0e8e1572e317fb53c +Shared = 8e13270d184454274d6536ebed0f1d3d00b0b34f3bef71a60bcb0b496ed8d34984d888c7dec0535338f15b38794917be17009a72544f58d78acfa52f8bc13714 + +Group = secp384r1 +Hash = SHA-512 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 044d76508344d25fa6e170aa97a7b6ef326b5c3121f71b53eab55b3d8e5e3be4de69e973fca0c7532a890ab39ee7565cdde5b48315d8100b242891fcbbb0b3791ca076766b52cc92c9cfee3fe78363635551ce977b6c167b172364d676bbc22999 +N = 048a5f0631ba1bd969de6ae3fa6a23b08dae76f058a48748dfb00c17693d6c34eb572f5d9ea66977cabce241ce1e0c929abab70d85814619ee655d3177ff31e664dfe00b280b33e70a261a12a9068a174505ad86ed721587342a3d98e626a768cd +W0 = 29f78045304c1979877979fea9c774721c16895224fb8c58a7157a5d3d1753265625a8f23d2891d25cc1beb2decea0d7 +W1 = 3a4d261d908b3fcf344ea5d192c692808262666af9673a6bb656c9211b5c7408f8285f33c7f79e6ad88d11ef64787cc6 +L = 04a5748f040b4ad62cd4865f025f8d42bfde19d4475b8f313bb9219af252fabbefc02457fb3e4e604b77fe366d264d107f563fec2834058fafcfe351cfc658970fc3c223d7845fda96c6522dc71400f8f247bb3e57b375136c3d17946f7d783013 +X = f5ec2d3e5eead93df40872d53a6dda61a3760132924bf1d1b2e690de34dff529856332632d04e5390d60830630f52627 +ShareP = 04f638a4fba0d923e34bbb0f3540ba2cabd926e7af9a1ef6e60766a307f22e3406fec7452eef9703d197140959b13d198700095b17fad8129d77f0ae180bd8294acf13c6759f4ab31a8df7b04dfadad1e02b6cbd71f2c2b3640c8d501183d89836 +Y = e3f3a44ddc1cbd01edbecd816fea7ccc9a91a424b355ffa3b9a5c2252e04173aae769a590ee585039802c0c721a5dac6 +ShareV = 0418931acdae2f605fc6366b7e190d90933b08675e552bf663600fc210df3a9221fef525b7838fc191c122b05ff20fb2a460ea1d5f33ceca5a3dd87f773aa16c019cbb49653468ea6b5f5a66c1b4447b5d547f9f3d1b0d1787a60cf494a7116edb +ConfirmP = d4a40083c7617561ced91efaf7cd95ee5e3f8025ff6bd20a759d2e48e6e7a1c9f0030f0636eddbd80780787580e02e0f058a1c9d0493505d112e8ca27be96d48 +ConfirmV = 7ead1a4296de95869583fd17fc2e3a3cab54d11b8880db2498fe786ba53e2ab95362a6f660321aeb896c3fb3f108363bc9c4319fb9f103dde372c6d3338800bb +Shared = 50bfb13ebc436edbcb269b1e8a419bca5d2ad86b12520af464fd4952358ad6bee28a6d2c83042d9de64994d5050a84ed84b45752160a833fda763a1bdd9c70ab + +Group = secp384r1 +Hash = SHA-512 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0409e7ade41437cf2f19d64295a5acb038b25ae1ba680b9b817efc1b2185242d00de797964abfbe3b35940576e0bab75ea4dd62371e3087a0a2a564a86721e297a176b6b3388ba8611e13838a2299f53a53430169d99fa3e874a11836711bb2b6c +N = 044ca712c1b980a645b04cc1a8e2df6ffa73fb7800aae02d02458483ebbb44aa2690bdca52b1240b7e627a87a4e99b1d21a87010a87b4efe1f638358aa6713d2b6db94eaa07733a2a94d6313bb20e1d113eb786f42c82c5ae59a3c5bfc18660863 +W0 = d4a29bc0694fe05149ace88c2bdef794331e69cdefa5a1eaf4df55d4022ea51ecff1d41b2060d16b569ed96a3c7fdec6 +W1 = 63d5239e74ca479161437329ef9ca712ba14abb1f5887eef678d35c95d0d003db60b7ee7b09dd79ad37d9fbbd320d042 +L = 046749e910ecb1645b1a47583872c2b42540d3e36c50d370f76a3c5e59befcdc8972099027eeb4a694e19ab1ba5d10f910cc2b58063bd53962dca98d0ea345baa09103765b2b64b5a185421613f99ffaca824126b6ade05007d63b216a2e3e5251 +X = 908149bdc44926224dbbe23741444eb0ff72f9927144a61c034614886d877c498e36e946a365c211b063850f70a55c22 +ShareP = 045e5b324cba97a6197d822f65e121d1dc2be0cfa835b164d2fce296fc96cd5822f0c90506a589da8b3c6697ba4cef329778315d28c703472c481b4ca4233660a77005e4673dce23c8585e54984b3e12e07e8844b09ebe69205fcfbee90ab066d4 +Y = f2b81a2d604c730e7298d38c29612a20a366fcfbdffaf3d01d227aa5e653701304c6287ca373e56ac5ee293aefba4971 +ShareV = 04fcdac2f569f40361695e90882344cc5e6b60c0f235f4b2043183609fd83827f1aa7fe3c233d08ac3a6eb0f7afb105507d9cc0979aa4d7ed5c21ca862451c83818b19ab5a1879e770c298356f5205ba7aaabaf9b09d535aee52e07650eaf0c022 +ConfirmP = 24a8ad4d3b4f77532222b1ae55667957f6eb938cc1684a64885168a199fcdbf7394814fd3fc593f36147d2a480edaeab095a897a8c7fe07654c0d5ebb48464c4 +ConfirmV = 1cf64b7c55767b55b254cbbca16c3de04ec27b1177bb16fcb964296eeb3e422e40316800ad4524bcd4bc1253c9acc297bacd13524ef4180062a592a459493a50 +Shared = b0a6e5a6c1038717faf2595c53df31492e5a59e1be7c6ebddc03126c62d3d6e7ab6b5218703adc5c52c7d5812689cdf9a205003c94f217f8888101c6f3f70129 + +Group = secp521r1 +Hash = SHA-512 +Seed = +Context = +ProverId = +VerifierId = +M = 04004c9d4a696706eb53b7c177757d4b7ae0868decca6ebc6b6697a9333b6df7b7e4f54a74cba23246e777a1d11fe7ad1b36b120945cffbf03a6b8a97a427f1461d948008d8a9ab72f6a6a8c264907ffee04d73a6c515fecbe2877d61a3331b630eaf9a535c27850fe973463db87a2c0357a12d64fed58f3bcf3b4bafcaf8e6d380cd5f478 +N = 0401d914ba2627e978d3868a8da87e527c2608991844ccc91c40226ee7d9d5b0892c2bc92ff3cd0ef05b9be4fb8d8aa325a0754dde4b314e160714a1cfc8764dcb51f7011ad1615062c2c56ee128676fa1822e73846d00225e7a44d3571a23aa8c519dcb8b0ee40a719676655babd45af1aa0bdd2b2cd071bf4fb4be6352411ef6390a836a +W0 = 00642e1cb5de3fa753ae7de6eb5d765ed444505e943d041310c268d143e6d2620c8c5028cdde16c1eb6b9c736a5af72d62397a80efe53bc2cf34d1c2ac151845d649 +W1 = 016fc3f55cf20c17754d6d98bab948a51bddab2214c0d14d5325d8372d7d36612d59a542c050c58810f992b4a8647ec4a57ba57b02d22dc065aab49ae32a6137c0bc +L = 040055647e2fd6cad5c6d82fb56b640050e7c51ea45bfb9ebdfc2be7129431d9e40b03e6da51db712753ae8010b1283f4813ca32963d2a2c6ee985f0c9d821f96ac99c01b1e4e1cbffe7991795e64f8836b8596d843cde37b6bbba102b7609f7a9fcf856c6f05ff13a337ffbd3441171cb0d79f77de689ba3644a53639431222c72dd36adc +X = 01e4a9ee77daa676ac1ab5d4f9f750ba1268857985ad1507c55607d3bc9cd67264801555851732fb73e799a5a35b063c4c139b4f3d363a18d3d1f57f5b9db7364e63 +ShareP = 0401e6ded624df794e2c328dd15c12074b44cad6b768ae20742366a6469121e8595051f50523ac3ef60190f70703b6fb3dba7061f57de7b079c1db7476cee4f804cd5c00e0587b5380bb9ac1d42e1c11b050fe933efc06d58731695c67f006777082f415cc8ae400fa4dab1eceea55e69c4aee669ded9bd45ae23fdc95ef45ae5f1d6c3d55 +Y = 0045897c7f2e3a55a159cdd6ecdbafef1eab7e5654cde615cd1dc8e602cf7ab2b75b000bd50c6c73e5f2c91d4eb0dd6175530bad30edae5cbbda12b791fe6f4d71f4 +ShareV = 0400ca94b41aa90481bde86c9a241f0b1eaa552b97d645f7d45228cef031ce1c7eefaace79658675d239b9fd1df83b75a5e5cc8ebfd0ce73b803635572bb0954a212bc000cdd202a73192d5692e2b7a636593df472105bbb6afcfe395977e7c42882cbb0429f7c223bc35b790ab46ed94778b413f9cd57e3b84ddebeed899520243820d23b +ConfirmP = 64e2175c0ca36cbd8508eb54a8eaf54a84d9653aaed2eb1ca8884d565cdbe70869a2137b2f67d7ceed9964e9fd3c83f068192d3833cddd313e6c8cd3c46f8f90 +ConfirmV = a753ffe9b81ab964f071f33657248fd3b3fb919fc95a2137119b1a8797275fa31305fbf1334a7667f704231cc7c8d4ac455213ffb3ce613f51cb0b2a8276dbe6 +Shared = 6590a060ba0d30fed9e573098094270ebee0f90ef742f386cca58afbfe6cb7ad4991d23fafac155551ad6f2a57fadb2c55362ac8a164465a0fb2577ca88cc910 + +Group = secp521r1 +Hash = SHA-512 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04018a90d67da6db6aa3d9470633e151b3245f986aa6f44c557b6665ed4af9fcfc706ecec4f41674a00bc1fa8cdcb27fc7cfbd63b3d374fe9fce4e3aa69d8c5df4e464002fe393c6b26f0f297686653448e6d51cd56d8f91c1c66752e806a800b29b212673f790fe8d57f22e72647d5bac306019a4197e64a593a11bd810937a3321a935b2 +N = 04005d188958b3273562d7f725187808db4e703cb28248ca223b4b21f5363446bb211196d7f7e0fab9165c88ee48d9df5d60a3a7aaa6da3b9e668438f715ee1188b2c800b6cdba515878fa55f9b63bbb1425f78607eb0af143b55ab30231316111af419452f7a2913f1a4e07ed7c66f23defd6663a830e52385e06839ef248868195c4d5f2 +W0 = 016d7b8e7fc18f8b99c79f7f9cf6c2a1a65d9bfbb03cb4cf4dbbcea08d32e9e7b804d94d3829243d2d009ee14e995e15c07cc53566db53a58bd937b36c1b723d1eab +W1 = 01f7fee64460119a905f1304792a306e58b48ac53f38e0934a598b2005de2fbc94e49df0f925d4b907c0a802498841751f7f5f1c127f6bf680d9c437d8d0348cc6e8 +L = 0400b4107df6e79b12169451043fb4bd72ae7cae6142dbce61fb89416dd1c1eaf259ee183d9aeb7900ae5b550e8eba52476eaada5f795e6fdd02d4b1aca0525771929701a994ae3fc361888c1fa66edb941e27ab5e70fad07b37e022f01c71fd66d50168575effc08fada30d73536d2b16b97664cf893732eaa657209ae64e3f9ef03ed56e +X = 00d0f210c266919fb9098e367111d169ff47da7d369341056bcf55c2da014767bdd6b511afbcec20968d429cd50cc38cf1662886a6dbe75eaa0960a4d3f6fd9ae545 +ShareP = 040075596a2430157d2043fa974692aa600deade91719efad172e61477f29f1f05fd7316fe6a80f87a9ac1f7bedfef1a4756836f71a0c3a070a4815305a78b9ef89bfe0116ceafb2522d8624f9b6cdb3d5d3c24662d559ec156fc80ffc0cb7bdfac122479227dabdd3de6b0c02647fea266ef4ac9bd663713f4aecbdf16ed13d9c6431bbe8 +Y = 016419c39b859a06c01d0c9a240b5e7e956d4c815bfb1814b85bc07987bee0092e82f3d75624db22e46d1d9a5304f9cf548a756dacaa2f58abe86c82e1f15e1ede6b +ShareV = 040040f2230ff5016ba665b8760716b426fee0fa22cff7fbed54148e2e28a01f926d917f4b9b28379dce87e868488b64ab632776ee4adc961017cedf66d48cddc71f9a017202ea953ec51609cdeb9784b0ae0932dade615de70045fde0fbe130679b3f84defd790cbfb6997c00ead7d92db85edf28de63d36a4db63a1703fa295f4397a6a8 +ConfirmP = 51a769359646b6179f58d3fe1bd65d4e62fe9a9658ef2cb4e603f13da889cb0cab709c9256bba7d2b3eda782cdb5fac45b0b3351fe5b4b001ba243e4d0ea08e4 +ConfirmV = 6a80fcb9c32dcab9cf48089a3b47f0c0defc203e8c4e0949037f1f3c99c286b4d3752f03c0902b6863dcf30d922b4fb1dc23a715826da382b545bf78ea4d9152 +Shared = 0ca09f2e36ce98a3105a6ed0e337490eadb1c060ca3967c46228ba6249444aaa8f326574d13306fb2934a2fe4e73e7402ea8f78fadba9fc71df0fd350da5f272 + +Group = secp521r1 +Hash = SHA-512 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0401ec5de164f4cb5cbad06f0a5b4a4147366c49f043f706c985fd266d469b5a6029f7bb85e2a4bec78e69353608824bf15565493aecc2e2f9aed74388f44f0854fa080063eedfe1fc88e9e58e4ab61b1617b642cf5236c7b746b1e07da461dcb27708fff098cae4049abe9e79925abefe6780ac00f822fb0b9e9cb78f6b3f0ec4d1e5b83a +N = 0400a5b9e1a5ff1ed5f4e6a5bc766f90f39f47e20dd6530f0e7614f5032b3cfb27e8ee8a500cbf61fc1f3dc2331e0b0fef5d3fe6eb6c3057dd81f949b18f224375f3bb00b3bff5e8e0571a16836a3d7c3f6b359618b3a5a6edc309577dfc50ef889ea45d7f1a3b356924a0e7e9b667707110e756c569c71cba209379ace55f38f49dea5b51 +W0 = 009648c04fe79a7b460e90e7b27c3f80c01d2b850eeadb7ce49bea54b22b969dfa56f0ebd94e07ccdd0fb9999861b8b27a5888929b528e225b9f544bbfb184e3b6ea +W1 = 0099571b91f58e403259d55bf12a12d56e13d1b06c57cab981ddc2c3a46ffb226deebe63246e457f97e5a29a6e7db7218b66c4e651223092d9553decc1816bba804f +L = 040184ef7358e4e2d338be8da23aca28afcb6b898adb3ea4a0ace503a675e2df59ff3037f09c5645ce72f3a5ebb9e6300e9b1ec8a396a7c1b055bc6123d047dbae8f98000a4a68e44423bade6e640337f577b08807b8a924138adb9b35375135453cad91a387887553171f572b93499c1cd0d717b7bbe3ac0545f989646d4baf6d2adfebd6 +X = 0147454e05d426eb22d62f89c794b09bb1bd3e1407aee76f4c3bdee2d27e389fcf711449cf90d79bc73745ee91031c09f0a54d8c8ce986208f55166ad001a480d84e +ShareP = 0400e7f1cd291d5a6696fe39a079bcb6529cefbfe00946e201bb3d807331f4a473cd5a17475f6e2b4d22a4a05d627aae6c35f37af33582eeacef965cce0fdbac622c2e01a5b1806fb0f406ed9e30229fe38de704be42826a9a9410d37a8c359306fc1bafc681102edaa5db64d60cfbb445fc01bce4a96757a1cd9ad7f286ad36cf938c8cd0 +Y = 00add8b729d36af660853243217ebd2f6e9ca059df54f2135da1921e69c31f9c9072197181dbce1d8fffa886b36ee4194b372bd36dda812b560acab6a6a1f58b860c +ShareV = 0400b494021619d4486ed0fdfdebf9b399cc0d01c2d8668271be3894b0a227fb06af873cf189a0b9198482ccf7fd32d2c6e14e8d0e8e5914cac1cd08db3a4ecc06e8af017f9454b8b9c667dcc9759f47fc5339e2c1d4757086701f0a1b2cbdb7047e96a096076285426e08b475c7986ab58e9c6926f5f0a8773cae7efee190458983c473ae +ConfirmP = 9245c8b860702bb402446f3bea7221a148fbdbf5a86ae31503db15c9caa0c751a6db2db93016052ff97c630e96ff2f3994fbcf282114855a0368e51e5333bc0b +ConfirmV = 7818a39a44e74491bfbbf617e13456a5fedf6a6f81358d37541612421cc6a3fbae26b2300485ba53c88211adacd2e85989e589ff91f2d18f4089aa24382c494c +Shared = d1a60cbe82a49a4877a546c32851c95b1453c38fd1bb3ab0d68999004fec6cdf46098b0c1ea7996b71801a4fb1b1f715ccf23ec7ac818deaf9adc4c40ba46f18 + +Group = brainpool256r1 +Hash = SHA-256 +Seed = +Context = +ProverId = +VerifierId = +M = 049c658d0c9aa920c43088f24df7ae43762ce4657212a0e80fa70100db42dc158d2f10ab8edefaf04d4d94fd73a5e7be43b856437ccebd3bec3344dac048fb5adb +N = 041b9ecd6b44192b7e7aee41bf6cec3d01afe6feb22b4ed00f21cfe61b78387c83662b3be8f205acda349d0ff446dcecfe0a9003783b07ab2b11388e2336ae0b3c +W0 = 1072815e453fdb7e67dc238906cfbd8902d473bef4697880c59a9d4be06a8de8 +W1 = 351dcfc9c99d1c5a600be1d2b95459f393b9ede17225366c4db7b5c4e73923eb +L = 040006b394bfa65e2d2c34c7a881272a536f6ab83e09444f884e89cc894d3a03612be6a2a8ae35d514ff13e0b6fcf8120a0a2b005a51a0050572de547c8abded28 +X = 13f1e84b6afa0897e0506aff4924af724b5f11362af5bccd5d1cf498ad1555e0 +ShareP = 0472354f8de50b69891640a282b6afd038e808c4536e2e00b8754f27bc71df5f2e795e3f6fe58a9ca9cbe2eacfd76929d1ca3e9dbceb45cd33867924ba5698c249 +Y = 5dcdddab7fd97af106877cce48909cad7e4e988a9df62eeb447a742b066cff69 +ShareV = 041822a02ccdf53512a502c471864c8302b079d5d0aa6631025ca620efc103bccd1e2a66c2b75cc8dea11133db7f0f0e568233442ec75ee4045c35d2a852d5ed70 +ConfirmP = 24a449a58a3782d14fc3cbd1db3be90a9fca0b28becde986d72463e327b3943b +ConfirmV = b85f12ffd794f8985dbb5566cd0a3061041abd2f5c4741b06bb1d0f80ed4e332 +Shared = ea1c0a90cc32d35de468df8e2f00983dcee5e8cc213d3bd2d709175d7ed13888 + +Group = brainpool256r1 +Hash = SHA-256 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0488890c905f23f6f4d3fc5e5843f04e7c5b4445d75a3e7c7aad38709046af9ff55f7c9acf4f432bb970362f6209456a60818480f71a678ce8128c7562aea5456c +N = 049451df8e2a8a347d51e3bbcdda390ae6dd14705ac2be9fd40a1c5529f7fca61c8fcde96f40e0306441d67c84c9328f5fcef1991abeaa58d46bf4d560fee81bbe +W0 = 0a26a44589553fab5f02f36dcdda3d8ebe0a5b6e344f24da4fa0a2c23bba196a +W1 = 2f532a99a7c17c2ffed1e9f5b4cf6d16cba06e50c2c7cd86cb5b078d2f44d515 +L = 04a63744b8bf25ae36c8181707b6a4593de032cf48541e7de00bd5a9350c46729d467662962b85759efc6716009a6925692c3c43c8151d5c616220854103a6feaa +X = 6e2a4bca9e12a2a445463c2d540cdbf79c98cc663d4c489e13c6fca28ce14b1c +ShareP = 0470097aaa4a1bd9a6593310a8fffd93cea79e56251a2313e382598960bc3b1b7b8335c552fab6371f99c042ef701ceddf2dc353c53fdb4db64fb4200026924078 +Y = 4a82681217e101c60636c0554723c433d8ec083e656f172b7fcc6740fb57b1d7 +ShareV = 0430eac74f238855b53dfe4e58dcd5c387e18c57961139ba4b99d5e37f2569705d376d0272564018ca01f4adae76f04f9d3694b0275c12d08de62d85c213678c9c +ConfirmP = aa5169e8f96a71e5499ad07c0435c3e90db5c60d1fb0ec48a90439a2f4f18a47 +ConfirmV = d9d28520ff36442370ce760a067ca67ca885fd089b9af5c59372969bd1486f5a +Shared = a8446d2d3219206b88994a37dc21a86e15c6fd0d7d55a62147c9d55dd4547826 + +Group = brainpool256r1 +Hash = SHA-256 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 044ab206d870dc6b02d77876d27879e401aad66a1ca99ad269875f4498709dd62618aa2ef80c2486a170ad3dbbb975a1e9f1fabe5dd024a9a0b6be0977cd630cc6 +N = 041b066f5210382e84f0516b4f31130265951a41d47a5cb4ad954bef338b098b665fdde18c308ca2dda67e1f246382c4c11359f82f776c45a45621e9dd30537890 +W0 = 121577750021770be5e6a0298ec4af093f7a118b7c271f13716672f9080dac33 +W1 = 593f6f2e329742c4a6bb31ec13d11377384caf17e01725a5f189c50971bb99d4 +L = 046c272c487246fab178786894e8a901ee1fecb4a832d26074815ba385f89a1ffd1bb425fb8866d12cd32dcf6328f6a965500869779ed4ac8370898402f5d32f24 +X = 38b54cf7eba8175b5607ae9a5e030ac4f9abd7655bc78965e460e2343e359d78 +ShareP = 04a66f4a59a4e6e5a6b9fa225ac7177051aa9825e4c15e9c7445211cb24e69af5a9641e73db024413ab07632081b277edb3c2c2a7d6833d922ef87135858ad574c +Y = 5e596a69084034d8cc6734fe5ab0a2ddf48f7b33fc03f844131a95cedabf3b0e +ShareV = 046385e271df24cf91519bb32390dcdb762f3c93eeb328d980a076414d5465aceba341777c2dd57a3b3cacf89ace97fa7efec57512bdf6a80620c9483db20108eb +ConfirmP = c5d78bd7a9b4b390584b33ec22e57fc7b4988873043b56ce72def45f96c1549a +ConfirmV = 5c3fa53ff10df3abf4aab41aa640631d0e14685b939eac2adffd0225c9443a86 +Shared = e1236d230e304d78fe85b7e4045077e18f023a666ccde114d33c5db25442c2f1 + +Group = brainpool384r1 +Hash = SHA-384 +Seed = +Context = +ProverId = +VerifierId = +M = 04597bc7f9b407c143e76f4dcf25ccfa192183af991c0910458942532f1cb587b3ac171d2858a404504453896299fb829140a8623db3f79d617a3d6b05cefc7fa2d1b1a08d9b42fe07f6dc0dd23560e32eca8a671d2b91753b74a9b28d12fefe66 +N = 0428477ec4ac99326de1454543496f59ec003d7fbbd101aa7c8494eed49a005d1abd69c5225cb394179162df9dfdfb6ae929a1a3890be2f58d2264982517e0a85d0fcb18d117672f56874c09852e9e3a7b17a057eb91d14fea6716b43032d9d7c9 +W0 = 852fbba1993c0666b0362c7e5a83c125fefbceb09a194d25a50408a6f3f61d71f14b76f2092462f498a0fb31c195b1cd +W1 = 790b2e69edcc979c12c5388678cd2513247f4f94e973589e550be19c1f57f8b3efeb8775cbc8c4b1ad43f92f1349a5e7 +L = 0432c51485305e5a218a10c55b43761db898cc1f126c962dc3e834e3b71457c8f11dbc527d54a2014ac47aadfacbed37f80a1dd5e6d136a6027eee7c8c1c6fe8d7c43651ae138b85ccfb559cbffbf9c0370fa771c3ad94326b47e1edfcc28dd684 +X = 3ba54f82d73c592c5d0c731cfc70f796ebf57cb0595190e73c94bd3be7f3154bdb43887a77eb41ead5c6be3b34dc94ef +ShareP = 042cd26f3673f744b5c5e276b3e4c08bf6c9280d6cc677044e9ef4924ff25aaee13b93bfb74c4e95e291449ec2181fb6c353664285c0c8cfa22a879d8328e190e0efbf191caf934fc5a56de7050c67f2c4f57e6193033d0d324eb2a97962cf9ca7 +Y = 780b9d74fade6cc1dd4bf34c7e1dcc270f89c3298bfeab088cd8d7681feb258e80e617b4bc88f4dacd540e5f25fcee64 +ShareV = 0472ef0503c384b219b3f484ac7dd8fa4f6fe4214234c5ad369d47fed567fa505f468f0bde7aef10b070960b251e716abd3a7f697ae535c4932778fc55e424f3b2a313789993bd3b674836929390d7d499b816d6814593c9b2f3b2bc3cb7b01b50 +ConfirmP = 52e32f54d110a591c668c7734ee3fc843bcfcc99824ffdb9c03627395aab2770f759aaede2e0ff7a6b3d519ec206a21b +ConfirmV = a2f7fa2f8eb937751dc081744123453de26e4142972d26796dc748741e5014eafacaa21aaad8cdde34f894e4525edceb +Shared = 38067506df25a206413dac3f51e6c5847a4caedb43b13d1b46d3ab70177d5b1779a74e0b4769cc4da38769d543701f15 + +Group = brainpool384r1 +Hash = SHA-384 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0467ac233a81d514e0c4fe7f8bdcbcc1de87be0485a0cf9bfcb051ffcc1b131bc66ba7f4c9dab9165ef9226fb6650909132e6cd586e4fb8c888e4cb69fcf22044b232d908cec17783616057ebbd1e4d69bb2f61ef056c54aa25ffe5fe0d94d89d1 +N = 041ea800cf5c630c4b66b82c83d567ee27cd5b6fea47ecd983cd4c70c26530a839642aa35e6704a8560d00bf8cb1a393c233e6c0521a8df6c5bb6546194d647a00d2f4aa0e84238e91a63514c38817461ec1c91cd47286989150580739f8c46748 +W0 = 1ea6d1ab5f3c587124e63efade65d7e4f4d257882e69a23d89433df8258d4132d3432fb5b60e06995a27635d96abe9a2 +W1 = 25df1315f894fac0e21ce3f9aec7045615fe4d4fe5c5eb6b34db710c89540f81851b8f034870773c906623d4169c1d12 +L = 042ad2d1017f392aa680ee1566f255111c5b5c8fec51fd8e5062d3b7cd3abef775ec5dba7589a5ac4850d6a6ddaa1e0ba56e672d5abb699cb1c1aaecad36cd2caed3618ca048196ab9dded981f478d462a12c42546299a7e3270900f4d755ba193 +X = 7984e342993f3733e277e5525c18d48da400b5ca5006986af71a84048f867703d8b16a592539d2545d8ea0da3f35f66c +ShareP = 0484e4371d7a4e4a17751de45e02820ebd9725961d2aaa5ec5f0e2ae3f67f7c0a8296c8cd833b03ee0a94936f5a54cf4f648219d988704bd6cc071f2c329aa1a0d6a5c26f1963341dc160dead195a4d00f5628497c670d36d0f3e14aadcf7cb5a0 +Y = 28bcf7305fd5dfd47b667264b337f022b8bf7fb8d9569cbab5f29bd42d0c8a96072bef85fe92ebac2e3a81ee187dc3b9 +ShareV = 044433e8c41836c79b4b60cd0b6d27764b92b7a688079b2fd7826e39ab65d4e887816ac3be78839daff1b477168c6960c584939839eabac31f4c5c1bd55246b3710ffe6d871f71bfe76c50a06010b57a85a3598407a2135994e87c4141605ba585 +ConfirmP = 48861667c0223294cd2acdb8057627af916da41bb832b09c15c8dd61ae4b436700336ee88d26a77d1ba899748f311503 +ConfirmV = f07190be655c6ab62ebda47d454ddb4b3e773f0f323f1eea104336127423646ef34553171ddbdd10d604c00a4fe7de1f +Shared = cab28c9b1ed1a3757b0bdc1221ebf00d673c590022d6382c61558c37e7c19b4e6354c490bf6bfd59473b192afa128e25 + +Group = brainpool384r1 +Hash = SHA-384 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0489cfd8122236e96cedb66cc3c2099cb6d804ef7427f8bbd6756da18a36a87d8559fa703c619e537f75e741d1fd31e73b363e6af6807aaf457cc7b39fd465f42799dadae8d0166c056c0d7f0c1daea9fec66a5976b4f732af69434e5e31777f94 +N = 0449ce21989100fc9c859fe026e23af3a7fbba82f598b738e2a233a29bc8f7f28500ebac0edbf824001d3e14f403c6e0a260013aa9890f3852030e8f7ccb1d9d4c4b289d61da0333020d65e74950cb051b3cebdd10b1d7a7e7474a81525016eaf6 +W0 = 6a00be86a6977d9d8fe5716c25326b5a58a5e69a110e4d1e45cb6dd3db49fb0ce42a724c254be43892f0183c73440153 +W1 = 459f74ca9ef5533fcf86bce1f0799d45fbe92828f7c8040416a7d8ede70009cd7cb79771c4a4addc8a2892d2e8b1d415 +L = 0482e764eec8c3b19e1014fb7e1c88e43df97ba2ea80900557e58dc601d6e0ba5de105a577fc88c2aa4e9055a75f843e580a0a3f96c94c14d31502bd3d93562a64a2221f35d5f81a7826f250c14d1122df7fe1b29b8c6910098e5eba454588a3b6 +X = 570c964053feeccceb4e3e0c1b5e284adf5caa0fa0c60449bf0a1e1e62215f60f613392adb4f6a8f01030f59f157f193 +ShareP = 0403f7b621cfee36bcb565e25e30e6ba0b1ce3786ee6a0963e6c62b522be9046540a749d25af732ed23471bbd10b30e7aa607a18e785687a69fe8c5e3de4fcd3d74aaff73277b80a0805a6ab747eaadb3f9cc79d6deb31ec3a582a3daa8560201e +Y = 60e77d99b7baaa4b1202c9f3af846e4075306d6325bd6f82c9719e83d4a6b775012df4d62086b6403c73585944e8018b +ShareV = 0449ee08129abe50f36d538bd9be8c5aa9ee3d138c36428faa3c71a6f87cee01f22a2064ddb030c1428c69c5090ccc6989380051bf747e52f56a212feb2efc88bd469cb09549eb6da7b806ef764b2ebf03d14403c7b29d56d5e8e11383a355628d +ConfirmP = 636ed1bc84a02d340f1c0d2fd74dee25e576f5655d25e6f6a0c955e7d69899c49e4e2bc9dbb6aa4ad17e5dcd7e08f511 +ConfirmV = e35a9665be61f71e360234747920d9e6cba69a9f01e335d5f25ec2201eaf7a4a0f2ba4f634c47c1d1665c7363b55701b +Shared = 6d2e829e45b42f7bbc641073af8684c84f0b51e6325d1a24a09c66e42d066b8de76aa1f496156a6c39bed878a966bb7e + +Group = brainpool512r1 +Hash = SHA-512 +Seed = +Context = +ProverId = +VerifierId = +M = 0498ed07f92b6c261088c4186a08a89617f9a45b00ee0c98e0ef743f72a72167cef579abb38910ec4692ad1e1d68b4fa278d0efe76f48c8cc68821b4cfd985f9c0691bf58745ff585143f0fe8d30c1752df9eb86b7809d4f9117bf1b2e283018f52f3f48affed2ddfb3f111fc0ca36c6416e484072553fafcd9534a99eae43e20d +N = 04a9e37f676265ea68a5455d4d607a1120b117e672c6febfc530905be25f343654da5d14a8d1dc92ce7f2c12fc19999829e76d5a8c4ade7eae0ea68941855bb90c876b070aca17ce26dd516899dbbc8682d1d86cfbb0f546f1e654f906722ad87cc2a70bb0d3c90f62da8138d0da3b30444460194743366c953dbfb40eb9909634 +W0 = 8783bd6cc856c4dc8f7a0871e7a90f40dbe064761da260413d451ad9319006b40106412267b1908cf8a8629800d2cf2f09bed0528ad5364ef21aa0458b0c82f1 +W1 = 2652795159904cda7130fa38ae2fc9d5075e00fbcc79bec2ea9eebba69cfd6a172c0aef7f5e599a2ed0e02a51ed9065c31092ccb73fb854e686c7066ad1287b2 +L = 047200b56b33b8b48f8022526e75a044d45569afd0c97ac12c13ba2b26b6172c4637044f4a7831b08116c0d9ef9695c689dc58bad823c8b3bd89a468999fa886f370187f2ba9a23658b1136531b66090a8b072aa241138619a3319b896db6f0769043ac9ea988c01296b721616bbff52e91c02b18441bca072946e5e265b9fd2ea +X = 817d7b6b7bd4ab11561c1af4ff569becbbbb65edceadb264fa25da5d664a305207a2d691c0e1a212dd8efc61bfcb908036dad50ad1e31e6e997e9ac280564a67 +ShareP = 043930e1d59b4bfb0e3af24e68d6bf08ad29e68f2ebd1d43963542e6b688ee8b4e4a61a9dc0cc2473799917d0ecec38c6c1182933e71ee1eec8381b2a450cc0c459c622bf132c20547a182876c57f12aa9a0d9f8329508ab7260f79ba635a3a4e45aac07dff7b91b71c505b97fde70b48eae9a666b5cbd89ebc4678008095f0f99 +Y = 061fd07b329813e39e7e366064ff72c1232cd37b0953ff9806be1791bef44e4218c36905bbedc2cb701576dca32d0dc5f79c644d6cec3ee6583d8cfe1e13c27a +ShareV = 0485854aaf0ba1ba4a39bf5ae163c9345e49604b38c168fb0d005fd8193e8e57641384ec3ffab947c29527408d74593e67972d605bb7c97383c00a40a17ad7203a1a3417b44fef922d95f1d8d1045524fde5abd5d7040aa384d5895713e318f884ea42285479c6b95f67831a49e98e16d31f4d63c96338996cd6db5183e07d9e31 +ConfirmP = 5897c3fb1cf4efc81464a08bd064e09dc0a4dd36c99b52d23e395fd6780f09b0f00077555069db19e7182fc9d81507b5987d88821afc80825933cb56745e5320 +ConfirmV = 64a2ae262e3d3d77df83da817919d18ea7b6ff93e04ea88db368bfef5e01dec2e6e3db2cdf9e1bf095fec0fcc156b9c7fbe0bdeae34035e9e575a238df397959 +Shared = 8f13d0404007eeea2b7c0e98886929eb50d48945298cf7a92503812ab21610488cc362e2db1699edc8d3e799935c0c78c380ddea0c7e4b3981d6756c77b81518 + +Group = brainpool512r1 +Hash = SHA-512 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 0457c6926f80fa33c4bcf49da2ebac7be1107baec8a02b5af68ceb1c6c49127d6cc17b86d5b27c4e59eb4ce5726efda0bc848b0c9b085bbe9d184c46969070bef624bba5c8770e92211d683a3c16c1438fecf32d8430906796c107c5a4012d767204d30c7865586baaaaa841b3812a155915f5e7455b277f078a5e6ba24c19ca11 +N = 0461fa4abf3d1c8dedf2770a34f11bebeddd66a741736823e25fb0ab503aa10bca526a0cff5b5e990c3c7193548ef176ccd5fab5127d80c66c4b1a33ae07f498e518b2e1f9ff91246e46ecc643352edc4c19059d20b88d7089ef4d69747fb1837d87f00fe9f6efabeb53cc29a9c0f108527e3cd9023179c491ede0ad865261bb4e +W0 = 296ec68df57e2d983a5b9a35c817a3fc779f7cf7e205f0c943f41f2633bec80b6d1ab59a91c5d5215f01b92d7c65f52068e8e7876cd1127892a1d355209841c5 +W1 = 35c10d32e105c29746c2431378f44b9076fb6e00d675ad757438ab8ea4f3dbbdd2ac1f43ead3ca0de36c02b0e7e83775be0ce4da958fb898c5aa4949beec0a4c +L = 04377d310b28d164f71cc1c7a0210dfc158cbee09e1e316289b68c0a020db1fe75fea5944f49636000bcba016e8ce8725fe38a94434f6a85192a62862b7c95a7a83ff29640abd18231cb90beda9caddcb6dc1b2af4543d4d5f373552f5beb4daa58734bcccfcaffff1a3bdcd9610e891c0cecfcd2a7f373609aef192a21047f014 +X = 66bccfe4a02f795ccbf4c3517d85fb8d8933d4475d7284c4b0d4e7941164bd0e610f9ef84bfff378b4f335e842e250ef1354a3d02e334fe2d22fde38c2997389 +ShareP = 049f4d453eefa042999c8a9405a8f88deeacbed016a1b31374a6db3d8035ae6570d5bee1aa30a9d3e35f08261851a3875aab2b66a54006ee93f7b02483c0ee9b3692a5e9ca63710554bb507eb142d1ef354049637c71e686407864694dfe5da1413494e058f0c2b74429be2d39471548e667f1b56e13f5bd7b798de17c7061b015 +Y = 941b7001118c329cfc63eebfd7fd57cbac8d5646861e9fc6669a18cfd85fc6f5539aee7dd9682893391f241823326b2fd80be0d74bba4127579e590e616323e5 +ShareV = 041121877ce2c5055602c58fd5a5b038ec911a8a98896924a6931912c5cc4ab26511b828ddc0c76d6120678626e12494ee80509f0a2777bbc5ee45a99f9a6d46e86b56813e8bd36f9a5b8c0dbc2c00a3636b5c9cc6ce74c87df9c94f048fc100cb4655e47096a6d98f635f6ab11d72f7d5bfc394559a5c84735a6978d54069123d +ConfirmP = a28f15434db099988c61f5288b9d3b09c48b747f8c63650f26590ef41b231c1facfd24b4655568e342e20025b46cbc5f8ff3c44b4cb5c9e14a9d3d915f131e7a +ConfirmV = c4bea5ff693d4204b11b31ba7db98e4d581911237123a76282301c4ec84edc9e3859f680652b483b8e617e2f30810dbf91dff8d08fabf21c3cf742781b24b13f +Shared = 7ad540bd313006ed02faae91065d1a314d9238c6ad0aee652aeb8f1e5d509ddebd99e5fe2fa93dc423a635e69185c80e52e3724acbd4d91b21da8e36f5c9d718 + +Group = brainpool512r1 +Hash = SHA-512 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04561e5c6ee7a35e960976d8e4ced4d541667f1579f77cbdc4f5dfa04ac2dd6599dabe15b98cc78237269bc4df78b4357830f3dd8ce3d11d43dfb0927f9d632d742344c8e082b353963b54ddb42817406bf00da9570d7568cf1079ce55992ca68117eb5f45098186162a5a776adae2ff93bfc6c91067616d752c9705171819c413 +N = 049ff9f5b7840d6ca547728318ee37b664b9069fc464aa89a8e89d6cf385682a9e67f2ace3660001928fbf0077446c04ac96a64edeefae33d02175f62eb2a9d8afa3f10036463694c42a68de7d5aa30eee5c6b0b9df6225e05ad5e768cb351574b12ca895d01d1e593f7b1b0e37f40d9969575b16e10607c175ddfc25a7223ba85 +W0 = 690dc78b79a45bbcef4fd42446165b0e1becf34d3fdd4ed55173cabb41d045f2934d32b4d8dbca487067352c4717de424968f892d50d9b8734e8207b8cea1940 +W1 = 0c818635fd42664c5475bb2a616f50438adb2c20ce7af78fce24b1c7227ab8900c9ae5fee90f6f804f90a2bfcd48dac894cb010d7169fb2193139030c0644580 +L = 0435a887397edaa292d9eefce54255f737717064f76b124286b08f081506e0c5da16e7c61e55460f24dcf4810ce4d7feba577f7af5a2ca5c1075fe00598f6ad7f86ce5695ef3f96e0b1e9f84e70ea334ba147f25c570f2f417dc1f2ee462abd6bdfb5d29246cc4e464bece1e0ba56568377f667f0f2013d938304a4d145e221c40 +X = 3e960a95a1a5609143a398e3317553185e34ab33e44b961be64c23f4dbfec9c8b075ee92bb128d0f0e5400288af21aa6c113ffd714a86387a32cb3c0a679845c +ShareP = 0466c5754f73eec436706fc0923a5531d5fffb85c64c0022bb134eaff39afc8a888a53c7972630c78fef9e8cee574b19ee525dc98d8f3ec750208ae27aa234897731a0bc692323ad83714b8f596a9b2ce6a5f6f7d2ad613288cab9a25f62fb0e7b6968ac4352bb470df8af0b4fc2584525b8c7a9df64442974f256285782849582 +Y = 168614e5bc7e4d45a3fa896e538e1e72ff996c63b19e526dfbdb1e5ce1fe3a2030e360aa0dee2ca988c8139f3620e3c4f286e596fddff60a149fa072746d90f1 +ShareV = 04153c58ca794c57d78b546a65df052d9f55571fa1f0bedd92025feac40e151a6f230c8a1747db252191d75f484f4f4751b4f0a882cba8f871537d1e8e381ceff397349017727635b254738d06f67b597497c3fd9ab4efe0189915a08f8828c7aaed8c9d91ab25025a31f260f84cc1ad18c7dfa6bf6b21bff5b70c7a110bd4213c +ConfirmP = 91ba549a8a90cb57d9823902616ec7c59f7edca152346232e27e8ebac96e0ce665aeeb7c8c4cb84cb229dd3e9946ac6ce0f612723787800754404c22e378cb50 +ConfirmV = d48b355be266e328cb861621afd905ba6f591320b91d6ec500198abf2f8f3bb2d9e9150484de3ca023441010f6fa9cb0f5d3587c5d613304fd61ba5771f9974e +Shared = 6564e4f9fa5ee642cda741ace2a0e1ab4106bc4f9c775ec559fdcb6a99bd30c406705c04ee096667f3977e015d72bc0e3875cd4ce0e4d02cc62912baff77d588 + +Group = numsp512d1 +Hash = SHA-512 +Seed = +Context = +ProverId = +VerifierId = +M = 0441548bd5a82ccc9676960fd06331fe936c080b5996268d252cd9b959dd308bfe66e2c825d8d651ec93d2ac34d840a8eb74a9351ca321265b6f13afe2f26034565cb9378adf87a2249d20b7e6782504cfd0a5400dc67cd9c0c1237ff5f0d258638142dbf6b26dc6fe8ac8ce8c21a1d426dac2a1064e650d7af9353f0eef00dbc1 +N = 047b69f8e2c67c61677173fab13404586d9814b72e82438aacbe8df562546d859acf7ff41f7227d13495c004eb9d803776f1e72508f83aa62cb304993ed64212fa026ca7e1c8c7ad012c374536d7fea941467cd5c079bec5653d1f2595d8c1a05d2221d9d124bf59a2febc5cefb9b6069d4e2fe4e969a99a6ec763ac5523450c7a +W0 = 5525f820a2dfd7185756b604c818508fbac50b1a59d216a443e86013bf656aa9f9b99dbd8c27d53263116df46178cf2d0b604feb7e71a44518089e1928976708 +W1 = 2c28dd2c77b9a01762ac5b81b84fbdd10942eca028ba192030f2651c331da8da3c85c2b992c9723f66302abc2b8814865a88bdc804a5ec91d32b7d2fa49ca627 +L = 04975044b5398b540747e9f081bfd464afbf0145fdeeca0be665f0842c14e99908458738f8d93c72f9272e189442f4de1b1992e6eae4590a2e0c4fee84813168ccaf259ab944266a837f660c4460f330e41a61abeaf7079462645d943d954febee93beffb194e920b34e999150626d471b3a28cc328bcb53748502ada6d757308b +X = 6d3dc723a8d1021ea942f2f39c9bcadf007226b587116fcac9c3e6cc97548d94bd7f3b4e6bcadef405d023a587033795bf82af65cc25e637561f236a6b2c7dca +ShareP = 0476a993275d2f4d6b1fdbf0f0e799fd2b62d2b0e1d64e456ca58344366130beb0c34b13d2449c9487d82b04e8bfc7dca5ba4bfd162131f87b5e9c626fb81d260f48594135dc21801a6ba57b40eb3c2f9feb166c6b16940b956a0cb0579bb9e3fbb0cac39864744f912f358cd020f1fa33a3ebf85573af6e37b52bbefda1a67849 +Y = 3042ae0605313911e6bd0ede03b2eeb963d8d1bdd985c2fbdf31c521a42aac22c10fd5a3ace947521d3470010ebffb67f1169ac69186a97d4bf7927da6c7bbbf +ShareV = 043597b4ed8038bed8b5cf60954c91791d70e7bad8a9b0a6da2a059d9975c9f1a41648b2430f02e0f0c22081bc34a2cd32f4d8995026bf6230130b8094c3bb806cc415bd01ebc280950447bab1d371e8d6779a5b6562761d6a58c5ee01a8d5bd2d8a2be880688ee72241a6aa4a0c656d0f7042750a9350089915d80ddd43003125 +ConfirmP = 48b325a0ae63567b9bf614b02622e747bc380ab0c4391cca2a5d95899c9552b23e01500bf8b93f1a992f42ebb347940b5aba075d4a1f02ef23030a8c11c938bb +ConfirmV = 3719defb1e714125f2b70cccece19e91f0c8fbf9e5a75740295d7b7fe89824964533057f8fbda52dfc5fe4e17f80d5305e6377759de11b1c395b04731f07254d +Shared = 276f77f943ed983c97086cf65313ed5ea9565422c3a1abe748a1b504230488819729afc6044c09ec5a5a5684783c0f07fb06d666e6e6d0d058b80108c219b350 + +Group = numsp512d1 +Hash = SHA-512 +Seed = 616263 +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04f019bd871e8b36f636daf2d284b4184b213ab9ca6725ae40c8db7bc75966805299a8681d77aab953226b2713da87d4598eac81daecbcf3a70c2b24cc96144da50bdbb1736c2e277a78db1a9a2e58dd2bc6d612fa0cc54b07e00d3907e974394f8f5b5264dd2630433701ec1aa3e171729f41a85b618036173c5f06c9115dc78e +N = 046d26ac8453e7f613d772d7ea478c509f45779044a024273102cd1d2fdca1282ec91f861f7119d872f0417a23542cf6e9b9675d18f51c1e9464d7fd0d9187b9a0d5d0d6a106b07c69c621a74c06dd16f9a2c7ffc1beaa855eeb0d50ad14eb1349f01935b36861d563c3ffa0b45a83b8c89a233f63704ad58a14d0a06e322fb7f0 +W0 = c2ce82ad4a7bbb01950010a30e33eb6ac818dd7c890c82ae30d069efd2800d101f59af4edd141b3c16c7ea9d595c9a1da6497256601eeddee2306b49b83f4d26 +W1 = 1e85762ff8d01b48450b04c273a1f5c80238ff5a0a3400e639858b91c7e4f0f9768e823a1d4b23e05b65af3a0e18b7d234e33ccae1a7d729c3fcf632574a1463 +L = 04e2e909e0a9d02da33690ccdd68803da546f085746221d7060051efca41777cfd501db93fd9a856f1b0ab8e699d8324e745ae5f14af51c8de2c13c317476da35b142797c6d8b1069dfaf98a732c247dc986f972b602134a1c45207a03f03fbbabbeecb0ea5358c301ea451d4fa0f7a4c6b4c77a08a0716d77c5db83d4a161da11 +X = 3676c7ed0d2fa29a5c6874ae78f850ab91edba0e7be9e9ca0b258829ae9636f8f2d280cef157b256622e5b542ef63b0317975a1d47a6ccc2722f5c70d5384b87 +ShareP = 040e290e370dd5d97ec6e69a0b285a87f41d72b633f8dff28e1e2c59a05ff6dde32af34a5b40636eebb96bd0dfef89d9f536edbd66f73a4d5fe8e2fec5cc14003c4f18944517d6d1db386c4affd9de981ac998ce783ed1266981de776d0a5823332e0aec661ec1ccc0ddbff08e891ee4728214351157d04ecf77adab7ad5eacda8 +Y = 2f72233e21cb9795b93bae86cf6ce4deefe411d34e8a85f13d14b90886b7dead0cdad2f35da2bd01e2b7340280aed211520f414b7d9cc807baecab0302b92ac2 +ShareV = 0453927bc602ca55094d1d4d3c949ae28b01d67ee17cac90ab35b9b314cf7ef8e44dbf3bbc279eb13c6d76859d4d00126713971578513e0c27a9caa7e901cf5cc4e87c5397e43b1f150a7f339f9dbe212428bf98a88caeb7da48c11c2e3ab6a0eee9b118472ab46de9d354defe920e72ec8bdbf8488c0e7cc03afcac1c6a736088 +ConfirmP = 69f84f6002050e2a0c0ca690d987cb10e90aaaaef46e62a42f4c6a118755d8d260078391bc52b071f87a60b1f4509701aea009aa956aaaa646fd46f2a4f24a17 +ConfirmV = b86d114df6bac43e1e3f7c37092223c321d862070a2721d35efefb9ca457eed3a0a70e79860c8b166cd64f4f8d32103d16a49c82ab766e54dcf7cb1bf5f7992e +Shared = 0940f861bcf6aba476897888520eb82d439078e9a2266fd000d0b2ef522f37278c004559ed47650b3fa8df92ef5abe7f05db1a3eb10290641e2aa68a8713c99c + +Group = numsp512d1 +Hash = SHA-512 +Seed = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f +Context = 5350414b45322b20637573746f6d20706172616d6574657273204b4154 +ProverId = 636c69656e74 +VerifierId = 736572766572 +M = 04eb30e35d2c973438712e084b36996bc02587fb7665b6111ecab3148779becaf9c449af7aeea57854f8ed4f710b7221d1f422c9dc0a6ed03016c6dd286c73587fa8a7ccce388e8e2f0753cfc0c70585372d1edb78b17e0c7af8e178ca7f15d2f33eafba431c4753bdf6d2ca9f793c6912fc0c8580355f380f0c7c308ce62c0805 +N = 044b36cae761b7864597231859a2fd34ead1d3fa0e0e5167c96eeab8569e9db98679517a74ba0dd8a94aaadaaa3c982357b22487525eee9797b93b2424bea6215998263dbdd42db0fb31383da5d1018904616ba67ff25b87ed5bba1e0e6e2d6cef1b1f857262891235367567269aae941908888fc6148d26f90d49db4912dbcce9 +W0 = 92a76dee4dcc51fc1d4a00dc6dcab16302feea8a23daaab941384fe02956cee78bf2781a1ff6554c445204b5370b47a7f19968e2ddf0b786f729bf6461576e98 +W1 = ae0e625f70f720a09fb69470fe6eaf35c8a719db0961d79a82f612dd7e388acf20720fc414be1cfa429ba09ef83748a9b85e2572809c7538e76f0f6ee07e1b19 +L = 0452c68b77527eefc289239840e517f0faf36777591aace1e8435fda18c1b4aad51aa30774e88258a4e7aaa5a76de1f8095cb22df084fed96cb611abf0b55758cfa05cd1aa97e533f4a2b75683482d9fb0d01abe799fce2bfbc5f017a24aed4a8e02c781f23d4cdee45c33218812149e26e5938446328c6ec9beb10f9da200b66d +X = 56baf7737ae5e0ad0035df73004060d3e111d24de87224d80b7ebce9f5f1981883e8dd985b417f74b551de1bb5dc3261fc147a765e3af03fa50f86cc71f6c212 +ShareP = 0463908790e646289fb484feca2d783a1d51048e178753fe3550fd1f540251c5a21095ee6f50a92c369b05a138ec6d9e7b268dc02f29cba63852729a2d93abaf878d0cf7a82a85d0204a674c5502aea8032ece6e9f274f7fe6c15ce5876b698b4b6291b4936888a235366be278089b311f1e503e3c6e140cad33ec7b5afa9a5986 +Y = 6f7c1a2a33cadff68c6c3fa5d647427aa0961adb02ac11f564597e9895fa164d111022d5411f6b3b5315d9360259656520d0d024695788bb2f16c92f4ecd8994 +ShareV = 04540d3f14a9f862b157ea4de780a61586394eab242aa9943c0c2d843ec8f8605e117a34d72c04cb390bb0f6c34ee93edf608c39d7c08e6720dc0feeec84a6f4cfacae32c9d29d22e43724bf4e000a639137cf09628754d893ed98a311ffc82cd01843e73fdbfc05552a1e439c56a8f9af54c9302e70084fa5666923bdea097d9f +ConfirmP = a413b8649f96d28b5bc2efa712da812d193824955fb9f4dcfc5503695145be7423617cfbf87375378a52f6d8c5b35ab903a760e95612b36ddf5a3d598bf66f87 +ConfirmV = 856790885237422b34307682e6aa05cdebe969a040dd6c6bbe6da09e665a5c407b1e547a974b5d8065512f2cb05d5bac0440038d197a20697f20fa4e3b6ef141 +Shared = 070ae7e555612b466504aff93778cb3afe11e024a057295aff9f7c799f65f758ef9bd80e47b6b2efe28dd29b37e2cc3085bcba66c3ae9e869fd438eafb37fd0a diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index a25c0cb0f78..411336fea23 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -5824,6 +5824,307 @@ class FFI_SRP6_Test final : public FFI_Test { } }; +class FFI_SPAKE2P_Test final : public FFI_Test { + public: + std::string name() const override { return "FFI SPAKE2+"; } + + bool skip_this_test() const override { + #if !defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + return true; + #else + return false; + #endif + } + + void ffi_test(Test::Result& result, botan_rng_t rng) override { + const char* password = "hunter2"; + const std::vector prover_id = {'c', 'l', 'i', 'e', 'n', 't'}; + const std::vector verifier_id = {'s', 'e', 'r', 'v', 'e', 'r'}; + const std::vector context = {'f', 'f', 'i'}; + const auto salt = Botan::hex_decode("adb63d2727f971e1b52b7ba1e42ab73c"); + + botan_spake2p_params_t params; + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_PARAMETER, botan_spake2p_params_init, (¶ms, "P37-MD5")); + TEST_FFI_OK(botan_spake2p_params_init, (¶ms, "P256-SHA256")); + + size_t share_size = 0; + TEST_FFI_OK(botan_spake2p_params_share_size, (params, &share_size)); + result.test_sz_eq("share size", share_size, 65); + + size_t confirmation_size = 0; + TEST_FFI_OK(botan_spake2p_params_confirmation_size, (params, &confirmation_size)); + result.test_sz_eq("confirmation size", confirmation_size, 32); + + ViewBytesSink secret; + TEST_FFI_OK(botan_spake2p_derive_secret, + (params, + password, + prover_id.data(), + prover_id.size(), + verifier_id.data(), + verifier_id.size(), + salt.data(), + salt.size(), + secret.delegate(), + secret.callback())); + result.test_sz_eq("secret length", secret.size(), 64); + + ViewBytesSink record; + TEST_FFI_OK(botan_spake2p_registration_record, + (params, rng, secret.data(), secret.size(), record.delegate(), record.callback())); + result.test_sz_eq("record length", record.size(), 32 + 65); + + auto init_prover = [&](botan_spake2p_prover_t* prover) { + TEST_FFI_OK(botan_spake2p_prover_init, + (prover, + params, + secret.data(), + secret.size(), + prover_id.data(), + prover_id.size(), + verifier_id.data(), + verifier_id.size(), + context.data(), + context.size())); + }; + + auto init_verifier = [&](botan_spake2p_verifier_t* verifier) { + TEST_FFI_OK(botan_spake2p_verifier_init, + (verifier, + params, + record.data(), + record.size(), + prover_id.data(), + prover_id.size(), + verifier_id.data(), + verifier_id.size(), + context.data(), + context.size())); + }; + + // A successful exchange + { + botan_spake2p_prover_t prover; + init_prover(&prover); + botan_spake2p_verifier_t verifier; + init_verifier(&verifier); + + ViewBytesSink share_p; + TEST_FFI_OK(botan_spake2p_prover_generate_message, (prover, rng, share_p.delegate(), share_p.callback())); + result.test_sz_eq("share length", share_p.size(), share_size); + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_spake2p_prover_generate_message, + (prover, rng, share_p.delegate(), share_p.callback())); + + ViewBytesSink verifier_msg; + TEST_FFI_OK( + botan_spake2p_verifier_process_message, + (verifier, rng, share_p.data(), share_p.size(), verifier_msg.delegate(), verifier_msg.callback())); + result.test_sz_eq("verifier message length", verifier_msg.size(), share_size + confirmation_size); + + // The shared secret is not available before confirmation + ViewBytesSink key2; + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_spake2p_verifier_shared_secret, + (verifier, key2.delegate(), key2.callback())); + + ViewBytesSink confirmation; + TEST_FFI_OK(botan_spake2p_prover_process_message, + (prover, + rng, + verifier_msg.data(), + verifier_msg.size(), + confirmation.delegate(), + confirmation.callback())); + result.test_sz_eq("confirmation length", confirmation.size(), confirmation_size); + + TEST_FFI_OK(botan_spake2p_verifier_verify_confirmation, + (verifier, confirmation.data(), confirmation.size())); + + ViewBytesSink key1; + TEST_FFI_OK(botan_spake2p_prover_shared_secret, (prover, key1.delegate(), key1.callback())); + result.test_sz_eq("shared secret length", key1.size(), 32); + + TEST_FFI_OK(botan_spake2p_verifier_shared_secret, (verifier, key2.delegate(), key2.callback())); + + result.test_bin_eq("Shared secrets match", key1.get(), key2.get()); + + TEST_FFI_OK(botan_spake2p_prover_destroy, (prover)); + TEST_FFI_OK(botan_spake2p_verifier_destroy, (verifier)); + } + + // An exchange where the verifier explicitly skips the prover's confirmation + { + botan_spake2p_prover_t prover; + init_prover(&prover); + botan_spake2p_verifier_t verifier; + init_verifier(&verifier); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, botan_spake2p_verifier_skip_confirmation, (verifier)); + + ViewBytesSink share_p; + TEST_FFI_OK(botan_spake2p_prover_generate_message, (prover, rng, share_p.delegate(), share_p.callback())); + + ViewBytesSink verifier_msg; + TEST_FFI_OK( + botan_spake2p_verifier_process_message, + (verifier, rng, share_p.data(), share_p.size(), verifier_msg.delegate(), verifier_msg.callback())); + + ViewBytesSink confirmation; + TEST_FFI_OK(botan_spake2p_prover_process_message, + (prover, + rng, + verifier_msg.data(), + verifier_msg.size(), + confirmation.delegate(), + confirmation.callback())); + + TEST_FFI_OK(botan_spake2p_verifier_skip_confirmation, (verifier)); + + ViewBytesSink key1; + TEST_FFI_OK(botan_spake2p_prover_shared_secret, (prover, key1.delegate(), key1.callback())); + ViewBytesSink key2; + TEST_FFI_OK(botan_spake2p_verifier_shared_secret, (verifier, key2.delegate(), key2.callback())); + + result.test_bin_eq("Shared secrets match", key1.get(), key2.get()); + + // Having skipped, the confirmation can no longer be checked + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_spake2p_verifier_verify_confirmation, + (verifier, confirmation.data(), confirmation.size())); + + TEST_FFI_OK(botan_spake2p_prover_destroy, (prover)); + TEST_FFI_OK(botan_spake2p_verifier_destroy, (verifier)); + } + + // A tampered confirmation is rejected and terminates the exchange + { + botan_spake2p_prover_t prover; + init_prover(&prover); + botan_spake2p_verifier_t verifier; + init_verifier(&verifier); + + ViewBytesSink share_p; + TEST_FFI_OK(botan_spake2p_prover_generate_message, (prover, rng, share_p.delegate(), share_p.callback())); + + ViewBytesSink verifier_msg; + TEST_FFI_OK( + botan_spake2p_verifier_process_message, + (verifier, rng, share_p.data(), share_p.size(), verifier_msg.delegate(), verifier_msg.callback())); + + // Tamper with the verifier's key confirmation + std::vector tampered_msg(verifier_msg.get().begin(), verifier_msg.get().end()); + tampered_msg[tampered_msg.size() - 1] ^= 0x01; + + ViewBytesSink confirmation; + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_MAC, + botan_spake2p_prover_process_message, + (prover, + rng, + tampered_msg.data(), + tampered_msg.size(), + confirmation.delegate(), + confirmation.callback())); + + ViewBytesSink key; + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, + botan_spake2p_prover_shared_secret, + (prover, key.delegate(), key.callback())); + + const std::vector zeros(confirmation_size); + TEST_FFI_RC(BOTAN_FFI_ERROR_BAD_MAC, + botan_spake2p_verifier_verify_confirmation, + (verifier, zeros.data(), zeros.size())); + + TEST_FFI_RC(BOTAN_FFI_ERROR_INVALID_OBJECT_STATE, botan_spake2p_verifier_skip_confirmation, (verifier)); + + TEST_FFI_OK(botan_spake2p_prover_destroy, (prover)); + TEST_FFI_OK(botan_spake2p_verifier_destroy, (verifier)); + } + + // Custom system parameters over the same group, so the secret can be reused + if(Botan::EC_Group::from_name("secp256r1").hash_to_curve_supported("SHA-256")) { + botan_ec_group_t group; + TEST_FFI_OK(botan_ec_group_from_name, (&group, "secp256r1")); + + const std::vector seed = {'f', 'f', 'i', ' ', 's', 'e', 'e', 'd'}; + botan_spake2p_params_t cparams; + const int rc = botan_spake2p_params_init_custom(&cparams, group, seed.data(), seed.size(), "SHA-256"); + + if(rc == BOTAN_FFI_SUCCESS) { + ViewBytesSink crecord; + TEST_FFI_OK(botan_spake2p_registration_record, + (cparams, rng, secret.data(), secret.size(), crecord.delegate(), crecord.callback())); + + botan_spake2p_prover_t prover; + TEST_FFI_OK(botan_spake2p_prover_init, + (&prover, + cparams, + secret.data(), + secret.size(), + prover_id.data(), + prover_id.size(), + verifier_id.data(), + verifier_id.size(), + context.data(), + context.size())); + + botan_spake2p_verifier_t verifier; + TEST_FFI_OK(botan_spake2p_verifier_init, + (&verifier, + cparams, + crecord.data(), + crecord.size(), + prover_id.data(), + prover_id.size(), + verifier_id.data(), + verifier_id.size(), + context.data(), + context.size())); + + ViewBytesSink share_p; + TEST_FFI_OK(botan_spake2p_prover_generate_message, + (prover, rng, share_p.delegate(), share_p.callback())); + + ViewBytesSink verifier_msg; + TEST_FFI_OK( + botan_spake2p_verifier_process_message, + (verifier, rng, share_p.data(), share_p.size(), verifier_msg.delegate(), verifier_msg.callback())); + + ViewBytesSink confirmation; + TEST_FFI_OK(botan_spake2p_prover_process_message, + (prover, + rng, + verifier_msg.data(), + verifier_msg.size(), + confirmation.delegate(), + confirmation.callback())); + + TEST_FFI_OK(botan_spake2p_verifier_verify_confirmation, + (verifier, confirmation.data(), confirmation.size())); + + ViewBytesSink key1; + TEST_FFI_OK(botan_spake2p_prover_shared_secret, (prover, key1.delegate(), key1.callback())); + ViewBytesSink key2; + TEST_FFI_OK(botan_spake2p_verifier_shared_secret, (verifier, key2.delegate(), key2.callback())); + + result.test_bin_eq("Shared secrets match", key1.get(), key2.get()); + + TEST_FFI_OK(botan_spake2p_prover_destroy, (prover)); + TEST_FFI_OK(botan_spake2p_verifier_destroy, (verifier)); + TEST_FFI_OK(botan_spake2p_params_destroy, (cparams)); + } else { + result.test_is_true("custom params requires hash to curve support", + rc == BOTAN_FFI_ERROR_NOT_IMPLEMENTED); + } + + TEST_FFI_OK(botan_ec_group_destroy, (group)); + } + + TEST_FFI_OK(botan_spake2p_params_destroy, (params)); + } +}; + // NOLINTEND(*-init-variables) BOTAN_REGISTER_TEST("ffi", "ffi_utils", FFI_Utils_Test); @@ -5881,6 +6182,7 @@ BOTAN_REGISTER_TEST("ffi", "ffi_oid", FFI_OID_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ec_group", FFI_EC_Group_Test); BOTAN_REGISTER_TEST("ffi", "ffi_ec_points", FFI_EC_Point_Test); BOTAN_REGISTER_TEST("ffi", "ffi_srp6", FFI_SRP6_Test); +BOTAN_REGISTER_TEST("ffi", "ffi_spake2p", FFI_SPAKE2P_Test); #if defined(BOTAN_HAS_X509) BOTAN_REGISTER_TEST("ffi", "ffi_cert_alt_names", FFI_Cert_AlternativeNames_Test); diff --git a/src/tests/test_spake2p.cpp b/src/tests/test_spake2p.cpp new file mode 100644 index 00000000000..0da7d2def6b --- /dev/null +++ b/src/tests/test_spake2p.cpp @@ -0,0 +1,455 @@ +/* +* (C) 2024,2026 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + #include "test_rng.h" + #include + #include +#endif + +namespace Botan_Tests { + +namespace { + +#if defined(BOTAN_HAS_PAKE_SPAKE2PLUS) + +std::optional spake2p_params(const std::string& group_name, + const std::string& hash_fn) { + using Botan::SPAKE2p::SystemParameters; + + if(!Botan::EC_Group::supports_named_group(group_name)) { + return std::nullopt; + } + + if(group_name == "secp256r1" && hash_fn == "SHA-256") { + return SystemParameters::rfc9383_p256_sha256(); + } else if(group_name == "secp256r1" && hash_fn == "SHA-512") { + return SystemParameters::rfc9383_p256_sha512(); + } else if(group_name == "secp384r1" && hash_fn == "SHA-256") { + return SystemParameters::rfc9383_p384_sha256(); + } else if(group_name == "secp384r1" && hash_fn == "SHA-512") { + return SystemParameters::rfc9383_p384_sha512(); + } else if(group_name == "secp521r1" && hash_fn == "SHA-512") { + return SystemParameters::rfc9383_p521_sha512(); + } else { + throw Test_Error("Unexpected group/hash combination in SPAKE2+ test data"); + } +} + +std::vector cat_bin(std::vector a, const std::vector& b) { + a.insert(a.end(), b.begin(), b.end()); + return a; +} + +void spake2p_exchange_kat(Test::Result& result, + const Botan::SPAKE2p::SystemParameters& params, + const VarMap& vars, + Botan::RandomNumberGenerator& rng) { + const auto context = vars.get_req_bin("Context"); + const auto prover_id = vars.get_req_bin("ProverId"); + const auto verifier_id = vars.get_req_bin("VerifierId"); + const auto w0_bytes = vars.get_req_bin("W0"); + const auto w1_bytes = vars.get_req_bin("W1"); + const auto exp_record = cat_bin(w0_bytes, vars.get_req_bin("L")); + const auto exp_share_p = vars.get_req_bin("ShareP"); + const auto exp_verifier_msg = cat_bin(vars.get_req_bin("ShareV"), vars.get_req_bin("ConfirmV")); + const auto exp_confirm_p = vars.get_req_bin("ConfirmP"); + const auto exp_shared = vars.get_req_bin("Shared"); + + const auto& group = params.group(); + + const auto w0 = Botan::EC_Scalar::deserialize(group, w0_bytes).value(); + const auto w1 = Botan::EC_Scalar::deserialize(group, w1_bytes).value(); + + const auto secret = Botan::SPAKE2p::ProverSecret::from_prehashed(w0, w1); + result.test_bin_eq("Prover secret serialization", secret.serialize(), cat_bin(w0_bytes, w1_bytes)); + + const auto record = secret.registration_record(rng); + result.test_bin_eq("Registration record", record.serialize(), exp_record); + + const auto record2 = Botan::SPAKE2p::RegistrationRecord::deserialize(params, exp_record); + result.test_bin_eq("Registration record deserialization", record2.serialize(), exp_record); + + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record2, prover_id, verifier_id, context); + + Fixed_Output_RNG x_rng(rng); + x_rng.add_entropy(vars.get_req_bin("X")); + const auto share_p = prover.generate_message(x_rng); + result.test_bin_eq("shareP", share_p, exp_share_p); + result.test_sz_eq("share size", share_p.size(), params.share_size()); + + Fixed_Output_RNG y_rng(rng); + y_rng.add_entropy(vars.get_req_bin("Y")); + const auto verifier_msg = verifier.process_message(share_p, y_rng); + result.test_bin_eq("shareV || confirmV", verifier_msg, exp_verifier_msg); + + const auto confirm_p = prover.process_message(verifier_msg, rng); + result.test_bin_eq("confirmP", confirm_p, exp_confirm_p); + result.test_sz_eq("confirmation size", confirm_p.size(), params.confirmation_size()); + result.test_bin_eq("Prover shared secret", prover.shared_secret(), exp_shared); + + result.test_no_throw("Prover confirmation accepted", [&]() { verifier.verify_confirmation(confirm_p); }); + result.test_bin_eq("Verifier shared secret", verifier.shared_secret(), exp_shared); +} + +class SPAKE2p_KAT_Tests final : public Text_Based_Test { + public: + SPAKE2p_KAT_Tests() : + Text_Based_Test( + "pake/spake2p.vec", + "Group,Hash,Context,ProverId,VerifierId,W0,W1,L,X,ShareP,Y,ShareV,ConfirmP,ConfirmV,Shared") {} + + Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override { + Test::Result result("SPAKE2+ KAT"); + + const auto params = spake2p_params(vars.get_req_str("Group"), vars.get_req_str("Hash")); + if(!params) { + result.test_note("Skipping test due to unavailable group"); + return result; + } + + spake2p_exchange_kat(result, *params, vars, this->rng()); + + return result; + } +}; + +BOTAN_REGISTER_TEST("pake", "spake2p_kat", SPAKE2p_KAT_Tests); + +class SPAKE2p_Custom_Params_KAT_Tests final : public Text_Based_Test { + public: + SPAKE2p_Custom_Params_KAT_Tests() : + Text_Based_Test( + "pake/spake2p_custom.vec", + "Group,Hash,Seed,Context,ProverId,VerifierId,M,N,W0,W1,L,X,ShareP,Y,ShareV,ConfirmP,ConfirmV,Shared") {} + + bool skip_this_test(const std::string& /*header*/, const VarMap& vars) override { + return !Botan::EC_Group::supports_named_group(vars.get_req_str("Group")); + } + + Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override { + const std::string group_name = vars.get_req_str("Group"); + const std::string hash_fn = vars.get_req_str("Hash"); + + Test::Result result("SPAKE2+ custom params " + group_name + " " + hash_fn); + + const auto group = Botan::EC_Group::from_name(group_name); + + if(!group.hash_to_curve_supported(hash_fn)) { + result.test_note("Skipping test due to missing hash2curve support"); + return result; + } + + const auto params = Botan::SPAKE2p::SystemParameters::custom(group, vars.get_req_bin("Seed"), hash_fn); + + result.test_bin_eq("Derived M", params.spake2p_m().serialize_uncompressed(), vars.get_req_bin("M")); + result.test_bin_eq("Derived N", params.spake2p_n().serialize_uncompressed(), vars.get_req_bin("N")); + + spake2p_exchange_kat(result, params, vars, this->rng()); + + return result; + } +}; + +BOTAN_REGISTER_TEST("pake", "spake2p_custom_kat", SPAKE2p_Custom_Params_KAT_Tests); + +class SPAKE2p_RT_Tests final : public Test { + public: + std::vector run() override { + std::vector results; + + results.push_back(test_exchange("P256-SHA256", Botan::SPAKE2p::SystemParameters::rfc9383_p256_sha256())); + results.push_back(test_exchange("P256-SHA512", Botan::SPAKE2p::SystemParameters::rfc9383_p256_sha512())); + + if(Botan::EC_Group::supports_named_group("secp384r1")) { + results.push_back(test_exchange("P384-SHA256", Botan::SPAKE2p::SystemParameters::rfc9383_p384_sha256())); + results.push_back(test_exchange("P384-SHA512", Botan::SPAKE2p::SystemParameters::rfc9383_p384_sha512())); + } + + if(Botan::EC_Group::supports_named_group("secp521r1")) { + results.push_back(test_exchange("P521-SHA512", Botan::SPAKE2p::SystemParameters::rfc9383_p521_sha512())); + } + + results.push_back(test_custom_params()); + + return results; + } + + private: + Test::Result test_exchange(const std::string& name, const Botan::SPAKE2p::SystemParameters& params) { + Test::Result result("SPAKE2+ round trip " + name); + result.start_timer(); + + const std::vector prover_id = {'c', 'l', 'i', 'e', 'n', 't'}; + const std::vector verifier_id = {'s', 'e', 'r', 'v', 'e', 'r'}; + const auto context = this->rng().random_vec(16); + + const auto secret = random_secret(params); + const auto record = secret.registration_record(this->rng()); + + // Test that serialization of the secret and record round trips + const auto secret2 = Botan::SPAKE2p::ProverSecret::deserialize(params, secret.serialize()); + result.test_bin_eq("Prover secret roundtrips", secret2.serialize(), secret.serialize()); + const auto record2 = Botan::SPAKE2p::RegistrationRecord::deserialize(params, record.serialize()); + result.test_bin_eq("Registration record roundtrips", record2.serialize(), record.serialize()); + + // A successful exchange, using the deserialized secret and record + { + Botan::SPAKE2p::ProverContext prover(params, secret2, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record2, prover_id, verifier_id, context); + + result.test_throws("Prover cannot process before generating", + [&]() { prover.process_message({}, this->rng()); }); + result.test_throws("No prover secret before completion", + [&]() { prover.shared_secret(); }); + + const auto share_p = prover.generate_message(this->rng()); + result.test_throws("Prover share can be generated only once", + [&]() { prover.generate_message(this->rng()); }); + + const auto verifier_msg = verifier.process_message(share_p, this->rng()); + result.test_throws("Verifier processes only one share", + [&]() { verifier.process_message(share_p, this->rng()); }); + result.test_throws("No verifier secret before confirmation", + [&]() { verifier.shared_secret(); }); + + const auto confirm_p = prover.process_message(verifier_msg, this->rng()); + verifier.verify_confirmation(confirm_p); + + result.test_bin_eq("Shared secrets match", prover.shared_secret(), verifier.shared_secret()); + } + + // An exchange where the verifier explicitly skips the prover's confirmation + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, context); + + result.test_throws("Cannot skip confirmation before responding", + [&]() { verifier.skip_confirmation(); }); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + const auto confirm_p = prover.process_message(verifier_msg, this->rng()); + + verifier.skip_confirmation(); + result.test_bin_eq("Shared secrets match", prover.shared_secret(), verifier.shared_secret()); + + // Having skipped, the confirmation can no longer be checked + result.test_throws("Cannot verify confirmation after skipping", + [&]() { verifier.verify_confirmation(confirm_p); }); + } + + // A prover with the wrong password fails during confirmation + { + const auto wrong_secret = random_secret(params); + Botan::SPAKE2p::ProverContext prover(params, wrong_secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, context); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + result.test_throws( + "Wrong password is detected", [&]() { prover.process_message(verifier_msg, this->rng()); }); + + // After a failure the context cannot be used further + result.test_throws("No reuse after failure", + [&]() { prover.process_message(verifier_msg, this->rng()); }); + result.test_throws("No secret after failure", [&]() { prover.shared_secret(); }); + } + + // A context or identity mismatch fails during confirmation + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + const auto bad_context = this->rng().random_vec(16); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, bad_context); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + result.test_throws( + "Context mismatch is detected", [&]() { prover.process_message(verifier_msg, this->rng()); }); + } + + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, {}, context); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + result.test_throws( + "Identity mismatch is detected", [&]() { prover.process_message(verifier_msg, this->rng()); }); + } + + // A tampered prover confirmation is rejected by the verifier + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, context); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + auto confirm_p = prover.process_message(verifier_msg, this->rng()); + confirm_p[0] ^= 0x01; + result.test_throws("Tampered confirmation is detected", + [&]() { verifier.verify_confirmation(confirm_p); }); + result.test_throws("No secret after failure", [&]() { verifier.shared_secret(); }); + result.test_throws("No skipping confirmation after failure", + [&]() { verifier.skip_confirmation(); }); + } + + // Malformed key shares are rejected + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id, context); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id, context); + + auto share_p = prover.generate_message(this->rng()); + + auto truncated = share_p; + truncated.pop_back(); + result.test_throws("Truncated share is rejected", + [&]() { verifier.process_message(truncated, this->rng()); }); + + auto compressed_hdr = share_p; + compressed_hdr[0] = 0x02; + result.test_throws("Share without uncompressed header is rejected", + [&]() { verifier.process_message(compressed_hdr, this->rng()); }); + + auto off_curve = share_p; + off_curve[share_p.size() - 1] ^= 0x01; + result.test_throws("Share not on the curve is rejected", + [&]() { verifier.process_message(off_curve, this->rng()); }); + + const auto verifier_msg = verifier.process_message(share_p, this->rng()); + + auto bad_verifier_msg = verifier_msg; + bad_verifier_msg.pop_back(); + result.test_throws("Truncated verifier message is rejected", + [&]() { prover.process_message(bad_verifier_msg, this->rng()); }); + } + + // Malformed registration records are rejected + { + auto record_bytes = record.serialize(); + record_bytes.pop_back(); + result.test_throws("Truncated record is rejected", [&]() { + Botan::SPAKE2p::RegistrationRecord::deserialize(params, record_bytes); + }); + } + + result.end_timer(); + return result; + } + + Test::Result test_custom_params() { + Test::Result result("SPAKE2+ custom system parameters"); + result.start_timer(); + + const auto group = Botan::EC_Group::from_name("secp256r1"); + + const auto seed = this->rng().random_vec(32); + + if(!group.hash_to_curve_supported("SHA-256")) { + result.test_note("Skipping test due to missing hash2curve support"); + return result; + } + + const auto params = Botan::SPAKE2p::SystemParameters::custom(group, seed, "SHA-256"); + + const auto rfc_params = Botan::SPAKE2p::SystemParameters::rfc9383_p256_sha256(); + result.test_is_true("Custom M differs from RFC 9383 M", params.spake2p_m() != rfc_params.spake2p_m()); + result.test_is_true("Custom N differs from RFC 9383 N", params.spake2p_n() != rfc_params.spake2p_n()); + + const auto secret = random_secret(params); + const auto record = secret.registration_record(this->rng()); + + { + Botan::SPAKE2p::ProverContext prover(params, secret, {}, {}, {}); + Botan::SPAKE2p::VerifierContext verifier(params, record, {}, {}, {}); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + const auto confirm_p = prover.process_message(verifier_msg, this->rng()); + verifier.verify_confirmation(confirm_p); + + result.test_bin_eq("Shared secrets match", prover.shared_secret(), verifier.shared_secret()); + } + + // Peers using different seeds fail during confirmation + { + const auto other_seed = this->rng().random_vec(32); + const auto other_params = Botan::SPAKE2p::SystemParameters::custom(group, other_seed, "SHA-256"); + + Botan::SPAKE2p::ProverContext prover(params, secret, {}, {}, {}); + Botan::SPAKE2p::VerifierContext verifier(other_params, record, {}, {}, {}); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + result.test_throws( + "Seed mismatch is detected", [&]() { prover.process_message(verifier_msg, this->rng()); }); + } + + result.end_timer(); + return result; + } + + Botan::SPAKE2p::ProverSecret random_secret(const Botan::SPAKE2p::SystemParameters& params) { + auto w0 = Botan::EC_Scalar::random(params.group(), this->rng()); + auto w1 = Botan::EC_Scalar::random(params.group(), this->rng()); + return Botan::SPAKE2p::ProverSecret::from_prehashed(std::move(w0), std::move(w1)); + } +}; + +BOTAN_REGISTER_TEST("pake", "spake2p_rt", SPAKE2p_RT_Tests); + +class SPAKE2p_Password_Tests final : public Test { + public: + std::vector run() override { + Test::Result result("SPAKE2+ password registration"); + result.start_timer(); + + const auto params = Botan::SPAKE2p::SystemParameters::rfc9383_p256_sha256(); + + const std::vector prover_id = {'c', 'l', 'i', 'e', 'n', 't'}; + const std::vector verifier_id = {'s', 'e', 'r', 'v', 'e', 'r'}; + const auto salt = this->rng().random_vec(16); + + const std::string password = "correct horse battery staple"; + + const auto secret = + Botan::SPAKE2p::ProverSecret::from_password(params, password, prover_id, verifier_id, salt); + + // The record is derived from the same password independently + const auto record = Botan::SPAKE2p::RegistrationRecord::from_password( + params, password, prover_id, verifier_id, salt, this->rng()); + + { + Botan::SPAKE2p::ProverContext prover(params, secret, prover_id, verifier_id); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + const auto confirm_p = prover.process_message(verifier_msg, this->rng()); + verifier.verify_confirmation(confirm_p); + + result.test_bin_eq("Shared secrets match", prover.shared_secret(), verifier.shared_secret()); + } + + { + const auto wrong_secret = + Botan::SPAKE2p::ProverSecret::from_password(params, "hunter2", prover_id, verifier_id, salt); + + Botan::SPAKE2p::ProverContext prover(params, wrong_secret, prover_id, verifier_id); + Botan::SPAKE2p::VerifierContext verifier(params, record, prover_id, verifier_id); + + const auto verifier_msg = verifier.process_message(prover.generate_message(this->rng()), this->rng()); + result.test_throws( + "Wrong password is detected", [&]() { prover.process_message(verifier_msg, this->rng()); }); + } + + result.end_timer(); + return {result}; + } +}; + +BOTAN_REGISTER_TEST("pake", "spake2p_password", SPAKE2p_Password_Tests); + +#endif + +} // namespace + +} // namespace Botan_Tests