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