Botan 3.13.0
Crypto and TLS for C&
spake2p.cpp
Go to the documentation of this file.
1/*
2* (C) 2024,2025,2026 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#include <botan/spake2p.h>
8
9#include <botan/exceptn.h>
10#include <botan/hash.h>
11#include <botan/hex.h>
12#include <botan/kdf.h>
13#include <botan/mac.h>
14#include <botan/mem_ops.h>
15#include <botan/pwdhash.h>
16#include <botan/internal/buffer_stuffer.h>
17#include <botan/internal/concat_util.h>
18#include <botan/internal/fmt.h>
19#include <botan/internal/loadstor.h>
20#include <botan/internal/mem_utils.h>
21
22namespace Botan::SPAKE2p {
23
24namespace {
25
26std::array<uint8_t, 8> le64_length(std::span<const uint8_t> data) {
27 return store_le(static_cast<uint64_t>(data.size()));
28}
29
30std::pair<EC_Scalar, EC_Scalar> derive_w0_w1(const SystemParameters& params,
31 std::string_view password,
32 std::span<const uint8_t> prover_id,
33 std::span<const uint8_t> verifier_id,
34 std::span<const uint8_t> salt) {
35 /*
36 * RFC 9383 Section 3.2
37 *
38 * w0s || w1s = PBKDF(len(pw) || pw ||
39 * len(idProver) || idProver ||
40 * len(idVerifier) || idVerifier)
41 * w0 = w0s mod p
42 * w1 = w1s mod p
43 */
44 secure_vector<uint8_t> pbkdf_input(3 * 8 + password.size() + prover_id.size() + verifier_id.size());
45 BufferStuffer stuffer(pbkdf_input);
46
47 auto append_with_le64_length = [&](std::span<const uint8_t> data) {
48 stuffer.append(le64_length(data));
49 stuffer.append(data);
50 };
51
52 append_with_le64_length(as_span_of_bytes(password));
53 append_with_le64_length(prover_id);
54 append_with_le64_length(verifier_id);
55 BOTAN_ASSERT_NOMSG(stuffer.full());
56
57 /*
58 * RFC 9106 Section 4
59 *
60 * If much less memory is available, a uniformly safe option is
61 * Argon2id with t=3 iterations, p=4 lanes, m=2^(16) (64 MiB of RAM)
62 */
63 auto pwhash = PasswordHashFamily::create_or_throw("Argon2id")->from_params(64 * 1024, 3, 4);
64
65 /*
66 * RFC 9383 Section 3.2
67 *
68 * To control bias, each half must be of length at least
69 * ceil(log2(p)) + k bits, with k >= 64
70 */
71 const size_t half_len = params.group().get_order_bytes() + 16;
72
73 secure_vector<uint8_t> w0s_w1s(2 * half_len);
74 const std::string_view pbkdf_input_sv(cast_uint8_ptr_to_char(pbkdf_input.data()), pbkdf_input.size());
75 pwhash->hash(w0s_w1s, pbkdf_input_sv, salt);
76
77 auto w0 = EC_Scalar::from_bytes_mod_order(params.group(), std::span{w0s_w1s}.first(half_len));
78 auto w1 = EC_Scalar::from_bytes_mod_order(params.group(), std::span{w0s_w1s}.last(half_len));
79
80 return {std::move(w0), std::move(w1)};
81}
82
83struct SessionKeys {
84 secure_vector<uint8_t> shared_key;
85 std::vector<uint8_t> confirm_p;
86 std::vector<uint8_t> confirm_v;
87};
88
89SessionKeys spake2p_key_schedule(const SystemParameters& params,
90 std::span<const uint8_t> context,
91 std::span<const uint8_t> prover_id,
92 std::span<const uint8_t> verifier_id,
93 std::span<const uint8_t> share_p,
94 std::span<const uint8_t> share_v,
95 const EC_AffinePoint& z,
96 const EC_AffinePoint& v,
97 const EC_Scalar& w0) {
98 auto hash = HashFunction::create_or_throw(params.hash_function());
99
100 auto hash_with_le64_length = [&](std::span<const uint8_t> data) {
101 hash->update(le64_length(data));
102 hash->update(data);
103 };
104
105 /*
106 * RFC 9383 Section 3.3
107 *
108 * TT = len(Context) || Context
109 * || len(idProver) || idProver
110 * || len(idVerifier) || idVerifier
111 * || len(M) || M
112 * || len(N) || N
113 * || len(shareP) || shareP
114 * || len(shareV) || shareV
115 * || len(Z) || Z
116 * || len(V) || V
117 * || len(w0) || w0
118 */
119 hash_with_le64_length(context);
120 hash_with_le64_length(prover_id);
121 hash_with_le64_length(verifier_id);
122 hash_with_le64_length(params.spake2p_m().serialize_uncompressed());
123 hash_with_le64_length(params.spake2p_n().serialize_uncompressed());
124 hash_with_le64_length(share_p);
125 hash_with_le64_length(share_v);
126 hash_with_le64_length(z.serialize_uncompressed());
127 hash_with_le64_length(v.serialize_uncompressed());
128 hash_with_le64_length(w0.serialize());
129
130 const auto k_main = hash->final();
131
132 /*
133 * RFC 9383 Section 3.4
134 *
135 * K_main = Hash(TT)
136 * K_confirmP || K_confirmV = KDF(nil, K_main, "ConfirmationKeys")
137 * K_shared = KDF(nil, K_main, "SharedKey")
138 *
139 * confirmP = MAC(K_confirmP, shareV)
140 * confirmV = MAC(K_confirmV, shareP)
141 */
142 auto kdf = KDF::create_or_throw(fmt("HKDF({})", params.hash_function()));
143 auto mac = MessageAuthenticationCode::create_or_throw(fmt("HMAC({})", params.hash_function()));
144
145 const size_t mac_key_len = hash->output_length();
146 const auto confirm_keys = kdf->derive_key<secure_vector<uint8_t>>(2 * mac_key_len, k_main, "", "ConfirmationKeys");
147
148 SessionKeys keys;
149 keys.shared_key = kdf->derive_key<secure_vector<uint8_t>>(hash->output_length(), k_main, "", "SharedKey");
150
151 mac->set_key(std::span{confirm_keys}.first(mac_key_len));
152 mac->update(share_v);
153 keys.confirm_p = mac->final_stdvec();
154
155 mac->set_key(std::span{confirm_keys}.last(mac_key_len));
156 mac->update(share_p);
157 keys.confirm_v = mac->final_stdvec();
158
159 return keys;
160}
161
162std::tuple<EC_Group, EC_AffinePoint, EC_AffinePoint> spake2p_group_params(std::string_view group_name,
163 std::string_view m_hex,
164 std::string_view n_hex) {
165 auto group = EC_Group::from_name(group_name);
166 EC_AffinePoint m(group, hex_decode(m_hex));
167 EC_AffinePoint n(group, hex_decode(n_hex));
168 return {std::move(group), std::move(m), std::move(n)};
169}
170
171// The M/N constants from RFC 9383 Section 4
172
173constexpr std::string_view SPAKE2P_P256_M = "02886e2f97ace46e55ba9dd7242579f2993b64e16ef3dcab95afd497333d8fa12f";
174
175constexpr std::string_view SPAKE2P_P256_N = "03d8bbd6c639c62937b04d997f38c3770719c629d7014d49a24b4f98baa1292b49";
176
177constexpr std::string_view SPAKE2P_P384_M =
178 "030ff0895ae5ebf6187080a82d82b42e2765e3b2f8749c7e05eba366434b363d3dc36f15314739074d2eb8613fceec2853";
179
180constexpr std::string_view SPAKE2P_P384_N =
181 "02c72cf2e390853a1c1c4ad816a62fd15824f56078918f43f922ca21518f9c543bb252c5490214cf9aa3f0baab4b665c10";
182
183constexpr std::string_view SPAKE2P_P521_M =
184 "02003f06f38131b2ba2600791e82488e8d20ab889af753a41806c5db18d37d85608cfae06b82e4a72cd744c719193562a653ea1f119ee"
185 "f9356907edc9b56979962d7aa";
186
187constexpr std::string_view SPAKE2P_P521_N =
188 "0200c7924b9ec017f3094562894336a53c50167ba8c5963876880542bc669e494b2532d76c5b53dfb349fdf69154b9e0048c58a42e8ed"
189 "04cef052a3bc349d95575cd25";
190
191} // namespace
192
193SystemParameters::SystemParameters(EC_Group group, EC_AffinePoint m, EC_AffinePoint n, std::string_view hash_fn) :
194 m_group(std::move(group)), m_spake2p_m(std::move(m)), m_spake2p_n(std::move(n)), m_hash_fn(hash_fn) {}
195
197 auto [group, m, n] = spake2p_group_params("secp256r1", SPAKE2P_P256_M, SPAKE2P_P256_N);
198 return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-256");
199}
200
202 auto [group, m, n] = spake2p_group_params("secp256r1", SPAKE2P_P256_M, SPAKE2P_P256_N);
203 return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512");
204}
205
207 auto [group, m, n] = spake2p_group_params("secp384r1", SPAKE2P_P384_M, SPAKE2P_P384_N);
208 return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-256");
209}
210
212 auto [group, m, n] = spake2p_group_params("secp384r1", SPAKE2P_P384_M, SPAKE2P_P384_N);
213 return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512");
214}
215
217 auto [group, m, n] = spake2p_group_params("secp521r1", SPAKE2P_P521_M, SPAKE2P_P521_N);
218 return SystemParameters(std::move(group), std::move(m), std::move(n), "SHA-512");
219}
220
222 std::span<const uint8_t> seed,
223 std::string_view hash_fn) {
224 BOTAN_ARG_CHECK(group.has_cofactor() == false, "SPAKE2+ is not supported for groups with a cofactor");
225
226 if(!group.hash_to_curve_supported(hash_fn)) {
227 throw Not_Implemented("SPAKE2+ custom params require hash2curve support which is not available for this curve");
228 }
229
230 auto m = EC_AffinePoint::hash_to_curve_ro(group, hash_fn, seed, "SPAKE2+ M");
231 auto n = EC_AffinePoint::hash_to_curve_ro(group, hash_fn, seed, "SPAKE2+ N");
232
233 return SystemParameters(group, std::move(m), std::move(n), hash_fn);
234}
235
237 return 1 + 2 * m_group.get_p_bytes();
238}
239
241 if(m_hash_fn == "SHA-256") {
242 return 32;
243 } else if(m_hash_fn == "SHA-384") {
244 return 48;
245 } else if(m_hash_fn == "SHA-512") {
246 return 64;
247 } else {
248 return HashFunction::create_or_throw(m_hash_fn)->output_length();
249 }
250}
251
253 std::string_view password,
254 std::span<const uint8_t> prover_id,
255 std::span<const uint8_t> verifier_id,
256 std::span<const uint8_t> salt,
258 return ProverSecret::from_password(params, password, prover_id, verifier_id, salt).registration_record(rng);
259}
260
261RegistrationRecord RegistrationRecord::deserialize(const SystemParameters& params, std::span<const uint8_t> record) {
262 const size_t scalar_len = params.group().get_order_bytes();
263 const size_t point_len = params.share_size();
264
265 if(record.size() != scalar_len + point_len) {
266 throw Decoding_Error("Invalid length for SPAKE2+ registration record");
267 }
268
269 auto w0 = EC_Scalar::deserialize(params.group(), record.first(scalar_len));
270 auto l = EC_AffinePoint::deserialize_uncompressed(params.group(), record.last(point_len));
271
272 if(!w0 || !l) {
273 throw Decoding_Error("Invalid SPAKE2+ registration record");
274 }
275
276 return RegistrationRecord(std::move(*w0), std::move(*l));
277}
278
280 return concat<secure_vector<uint8_t>>(m_w0.serialize(), m_l.serialize_uncompressed());
281}
282
284 std::string_view password,
285 std::span<const uint8_t> prover_id,
286 std::span<const uint8_t> verifier_id,
287 std::span<const uint8_t> salt) {
288 auto [w0, w1] = derive_w0_w1(params, password, prover_id, verifier_id, salt);
289 return ProverSecret(std::move(w0), std::move(w1));
290}
291
293 return ProverSecret(std::move(w0), std::move(w1));
294}
295
296ProverSecret ProverSecret::deserialize(const SystemParameters& params, std::span<const uint8_t> secret) {
297 if(auto w0_w1 = EC_Scalar::deserialize_pair(params.group(), secret)) {
298 return ProverSecret(std::move(w0_w1->first), std::move(w0_w1->second));
299 } else {
300 throw Decoding_Error("Invalid SPAKE2+ prover secret");
301 }
302}
303
307
309 // RFC 9383 Section 3.2: "the registration record L=w1*P"
310 return RegistrationRecord(m_w0, EC_AffinePoint::g_mul(m_w1, rng));
311}
312
314 const ProverSecret& secret,
315 std::span<const uint8_t> prover_id,
316 std::span<const uint8_t> verifier_id,
317 std::span<const uint8_t> context) :
318 m_params(params),
319 m_secret(secret),
320 m_prover_id(prover_id.begin(), prover_id.end()),
321 m_verifier_id(verifier_id.begin(), verifier_id.end()),
322 m_context(context.begin(), context.end()) {}
323
325 BOTAN_STATE_CHECK(m_state == State::Initial);
326
327 const auto x = EC_Scalar::random(m_params.group(), rng);
328 const auto g = EC_AffinePoint::generator(m_params.group());
329
330 // RFC 9383 Section 3.3: X = x*P + w0*M
331 if(auto share_p = EC_AffinePoint::mul_px_qy(g, x, m_params.spake2p_m(), m_secret.m_w0, rng)) {
332 m_our_message = std::make_pair(share_p->serialize_uncompressed(), x);
333 m_state = State::ShareGenerated;
334 return m_our_message->first;
335 } else {
336 throw Internal_Error("Computed the identity element during SPAKE2+ key exchange");
337 }
338}
339
340std::vector<uint8_t> ProverContext::process_message(std::span<const uint8_t> peer_message, RandomNumberGenerator& rng) {
341 BOTAN_STATE_CHECK(m_state == State::ShareGenerated);
342
343 const size_t share_size = m_params.share_size();
344 const size_t confirm_size = m_params.confirmation_size();
345
346 if(peer_message.size() != share_size + confirm_size) {
347 throw Decoding_Error("Invalid length for SPAKE2+ verifier message");
348 }
349
350 const auto share_v = peer_message.first(share_size);
351 const auto confirm_v = peer_message.last(confirm_size);
352
353 const auto y = EC_AffinePoint::deserialize_uncompressed(m_params.group(), share_v);
354 if(!y) {
355 throw Decoding_Error("Invalid SPAKE2+ key share");
356 }
357
358 const auto& w0 = m_secret.m_w0;
359 const auto& w1 = m_secret.m_w1;
360 const auto& n = m_params.spake2p_n();
361 const auto& x = m_our_message->second;
362
363 // RFC 9383 Section 3.3: Z = h*x*(Y - w0*N), V = h*w1*(Y - w0*N)
364 const auto z = EC_AffinePoint::mul_px_qy(*y, x, n, (x * w0).negate(), rng);
365 const auto v = EC_AffinePoint::mul_px_qy(*y, w1, n, (w1 * w0).negate(), rng);
366
367 if(!z || !v) {
368 throw Decoding_Error("Invalid SPAKE2+ key share");
369 }
370
371 auto keys =
372 spake2p_key_schedule(m_params, m_context, m_prover_id, m_verifier_id, m_our_message->first, share_v, *z, *v, w0);
373
374 if(!constant_time_compare(keys.confirm_v, confirm_v)) {
375 m_our_message.reset();
376 m_state = State::Failed;
377 throw Invalid_Authentication_Tag("SPAKE2+ key confirmation failed");
378 }
379
380 m_shared_secret = std::move(keys.shared_key);
381 m_our_message.reset();
382 m_state = State::Complete;
383
384 return keys.confirm_p;
385}
386
388 BOTAN_STATE_CHECK(m_state == State::Complete);
389 return m_shared_secret;
390}
391
393 const RegistrationRecord& record,
394 std::span<const uint8_t> prover_id,
395 std::span<const uint8_t> verifier_id,
396 std::span<const uint8_t> context) :
397 m_params(params),
398 m_record(record),
399 m_prover_id(prover_id.begin(), prover_id.end()),
400 m_verifier_id(verifier_id.begin(), verifier_id.end()),
401 m_context(context.begin(), context.end()) {}
402
403std::vector<uint8_t> VerifierContext::process_message(std::span<const uint8_t> peer_message,
405 BOTAN_STATE_CHECK(m_state == State::Initial);
406
407 const auto x = EC_AffinePoint::deserialize_uncompressed(m_params.group(), peer_message);
408 if(!x) {
409 throw Decoding_Error("Invalid SPAKE2+ key share");
410 }
411
412 const auto& w0 = m_record.m_w0;
413
414 const auto y = EC_Scalar::random(m_params.group(), rng);
415 const auto g = EC_AffinePoint::generator(m_params.group());
416
417 // RFC 9383 Section 3.3: Y = y*P + w0*N
418 const auto share_v_pt = EC_AffinePoint::mul_px_qy(g, y, m_params.spake2p_n(), w0, rng);
419 if(!share_v_pt) {
420 throw Internal_Error("Computed the identity element during SPAKE2+ key exchange");
421 }
422 const auto share_v = share_v_pt->serialize_uncompressed();
423
424 // RFC 9383 Section 3.3: Z = h*y*(X - w0*M), V = h*y*L
425 const auto z = EC_AffinePoint::mul_px_qy(*x, y, m_params.spake2p_m(), (y * w0).negate(), rng);
426 if(!z) {
427 throw Decoding_Error("Invalid SPAKE2+ key share");
428 }
429 const auto v = m_record.m_l.mul(y, rng);
430
431 auto keys = spake2p_key_schedule(m_params, m_context, m_prover_id, m_verifier_id, peer_message, share_v, *z, v, w0);
432
433 m_shared_secret = std::move(keys.shared_key);
434 m_expected_confirmation = std::move(keys.confirm_p);
435 m_state = State::Responded;
436
437 return concat<std::vector<uint8_t>>(share_v, keys.confirm_v);
438}
439
440void VerifierContext::verify_confirmation(std::span<const uint8_t> confirmation) {
441 BOTAN_STATE_CHECK(m_state == State::Responded);
442
443 if(!constant_time_compare(m_expected_confirmation, confirmation)) {
444 m_expected_confirmation.clear();
445 m_shared_secret.clear();
446 m_state = State::Failed;
447 throw Invalid_Authentication_Tag("SPAKE2+ key confirmation failed");
448 }
449
450 m_expected_confirmation.clear();
451 m_state = State::Complete;
452}
453
455 BOTAN_STATE_CHECK(m_state == State::Responded);
456
457 m_expected_confirmation.clear();
458 m_state = State::Complete;
459}
460
462 BOTAN_STATE_CHECK(m_state == State::Complete);
463 return m_shared_secret;
464}
465
466} // namespace Botan::SPAKE2p
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
Helper class to ease in-place marshalling of concatenated fixed-length values.
constexpr void append(std::span< const uint8_t > buffer)
constexpr bool full() const
static EC_AffinePoint hash_to_curve_ro(const EC_Group &group, std::string_view hash_fn, std::span< const uint8_t > input, std::span< const uint8_t > domain_sep)
static std::optional< EC_AffinePoint > deserialize_uncompressed(const EC_Group &group, std::span< const uint8_t > bytes)
static std::optional< EC_AffinePoint > mul_px_qy(const EC_AffinePoint &p, const EC_Scalar &x, const EC_AffinePoint &q, const EC_Scalar &y, RandomNumberGenerator &rng)
static EC_AffinePoint g_mul(const EC_Scalar &scalar, RandomNumberGenerator &rng)
Multiply by the group generator returning a complete point.
T serialize_uncompressed() const
Definition ec_apoint.h:232
static EC_AffinePoint generator(const EC_Group &group)
Return the standard group generator.
Definition ec_apoint.cpp:84
static EC_Group from_name(std::string_view name)
Definition ec_group.cpp:478
size_t get_order_bytes() const
Definition ec_group.cpp:666
static std::optional< EC_Scalar > deserialize(const EC_Group &group, std::span< const uint8_t > bytes)
static T serialize_pair(const EC_Scalar &r, const EC_Scalar &s)
Definition ec_scalar.h:156
T serialize() const
Definition ec_scalar.h:139
static EC_Scalar from_bytes_mod_order(const EC_Group &group, std::span< const uint8_t > bytes)
Definition ec_scalar.cpp:56
static EC_Scalar random(const EC_Group &group, RandomNumberGenerator &rng)
Definition ec_scalar.cpp:64
static std::optional< std::pair< EC_Scalar, EC_Scalar > > deserialize_pair(const EC_Group &group, std::span< const uint8_t > bytes)
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
static std::unique_ptr< KDF > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition kdf.cpp:208
static std::unique_ptr< MessageAuthenticationCode > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition mac.cpp:149
static std::unique_ptr< PasswordHashFamily > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition pwdhash.cpp:123
std::vector< uint8_t > process_message(std::span< const uint8_t > peer_message, RandomNumberGenerator &rng)
Definition spake2p.cpp:340
std::vector< uint8_t > generate_message(RandomNumberGenerator &rng)
Definition spake2p.cpp:324
secure_vector< uint8_t > shared_secret() const
Definition spake2p.cpp:387
ProverContext(const SystemParameters &params, const ProverSecret &secret, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > context={})
Definition spake2p.cpp:313
static ProverSecret from_prehashed(EC_Scalar w0, EC_Scalar w1)
Definition spake2p.cpp:292
static ProverSecret from_password(const SystemParameters &params, std::string_view password, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > salt)
Definition spake2p.cpp:283
static ProverSecret deserialize(const SystemParameters &params, std::span< const uint8_t > secret)
Definition spake2p.cpp:296
RegistrationRecord registration_record(RandomNumberGenerator &rng) const
Definition spake2p.cpp:308
secure_vector< uint8_t > serialize() const
Definition spake2p.cpp:304
secure_vector< uint8_t > serialize() const
Definition spake2p.cpp:279
static RegistrationRecord deserialize(const SystemParameters &params, std::span< const uint8_t > record)
Definition spake2p.cpp:261
static RegistrationRecord from_password(const SystemParameters &params, std::string_view password, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > salt, RandomNumberGenerator &rng)
Definition spake2p.cpp:252
const EC_Group & group() const
Definition spake2p.h:115
const EC_AffinePoint & spake2p_n() const
Definition spake2p.h:125
static SystemParameters rfc9383_p256_sha512()
Definition spake2p.cpp:201
static SystemParameters rfc9383_p521_sha512()
Definition spake2p.cpp:216
static SystemParameters rfc9383_p384_sha256()
Definition spake2p.cpp:206
const std::string & hash_function() const
Definition spake2p.h:130
static SystemParameters rfc9383_p256_sha256()
Definition spake2p.cpp:196
static SystemParameters rfc9383_p384_sha512()
Definition spake2p.cpp:211
const EC_AffinePoint & spake2p_m() const
Definition spake2p.h:120
static SystemParameters custom(const EC_Group &group, std::span< const uint8_t > seed, std::string_view hash_fn)
Definition spake2p.cpp:221
std::vector< uint8_t > process_message(std::span< const uint8_t > peer_message, RandomNumberGenerator &rng)
Definition spake2p.cpp:403
VerifierContext(const SystemParameters &params, const RegistrationRecord &record, std::span< const uint8_t > prover_id, std::span< const uint8_t > verifier_id, std::span< const uint8_t > context={})
Definition spake2p.cpp:392
secure_vector< uint8_t > shared_secret() const
Definition spake2p.cpp:461
void verify_confirmation(std::span< const uint8_t > confirmation)
Definition spake2p.cpp:440
std::span< const uint8_t > as_span_of_bytes(const char *s, size_t len)
Definition mem_utils.h:59
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
size_t hex_decode(uint8_t output[], const char input[], size_t input_length, size_t &input_consumed, bool ignore_ws)
Definition hex.cpp:75
const char * cast_uint8_ptr_to_char(const uint8_t *b)
Definition mem_ops.h:323
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
bool constant_time_compare(std::span< const uint8_t > x, std::span< const uint8_t > y)
Definition mem_ops.cpp:17