dice-hash
Hash function for stl types and container
Loading...
Searching...
No Matches
Blake2Xb.hpp
1#ifndef DICE_HASH_BLAKE2XB_HPP
2#define DICE_HASH_BLAKE2XB_HPP
3
9#if __has_include(<sodium.h>)
10
11#include <dice/hash/blake/Blake2b.hpp>
12
13#include <algorithm>
14#include <bit>
15#include <cstddef>
16#include <cstdint>
17#include <span>
18#include <stdexcept>
19#include <limits>
20#include <cassert>
21
22#include <sodium.h>
23
24namespace dice::hash::blake2xb {
25
26 namespace detail {
27 template<typename T>
28 inline std::byte const *byte_iter(T const &value) noexcept {
29 return reinterpret_cast<std::byte const *>(&value);
30 }
31
32 template<typename T>
33 inline std::byte *byte_iter_mut(T &value) noexcept {
34 return reinterpret_cast<std::byte *>(&value);
35 }
36
37 template<typename T>
38 inline T little_endian(T const &value) noexcept {
39 if constexpr (std::endian::native == std::endian::little) {
40 return value;
41 } else {
42 auto const *input_beg = byte_iter(value);
43 auto const *input_end = input_beg + sizeof(T);
44 T output;
45
46 std::reverse_copy(input_beg, input_end, byte_iter_mut(output));
47 return output;
48 }
49 }
50 } // namespace detail
51
52 inline constexpr size_t unknown_output_extent = 0;
53 inline constexpr size_t min_output_extent = 1;
54 inline constexpr size_t max_output_extent = std::numeric_limits<uint32_t>::max() - 1;
55 using ::dice::hash::blake2b::dynamic_output_extent;
56
57 using ::dice::hash::blake2b::salt_extent;
58 using ::dice::hash::blake2b::default_salt;
59
60 using ::dice::hash::blake2b::personality_extent;
61 using ::dice::hash::blake2b::default_personality;
62
63 using ::dice::hash::blake2b::min_key_extent;
64 using ::dice::hash::blake2b::max_key_extent;
65 using ::dice::hash::blake2b::default_key_extent;
66
67 using ::dice::hash::blake2b::generate_key;
68
72 template<size_t OutputExtent = dynamic_output_extent>
73 requires (OutputExtent == dynamic_output_extent || (OutputExtent >= min_output_extent && OutputExtent <= max_output_extent))
74 struct Blake2Xb {
78 static constexpr size_t output_extent = OutputExtent;
79
80 static constexpr size_t min_key_extent = ::dice::hash::blake2xb::min_key_extent;
81 static constexpr size_t max_key_extent = ::dice::hash::blake2xb::max_key_extent;
82 static constexpr size_t default_key_extent = ::dice::hash::blake2xb::default_key_extent;
83
84 private:
85 static constexpr uint32_t unknown_output_extend_magic = std::numeric_limits<uint32_t>::max();
86
87 struct ParamBlock {
88 uint8_t digest_len;
89 uint8_t key_len;
90 uint8_t fanout;
91 uint8_t depth;
92 uint32_t leaf_len;
93 uint32_t node_off;
94 uint32_t xof_digest_len;
95 uint8_t node_depth;
96 uint8_t inner_len;
97 uint8_t reserved[14];
98 uint8_t salt[salt_extent];
99 uint8_t personality[personality_extent];
100 };
101
102 ParamBlock param_{};
103 crypto_generichash_blake2b_state state_;
104
105 void init_state(std::span<std::byte const> key) {
106 static constexpr std::array<uint64_t, 8> init_vec{0x6a09e667f3bcc908ULL,
107 0xbb67ae8584caa73bULL,
108 0x3c6ef372fe94f82bULL,
109 0xa54ff53a5f1d36f1ULL,
110 0x510e527fade682d1ULL,
111 0x9b05688c2b3e6c1fULL,
112 0x1f83d9abfb41bd6bULL,
113 0x5be0cd19137e2179ULL};
114
115#if SODIUM_LIBRARY_VERSION_MAJOR > 10 || (SODIUM_LIBRARY_VERSION_MAJOR == 10 && SODIUM_LIBRARY_VERSION_MINOR >= 2)
116 // In libsodium 1.0.17, the crypto_generichash_blake2b_state struct was made
117 // opaque. We have to copy the internal definition of the real struct here
118 // so we can properly initialize it.
119 // see https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_generichash/blake2b/ref/blake2.h
120 struct Blake2bState {
121 uint64_t h[8];
122 uint64_t t[2];
123 uint64_t f[2];
124 uint8_t buf[256];
125 size_t buflen;
126 uint8_t last_node;
127 };
128 auto *state = reinterpret_cast<Blake2bState *>(&state_);
129#else
130 auto *state = &state_;
131#endif
132
133 static_assert(sizeof(ParamBlock) == sizeof(init_vec));
134 std::span<uint64_t const, init_vec.size()> param{*reinterpret_cast<uint64_t const(*)[init_vec.size()]>(&param_)};
135
136 // state->h = init_vec xor param
137 for (size_t ix = 0; ix < init_vec.size(); ++ix) {
138 state->h[ix] = init_vec[ix] ^ detail::little_endian(param[ix]);
139 }
140
141 // zero everything between state->t and state->last_node (inclusive)
142 std::fill(detail::byte_iter_mut(state->t),
143 detail::byte_iter_mut(state->last_node) + sizeof(state->last_node),
144 std::byte{});
145
146 if (!key.empty()) {
147 std::array<std::byte, 128> block; {
148 auto write_end = std::copy(key.begin(), key.end(), block.begin());
149 std::fill(write_end, block.end(), std::byte{});
150 }
151
152 digest(block);
153 sodium_memzero(block.data(), block.size());// erase key from stack
154 }
155 }
156
157 struct PrivateTag {};
158 static constexpr PrivateTag private_tag{};
159
160 Blake2Xb(PrivateTag,
161 size_t output_len,
162 std::span<std::byte const> key,
163 std::span<std::byte const, salt_extent> salt,
164 std::span<std::byte const, personality_extent> personality) {
165
166 if (output_len == 0) {
167 output_len = unknown_output_extend_magic;
168 } else if (output_len > max_output_extent) {
169 throw std::runtime_error{"Output length too large"};
170 }
171
172 if (!key.empty()) {
173 if (key.size() < min_key_extent || key.size() > max_key_extent) {
174 throw std::runtime_error{"Invalid blake2b key size"};
175 }
176 }
177
178 if (auto const res = sodium_init(); res == -1) {
179 throw std::runtime_error{"Could not initialize sodium"};
180 }
181
182 param_.digest_len = crypto_generichash_blake2b_BYTES_MAX;
183 param_.key_len = static_cast<uint8_t>(key.size());
184 param_.fanout = 1;
185 param_.depth = 1;
186 param_.xof_digest_len = detail::little_endian(output_len);
187
188 std::copy(salt.begin(), salt.end(), detail::byte_iter_mut(param_.salt));
189 std::copy(personality.begin(), personality.end(), detail::byte_iter_mut(param_.personality));
190
191 init_state(key);
192 }
193
194 public:
202 explicit Blake2Xb(size_t output_len,
203 std::span<std::byte const> key = {},
204 std::span<std::byte const, salt_extent> salt = default_salt,
205 std::span<std::byte const, personality_extent> personality = default_personality) /*noexcept(sodium is initialized && output_len is within size constraints && key.size() is within size constaints)*/
206 requires (output_extent == dynamic_output_extent)
207 : Blake2Xb{private_tag, output_len, key, salt, personality} {
208 }
209
216 explicit Blake2Xb(std::span<std::byte const> key = {},
217 std::span<std::byte const, salt_extent> salt = default_salt,
218 std::span<std::byte const, personality_extent> personality = default_personality) /*noexcept(sodium is initialized && key.size() is within size constraints)*/
219 : Blake2Xb{private_tag, output_extent == dynamic_output_extent ? 0 : output_extent, key, salt, personality} {
220 }
221
225 void digest(std::span<std::byte const> data) noexcept {
226 auto const res = crypto_generichash_blake2b_update(&state_,
227 reinterpret_cast<unsigned char const *>(data.data()),
228 data.size());
229 // cannot fail, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L263
230 assert(res == 0);
231 (void) res;
232 }
233
239 void finish(std::span<std::byte, output_extent> out) && noexcept(output_extent != dynamic_output_extent) {
240 if constexpr (output_extent == dynamic_output_extent) {
241 auto const expected_out_len = detail::little_endian(param_.xof_digest_len);
242 if (expected_out_len != unknown_output_extend_magic && out.size() != expected_out_len) {
243 // was known in advance and does not match
244 throw std::runtime_error{"Buffer length must match output length"};
245 }
246 }
247
248 std::array<std::byte, crypto_generichash_blake2b_BYTES_MAX> h0;
249 auto res = crypto_generichash_blake2b_final(&state_,
250 reinterpret_cast<unsigned char *>(h0.data()),
251 h0.size());
252 // cannot fail on proper use, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L299
253 assert(res == 0);
254
255 param_.key_len = 0;
256 param_.fanout = 0;
257 param_.depth = 0;
258 param_.leaf_len = detail::little_endian(static_cast<uint32_t>(crypto_generichash_blake2b_BYTES_MAX));
259 param_.xof_digest_len = detail::little_endian(static_cast<uint32_t>(out.size()));
260 param_.node_depth = 0;
261 param_.inner_len = crypto_generichash_blake2b_BYTES_MAX;
262
263 size_t pos = 0;
264 size_t remaining = out.size();
265
266 while (remaining > 0) {
267 param_.node_off = detail::little_endian(static_cast<uint32_t>(pos / crypto_generichash_blake2b_BYTES_MAX));
268
269 size_t const len = std::min(static_cast<size_t>(crypto_generichash_blake2b_BYTES_MAX), remaining);
270 param_.digest_len = static_cast<uint8_t>(len);
271
272 init_state({});
273 res = crypto_generichash_blake2b_update(&state_,
274 reinterpret_cast<unsigned char const *>(h0.data()),
275 h0.size());
276 assert(res == 0);
277
278 res = crypto_generichash_blake2b_final(&state_,
279 reinterpret_cast<unsigned char *>(out.data()) + pos,
280 len);
281 assert(res == 0);
282
283 pos += len;
284 remaining -= len;
285 }
286
287 (void) res;
288 }
289
295 [[nodiscard]] constexpr size_t concrete_output_extent() const noexcept {
296 if constexpr (output_extent == dynamic_output_extent) {
297 auto const expected_out_len = detail::little_endian(param_.xof_digest_len);
298 if (expected_out_len == unknown_output_extend_magic) {
299 return unknown_output_extent;
300 } else {
301 return expected_out_len;
302 }
303 } else {
304 return output_extent;
305 }
306 }
307
311 static void hash_single(std::span<std::byte const> data,
312 std::span<std::byte, output_extent> out,
313 std::span<std::byte const> key = {},
314 std::span<std::byte const, salt_extent> salt = default_salt,
315 std::span<std::byte const, personality_extent> personality = default_personality) /*noexcept(sodium is initialized && key is within size constraints)*/ {
316 auto blake = [&]() {
317 if constexpr (output_extent == dynamic_output_extent) {
318 return Blake2Xb{out.size(), key, salt, personality};
319 } else {
320 return Blake2Xb{key, salt, personality};
321 }
322 }();
323
324 blake.digest(data);
325 std::move(blake).finish(out);
326 }
327 };
328
329} // namespace dice::hash::blake2xb
330
331#else
332#error "Cannot include Blake2Xb.hpp if sodium is not available"
333#endif // __has_include(<sodium.h>)
334
335#endif//DICE_HASH_BLAKE2XB_HPP