FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
crypto.hpp
Go to the documentation of this file.
1#pragma once
2// Cryptographic primitives needed by the venue connectors: HMAC-SHA256 (REST/WS signing),
3// HMAC-SHA384 (Gemini), SHA-1 (WebSocket accept key), SHA-256, base64 and base64url (own
4// implementation), CSPRNG bytes, Ed25519 (Binance session logon / Ed25519 API keys) and ECDSA P-256
5// (ES256 JWTs of Coinbase CDP keys). OpenSSL 3 underneath; the header keeps OpenSSL out of the
6// public interface.
7//
8// HmacSha256Key hashes the key pads once and keeps the inner/outer SHA-256 midstates, so a
9// signature costs the message blocks plus two compressions, with no allocation and no provider
10// lookup (OpenSSL's SHA-256 block function uses SHA-NI / AVX2 when the CPU has them).
11#include <array>
12#include <cstddef>
13#include <cstdint>
14#include <span>
15#include <string>
16#include <string_view>
17
18namespace fastmm::net {
19
20inline constexpr std::size_t kSha1Size = 20;
21inline constexpr std::size_t kSha256Size = 32;
22inline constexpr std::size_t kSha384Size = 48;
23inline constexpr std::size_t kSha512Size = 64;
25using Sha1Digest = std::array<std::uint8_t, kSha1Size>;
26using Sha256Digest = std::array<std::uint8_t, kSha256Size>;
28// Fixed-capacity, NUL-terminated hex string (no heap). `size()` excludes the terminator.
29template <std::size_t N>
30class FixedHexString {
31 public:
32 static constexpr std::size_t kCapacity = 2 * N;
33
34 constexpr FixedHexString() noexcept { data_[0] = '\0'; }
35 explicit FixedHexString(std::span<const std::uint8_t, N> bytes) noexcept {
36 static constexpr char kDigits[] = "0123456789abcdef";
37 for (std::size_t i = 0; i < N; ++i) {
38 data_[2 * i] = kDigits[bytes[i] >> 4];
39 data_[2 * i + 1] = kDigits[bytes[i] & 0x0F];
40 }
41 data_[kCapacity] = '\0';
42 }
43
44 std::string_view view() const noexcept { return {data_.data(), kCapacity}; }
45 const char* c_str() const noexcept { return data_.data(); }
46 constexpr std::size_t size() const noexcept { return kCapacity; }
47 operator std::string_view() const noexcept {
48 return view();
49 } // NOLINT(google-explicit-constructor)
50
51 private:
52 std::array<char, kCapacity + 1> data_{};
53};
54
55using HexSha256 = FixedHexString<kSha256Size>;
56using HexSha384 = FixedHexString<kSha384Size>;
59// HMAC-SHA256(key, data). `out` receives 32 bytes. Never throws; returns false on OpenSSL
60// failure (should not happen for valid inputs).
61bool hmac_sha256(std::string_view key,
62 std::string_view data,
63 std::span<std::uint8_t, kSha256Size> out) noexcept;
64HexSha256 hmac_sha256_hex(std::string_view key, std::string_view data) noexcept;
65// HMAC-SHA384(key, data) as lowercase hex (Gemini signs its requests and the WebSocket upgrade
66// with it). Control path: the key schedule is not kept. Never fails for valid inputs.
67HexSha384 hmac_sha384_hex(std::string_view key, std::string_view data) noexcept;
68// HMAC-SHA512(key, data) as lowercase hex (Gate APIv4 signs REST requests and WebSocket logins with
69// it) and SHA-512(data) as lowercase hex (the body digest inside a Gate signature string). Control
70// path: no key schedule is kept. Never fail for valid inputs.
71HexSha512 hmac_sha512_hex(std::string_view key, std::string_view data) noexcept;
72HexSha512 sha512_hex(std::string_view data) noexcept;
74// HMAC-SHA256 with the key schedule precomputed (see the header comment). Copyable; a
75// default-constructed key signs with the empty key.
76class HmacSha256Key {
77 public:
78 HmacSha256Key() noexcept : HmacSha256Key(std::string_view{}) {}
79 explicit HmacSha256Key(std::string_view key) noexcept;
81 void sign(std::string_view data, std::span<std::uint8_t, kSha256Size> out) const noexcept;
82 [[nodiscard]] HexSha256 sign_hex(std::string_view data) const noexcept;
84 // Opaque storage for one OpenSSL SHA256_CTX (112 bytes; checked in crypto.cpp).
85 static constexpr std::size_t kCtxBytes = 128;
86
87 private:
88 alignas(16) std::array<std::uint8_t, kCtxBytes> inner_{};
89 alignas(16) std::array<std::uint8_t, kCtxBytes> outer_{};
90};
91
92bool sha1(std::string_view data, std::span<std::uint8_t, kSha1Size> out) noexcept;
93bool sha256(std::string_view data, std::span<std::uint8_t, kSha256Size> out) noexcept;
95Sha1Digest sha1(std::string_view data) noexcept;
96Sha256Digest sha256(std::string_view data) noexcept;
98// RFC 4648 §4 base64 with '=' padding. `base64_encoded_size(n)` is the exact output size.
99constexpr std::size_t base64_encoded_size(std::size_t n) noexcept {
100 return ((n + 2) / 3) * 4;
102constexpr std::size_t base64_max_decoded_size(std::size_t n) noexcept {
103 return (n / 4) * 3 + 2;
105
106// Writes into `out` (must be >= base64_encoded_size(in.size())); returns bytes written or 0 if
107// the output is too small.
108std::size_t base64_encode(std::span<const std::uint8_t> in, std::span<char> out) noexcept;
109std::string base64_encode(std::span<const std::uint8_t> in);
110std::string base64_encode(std::string_view in);
112// RFC 4648 §5 base64url without padding, the encoding of JWS/JWT segments. Writes into `out`
113// (>= base64url_encoded_size(in.size())); returns bytes written, 0 if `out` is too small.
114constexpr std::size_t base64url_encoded_size(std::size_t n) noexcept {
115 return (n * 4 + 2) / 3;
117std::size_t base64url_encode(std::span<const std::uint8_t> in, std::span<char> out) noexcept;
118std::string base64url_encode(std::string_view in);
119// Unpadded base64url; SIZE_MAX on malformed input or insufficient output space.
120std::size_t base64url_decode(std::string_view in, std::span<std::uint8_t> out) noexcept;
121
122// Decodes standard base64 (padding required, no whitespace). Returns bytes written or
123// SIZE_MAX on malformed input / insufficient output space.
124std::size_t base64_decode(std::string_view in, std::span<std::uint8_t> out) noexcept;
125bool base64_decode(std::string_view in, std::string& out);
127// CSPRNG (RAND_bytes). Returns false if the RNG is unavailable.
128bool random_bytes(std::span<std::uint8_t> out) noexcept;
129
130// Ed25519 signature (base64) of `data` using a PEM-encoded PKCS#8 private key.
131// Returns an empty string if the key cannot be loaded or is not Ed25519. Parses the PEM on
132// every call: keep an Ed25519Key for repeated signing.
133std::string ed25519_sign_base64(std::string_view private_key_pem, std::string_view data);
134
135inline constexpr std::size_t kEd25519SignatureSize = 64;
136inline constexpr std::size_t kEd25519Base64Size = 88; // base64_encoded_size(64)
138// A parsed Ed25519 key (private, or public only for verification). Move-only.
139class Ed25519Key {
140 public:
141 Ed25519Key() noexcept = default;
142 ~Ed25519Key();
143 Ed25519Key(Ed25519Key&& o) noexcept : pkey_(o.pkey_), private_(o.private_) { o.pkey_ = nullptr; }
145 Ed25519Key(const Ed25519Key&) = delete;
146 Ed25519Key& operator=(const Ed25519Key&) = delete;
148 // PKCS#8 "PRIVATE KEY" PEM. Not valid() if the PEM is not an Ed25519 private key.
149 [[nodiscard]] static Ed25519Key from_private_pem(std::string_view pem);
150 // SubjectPublicKeyInfo "PUBLIC KEY" PEM (what Binance asks for when registering the key).
151 [[nodiscard]] static Ed25519Key from_public_pem(std::string_view pem);
152
153 [[nodiscard]] bool valid() const noexcept { return pkey_ != nullptr; }
154 [[nodiscard]] bool has_private() const noexcept { return pkey_ != nullptr && private_; }
156 // Writes the 64-byte signature; false without a private key.
157 bool sign(std::string_view data,
158 std::span<std::uint8_t, kEd25519SignatureSize> out) const noexcept;
159 // Base64 signature into `out` (>= kEd25519Base64Size); returns bytes written, 0 on failure.
160 std::size_t sign_base64(std::string_view data, std::span<char> out) const noexcept;
161 [[nodiscard]] std::string sign_base64(std::string_view data) const;
162 [[nodiscard]] bool verify(std::string_view data,
163 std::span<const std::uint8_t> signature) const noexcept;
164 // Verifies a base64 signature (Binance's `signature` parameter).
165 [[nodiscard]] bool verify_base64(std::string_view data,
166 std::string_view signature) const noexcept;
167 // "-----BEGIN PUBLIC KEY-----" PEM of the key (empty when invalid).
168 [[nodiscard]] std::string public_pem() const;
169
170 private:
171 void* pkey_ = nullptr; // EVP_PKEY*
172 bool private_ = false;
173};
174
175// ECDSA on P-256 with SHA-256 (JWS "ES256", RFC 7518 §3.4): the signature is r || s, 32 bytes
176// each, big-endian. Coinbase CDP API keys are such keys ("-----BEGIN EC PRIVATE KEY-----").
177// Signing draws a fresh random nonce (OpenSSL), so two signatures of the same data differ.
178// Move-only; control path (OpenSSL allocates per signature).
179inline constexpr std::size_t kEs256SignatureSize = 64;
180
182 public:
183 EcdsaP256Key() noexcept = default;
184 ~EcdsaP256Key();
185 EcdsaP256Key(EcdsaP256Key&& o) noexcept : pkey_(o.pkey_), private_(o.private_) {
186 o.pkey_ = nullptr;
188 EcdsaP256Key& operator=(EcdsaP256Key&& o) noexcept;
189 EcdsaP256Key(const EcdsaP256Key&) = delete;
192 // SEC 1 "EC PRIVATE KEY" or PKCS#8 "PRIVATE KEY" PEM. Not valid() unless it is a P-256 key.
193 [[nodiscard]] static EcdsaP256Key from_private_pem(std::string_view pem);
194 // SubjectPublicKeyInfo "PUBLIC KEY" PEM.
195 [[nodiscard]] static EcdsaP256Key from_public_pem(std::string_view pem);
196
197 [[nodiscard]] bool valid() const noexcept { return pkey_ != nullptr; }
198 [[nodiscard]] bool has_private() const noexcept { return pkey_ != nullptr && private_; }
200 // r || s of SHA-256(data); false without a private key.
201 bool sign(std::string_view data, std::span<std::uint8_t, kEs256SignatureSize> out) const noexcept;
202 [[nodiscard]] bool verify(std::string_view data,
203 std::span<const std::uint8_t> signature) const noexcept;
204 // "-----BEGIN PUBLIC KEY-----" PEM of the key (empty when invalid).
205 [[nodiscard]] std::string public_pem() const;
206
207 private:
208 void* pkey_ = nullptr; // EVP_PKEY*
209 bool private_ = false;
210};
211
212} // namespace fastmm::net
Definition crypto.hpp:183
std::string public_pem() const
"\--\---BEGIN PUBLIC KEY\--\---" PEM of the key (empty when invalid).
static EcdsaP256Key from_private_pem(std::string_view pem)
SEC 1 "EC PRIVATE KEY" or PKCS#8 "PRIVATE KEY" PEM. Not valid() unless it is a P-256 key.
bool verify(std::string_view data, std::span< const std::uint8_t > signature) const noexcept
EcdsaP256Key & operator=(EcdsaP256Key &&o) noexcept
bool has_private() const noexcept
Definition crypto.hpp:200
EcdsaP256Key() noexcept=default
bool sign(std::string_view data, std::span< std::uint8_t, kEs256SignatureSize > out) const noexcept
r || s of SHA-256(data); false without a private key.
static EcdsaP256Key from_public_pem(std::string_view pem)
SubjectPublicKeyInfo "PUBLIC KEY" PEM.
bool valid() const noexcept
Definition crypto.hpp:199
A parsed Ed25519 key (private, or public only for verification). Move-only.
Definition crypto.hpp:141
Ed25519Key() noexcept=default
std::string public_pem() const
"\--\---BEGIN PUBLIC KEY\--\---" PEM of the key (empty when invalid).
static Ed25519Key from_private_pem(std::string_view pem)
PKCS#8 "PRIVATE KEY" PEM. Not valid() if the PEM is not an Ed25519 private key.
bool verify_base64(std::string_view data, std::string_view signature) const noexcept
Verifies a base64 signature (Binance's signature parameter).
bool sign(std::string_view data, std::span< std::uint8_t, kEd25519SignatureSize > out) const noexcept
Writes the 64-byte signature; false without a private key.
static Ed25519Key from_public_pem(std::string_view pem)
SubjectPublicKeyInfo "PUBLIC KEY" PEM (what Binance asks for when registering the key).
bool valid() const noexcept
Definition crypto.hpp:155
bool has_private() const noexcept
Definition crypto.hpp:156
bool verify(std::string_view data, std::span< const std::uint8_t > signature) const noexcept
std::size_t sign_base64(std::string_view data, std::span< char > out) const noexcept
Base64 signature into out (>= kEd25519Base64Size); returns bytes written, 0 on failure.
Ed25519Key(Ed25519Key &&o) noexcept
Definition crypto.hpp:145
Ed25519Key & operator=(Ed25519Key &&o) noexcept
Fixed-capacity, NUL-terminated hex string (no heap). size() excludes the terminator.
Definition crypto.hpp:32
std::string_view view() const noexcept
Definition crypto.hpp:46
static constexpr std::size_t kCapacity
Definition crypto.hpp:34
constexpr std::size_t size() const noexcept
Definition crypto.hpp:48
const char * c_str() const noexcept
Definition crypto.hpp:47
constexpr FixedHexString() noexcept
Definition crypto.hpp:36
HMAC-SHA256 with the key schedule precomputed (see the header comment).
Definition crypto.hpp:78
HmacSha256Key() noexcept
Definition crypto.hpp:80
void sign(std::string_view data, std::span< std::uint8_t, kSha256Size > out) const noexcept
HexSha256 sign_hex(std::string_view data) const noexcept
static constexpr std::size_t kCtxBytes
Opaque storage for one OpenSSL SHA256_CTX (112 bytes; checked in crypto.cpp).
Definition crypto.hpp:87
Definition reactor.hpp:37
bool random_bytes(std::span< std::uint8_t > out) noexcept
CSPRNG (RAND_bytes). Returns false if the RNG is unavailable.
bool sha1(std::string_view data, std::span< std::uint8_t, kSha1Size > out) noexcept
bool sha256(std::string_view data, std::span< std::uint8_t, kSha256Size > out) noexcept
constexpr std::size_t kSha256Size
Definition crypto.hpp:23
constexpr std::size_t kEd25519Base64Size
base64_encoded_size(64)
Definition crypto.hpp:138
constexpr std::size_t kSha1Size
Definition crypto.hpp:22
constexpr std::size_t base64_max_decoded_size(std::size_t n) noexcept
Definition crypto.hpp:104
HexSha512 sha512_hex(std::string_view data) noexcept
std::array< std::uint8_t, kSha1Size > Sha1Digest
Definition crypto.hpp:27
constexpr std::size_t kSha512Size
Definition crypto.hpp:25
constexpr std::size_t base64_encoded_size(std::size_t n) noexcept
RFC 4648 §4 base64 with '=' padding. base64_encoded_size(n) is the exact output size.
Definition crypto.hpp:101
std::size_t base64url_decode(std::string_view in, std::span< std::uint8_t > out) noexcept
Unpadded base64url; SIZE_MAX on malformed input or insufficient output space.
constexpr std::size_t base64url_encoded_size(std::size_t n) noexcept
RFC 4648 §5 base64url without padding, the encoding of JWS/JWT segments.
Definition crypto.hpp:116
constexpr std::size_t kEs256SignatureSize
ECDSA on P-256 with SHA-256 (JWS "ES256", RFC 7518 §3.4): the signature is r || s,...
Definition crypto.hpp:181
constexpr std::size_t kEd25519SignatureSize
Definition crypto.hpp:137
std::string ed25519_sign_base64(std::string_view private_key_pem, std::string_view data)
Ed25519 signature (base64) of data using a PEM-encoded PKCS#8 private key.
bool hmac_sha256(std::string_view key, std::string_view data, std::span< std::uint8_t, kSha256Size > out) noexcept
HMAC-SHA256(key, data).
std::size_t base64_encode(std::span< const std::uint8_t > in, std::span< char > out) noexcept
Writes into out (must be >= base64_encoded_size(in.size())); returns bytes written or 0 if the output...
HexSha512 hmac_sha512_hex(std::string_view key, std::string_view data) noexcept
HMAC-SHA512(key, data) as lowercase hex (Gate APIv4 signs REST requests and WebSocket logins with it)...
std::size_t base64url_encode(std::span< const std::uint8_t > in, std::span< char > out) noexcept
FixedHexString< kSha256Size > HexSha256
Definition crypto.hpp:57
std::size_t base64_decode(std::string_view in, std::span< std::uint8_t > out) noexcept
Decodes standard base64 (padding required, no whitespace).
HexSha256 hmac_sha256_hex(std::string_view key, std::string_view data) noexcept
constexpr std::size_t kSha384Size
Definition crypto.hpp:24
std::array< std::uint8_t, kSha256Size > Sha256Digest
Definition crypto.hpp:28
HexSha384 hmac_sha384_hex(std::string_view key, std::string_view data) noexcept
HMAC-SHA384(key, data) as lowercase hex (Gemini signs its requests and the WebSocket upgrade with it)...
FixedHexString< kSha384Size > HexSha384
Definition crypto.hpp:58