]>
Commit | Line | Data |
---|---|---|
6d25662f JG |
1 | // Copyright (c) 2016 Jack Grigg |
2 | // Copyright (c) 2016 The Zcash developers | |
3 | // Distributed under the MIT software license, see the accompanying | |
4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
5 | ||
6 | // Implementation of the Equihash Proof-of-Work algorithm. | |
7 | // | |
8 | // Reference | |
9 | // ========= | |
10 | // Alex Biryukov and Dmitry Khovratovich | |
11 | // Equihash: Asymmetric Proof-of-Work Based on the Generalized Birthday Problem | |
12 | // NDSS ’16, 21-24 February 2016, San Diego, CA, USA | |
13 | // https://www.internetsociety.org/sites/default/files/blogs-media/equihash-asymmetric-proof-of-work-based-generalized-birthday-problem.pdf | |
14 | ||
15 | #include "crypto/equihash.h" | |
16 | #include "util.h" | |
17 | ||
18 | #include <algorithm> | |
6d25662f JG |
19 | #include <iostream> |
20 | #include <stdexcept> | |
21 | ||
0a66f013 JG |
22 | #include <boost/optional.hpp> |
23 | ||
e891d64b JB |
24 | #ifdef __APPLE__ |
25 | #include <machine/endian.h> | |
26 | #include <libkern/OSByteOrder.h> | |
27 | ||
28 | #define htobe16(x) OSSwapHostToBigInt16(x) | |
29 | #define htole16(x) OSSwapHostToLittleInt16(x) | |
30 | #define be16toh(x) OSSwapBigToHostInt16(x) | |
31 | #define le16toh(x) OSSwapLittleToHostInt16(x) | |
32 | ||
33 | #define htobe32(x) OSSwapHostToBigInt32(x) | |
34 | #define htole32(x) OSSwapHostToLittleInt32(x) | |
35 | #define be32toh(x) OSSwapBigToHostInt32(x) | |
36 | #define le32toh(x) OSSwapLittleToHostInt32(x) | |
37 | ||
38 | #define htobe64(x) OSSwapHostToBigInt64(x) | |
39 | #define htole64(x) OSSwapHostToLittleInt64(x) | |
40 | #define be64toh(x) OSSwapBigToHostInt64(x) | |
41 | #define le64toh(x) OSSwapLittleToHostInt64(x) | |
42 | ||
43 | #define __BIG_ENDIAN BIG_ENDIAN | |
44 | #define __LITTLE_ENDIAN LITTLE_ENDIAN | |
45 | #define __BYTE_ORDER BYTE_ORDER | |
46 | #else | |
47 | #include | |
48 | #include | |
49 | #endif | |
50 | ||
2dbabb11 JG |
51 | EhSolverCancelledException solver_cancelled; |
52 | ||
e9574728 JG |
53 | template<unsigned int N, unsigned int K> |
54 | int Equihash<N,K>::InitialiseState(eh_HashState& base_state) | |
6d25662f | 55 | { |
09e9a329 JG |
56 | uint32_t le_N = htole32(N); |
57 | uint32_t le_K = htole32(K); | |
6d25662f | 58 | unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; |
a6dcf2ee | 59 | memcpy(personalization, "ZcashPoW", 8); |
09e9a329 JG |
60 | memcpy(personalization+8, &le_N, 4); |
61 | memcpy(personalization+12, &le_K, 4); | |
6d25662f JG |
62 | return crypto_generichash_blake2b_init_salt_personal(&base_state, |
63 | NULL, 0, // No key. | |
caa0348f | 64 | (512/N)*N/8, |
6d25662f JG |
65 | NULL, // No salt. |
66 | personalization); | |
67 | } | |
68 | ||
caa0348f JG |
69 | void GenerateHash(const eh_HashState& base_state, eh_index g, |
70 | unsigned char* hash, size_t hLen) | |
71 | { | |
72 | eh_HashState state; | |
73 | state = base_state; | |
caa0348f | 74 | eh_index lei = htole32(g); |
e273f05d JG |
75 | crypto_generichash_blake2b_update(&state, (const unsigned char*) &lei, |
76 | sizeof(eh_index)); | |
caa0348f JG |
77 | crypto_generichash_blake2b_final(&state, hash, hLen); |
78 | } | |
79 | ||
881ffbfc JG |
80 | void ExpandArray(const unsigned char* in, size_t in_len, |
81 | unsigned char* out, size_t out_len, | |
20abe208 | 82 | size_t bit_len, size_t byte_pad) |
881ffbfc JG |
83 | { |
84 | assert(bit_len >= 8); | |
85 | assert(8*sizeof(uint32_t) >= 7+bit_len); | |
86 | ||
20abe208 | 87 | size_t out_width { (bit_len+7)/8 + byte_pad }; |
881ffbfc JG |
88 | assert(out_len == 8*out_width*in_len/bit_len); |
89 | ||
90 | uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 }; | |
91 | ||
92 | // The acc_bits least-significant bits of acc_value represent a bit sequence | |
93 | // in big-endian order. | |
94 | size_t acc_bits = 0; | |
95 | uint32_t acc_value = 0; | |
96 | ||
97 | size_t j = 0; | |
98 | for (size_t i = 0; i < in_len; i++) { | |
99 | acc_value = (acc_value << 8) | in[i]; | |
100 | acc_bits += 8; | |
101 | ||
102 | // When we have bit_len or more bits in the accumulator, write the next | |
103 | // output element. | |
104 | if (acc_bits >= bit_len) { | |
105 | acc_bits -= bit_len; | |
20abe208 JG |
106 | for (size_t x = 0; x < byte_pad; x++) { |
107 | out[j+x] = 0; | |
108 | } | |
109 | for (size_t x = byte_pad; x < out_width; x++) { | |
881ffbfc JG |
110 | out[j+x] = ( |
111 | // Big-endian | |
112 | acc_value >> (acc_bits+(8*(out_width-x-1))) | |
113 | ) & ( | |
114 | // Apply bit_len_mask across byte boundaries | |
115 | (bit_len_mask >> (8*(out_width-x-1))) & 0xFF | |
116 | ); | |
117 | } | |
118 | j += out_width; | |
119 | } | |
120 | } | |
121 | } | |
122 | ||
123 | void CompressArray(const unsigned char* in, size_t in_len, | |
124 | unsigned char* out, size_t out_len, | |
20abe208 | 125 | size_t bit_len, size_t byte_pad) |
881ffbfc JG |
126 | { |
127 | assert(bit_len >= 8); | |
128 | assert(8*sizeof(uint32_t) >= 7+bit_len); | |
129 | ||
20abe208 | 130 | size_t in_width { (bit_len+7)/8 + byte_pad }; |
881ffbfc JG |
131 | assert(out_len == bit_len*in_len/(8*in_width)); |
132 | ||
133 | uint32_t bit_len_mask { ((uint32_t)1 << bit_len) - 1 }; | |
134 | ||
135 | // The acc_bits least-significant bits of acc_value represent a bit sequence | |
136 | // in big-endian order. | |
137 | size_t acc_bits = 0; | |
138 | uint32_t acc_value = 0; | |
139 | ||
140 | size_t j = 0; | |
141 | for (size_t i = 0; i < out_len; i++) { | |
142 | // When we have fewer than 8 bits left in the accumulator, read the next | |
143 | // input element. | |
144 | if (acc_bits < 8) { | |
145 | acc_value = acc_value << bit_len; | |
20abe208 | 146 | for (size_t x = byte_pad; x < in_width; x++) { |
881ffbfc JG |
147 | acc_value = acc_value | ( |
148 | ( | |
149 | // Apply bit_len_mask across byte boundaries | |
150 | in[j+x] & ((bit_len_mask >> (8*(in_width-x-1))) & 0xFF) | |
151 | ) << (8*(in_width-x-1))); // Big-endian | |
152 | } | |
153 | j += in_width; | |
154 | acc_bits += bit_len; | |
155 | } | |
156 | ||
157 | acc_bits -= 8; | |
158 | out[i] = (acc_value >> acc_bits) & 0xFF; | |
159 | } | |
160 | } | |
161 | ||
09e9a329 JG |
162 | // Big-endian so that lexicographic array comparison is equivalent to integer |
163 | // comparison | |
d4d76536 | 164 | void EhIndexToArray(const eh_index i, unsigned char* array) |
29d9986c | 165 | { |
bcf79c78 | 166 | BOOST_STATIC_ASSERT(sizeof(eh_index) == 4); |
933cb4cd JG |
167 | eh_index bei = htobe32(i); |
168 | memcpy(array, &bei, sizeof(eh_index)); | |
29d9986c JG |
169 | } |
170 | ||
09e9a329 JG |
171 | // Big-endian so that lexicographic array comparison is equivalent to integer |
172 | // comparison | |
d4d76536 | 173 | eh_index ArrayToEhIndex(const unsigned char* array) |
29d9986c | 174 | { |
bcf79c78 | 175 | BOOST_STATIC_ASSERT(sizeof(eh_index) == 4); |
933cb4cd JG |
176 | eh_index bei; |
177 | memcpy(&bei, array, sizeof(eh_index)); | |
178 | return be32toh(bei); | |
29d9986c JG |
179 | } |
180 | ||
d4d76536 | 181 | eh_trunc TruncateIndex(const eh_index i, const unsigned int ilen) |
c92c1f60 JG |
182 | { |
183 | // Truncate to 8 bits | |
bcf79c78 | 184 | BOOST_STATIC_ASSERT(sizeof(eh_trunc) == 1); |
c92c1f60 JG |
185 | return (i >> (ilen - 8)) & 0xff; |
186 | } | |
187 | ||
d4d76536 | 188 | eh_index UntruncateIndex(const eh_trunc t, const eh_index r, const unsigned int ilen) |
c92c1f60 JG |
189 | { |
190 | eh_index i{t}; | |
191 | return (i << (ilen - 8)) | r; | |
192 | } | |
193 | ||
5be6abbf JG |
194 | std::vector<eh_index> GetIndicesFromMinimal(std::vector<unsigned char> minimal, |
195 | size_t cBitLen) | |
6d25662f | 196 | { |
5be6abbf JG |
197 | assert(((cBitLen+1)+7)/8 <= sizeof(eh_index)); |
198 | size_t lenIndices { 8*sizeof(eh_index)*minimal.size()/(cBitLen+1) }; | |
199 | size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 }; | |
200 | std::vector<unsigned char> array(lenIndices); | |
201 | ExpandArray(minimal.data(), minimal.size(), | |
202 | array.data(), lenIndices, cBitLen+1, bytePad); | |
203 | std::vector<eh_index> ret; | |
204 | for (int i = 0; i < lenIndices; i += sizeof(eh_index)) { | |
205 | ret.push_back(ArrayToEhIndex(array.data()+i)); | |
206 | } | |
207 | return ret; | |
208 | } | |
036dcbd9 | 209 | |
5be6abbf JG |
210 | std::vector<unsigned char> GetMinimalFromIndices(std::vector<eh_index> indices, |
211 | size_t cBitLen) | |
212 | { | |
213 | assert(((cBitLen+1)+7)/8 <= sizeof(eh_index)); | |
214 | size_t lenIndices { indices.size()*sizeof(eh_index) }; | |
215 | size_t minLen { (cBitLen+1)*lenIndices/(8*sizeof(eh_index)) }; | |
216 | size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 }; | |
217 | std::vector<unsigned char> array(lenIndices); | |
218 | for (int i = 0; i < indices.size(); i++) { | |
219 | EhIndexToArray(indices[i], array.data()+(i*sizeof(eh_index))); | |
036dcbd9 | 220 | } |
5be6abbf JG |
221 | std::vector<unsigned char> ret(minLen); |
222 | CompressArray(array.data(), lenIndices, | |
223 | ret.data(), minLen, cBitLen+1, bytePad); | |
224 | return ret; | |
225 | } | |
226 | ||
d4d76536 | 227 | template<size_t WIDTH> |
caa0348f JG |
228 | StepRow<WIDTH>::StepRow(const unsigned char* hashIn, size_t hInLen, |
229 | size_t hLen, size_t cBitLen) | |
6d25662f | 230 | { |
caa0348f JG |
231 | assert(hLen <= WIDTH); |
232 | ExpandArray(hashIn, hInLen, hash, hLen, cBitLen); | |
6d25662f JG |
233 | } |
234 | ||
d4d76536 JG |
235 | template<size_t WIDTH> template<size_t W> |
236 | StepRow<WIDTH>::StepRow(const StepRow<W>& a) | |
6d25662f | 237 | { |
bcf79c78 | 238 | BOOST_STATIC_ASSERT(W <= WIDTH); |
d4d76536 | 239 | std::copy(a.hash, a.hash+W, hash); |
6d25662f JG |
240 | } |
241 | ||
d4d76536 | 242 | template<size_t WIDTH> |
caa0348f JG |
243 | FullStepRow<WIDTH>::FullStepRow(const unsigned char* hashIn, size_t hInLen, |
244 | size_t hLen, size_t cBitLen, eh_index i) : | |
245 | StepRow<WIDTH> {hashIn, hInLen, hLen, cBitLen} | |
6d25662f | 246 | { |
036dcbd9 | 247 | EhIndexToArray(i, hash+hLen); |
6d25662f JG |
248 | } |
249 | ||
d4d76536 JG |
250 | template<size_t WIDTH> template<size_t W> |
251 | FullStepRow<WIDTH>::FullStepRow(const FullStepRow<W>& a, const FullStepRow<W>& b, size_t len, size_t lenIndices, int trim) : | |
252 | StepRow<WIDTH> {a} | |
a3361e77 | 253 | { |
d4d76536 JG |
254 | assert(len+lenIndices <= W); |
255 | assert(len-trim+(2*lenIndices) <= WIDTH); | |
256 | for (int i = trim; i < len; i++) | |
257 | hash[i-trim] = a.hash[i] ^ b.hash[i]; | |
d07cf629 | 258 | if (a.IndicesBefore(b, len, lenIndices)) { |
d4d76536 JG |
259 | std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim); |
260 | std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices); | |
a683cc85 | 261 | } else { |
d4d76536 JG |
262 | std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim); |
263 | std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices); | |
a683cc85 | 264 | } |
6d25662f JG |
265 | } |
266 | ||
d4d76536 JG |
267 | template<size_t WIDTH> |
268 | FullStepRow<WIDTH>& FullStepRow<WIDTH>::operator=(const FullStepRow<WIDTH>& a) | |
6d25662f | 269 | { |
d4d76536 | 270 | std::copy(a.hash, a.hash+WIDTH, hash); |
a683cc85 | 271 | return *this; |
6d25662f JG |
272 | } |
273 | ||
d4d76536 JG |
274 | template<size_t WIDTH> |
275 | bool StepRow<WIDTH>::IsZero(size_t len) | |
6d25662f | 276 | { |
447444ae JG |
277 | // This doesn't need to be constant time. |
278 | for (int i = 0; i < len; i++) { | |
279 | if (hash[i] != 0) | |
280 | return false; | |
281 | } | |
282 | return true; | |
6d25662f JG |
283 | } |
284 | ||
d4d76536 | 285 | template<size_t WIDTH> |
5be6abbf JG |
286 | std::vector<unsigned char> FullStepRow<WIDTH>::GetIndices(size_t len, size_t lenIndices, |
287 | size_t cBitLen) const | |
29d9986c | 288 | { |
5be6abbf JG |
289 | assert(((cBitLen+1)+7)/8 <= sizeof(eh_index)); |
290 | size_t minLen { (cBitLen+1)*lenIndices/(8*sizeof(eh_index)) }; | |
291 | size_t bytePad { sizeof(eh_index) - ((cBitLen+1)+7)/8 }; | |
292 | std::vector<unsigned char> ret(minLen); | |
293 | CompressArray(hash+len, lenIndices, ret.data(), minLen, cBitLen+1, bytePad); | |
29d9986c JG |
294 | return ret; |
295 | } | |
296 | ||
d4d76536 JG |
297 | template<size_t WIDTH> |
298 | bool HasCollision(StepRow<WIDTH>& a, StepRow<WIDTH>& b, int l) | |
6d25662f | 299 | { |
447444ae JG |
300 | // This doesn't need to be constant time. |
301 | for (int j = 0; j < l; j++) { | |
302 | if (a.hash[j] != b.hash[j]) | |
303 | return false; | |
304 | } | |
305 | return true; | |
6d25662f JG |
306 | } |
307 | ||
d4d76536 | 308 | template<size_t WIDTH> |
caa0348f JG |
309 | TruncatedStepRow<WIDTH>::TruncatedStepRow(const unsigned char* hashIn, size_t hInLen, |
310 | size_t hLen, size_t cBitLen, | |
311 | eh_index i, unsigned int ilen) : | |
312 | StepRow<WIDTH> {hashIn, hInLen, hLen, cBitLen} | |
6d25662f | 313 | { |
036dcbd9 | 314 | hash[hLen] = TruncateIndex(i, ilen); |
6d25662f JG |
315 | } |
316 | ||
d4d76536 JG |
317 | template<size_t WIDTH> template<size_t W> |
318 | TruncatedStepRow<WIDTH>::TruncatedStepRow(const TruncatedStepRow<W>& a, const TruncatedStepRow<W>& b, size_t len, size_t lenIndices, int trim) : | |
319 | StepRow<WIDTH> {a} | |
c92c1f60 | 320 | { |
d4d76536 JG |
321 | assert(len+lenIndices <= W); |
322 | assert(len-trim+(2*lenIndices) <= WIDTH); | |
323 | for (int i = trim; i < len; i++) | |
324 | hash[i-trim] = a.hash[i] ^ b.hash[i]; | |
325 | if (a.IndicesBefore(b, len, lenIndices)) { | |
326 | std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim); | |
327 | std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim+lenIndices); | |
a683cc85 | 328 | } else { |
d4d76536 JG |
329 | std::copy(b.hash+len, b.hash+len+lenIndices, hash+len-trim); |
330 | std::copy(a.hash+len, a.hash+len+lenIndices, hash+len-trim+lenIndices); | |
a683cc85 | 331 | } |
c92c1f60 JG |
332 | } |
333 | ||
d4d76536 JG |
334 | template<size_t WIDTH> |
335 | TruncatedStepRow<WIDTH>& TruncatedStepRow<WIDTH>::operator=(const TruncatedStepRow<WIDTH>& a) | |
39f5cb35 | 336 | { |
d4d76536 | 337 | std::copy(a.hash, a.hash+WIDTH, hash); |
a683cc85 | 338 | return *this; |
39f5cb35 JG |
339 | } |
340 | ||
d4d76536 | 341 | template<size_t WIDTH> |
215b9e13 | 342 | std::shared_ptr<eh_trunc> TruncatedStepRow<WIDTH>::GetTruncatedIndices(size_t len, size_t lenIndices) const |
39f5cb35 | 343 | { |
10310478 | 344 | std::shared_ptr<eh_trunc> p (new eh_trunc[lenIndices], std::default_delete<eh_trunc[]>()); |
215b9e13 | 345 | std::copy(hash+len, hash+len+lenIndices, p.get()); |
39f5cb35 JG |
346 | return p; |
347 | } | |
348 | ||
e9574728 | 349 | template<unsigned int N, unsigned int K> |
51eb5273 | 350 | bool Equihash<N,K>::BasicSolve(const eh_HashState& base_state, |
5be6abbf | 351 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 | 352 | const std::function<bool(EhSolverCancelCheck)> cancelled) |
6d25662f | 353 | { |
e9574728 | 354 | eh_index init_size { 1 << (CollisionBitLength + 1) }; |
6d25662f JG |
355 | |
356 | // 1) Generate first list | |
357 | LogPrint("pow", "Generating first list\n"); | |
036dcbd9 | 358 | size_t hashLen = HashLength; |
d4d76536 JG |
359 | size_t lenIndices = sizeof(eh_index); |
360 | std::vector<FullStepRow<FullWidth>> X; | |
6d25662f | 361 | X.reserve(init_size); |
caa0348f JG |
362 | unsigned char tmpHash[HashOutput]; |
363 | for (eh_index g = 0; X.size() < init_size; g++) { | |
364 | GenerateHash(base_state, g, tmpHash, HashOutput); | |
365 | for (eh_index i = 0; i < IndicesPerHashOutput && X.size() < init_size; i++) { | |
366 | X.emplace_back(tmpHash+(i*N/8), N/8, HashLength, | |
367 | CollisionBitLength, (g*IndicesPerHashOutput)+i); | |
368 | } | |
5a360a5c | 369 | if (cancelled(ListGeneration)) throw solver_cancelled; |
6d25662f JG |
370 | } |
371 | ||
372 | // 3) Repeat step 2 until 2n/(k+1) bits remain | |
e9574728 | 373 | for (int r = 1; r < K && X.size() > 0; r++) { |
6d25662f JG |
374 | LogPrint("pow", "Round %d:\n", r); |
375 | // 2a) Sort the list | |
376 | LogPrint("pow", "- Sorting list\n"); | |
d151ab4f | 377 | std::sort(X.begin(), X.end(), CompareSR(CollisionByteLength)); |
5b4ebcd5 | 378 | if (cancelled(ListSorting)) throw solver_cancelled; |
6d25662f JG |
379 | |
380 | LogPrint("pow", "- Finding collisions\n"); | |
381 | int i = 0; | |
382 | int posFree = 0; | |
d4d76536 | 383 | std::vector<FullStepRow<FullWidth>> Xc; |
6d25662f JG |
384 | while (i < X.size() - 1) { |
385 | // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits | |
386 | int j = 1; | |
387 | while (i+j < X.size() && | |
e9574728 | 388 | HasCollision(X[i], X[i+j], CollisionByteLength)) { |
6d25662f JG |
389 | j++; |
390 | } | |
391 | ||
392 | // 2c) Calculate tuples (X_i ^ X_j, (i, j)) | |
393 | for (int l = 0; l < j - 1; l++) { | |
394 | for (int m = l + 1; m < j; m++) { | |
d4d76536 JG |
395 | if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) { |
396 | Xc.emplace_back(X[i+l], X[i+m], hashLen, lenIndices, CollisionByteLength); | |
6d25662f JG |
397 | } |
398 | } | |
399 | } | |
400 | ||
401 | // 2d) Store tuples on the table in-place if possible | |
402 | while (posFree < i+j && Xc.size() > 0) { | |
403 | X[posFree++] = Xc.back(); | |
404 | Xc.pop_back(); | |
405 | } | |
406 | ||
407 | i += j; | |
5b4ebcd5 | 408 | if (cancelled(ListColliding)) throw solver_cancelled; |
6d25662f JG |
409 | } |
410 | ||
411 | // 2e) Handle edge case where final table entry has no collision | |
412 | while (posFree < X.size() && Xc.size() > 0) { | |
413 | X[posFree++] = Xc.back(); | |
414 | Xc.pop_back(); | |
415 | } | |
416 | ||
417 | if (Xc.size() > 0) { | |
418 | // 2f) Add overflow to end of table | |
419 | X.insert(X.end(), Xc.begin(), Xc.end()); | |
420 | } else if (posFree < X.size()) { | |
421 | // 2g) Remove empty space at the end | |
422 | X.erase(X.begin()+posFree, X.end()); | |
423 | X.shrink_to_fit(); | |
424 | } | |
639c4004 JG |
425 | |
426 | hashLen -= CollisionByteLength; | |
d4d76536 | 427 | lenIndices *= 2; |
5b4ebcd5 | 428 | if (cancelled(RoundEnd)) throw solver_cancelled; |
6d25662f JG |
429 | } |
430 | ||
431 | // k+1) Find a collision on last 2n(k+1) bits | |
432 | LogPrint("pow", "Final round:\n"); | |
6d25662f JG |
433 | if (X.size() > 1) { |
434 | LogPrint("pow", "- Sorting list\n"); | |
639c4004 | 435 | std::sort(X.begin(), X.end(), CompareSR(hashLen)); |
5b4ebcd5 | 436 | if (cancelled(FinalSorting)) throw solver_cancelled; |
6d25662f | 437 | LogPrint("pow", "- Finding collisions\n"); |
1bb40a42 JG |
438 | int i = 0; |
439 | while (i < X.size() - 1) { | |
440 | int j = 1; | |
441 | while (i+j < X.size() && | |
442 | HasCollision(X[i], X[i+j], hashLen)) { | |
443 | j++; | |
6d25662f | 444 | } |
1bb40a42 JG |
445 | |
446 | for (int l = 0; l < j - 1; l++) { | |
447 | for (int m = l + 1; m < j; m++) { | |
448 | FullStepRow<FinalFullWidth> res(X[i+l], X[i+m], hashLen, lenIndices, 0); | |
c6a7e897 DH |
449 | if (DistinctIndices(X[i+l], X[i+m], hashLen, lenIndices)) { |
450 | auto soln = res.GetIndices(hashLen, 2*lenIndices, CollisionBitLength); | |
451 | assert(soln.size() == equihash_solution_size(N, K)); | |
452 | if (validBlock(soln)) { | |
453 | return true; | |
454 | } | |
1bb40a42 JG |
455 | } |
456 | } | |
457 | } | |
458 | ||
459 | i += j; | |
5b4ebcd5 | 460 | if (cancelled(FinalColliding)) throw solver_cancelled; |
6d25662f JG |
461 | } |
462 | } else | |
463 | LogPrint("pow", "- List is empty\n"); | |
464 | ||
51eb5273 | 465 | return false; |
6d25662f JG |
466 | } |
467 | ||
d4d76536 JG |
468 | template<size_t WIDTH> |
469 | void CollideBranches(std::vector<FullStepRow<WIDTH>>& X, const size_t hlen, const size_t lenIndices, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt) | |
c92c1f60 JG |
470 | { |
471 | int i = 0; | |
472 | int posFree = 0; | |
d4d76536 | 473 | std::vector<FullStepRow<WIDTH>> Xc; |
c92c1f60 JG |
474 | while (i < X.size() - 1) { |
475 | // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits | |
476 | int j = 1; | |
477 | while (i+j < X.size() && | |
478 | HasCollision(X[i], X[i+j], clen)) { | |
479 | j++; | |
480 | } | |
481 | ||
482 | // 2c) Calculate tuples (X_i ^ X_j, (i, j)) | |
483 | for (int l = 0; l < j - 1; l++) { | |
484 | for (int m = l + 1; m < j; m++) { | |
d4d76536 JG |
485 | if (DistinctIndices(X[i+l], X[i+m], hlen, lenIndices)) { |
486 | if (IsValidBranch(X[i+l], hlen, ilen, lt) && IsValidBranch(X[i+m], hlen, ilen, rt)) { | |
487 | Xc.emplace_back(X[i+l], X[i+m], hlen, lenIndices, clen); | |
488 | } else if (IsValidBranch(X[i+m], hlen, ilen, lt) && IsValidBranch(X[i+l], hlen, ilen, rt)) { | |
489 | Xc.emplace_back(X[i+m], X[i+l], hlen, lenIndices, clen); | |
c92c1f60 JG |
490 | } |
491 | } | |
492 | } | |
493 | } | |
494 | ||
495 | // 2d) Store tuples on the table in-place if possible | |
496 | while (posFree < i+j && Xc.size() > 0) { | |
497 | X[posFree++] = Xc.back(); | |
498 | Xc.pop_back(); | |
499 | } | |
500 | ||
501 | i += j; | |
502 | } | |
503 | ||
504 | // 2e) Handle edge case where final table entry has no collision | |
505 | while (posFree < X.size() && Xc.size() > 0) { | |
506 | X[posFree++] = Xc.back(); | |
507 | Xc.pop_back(); | |
508 | } | |
509 | ||
510 | if (Xc.size() > 0) { | |
511 | // 2f) Add overflow to end of table | |
512 | X.insert(X.end(), Xc.begin(), Xc.end()); | |
513 | } else if (posFree < X.size()) { | |
514 | // 2g) Remove empty space at the end | |
515 | X.erase(X.begin()+posFree, X.end()); | |
516 | X.shrink_to_fit(); | |
517 | } | |
518 | } | |
519 | ||
e9574728 | 520 | template<unsigned int N, unsigned int K> |
51eb5273 | 521 | bool Equihash<N,K>::OptimisedSolve(const eh_HashState& base_state, |
5be6abbf | 522 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 | 523 | const std::function<bool(EhSolverCancelCheck)> cancelled) |
c92c1f60 | 524 | { |
e9574728 | 525 | eh_index init_size { 1 << (CollisionBitLength + 1) }; |
1655db28 | 526 | eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) }; |
c92c1f60 JG |
527 | |
528 | // First run the algorithm with truncated indices | |
529 | ||
6b4f4475 | 530 | const eh_index soln_size { 1 << K }; |
215b9e13 | 531 | std::vector<std::shared_ptr<eh_trunc>> partialSolns; |
1655db28 | 532 | int invalidCount = 0; |
c92c1f60 JG |
533 | { |
534 | ||
535 | // 1) Generate first list | |
536 | LogPrint("pow", "Generating first list\n"); | |
036dcbd9 | 537 | size_t hashLen = HashLength; |
d4d76536 JG |
538 | size_t lenIndices = sizeof(eh_trunc); |
539 | std::vector<TruncatedStepRow<TruncatedWidth>> Xt; | |
c92c1f60 | 540 | Xt.reserve(init_size); |
caa0348f JG |
541 | unsigned char tmpHash[HashOutput]; |
542 | for (eh_index g = 0; Xt.size() < init_size; g++) { | |
543 | GenerateHash(base_state, g, tmpHash, HashOutput); | |
544 | for (eh_index i = 0; i < IndicesPerHashOutput && Xt.size() < init_size; i++) { | |
545 | Xt.emplace_back(tmpHash+(i*N/8), N/8, HashLength, CollisionBitLength, | |
546 | (g*IndicesPerHashOutput)+i, CollisionBitLength + 1); | |
547 | } | |
5a360a5c | 548 | if (cancelled(ListGeneration)) throw solver_cancelled; |
c92c1f60 JG |
549 | } |
550 | ||
551 | // 3) Repeat step 2 until 2n/(k+1) bits remain | |
e9574728 | 552 | for (int r = 1; r < K && Xt.size() > 0; r++) { |
c92c1f60 JG |
553 | LogPrint("pow", "Round %d:\n", r); |
554 | // 2a) Sort the list | |
555 | LogPrint("pow", "- Sorting list\n"); | |
d151ab4f | 556 | std::sort(Xt.begin(), Xt.end(), CompareSR(CollisionByteLength)); |
5b4ebcd5 | 557 | if (cancelled(ListSorting)) throw solver_cancelled; |
c92c1f60 JG |
558 | |
559 | LogPrint("pow", "- Finding collisions\n"); | |
560 | int i = 0; | |
561 | int posFree = 0; | |
d4d76536 | 562 | std::vector<TruncatedStepRow<TruncatedWidth>> Xc; |
c92c1f60 JG |
563 | while (i < Xt.size() - 1) { |
564 | // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits | |
565 | int j = 1; | |
566 | while (i+j < Xt.size() && | |
e9574728 | 567 | HasCollision(Xt[i], Xt[i+j], CollisionByteLength)) { |
c92c1f60 JG |
568 | j++; |
569 | } | |
570 | ||
571 | // 2c) Calculate tuples (X_i ^ X_j, (i, j)) | |
d4af3dd5 | 572 | bool checking_for_zero = (i == 0 && Xt[0].IsZero(hashLen)); |
c92c1f60 JG |
573 | for (int l = 0; l < j - 1; l++) { |
574 | for (int m = l + 1; m < j; m++) { | |
575 | // We truncated, so don't check for distinct indices here | |
d4af3dd5 JG |
576 | TruncatedStepRow<TruncatedWidth> Xi {Xt[i+l], Xt[i+m], |
577 | hashLen, lenIndices, | |
578 | CollisionByteLength}; | |
579 | if (!(Xi.IsZero(hashLen-CollisionByteLength) && | |
6b4f4475 JG |
580 | IsProbablyDuplicate<soln_size>(Xi.GetTruncatedIndices(hashLen-CollisionByteLength, 2*lenIndices), |
581 | 2*lenIndices))) { | |
d4af3dd5 JG |
582 | Xc.emplace_back(Xi); |
583 | } | |
c92c1f60 JG |
584 | } |
585 | } | |
586 | ||
587 | // 2d) Store tuples on the table in-place if possible | |
588 | while (posFree < i+j && Xc.size() > 0) { | |
589 | Xt[posFree++] = Xc.back(); | |
590 | Xc.pop_back(); | |
591 | } | |
592 | ||
593 | i += j; | |
5b4ebcd5 | 594 | if (cancelled(ListColliding)) throw solver_cancelled; |
c92c1f60 JG |
595 | } |
596 | ||
597 | // 2e) Handle edge case where final table entry has no collision | |
598 | while (posFree < Xt.size() && Xc.size() > 0) { | |
599 | Xt[posFree++] = Xc.back(); | |
600 | Xc.pop_back(); | |
601 | } | |
602 | ||
603 | if (Xc.size() > 0) { | |
604 | // 2f) Add overflow to end of table | |
605 | Xt.insert(Xt.end(), Xc.begin(), Xc.end()); | |
606 | } else if (posFree < Xt.size()) { | |
607 | // 2g) Remove empty space at the end | |
608 | Xt.erase(Xt.begin()+posFree, Xt.end()); | |
609 | Xt.shrink_to_fit(); | |
610 | } | |
639c4004 JG |
611 | |
612 | hashLen -= CollisionByteLength; | |
d4d76536 | 613 | lenIndices *= 2; |
5b4ebcd5 | 614 | if (cancelled(RoundEnd)) throw solver_cancelled; |
c92c1f60 JG |
615 | } |
616 | ||
617 | // k+1) Find a collision on last 2n(k+1) bits | |
618 | LogPrint("pow", "Final round:\n"); | |
619 | if (Xt.size() > 1) { | |
620 | LogPrint("pow", "- Sorting list\n"); | |
639c4004 | 621 | std::sort(Xt.begin(), Xt.end(), CompareSR(hashLen)); |
5b4ebcd5 | 622 | if (cancelled(FinalSorting)) throw solver_cancelled; |
c92c1f60 | 623 | LogPrint("pow", "- Finding collisions\n"); |
1bb40a42 JG |
624 | int i = 0; |
625 | while (i < Xt.size() - 1) { | |
626 | int j = 1; | |
627 | while (i+j < Xt.size() && | |
628 | HasCollision(Xt[i], Xt[i+j], hashLen)) { | |
629 | j++; | |
c92c1f60 | 630 | } |
1bb40a42 JG |
631 | |
632 | for (int l = 0; l < j - 1; l++) { | |
633 | for (int m = l + 1; m < j; m++) { | |
3c654f38 JG |
634 | TruncatedStepRow<FinalTruncatedWidth> res(Xt[i+l], Xt[i+m], |
635 | hashLen, lenIndices, 0); | |
636 | auto soln = res.GetTruncatedIndices(hashLen, 2*lenIndices); | |
637 | if (!IsProbablyDuplicate<soln_size>(soln, 2*lenIndices)) { | |
638 | partialSolns.push_back(soln); | |
639 | } | |
1bb40a42 JG |
640 | } |
641 | } | |
642 | ||
643 | i += j; | |
215b9e13 | 644 | if (cancelled(FinalColliding)) throw solver_cancelled; |
c92c1f60 JG |
645 | } |
646 | } else | |
647 | LogPrint("pow", "- List is empty\n"); | |
648 | ||
649 | } // Ensure Xt goes out of scope and is destroyed | |
650 | ||
651 | LogPrint("pow", "Found %d partial solutions\n", partialSolns.size()); | |
652 | ||
653 | // Now for each solution run the algorithm again to recreate the indices | |
654 | LogPrint("pow", "Culling solutions\n"); | |
215b9e13 | 655 | for (std::shared_ptr<eh_trunc> partialSoln : partialSolns) { |
5be6abbf | 656 | std::set<std::vector<unsigned char>> solns; |
0a66f013 JG |
657 | size_t hashLen; |
658 | size_t lenIndices; | |
caa0348f | 659 | unsigned char tmpHash[HashOutput]; |
0a66f013 JG |
660 | std::vector<boost::optional<std::vector<FullStepRow<FinalFullWidth>>>> X; |
661 | X.reserve(K+1); | |
662 | ||
663 | // 3) Repeat steps 1 and 2 for each partial index | |
39f5cb35 | 664 | for (eh_index i = 0; i < soln_size; i++) { |
0a66f013 JG |
665 | // 1) Generate first list of possibilities |
666 | std::vector<FullStepRow<FinalFullWidth>> icv; | |
667 | icv.reserve(recreate_size); | |
c92c1f60 | 668 | for (eh_index j = 0; j < recreate_size; j++) { |
215b9e13 | 669 | eh_index newIndex { UntruncateIndex(partialSoln.get()[i], j, CollisionBitLength + 1) }; |
caa0348f JG |
670 | if (j == 0 || newIndex % IndicesPerHashOutput == 0) { |
671 | GenerateHash(base_state, newIndex/IndicesPerHashOutput, | |
672 | tmpHash, HashOutput); | |
673 | } | |
674 | icv.emplace_back(tmpHash+((newIndex % IndicesPerHashOutput) * N/8), | |
675 | N/8, HashLength, CollisionBitLength, newIndex); | |
215b9e13 | 676 | if (cancelled(PartialGeneration)) throw solver_cancelled; |
c92c1f60 | 677 | } |
0a66f013 | 678 | boost::optional<std::vector<FullStepRow<FinalFullWidth>>> ic = icv; |
c92c1f60 JG |
679 | |
680 | // 2a) For each pair of lists: | |
036dcbd9 | 681 | hashLen = HashLength; |
0a66f013 JG |
682 | lenIndices = sizeof(eh_index); |
683 | size_t rti = i; | |
684 | for (size_t r = 0; r <= K; r++) { | |
685 | // 2b) Until we are at the top of a subtree: | |
686 | if (r < X.size()) { | |
687 | if (X[r]) { | |
688 | // 2c) Merge the lists | |
689 | ic->reserve(ic->size() + X[r]->size()); | |
690 | ic->insert(ic->end(), X[r]->begin(), X[r]->end()); | |
691 | std::sort(ic->begin(), ic->end(), CompareSR(hashLen)); | |
215b9e13 | 692 | if (cancelled(PartialSorting)) throw solver_cancelled; |
0a66f013 JG |
693 | size_t lti = rti-(1<<r); |
694 | CollideBranches(*ic, hashLen, lenIndices, | |
695 | CollisionByteLength, | |
696 | CollisionBitLength + 1, | |
215b9e13 | 697 | partialSoln.get()[lti], partialSoln.get()[rti]); |
0a66f013 JG |
698 | |
699 | // 2d) Check if this has become an invalid solution | |
700 | if (ic->size() == 0) | |
701 | goto invalidsolution; | |
702 | ||
703 | X[r] = boost::none; | |
704 | hashLen -= CollisionByteLength; | |
705 | lenIndices *= 2; | |
706 | rti = lti; | |
707 | } else { | |
708 | X[r] = *ic; | |
709 | break; | |
710 | } | |
711 | } else { | |
712 | X.push_back(ic); | |
713 | break; | |
714 | } | |
215b9e13 | 715 | if (cancelled(PartialSubtreeEnd)) throw solver_cancelled; |
c92c1f60 | 716 | } |
215b9e13 | 717 | if (cancelled(PartialIndexEnd)) throw solver_cancelled; |
c92c1f60 JG |
718 | } |
719 | ||
720 | // We are at the top of the tree | |
0a66f013 JG |
721 | assert(X.size() == K+1); |
722 | for (FullStepRow<FinalFullWidth> row : *X[K]) { | |
c6a7e897 DH |
723 | auto soln = row.GetIndices(hashLen, lenIndices, CollisionBitLength); |
724 | assert(soln.size() == equihash_solution_size(N, K)); | |
725 | solns.insert(soln); | |
23acf867 JG |
726 | } |
727 | for (auto soln : solns) { | |
728 | if (validBlock(soln)) | |
51eb5273 | 729 | return true; |
c92c1f60 | 730 | } |
215b9e13 | 731 | if (cancelled(PartialEnd)) throw solver_cancelled; |
2dbabb11 | 732 | continue; |
c92c1f60 JG |
733 | |
734 | invalidsolution: | |
735 | invalidCount++; | |
736 | } | |
737 | LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount); | |
738 | ||
51eb5273 | 739 | return false; |
c92c1f60 JG |
740 | } |
741 | ||
e9574728 | 742 | template<unsigned int N, unsigned int K> |
5be6abbf | 743 | bool Equihash<N,K>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln) |
6d25662f | 744 | { |
5be6abbf JG |
745 | if (soln.size() != SolutionWidth) { |
746 | LogPrint("pow", "Invalid solution length: %d (expected %d)\n", | |
747 | soln.size(), SolutionWidth); | |
6d25662f JG |
748 | return false; |
749 | } | |
750 | ||
d4d76536 | 751 | std::vector<FullStepRow<FinalFullWidth>> X; |
5be6abbf | 752 | X.reserve(1 << K); |
caa0348f | 753 | unsigned char tmpHash[HashOutput]; |
5be6abbf | 754 | for (eh_index i : GetIndicesFromMinimal(soln, CollisionBitLength)) { |
caa0348f JG |
755 | GenerateHash(base_state, i/IndicesPerHashOutput, tmpHash, HashOutput); |
756 | X.emplace_back(tmpHash+((i % IndicesPerHashOutput) * N/8), | |
757 | N/8, HashLength, CollisionBitLength, i); | |
6d25662f JG |
758 | } |
759 | ||
036dcbd9 | 760 | size_t hashLen = HashLength; |
d4d76536 | 761 | size_t lenIndices = sizeof(eh_index); |
6d25662f | 762 | while (X.size() > 1) { |
d4d76536 | 763 | std::vector<FullStepRow<FinalFullWidth>> Xc; |
6d25662f | 764 | for (int i = 0; i < X.size(); i += 2) { |
e9574728 | 765 | if (!HasCollision(X[i], X[i+1], CollisionByteLength)) { |
6d25662f | 766 | LogPrint("pow", "Invalid solution: invalid collision length between StepRows\n"); |
d4d76536 JG |
767 | LogPrint("pow", "X[i] = %s\n", X[i].GetHex(hashLen)); |
768 | LogPrint("pow", "X[i+1] = %s\n", X[i+1].GetHex(hashLen)); | |
6d25662f JG |
769 | return false; |
770 | } | |
d07cf629 | 771 | if (X[i+1].IndicesBefore(X[i], hashLen, lenIndices)) { |
6d25662f | 772 | LogPrint("pow", "Invalid solution: Index tree incorrectly ordered\n"); |
cc6c9ec0 | 773 | return false; |
6d25662f | 774 | } |
d4d76536 | 775 | if (!DistinctIndices(X[i], X[i+1], hashLen, lenIndices)) { |
6d25662f JG |
776 | LogPrint("pow", "Invalid solution: duplicate indices\n"); |
777 | return false; | |
778 | } | |
d4d76536 | 779 | Xc.emplace_back(X[i], X[i+1], hashLen, lenIndices, CollisionByteLength); |
6d25662f JG |
780 | } |
781 | X = Xc; | |
d4d76536 JG |
782 | hashLen -= CollisionByteLength; |
783 | lenIndices *= 2; | |
6d25662f JG |
784 | } |
785 | ||
786 | assert(X.size() == 1); | |
d4d76536 | 787 | return X[0].IsZero(hashLen); |
6d25662f | 788 | } |
e9574728 | 789 | |
ae37d2a4 JG |
790 | // Explicit instantiations for Equihash<96,3> |
791 | template int Equihash<96,3>::InitialiseState(eh_HashState& base_state); | |
51eb5273 | 792 | template bool Equihash<96,3>::BasicSolve(const eh_HashState& base_state, |
5be6abbf | 793 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 JG |
794 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
795 | template bool Equihash<96,3>::OptimisedSolve(const eh_HashState& base_state, | |
5be6abbf | 796 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 | 797 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
5be6abbf | 798 | template bool Equihash<96,3>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln); |
ae37d2a4 | 799 | |
eeb41778 JG |
800 | // Explicit instantiations for Equihash<200,9> |
801 | template int Equihash<200,9>::InitialiseState(eh_HashState& base_state); | |
802 | template bool Equihash<200,9>::BasicSolve(const eh_HashState& base_state, | |
5be6abbf | 803 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
eeb41778 JG |
804 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
805 | template bool Equihash<200,9>::OptimisedSolve(const eh_HashState& base_state, | |
5be6abbf | 806 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
eeb41778 | 807 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
5be6abbf | 808 | template bool Equihash<200,9>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln); |
eeb41778 | 809 | |
e9574728 JG |
810 | // Explicit instantiations for Equihash<96,5> |
811 | template int Equihash<96,5>::InitialiseState(eh_HashState& base_state); | |
51eb5273 | 812 | template bool Equihash<96,5>::BasicSolve(const eh_HashState& base_state, |
5be6abbf | 813 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 JG |
814 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
815 | template bool Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state, | |
5be6abbf | 816 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 | 817 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
5be6abbf | 818 | template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln); |
e9574728 JG |
819 | |
820 | // Explicit instantiations for Equihash<48,5> | |
821 | template int Equihash<48,5>::InitialiseState(eh_HashState& base_state); | |
51eb5273 | 822 | template bool Equihash<48,5>::BasicSolve(const eh_HashState& base_state, |
5be6abbf | 823 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 JG |
824 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
825 | template bool Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state, | |
5be6abbf | 826 | const std::function<bool(std::vector<unsigned char>)> validBlock, |
51eb5273 | 827 | const std::function<bool(EhSolverCancelCheck)> cancelled); |
5be6abbf | 828 | template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector<unsigned char> soln); |