]> Git Repo - VerusCoin.git/blame - src/crypto/equihash.cpp
Use template parameters to statically initialise Equihash
[VerusCoin.git] / src / crypto / equihash.cpp
CommitLineData
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
e9574728
JG
22template<unsigned int N, unsigned int K>
23int Equihash<N,K>::InitialiseState(eh_HashState& base_state)
6d25662f 24{
e9574728
JG
25 unsigned int n = N;
26 unsigned int k = K;
6d25662f
JG
27 unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {};
28 memcpy(personalization, "ZcashPOW", 8);
29 memcpy(personalization+8, &n, 4);
30 memcpy(personalization+12, &k, 4);
31 return crypto_generichash_blake2b_init_salt_personal(&base_state,
32 NULL, 0, // No key.
e9574728 33 N/8,
6d25662f
JG
34 NULL, // No salt.
35 personalization);
36}
37
c92c1f60
JG
38eh_trunc TruncateIndex(eh_index i, unsigned int ilen)
39{
40 // Truncate to 8 bits
41 assert(sizeof(eh_trunc) == 1);
42 return (i >> (ilen - 8)) & 0xff;
43}
44
45eh_index UntruncateIndex(eh_trunc t, eh_index r, unsigned int ilen)
46{
47 eh_index i{t};
48 return (i << (ilen - 8)) | r;
49}
50
6d25662f
JG
51StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
52 hash {new unsigned char[n/8]},
a3361e77 53 len {n/8}
6d25662f
JG
54{
55 eh_HashState state;
56 state = base_state;
57 crypto_generichash_blake2b_update(&state, (unsigned char*) &i, sizeof(eh_index));
58 crypto_generichash_blake2b_final(&state, hash, n/8);
6d25662f
JG
59}
60
61StepRow::~StepRow()
62{
63 delete[] hash;
64}
65
66StepRow::StepRow(const StepRow& a) :
67 hash {new unsigned char[a.len]},
a3361e77 68 len {a.len}
6d25662f 69{
6afef0dd 70 std::copy(a.hash, a.hash+a.len, hash);
6d25662f
JG
71}
72
a3361e77
JG
73FullStepRow::FullStepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
74 StepRow {n, base_state, i},
75 indices {i}
76{
77 assert(indices.size() == 1);
78}
79
80FullStepRow& FullStepRow::operator=(const FullStepRow& a)
6d25662f
JG
81{
82 unsigned char* p = new unsigned char[a.len];
6afef0dd 83 std::copy(a.hash, a.hash+a.len, p);
6d25662f
JG
84 delete[] hash;
85 hash = p;
86 len = a.len;
87 indices = a.indices;
88 return *this;
89}
90
a3361e77 91FullStepRow& FullStepRow::operator^=(const FullStepRow& a)
6d25662f
JG
92{
93 if (a.len != len) {
94 throw std::invalid_argument("Hash length differs");
95 }
96 if (a.indices.size() != indices.size()) {
97 throw std::invalid_argument("Number of indices differs");
98 }
99 unsigned char* p = new unsigned char[len];
100 for (int i = 0; i < len; i++)
101 p[i] = hash[i] ^ a.hash[i];
102 delete[] hash;
103 hash = p;
104 indices.reserve(indices.size() + a.indices.size());
105 indices.insert(indices.end(), a.indices.begin(), a.indices.end());
106 return *this;
107}
108
a3361e77 109void FullStepRow::TrimHash(int l)
6d25662f
JG
110{
111 unsigned char* p = new unsigned char[len-l];
6afef0dd 112 std::copy(hash+l, hash+len, p);
6d25662f
JG
113 delete[] hash;
114 hash = p;
115 len -= l;
116}
117
118bool StepRow::IsZero()
119{
120 char res = 0;
121 for (int i = 0; i < len; i++)
122 res |= hash[i];
123 return res == 0;
124}
125
126bool HasCollision(StepRow& a, StepRow& b, int l)
127{
128 bool res = true;
129 for (int j = 0; j < l; j++)
130 res &= a.hash[j] == b.hash[j];
131 return res;
132}
133
134// Checks if the intersection of a.indices and b.indices is empty
a3361e77 135bool DistinctIndices(const FullStepRow& a, const FullStepRow& b)
6d25662f
JG
136{
137 std::vector<eh_index> aSrt(a.indices);
138 std::vector<eh_index> bSrt(b.indices);
139
140 std::sort(aSrt.begin(), aSrt.end());
141 std::sort(bSrt.begin(), bSrt.end());
142
143 unsigned int i = 0;
144 for (unsigned int j = 0; j < bSrt.size(); j++) {
145 while (aSrt[i] < bSrt[j]) {
146 i++;
147 if (i == aSrt.size()) { return true; }
148 }
149 assert(aSrt[i] >= bSrt[j]);
150 if (aSrt[i] == bSrt[j]) { return false; }
151 }
152 return true;
153}
154
c92c1f60
JG
155bool IsValidBranch(const FullStepRow& a, const unsigned int ilen, const eh_trunc t)
156{
157 return TruncateIndex(a.indices[0], ilen) == t;
158}
159
160TruncatedStepRow::TruncatedStepRow(unsigned int n, const eh_HashState& base_state, eh_index i, unsigned int ilen) :
161 StepRow {n, base_state, i},
39f5cb35 162 lenIndices {1}
c92c1f60 163{
39f5cb35
JG
164 unsigned char* p = new unsigned char[len+lenIndices];
165 std::copy(hash, hash+len, p);
166 p[len] = TruncateIndex(i, ilen);
167 delete[] hash;
168 hash = p;
169}
170
171TruncatedStepRow::TruncatedStepRow(const TruncatedStepRow& a) :
172 StepRow {a},
173 lenIndices {a.lenIndices}
174{
175 unsigned char* p = new unsigned char[a.len+a.lenIndices];
176 std::copy(a.hash, a.hash+a.len+a.lenIndices, p);
177 delete[] hash;
178 hash = p;
c92c1f60
JG
179}
180
181TruncatedStepRow& TruncatedStepRow::operator=(const TruncatedStepRow& a)
182{
39f5cb35
JG
183 unsigned char* p = new unsigned char[a.len+a.lenIndices];
184 std::copy(a.hash, a.hash+a.len+a.lenIndices, p);
c92c1f60
JG
185 delete[] hash;
186 hash = p;
187 len = a.len;
39f5cb35 188 lenIndices = a.lenIndices;
c92c1f60
JG
189 return *this;
190}
191
192TruncatedStepRow& TruncatedStepRow::operator^=(const TruncatedStepRow& a)
193{
194 if (a.len != len) {
195 throw std::invalid_argument("Hash length differs");
196 }
39f5cb35 197 if (a.lenIndices != lenIndices) {
c92c1f60
JG
198 throw std::invalid_argument("Number of indices differs");
199 }
39f5cb35 200 unsigned char* p = new unsigned char[len+lenIndices+a.lenIndices];
c92c1f60
JG
201 for (int i = 0; i < len; i++)
202 p[i] = hash[i] ^ a.hash[i];
39f5cb35
JG
203 std::copy(hash+len, hash+len+lenIndices, p+len);
204 std::copy(a.hash+a.len, a.hash+a.len+a.lenIndices, p+len+lenIndices);
c92c1f60
JG
205 delete[] hash;
206 hash = p;
39f5cb35 207 lenIndices += a.lenIndices;
c92c1f60
JG
208 return *this;
209}
210
39f5cb35
JG
211void TruncatedStepRow::TrimHash(int l)
212{
213 unsigned char* p = new unsigned char[len-l+lenIndices];
214 std::copy(hash+l, hash+len+lenIndices, p);
215 delete[] hash;
216 hash = p;
217 len -= l;
218}
219
220eh_trunc* TruncatedStepRow::GetPartialSolution(eh_index soln_size) const
221{
222 assert(lenIndices == soln_size);
223 eh_trunc* p = new eh_trunc[lenIndices];
224 std::copy(hash+len, hash+len+lenIndices, p);
225 return p;
226}
227
e9574728
JG
228template<unsigned int N, unsigned int K>
229std::set<std::vector<eh_index>> Equihash<N,K>::BasicSolve(const eh_HashState& base_state)
6d25662f 230{
e9574728 231 eh_index init_size { 1 << (CollisionBitLength + 1) };
6d25662f
JG
232
233 // 1) Generate first list
234 LogPrint("pow", "Generating first list\n");
a3361e77 235 std::vector<FullStepRow> X;
6d25662f
JG
236 X.reserve(init_size);
237 for (eh_index i = 0; i < init_size; i++) {
e9574728 238 X.emplace_back(N, base_state, i);
6d25662f
JG
239 }
240
241 // 3) Repeat step 2 until 2n/(k+1) bits remain
e9574728 242 for (int r = 1; r < K && X.size() > 0; r++) {
6d25662f
JG
243 LogPrint("pow", "Round %d:\n", r);
244 // 2a) Sort the list
245 LogPrint("pow", "- Sorting list\n");
246 std::sort(X.begin(), X.end());
247
248 LogPrint("pow", "- Finding collisions\n");
249 int i = 0;
250 int posFree = 0;
a3361e77 251 std::vector<FullStepRow> Xc;
6d25662f
JG
252 while (i < X.size() - 1) {
253 // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
254 int j = 1;
255 while (i+j < X.size() &&
e9574728 256 HasCollision(X[i], X[i+j], CollisionByteLength)) {
6d25662f
JG
257 j++;
258 }
259
260 // 2c) Calculate tuples (X_i ^ X_j, (i, j))
261 for (int l = 0; l < j - 1; l++) {
262 for (int m = l + 1; m < j; m++) {
263 if (DistinctIndices(X[i+l], X[i+m])) {
264 Xc.push_back(X[i+l] ^ X[i+m]);
e9574728 265 Xc.back().TrimHash(CollisionByteLength);
6d25662f
JG
266 }
267 }
268 }
269
270 // 2d) Store tuples on the table in-place if possible
271 while (posFree < i+j && Xc.size() > 0) {
272 X[posFree++] = Xc.back();
273 Xc.pop_back();
274 }
275
276 i += j;
277 }
278
279 // 2e) Handle edge case where final table entry has no collision
280 while (posFree < X.size() && Xc.size() > 0) {
281 X[posFree++] = Xc.back();
282 Xc.pop_back();
283 }
284
285 if (Xc.size() > 0) {
286 // 2f) Add overflow to end of table
287 X.insert(X.end(), Xc.begin(), Xc.end());
288 } else if (posFree < X.size()) {
289 // 2g) Remove empty space at the end
290 X.erase(X.begin()+posFree, X.end());
291 X.shrink_to_fit();
292 }
293 }
294
295 // k+1) Find a collision on last 2n(k+1) bits
296 LogPrint("pow", "Final round:\n");
297 std::set<std::vector<eh_index>> solns;
298 if (X.size() > 1) {
299 LogPrint("pow", "- Sorting list\n");
300 std::sort(X.begin(), X.end());
301 LogPrint("pow", "- Finding collisions\n");
302 for (int i = 0; i < X.size() - 1; i++) {
a3361e77 303 FullStepRow res = X[i] ^ X[i+1];
6d25662f
JG
304 if (res.IsZero() && DistinctIndices(X[i], X[i+1])) {
305 solns.insert(res.GetSolution());
306 }
307 }
308 } else
309 LogPrint("pow", "- List is empty\n");
310
311 return solns;
312}
313
c92c1f60
JG
314void CollideBranches(std::vector<FullStepRow>& X, const unsigned int clen, const unsigned int ilen, const eh_trunc lt, const eh_trunc rt)
315{
316 int i = 0;
317 int posFree = 0;
318 std::vector<FullStepRow> Xc;
319 while (i < X.size() - 1) {
320 // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
321 int j = 1;
322 while (i+j < X.size() &&
323 HasCollision(X[i], X[i+j], clen)) {
324 j++;
325 }
326
327 // 2c) Calculate tuples (X_i ^ X_j, (i, j))
328 for (int l = 0; l < j - 1; l++) {
329 for (int m = l + 1; m < j; m++) {
330 if (DistinctIndices(X[i+l], X[i+m])) {
331 if (IsValidBranch(X[i+l], ilen, lt) && IsValidBranch(X[i+m], ilen, rt)) {
332 Xc.push_back(X[i+l] ^ X[i+m]);
333 Xc.back().TrimHash(clen);
334 } else if (IsValidBranch(X[i+m], ilen, lt) && IsValidBranch(X[i+l], ilen, rt)) {
335 Xc.push_back(X[i+m] ^ X[i+l]);
336 Xc.back().TrimHash(clen);
337 }
338 }
339 }
340 }
341
342 // 2d) Store tuples on the table in-place if possible
343 while (posFree < i+j && Xc.size() > 0) {
344 X[posFree++] = Xc.back();
345 Xc.pop_back();
346 }
347
348 i += j;
349 }
350
351 // 2e) Handle edge case where final table entry has no collision
352 while (posFree < X.size() && Xc.size() > 0) {
353 X[posFree++] = Xc.back();
354 Xc.pop_back();
355 }
356
357 if (Xc.size() > 0) {
358 // 2f) Add overflow to end of table
359 X.insert(X.end(), Xc.begin(), Xc.end());
360 } else if (posFree < X.size()) {
361 // 2g) Remove empty space at the end
362 X.erase(X.begin()+posFree, X.end());
363 X.shrink_to_fit();
364 }
365}
366
e9574728
JG
367template<unsigned int N, unsigned int K>
368std::set<std::vector<eh_index>> Equihash<N,K>::OptimisedSolve(const eh_HashState& base_state)
c92c1f60 369{
e9574728 370 eh_index init_size { 1 << (CollisionBitLength + 1) };
c92c1f60
JG
371
372 // First run the algorithm with truncated indices
373
e9574728 374 eh_index soln_size { 1 << K };
39f5cb35 375 std::vector<eh_trunc*> partialSolns;
c92c1f60
JG
376 {
377
378 // 1) Generate first list
379 LogPrint("pow", "Generating first list\n");
380 std::vector<TruncatedStepRow> Xt;
381 Xt.reserve(init_size);
382 for (eh_index i = 0; i < init_size; i++) {
e9574728 383 Xt.emplace_back(N, base_state, i, CollisionBitLength + 1);
c92c1f60
JG
384 }
385
386 // 3) Repeat step 2 until 2n/(k+1) bits remain
e9574728 387 for (int r = 1; r < K && Xt.size() > 0; r++) {
c92c1f60
JG
388 LogPrint("pow", "Round %d:\n", r);
389 // 2a) Sort the list
390 LogPrint("pow", "- Sorting list\n");
391 std::sort(Xt.begin(), Xt.end());
392
393 LogPrint("pow", "- Finding collisions\n");
394 int i = 0;
395 int posFree = 0;
396 std::vector<TruncatedStepRow> Xc;
397 while (i < Xt.size() - 1) {
398 // 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
399 int j = 1;
400 while (i+j < Xt.size() &&
e9574728 401 HasCollision(Xt[i], Xt[i+j], CollisionByteLength)) {
c92c1f60
JG
402 j++;
403 }
404
405 // 2c) Calculate tuples (X_i ^ X_j, (i, j))
406 for (int l = 0; l < j - 1; l++) {
407 for (int m = l + 1; m < j; m++) {
408 // We truncated, so don't check for distinct indices here
409 Xc.push_back(Xt[i+l] ^ Xt[i+m]);
e9574728 410 Xc.back().TrimHash(CollisionByteLength);
c92c1f60
JG
411 }
412 }
413
414 // 2d) Store tuples on the table in-place if possible
415 while (posFree < i+j && Xc.size() > 0) {
416 Xt[posFree++] = Xc.back();
417 Xc.pop_back();
418 }
419
420 i += j;
421 }
422
423 // 2e) Handle edge case where final table entry has no collision
424 while (posFree < Xt.size() && Xc.size() > 0) {
425 Xt[posFree++] = Xc.back();
426 Xc.pop_back();
427 }
428
429 if (Xc.size() > 0) {
430 // 2f) Add overflow to end of table
431 Xt.insert(Xt.end(), Xc.begin(), Xc.end());
432 } else if (posFree < Xt.size()) {
433 // 2g) Remove empty space at the end
434 Xt.erase(Xt.begin()+posFree, Xt.end());
435 Xt.shrink_to_fit();
436 }
437 }
438
439 // k+1) Find a collision on last 2n(k+1) bits
440 LogPrint("pow", "Final round:\n");
441 if (Xt.size() > 1) {
442 LogPrint("pow", "- Sorting list\n");
443 std::sort(Xt.begin(), Xt.end());
444 LogPrint("pow", "- Finding collisions\n");
445 for (int i = 0; i < Xt.size() - 1; i++) {
446 TruncatedStepRow res = Xt[i] ^ Xt[i+1];
447 if (res.IsZero()) {
39f5cb35 448 partialSolns.push_back(res.GetPartialSolution(soln_size));
c92c1f60
JG
449 }
450 }
451 } else
452 LogPrint("pow", "- List is empty\n");
453
454 } // Ensure Xt goes out of scope and is destroyed
455
456 LogPrint("pow", "Found %d partial solutions\n", partialSolns.size());
457
458 // Now for each solution run the algorithm again to recreate the indices
459 LogPrint("pow", "Culling solutions\n");
460 std::set<std::vector<eh_index>> solns;
e9574728 461 eh_index recreate_size { UntruncateIndex(1, 0, CollisionBitLength + 1) };
c92c1f60 462 int invalidCount = 0;
39f5cb35 463 for (eh_trunc* partialSoln : partialSolns) {
c92c1f60
JG
464 // 1) Generate first list of possibilities
465 std::vector<std::vector<FullStepRow>> X;
39f5cb35
JG
466 X.reserve(soln_size);
467 for (eh_index i = 0; i < soln_size; i++) {
c92c1f60
JG
468 std::vector<FullStepRow> ic;
469 ic.reserve(recreate_size);
470 for (eh_index j = 0; j < recreate_size; j++) {
e9574728
JG
471 eh_index newIndex { UntruncateIndex(partialSoln[i], j, CollisionBitLength + 1) };
472 ic.emplace_back(N, base_state, newIndex);
c92c1f60
JG
473 }
474 X.push_back(ic);
475 }
476
477 // 3) Repeat step 2 for each level of the tree
478 for (int r = 0; X.size() > 1; r++) {
479 std::vector<std::vector<FullStepRow>> Xc;
480 Xc.reserve(X.size()/2);
481
482 // 2a) For each pair of lists:
483 for (int v = 0; v < X.size(); v += 2) {
484 // 2b) Merge the lists
485 std::vector<FullStepRow> ic(X[v]);
486 ic.reserve(X[v].size() + X[v+1].size());
487 ic.insert(ic.end(), X[v+1].begin(), X[v+1].end());
488 std::sort(ic.begin(), ic.end());
e9574728 489 CollideBranches(ic, CollisionByteLength, CollisionBitLength + 1, partialSoln[(1<<r)*v], partialSoln[(1<<r)*(v+1)]);
c92c1f60
JG
490
491 // 2v) Check if this has become an invalid solution
492 if (ic.size() == 0)
493 goto invalidsolution;
494
495 Xc.push_back(ic);
496 }
497
498 X = Xc;
499 }
500
501 // We are at the top of the tree
502 assert(X.size() == 1);
503 for (FullStepRow row : X[0]) {
504 solns.insert(row.GetSolution());
505 }
39f5cb35 506 goto deletesolution;
c92c1f60
JG
507
508invalidsolution:
509 invalidCount++;
39f5cb35
JG
510
511deletesolution:
512 delete[] partialSoln;
c92c1f60
JG
513 }
514 LogPrint("pow", "- Number of invalid solutions found: %d\n", invalidCount);
515
516 return solns;
517}
518
e9574728
JG
519template<unsigned int N, unsigned int K>
520bool Equihash<N,K>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
6d25662f 521{
e9574728 522 eh_index soln_size { 1u << K };
6d25662f
JG
523 if (soln.size() != soln_size) {
524 LogPrint("pow", "Invalid solution size: %d\n", soln.size());
525 return false;
526 }
527
a3361e77 528 std::vector<FullStepRow> X;
6d25662f
JG
529 X.reserve(soln_size);
530 for (eh_index i : soln) {
e9574728 531 X.emplace_back(N, base_state, i);
6d25662f
JG
532 }
533
534 while (X.size() > 1) {
a3361e77 535 std::vector<FullStepRow> Xc;
6d25662f 536 for (int i = 0; i < X.size(); i += 2) {
e9574728 537 if (!HasCollision(X[i], X[i+1], CollisionByteLength)) {
6d25662f
JG
538 LogPrint("pow", "Invalid solution: invalid collision length between StepRows\n");
539 LogPrint("pow", "X[i] = %s\n", X[i].GetHex());
540 LogPrint("pow", "X[i+1] = %s\n", X[i+1].GetHex());
541 return false;
542 }
543 if (X[i+1].IndicesBefore(X[i])) {
544 return false;
545 LogPrint("pow", "Invalid solution: Index tree incorrectly ordered\n");
546 }
547 if (!DistinctIndices(X[i], X[i+1])) {
548 LogPrint("pow", "Invalid solution: duplicate indices\n");
549 return false;
550 }
551 Xc.push_back(X[i] ^ X[i+1]);
e9574728 552 Xc.back().TrimHash(CollisionByteLength);
6d25662f
JG
553 }
554 X = Xc;
555 }
556
557 assert(X.size() == 1);
558 return X[0].IsZero();
559}
e9574728
JG
560
561// Explicit instantiations for Equihash<96,5>
562template int Equihash<96,5>::InitialiseState(eh_HashState& base_state);
563template std::set<std::vector<eh_index>> Equihash<96,5>::BasicSolve(const eh_HashState& base_state);
564template std::set<std::vector<eh_index>> Equihash<96,5>::OptimisedSolve(const eh_HashState& base_state);
565template bool Equihash<96,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
566
567// Explicit instantiations for Equihash<48,5>
568template int Equihash<48,5>::InitialiseState(eh_HashState& base_state);
569template std::set<std::vector<eh_index>> Equihash<48,5>::BasicSolve(const eh_HashState& base_state);
570template std::set<std::vector<eh_index>> Equihash<48,5>::OptimisedSolve(const eh_HashState& base_state);
571template bool Equihash<48,5>::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
This page took 0.10007 seconds and 4 git commands to generate.