Browse Source

Implement validator and basic solver for Equihash

pull/4/head
Jack Grigg 8 years ago
parent
commit
6d25662f25
  1. 3
      src/Makefile.am
  2. 287
      src/crypto/equihash.cpp
  3. 75
      src/crypto/equihash.h

3
src/Makefile.am

@ -240,6 +240,8 @@ libbitcoin_wallet_a_SOURCES = \
crypto_libbitcoin_crypto_a_CPPFLAGS = $(BITCOIN_CONFIG_INCLUDES)
crypto_libbitcoin_crypto_a_SOURCES = \
crypto/common.h \
crypto/equihash.cpp \
crypto/equihash.h \
crypto/hmac_sha256.cpp \
crypto/hmac_sha256.h \
crypto/hmac_sha512.cpp \
@ -426,6 +428,7 @@ libzerocash_a_CPPFLAGS = -fPIC -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAV
if BUILD_BITCOIN_LIBS
include_HEADERS = script/bitcoinconsensus.h
libbitcoinconsensus_la_SOURCES = \
crypto/equihash.cpp \
crypto/hmac_sha512.cpp \
crypto/ripemd160.cpp \
crypto/sha1.cpp \

287
src/crypto/equihash.cpp

@ -0,0 +1,287 @@
// Copyright (c) 2016 Jack Grigg
// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Implementation of the Equihash Proof-of-Work algorithm.
//
// Reference
// =========
// Alex Biryukov and Dmitry Khovratovich
// Equihash: Asymmetric Proof-of-Work Based on the Generalized Birthday Problem
// NDSS ’16, 21-24 February 2016, San Diego, CA, USA
// https://www.internetsociety.org/sites/default/files/blogs-media/equihash-asymmetric-proof-of-work-based-generalized-birthday-problem.pdf
#include "crypto/equihash.h"
#include "util.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdexcept>
void validate_params(int n, int k)
{
if (k>=n) {
std::cerr << "n must be larger than k\n";
throw invalid_params();
}
if (n % 8 != 0) {
std::cerr << "Parameters must satisfy n = 0 mod 8\n";
throw invalid_params();
}
if ((n/(k+1)) % 8 != 0) {
std::cerr << "Parameters must satisfy n/(k+1) = 0 mod 8\n";
throw invalid_params();
}
}
int Equihash::InitialiseState(eh_HashState& base_state)
{
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {};
memcpy(personalization, "ZcashPOW", 8);
memcpy(personalization+8, &n, 4);
memcpy(personalization+12, &k, 4);
return crypto_generichash_blake2b_init_salt_personal(&base_state,
NULL, 0, // No key.
n/8,
NULL, // No salt.
personalization);
}
StepRow::StepRow(unsigned int n, const eh_HashState& base_state, eh_index i) :
hash {new unsigned char[n/8]},
len {n/8},
indices {i}
{
eh_HashState state;
state = base_state;
crypto_generichash_blake2b_update(&state, (unsigned char*) &i, sizeof(eh_index));
crypto_generichash_blake2b_final(&state, hash, n/8);
assert(indices.size() == 1);
}
StepRow::~StepRow()
{
delete[] hash;
}
StepRow::StepRow(const StepRow& a) :
hash {new unsigned char[a.len]},
len {a.len},
indices(a.indices)
{
for (int i = 0; i < len; i++)
hash[i] = a.hash[i];
}
StepRow& StepRow::operator=(const StepRow& a)
{
unsigned char* p = new unsigned char[a.len];
for (int i = 0; i < a.len; i++)
p[i] = a.hash[i];
delete[] hash;
hash = p;
len = a.len;
indices = a.indices;
return *this;
}
StepRow& StepRow::operator^=(const StepRow& a)
{
if (a.len != len) {
throw std::invalid_argument("Hash length differs");
}
if (a.indices.size() != indices.size()) {
throw std::invalid_argument("Number of indices differs");
}
unsigned char* p = new unsigned char[len];
for (int i = 0; i < len; i++)
p[i] = hash[i] ^ a.hash[i];
delete[] hash;
hash = p;
indices.reserve(indices.size() + a.indices.size());
indices.insert(indices.end(), a.indices.begin(), a.indices.end());
return *this;
}
void StepRow::TrimHash(int l)
{
unsigned char* p = new unsigned char[len-l];
for (int i = 0; i < len-l; i++)
p[i] = hash[i+l];
delete[] hash;
hash = p;
len -= l;
}
bool StepRow::IsZero()
{
char res = 0;
for (int i = 0; i < len; i++)
res |= hash[i];
return res == 0;
}
bool HasCollision(StepRow& a, StepRow& b, int l)
{
bool res = true;
for (int j = 0; j < l; j++)
res &= a.hash[j] == b.hash[j];
return res;
}
// Checks if the intersection of a.indices and b.indices is empty
bool DistinctIndices(const StepRow& a, const StepRow& b)
{
std::vector<eh_index> aSrt(a.indices);
std::vector<eh_index> bSrt(b.indices);
std::sort(aSrt.begin(), aSrt.end());
std::sort(bSrt.begin(), bSrt.end());
unsigned int i = 0;
for (unsigned int j = 0; j < bSrt.size(); j++) {
while (aSrt[i] < bSrt[j]) {
i++;
if (i == aSrt.size()) { return true; }
}
assert(aSrt[i] >= bSrt[j]);
if (aSrt[i] == bSrt[j]) { return false; }
}
return true;
}
Equihash::Equihash(unsigned int n, unsigned int k) :
n(n), k(k)
{
validate_params(n, k);
}
std::set<std::vector<eh_index>> Equihash::BasicSolve(const eh_HashState& base_state)
{
assert(CollisionBitLength() + 1 < 8*sizeof(eh_index));
eh_index init_size { ((eh_index) 1) << (CollisionBitLength() + 1) };
// 1) Generate first list
LogPrint("pow", "Generating first list\n");
std::vector<StepRow> X;
X.reserve(init_size);
for (eh_index i = 0; i < init_size; i++) {
X.emplace_back(n, base_state, i);
}
// 3) Repeat step 2 until 2n/(k+1) bits remain
for (int r = 1; r < k && X.size() > 0; r++) {
LogPrint("pow", "Round %d:\n", r);
// 2a) Sort the list
LogPrint("pow", "- Sorting list\n");
std::sort(X.begin(), X.end());
LogPrint("pow", "- Finding collisions\n");
int i = 0;
int posFree = 0;
std::vector<StepRow> Xc;
while (i < X.size() - 1) {
// 2b) Find next set of unordered pairs with collisions on the next n/(k+1) bits
int j = 1;
while (i+j < X.size() &&
HasCollision(X[i], X[i+j], CollisionByteLength())) {
j++;
}
// 2c) Calculate tuples (X_i ^ X_j, (i, j))
for (int l = 0; l < j - 1; l++) {
for (int m = l + 1; m < j; m++) {
if (DistinctIndices(X[i+l], X[i+m])) {
Xc.push_back(X[i+l] ^ X[i+m]);
Xc.back().TrimHash(CollisionByteLength());
}
}
}
// 2d) Store tuples on the table in-place if possible
while (posFree < i+j && Xc.size() > 0) {
X[posFree++] = Xc.back();
Xc.pop_back();
}
i += j;
}
// 2e) Handle edge case where final table entry has no collision
while (posFree < X.size() && Xc.size() > 0) {
X[posFree++] = Xc.back();
Xc.pop_back();
}
if (Xc.size() > 0) {
// 2f) Add overflow to end of table
X.insert(X.end(), Xc.begin(), Xc.end());
} else if (posFree < X.size()) {
// 2g) Remove empty space at the end
X.erase(X.begin()+posFree, X.end());
X.shrink_to_fit();
}
}
// k+1) Find a collision on last 2n(k+1) bits
LogPrint("pow", "Final round:\n");
std::set<std::vector<eh_index>> solns;
if (X.size() > 1) {
LogPrint("pow", "- Sorting list\n");
std::sort(X.begin(), X.end());
LogPrint("pow", "- Finding collisions\n");
for (int i = 0; i < X.size() - 1; i++) {
StepRow res = X[i] ^ X[i+1];
if (res.IsZero() && DistinctIndices(X[i], X[i+1])) {
solns.insert(res.GetSolution());
}
}
} else
LogPrint("pow", "- List is empty\n");
return solns;
}
bool Equihash::IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln)
{
eh_index soln_size { pow(2, k) };
if (soln.size() != soln_size) {
LogPrint("pow", "Invalid solution size: %d\n", soln.size());
return false;
}
std::vector<StepRow> X;
X.reserve(soln_size);
for (eh_index i : soln) {
X.emplace_back(n, base_state, i);
}
while (X.size() > 1) {
std::vector<StepRow> Xc;
for (int i = 0; i < X.size(); i += 2) {
if (!HasCollision(X[i], X[i+1], CollisionByteLength())) {
LogPrint("pow", "Invalid solution: invalid collision length between StepRows\n");
LogPrint("pow", "X[i] = %s\n", X[i].GetHex());
LogPrint("pow", "X[i+1] = %s\n", X[i+1].GetHex());
return false;
}
if (X[i+1].IndicesBefore(X[i])) {
return false;
LogPrint("pow", "Invalid solution: Index tree incorrectly ordered\n");
}
if (!DistinctIndices(X[i], X[i+1])) {
LogPrint("pow", "Invalid solution: duplicate indices\n");
return false;
}
Xc.push_back(X[i] ^ X[i+1]);
Xc.back().TrimHash(CollisionByteLength());
}
X = Xc;
}
assert(X.size() == 1);
return X[0].IsZero();
}

