dice-hash
Hash function for stl types and container
Loading...
Searching...
No Matches
Blake2b.hpp
1#ifndef DICE_HASH_BLAKE2B_HPP
2#define DICE_HASH_BLAKE2B_HPP
3
8#if __has_include(<sodium.h>)
9
10#include <sodium.h>
11
12#include <algorithm>
13#include <cassert>
14#include <random>
15#include <span>
16#include <stdexcept>
17
18namespace dice::hash::blake2b {
19
20 inline constexpr size_t min_output_extent = crypto_generichash_blake2b_BYTES_MIN;
21 inline constexpr size_t max_output_extent = crypto_generichash_blake2b_BYTES_MAX;
22 inline constexpr size_t default_output_extent = crypto_generichash_blake2b_BYTES;
23 inline constexpr size_t dynamic_output_extent = std::dynamic_extent;
24
25 inline constexpr size_t salt_extent = crypto_generichash_blake2b_SALTBYTES;
26 inline constexpr std::array<std::byte, salt_extent> default_salt{};
27
28 inline constexpr size_t personality_extent = crypto_generichash_blake2b_PERSONALBYTES;
29 inline constexpr std::array<std::byte, personality_extent> default_personality{};
30
31 inline constexpr size_t min_key_extent = crypto_generichash_blake2b_KEYBYTES_MIN;
32 inline constexpr size_t max_key_extent = crypto_generichash_blake2b_KEYBYTES_MAX;
33 inline constexpr size_t default_key_extent = crypto_generichash_blake2b_KEYBYTES;
34
38 template<size_t KeyExtent>
39 requires (KeyExtent == std::dynamic_extent || (KeyExtent >= min_key_extent && KeyExtent <= max_key_extent))
40 void generate_key(std::span<std::byte, KeyExtent> key_out) {
41 if constexpr (KeyExtent == std::dynamic_extent) {
42 if (key_out.size() < min_key_extent || key_out.size() > max_key_extent) {
43 throw std::runtime_error{"Invalid blake2b key size"};
44 }
45 }
46
47 using byte_utype = std::underlying_type_t<std::byte>;
48
49 std::random_device rng;
50 std::uniform_int_distribution<byte_utype> dist{std::numeric_limits<byte_utype>::min(), std::numeric_limits<byte_utype>::max()};
51
52 std::generate(key_out.begin(), key_out.end(), [&]() {
53 return static_cast<std::byte>(dist(rng));
54 });
55 }
56
57 namespace detail {
58 template<size_t InnerOutputExtent>
59 struct Blake2bInner {
60 crypto_generichash_blake2b_state state_;
61 };
62
63 template<>
64 struct Blake2bInner<dynamic_output_extent> {
65 crypto_generichash_blake2b_state state_;
66 size_t specified_output_len_;
67 };
68 } // namespace detail
69
70 template<size_t OutputExtent = dynamic_output_extent>
71 requires (OutputExtent == dynamic_output_extent || (OutputExtent >= min_output_extent && OutputExtent <= max_output_extent))
72 struct Blake2b {
76 static constexpr size_t output_extent = OutputExtent;
77
78 private:
79 detail::Blake2bInner<output_extent> inner_;
80
81 void init(size_t output_len,
82 std::span<std::byte const> key,
83 std::span<std::byte const, salt_extent> salt,
84 std::span<std::byte const, personality_extent> personality) {
85
86 if (output_len < min_output_extent || output_len > max_output_extent) {
87 throw std::runtime_error{"Invalid blake2b output size"};
88 }
89
90 if (!key.empty()) {
91 if (key.size() < min_key_extent || key.size() > max_key_extent) {
92 throw std::runtime_error{"Invalid blake2b key size"};
93 }
94 }
95
96 if (auto const res = sodium_init(); res == -1) {
97 throw std::runtime_error{"Could not initialize sodium"};
98 }
99
100 if constexpr (output_extent == dynamic_output_extent) {
101 inner_.specified_output_len_ = output_len;
102 }
103
104 auto const res = crypto_generichash_blake2b_init_salt_personal(&inner_.state_,
105 reinterpret_cast<unsigned char const *>(key.data()),
106 key.size(),
107 output_len,
108 reinterpret_cast<unsigned char const *>(salt.data()),
109 reinterpret_cast<unsigned char const *>(personality.data()));
110 // cannot fail here, all invariants have been checked,
111 // see: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/generichash_blake2b.c#LL69C47-L69C47
112 // and: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L148
113 // and: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L216
114 assert(res == 0);
115 }
116
117 public:
125 explicit Blake2b(size_t output_len,
126 std::span<std::byte const> key = {},
127 std::span<std::byte const, salt_extent> salt = default_salt,
128 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)*/
129 requires (output_extent == dynamic_output_extent) {
130 init(output_len, key, salt, personality);
131 }
132
139 explicit Blake2b(std::span<std::byte const> key = {},
140 std::span<std::byte const, salt_extent> salt = default_salt,
141 std::span<std::byte const, personality_extent> personality = default_personality) /*noexcept(sodium is initialized && key.size() is within size constraints)*/
142 requires (output_extent != dynamic_output_extent) {
143 init(output_extent, key, salt, personality);
144 }
145
149 void digest(std::span<std::byte const> data) noexcept {
150 auto const res = crypto_generichash_blake2b_update(&inner_.state_,
151 reinterpret_cast<unsigned char const *>(data.data()),
152 data.size());
153 // cannot fail, see: https://github.com/jedisct1/libsodium/blob/8d9ab6cd764926d4bf1168b122f4a3ff4ea686a0/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L263
154 assert(res == 0);
155 }
156
162 void finish(std::span<std::byte, output_extent> out) && noexcept(output_extent != dynamic_output_extent /*|| output is within size constraints*/) {
163 if constexpr (output_extent == dynamic_output_extent) {
164 auto const expected_out_len = inner_.specified_output_len_;
165 if (expected_out_len != 0 && out.size() != expected_out_len) {
166 // was known in advance and does not match
167 throw std::runtime_error{"Buffer length must match output length"};
168 }
169 }
170
171 auto const res = crypto_generichash_blake2b_final(&inner_.state_,
172 reinterpret_cast<unsigned char *>(out.data()),
173 out.size());
174 // cannot fail, all invariants have been checked, see: https://github.com/jedisct1/libsodium/blob/d787d2b1cf13ad2e69c3a7ebc3fb7b68b6430774/src/libsodium/crypto_generichash/blake2b/ref/blake2b-ref.c#L292
175 assert(res == 0);
176 }
177
183 [[nodiscard]] constexpr size_t concrete_output_extent() const noexcept {
184 if constexpr (output_extent == dynamic_output_extent) {
185 return inner_.specified_output_len_;
186 } else {
187 return output_extent;
188 }
189 }
190
194 static void hash_single(std::span<std::byte const> data,
195 std::span<std::byte, output_extent> out,
196 std::span<std::byte const> key = {},
197 std::span<std::byte const, salt_extent> salt = default_salt,
198 std::span<std::byte const, personality_extent> personality = default_personality) /*noexcept(sodium is initialized && output is within size constraints && key is within size constraints)*/ {
199 auto blake = [&]() {
200 if constexpr (output_extent == dynamic_output_extent) {
201 return Blake2b{out.size(), key, salt, personality};
202 } else {
203 return Blake2b{key, salt, personality};
204 }
205 }();
206
207 blake.digest(data);
208 std::move(blake).finish(out);
209 }
210 };
211
212} // namespace dice::hash::blake2b
213
214#else
215#error "Cannot include Blake2b.hpp if sodium is not available"
216#endif
217
218#endif//DICE_HASH_BLAKE2B_HPP