75
src/crypto/equihash.h

@ -0,0 +1,75 @@
// Copyright (c) 2016 Jack Grigg
// Copyright (c) 2016 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_EQUIHASH_H
#define BITCOIN_EQUIHASH_H
#include "crypto/sha256.h"
#include "utilstrencodings.h"
#include "sodium.h"
#include <cstring>
#include <set>
#include <vector>
typedef crypto_generichash_blake2b_state eh_HashState;
typedef uint32_t eh_index;
struct invalid_params { };
class StepRow
{
private:
unsigned char* hash;
unsigned int len;
std::vector<eh_index> indices;
public:
StepRow(unsigned int n, const eh_HashState& base_state, eh_index i);
~StepRow();
StepRow(const StepRow& a);
StepRow& operator=(const StepRow& a);
StepRow& operator^=(const StepRow& a);
void TrimHash(int l);
bool IsZero();
bool IndicesBefore(const StepRow& a) { return indices[0] < a.indices[0]; }
std::vector<eh_index> GetSolution() { return std::vector<eh_index>(indices); }
std::string GetHex() { return HexStr(hash, hash+len); }
friend inline const StepRow operator^(const StepRow& a, const StepRow& b) {
if (a.indices[0] < b.indices[0]) { return StepRow(a) ^= b; }
else { return StepRow(b) ^= a; }
}
friend inline bool operator==(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, a.len) == 0; }
friend inline bool operator<(const StepRow& a, const StepRow& b) { return memcmp(a.hash, b.hash, a.len) < 0; }
friend bool HasCollision(StepRow& a, StepRow& b, int l);
friend bool DistinctIndices(const StepRow& a, const StepRow& b);
};
bool HasCollision(StepRow& a, StepRow& b, int l);
bool DistinctIndices(const StepRow& a, const StepRow& b);
class Equihash
{
private:
unsigned int n;
unsigned int k;
public:
Equihash(unsigned int n, unsigned int k);
inline unsigned int CollisionBitLength() { return n/(k+1); }
inline unsigned int CollisionByteLength() { return CollisionBitLength()/8; }
int InitialiseState(eh_HashState& base_state);
std::set<std::vector<eh_index>> BasicSolve(const eh_HashState& base_state);
bool IsValidSolution(const eh_HashState& base_state, std::vector<eh_index> soln);
};
#endif // BITCOIN_EQUIHASH_H
Loading…
Cancel
Save