UNPKG

equihashjs-verify

Version:

Equihash JavaScript verification ported from ZCASH on Python

479 lines (406 loc) 18.4 kB
// # ZCASH implementation: https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/equihash.py var blake2b = require('blake2b') var _ = require('lodash') var Networks = require('./networks') function Equihash(network) { this.network = network || Networks.bitcoingold } Equihash.prototype = function () { // DEBUG = true // VERBOSE = true 'use strict' let word_size = 32, word_mask = 1 * Math.pow(2, 32) - 1, // (1 << word_size) - 1 overflow padLeft = function(str, size) { while (str.length < size) { str = "0" + str; } return str }, assert = function (condition, message) { if (!condition) { message = message || "Assertion failed"; if (typeof Error !== "undefined") { throw new Error(message); } throw message; // Fallback } }, // Ported expand_array = function (inp, out_len, bit_len, byte_pad) { byte_pad = byte_pad || 0 assert(bit_len >= 8 && word_size >= 7 + bit_len) let out_width = Math.trunc((bit_len + 7) / 8) + byte_pad assert(out_len == Math.trunc(8 * out_width * inp.length / bit_len)) let out = Buffer.alloc(out_len) let bit_len_mask = (1 << bit_len) - 1 // # The acc_bits least-significant bits of acc_value represent a bit sequence // # in big-endian order. let acc_bits = 0 let acc_value = 0 let j = 0 for (let i = 0; i < inp.length; i++) { acc_value = ((acc_value << 8) & word_mask) | inp[i] acc_bits += 8 // # When we have bit_len or more bits in the accumulator, write the next // # output element. if (acc_bits >= bit_len) { acc_bits -= bit_len for (let x = byte_pad; x < out_width; x++) { out[j + x] = ( // # Big-endian acc_value >> (acc_bits + (8 * (out_width - x - 1))) ) & ( // # Apply bit_len_mask across byte boundaries (bit_len_mask >> (8 * (out_width - x - 1))) & 0xFF ) } j += out_width } } return out }, // function compress_array(inp, out_len, bit_len, byte_pad = 0) { // // assert bit_len >= 8 and word_size >= 7+bit_len // var in_width = (bit_len + 7)//8 + byte_pad // // assert out_len == bit_len*len(inp)//(8*in_width) // var out = bytearray(out_len) // var bit_len_mask = (1 << bit_len) - 1 // // # The acc_bits least-significant bits of acc_value represent a bit sequence // // # in big-endian order. // var acc_bits = 0 // var acc_value = 0 // var j = 0 // for (var i = 0; i < out_len.length; i++) { // // # When we have fewer than 8 bits left in the accumulator, read the next // // # input element. // if (acc_bits < 8) { // acc_value = ((acc_value << bit_len) & word_mask) | inp[j] // for (var x = byte_pad; x < in_width; x++) { // acc_value = acc_value | ( // ( // // # Apply bit_len_mask across byte boundaries // inp[j + x] & ((bit_len_mask >> (8 * (in_width - x - 1))) & 0xFF) // ) << (8 * (in_width - x - 1))) // # Big-endian // } // j += in_width // acc_bits += bit_len // } // acc_bits -= 8 // out[i] = (acc_value >> acc_bits) & 0xFF // } // return out // } // Ported get_indices_from_minimal = function (minimal, bit_len) { let eh_index_size = 4 assert(Math.trunc((bit_len + 7) / 8) <= eh_index_size) let len_indices = Math.trunc(8 * eh_index_size * minimal.length / bit_len) let byte_pad = eh_index_size - Math.trunc((bit_len + 7) / 8) let expanded = expand_array(minimal, len_indices, bit_len, byte_pad) let data = [] for (let i = 0; i < len_indices; i += eh_index_size) { data.push(expanded.readUInt32BE(i, i + 4)) } return data }, // function get_minimal_from_indices(indices, bit_len) { // var eh_index_size = 4 // // assert (bit_len+7)//8 <= eh_index_size // var len_indices = len(indices) * eh_index_size // var min_len = bit_len * len_indices//(8*eh_index_size) // var byte_pad = eh_index_size - (bit_len + 7)//8 // var byte_indices = ''//bytearray(b''.join([struct.pack('>I', i) for i in indices])) // return compress_array(byte_indices, min_len, bit_len, byte_pad) // } // Ported hash_nonce = function (digest, nonce) { for (let i = 7; i >= 0; i--) { let buf = Buffer.alloc(4) let num = nonce.readUInt32BE(4 * i, 4 * (i + 1)) buf.writeUIntLE(num, 0, 4) digest.update(buf) } }, // Ported hash_xi = function (digest, xi) { let buf = Buffer.alloc(4) buf.writeUInt32LE(xi, 0, 4) digest.update(buf) return digest //# For chaining }, // Ported count_zeroes = function (h) { // # Convert to binary string let res = '' for (let i = 0; i < h.length; i++) { res += padLeft(h[i].toString(2), 8) } // # Count leading zeroes return (res + '1').indexOf('1') }, // Ported has_collision = function (ha, hb, i, l) { let res = [] for (let j = Math.trunc((i - 1) * l / 8); j < Math.trunc(i * l / 8); j++) { res.push(ha[j] == hb[j]) } return res.every(x => x === true); }, // Ported distinct_indices = function (a, b) { for (let i = 0; i < a.length; i++) { for (let j = 0; j < b.length; j++) { if (a[i] === b[j]) { return false } } } return true }, // Ported xor = function (ha, hb) { let zip = _.zip(ha, hb) let res = [] for (let i = 0; i < zip.length; i++) { res.push(zip[i][0] ^ zip[i][1]) } return Buffer.from(res) }, // function gbp_basic(digest, n, k) { // // '''Implementation of Basic Wagner's algorithm for the GBP.''' // validate_params(n, k) // var collision_length = n / (k + 1) // var hash_length = (k + 1) * ((collision_length + 7) / 8) // var indices_per_hash_output = 512 / n // // # 1) Generate first list // if (DEBUG) { // console.log('Generating first list') // } // var X = [] // var tmp_hash = '' // for (var i = 0; i < 2 ** (collision_length + 1); i++) { // var r = i % indices_per_hash_output // if (r == 0) { // // # X_i = H(I||V||x_i) // var curr_digest = digest.copy() // hash_xi(curr_digest, i / indices_per_hash_output) // tmp_hash = curr_digest.digest() // } // X.append( // // expand_array(bytearray(tmp_hash[r*n/8:(r+1)*n/8]), // // hash_length, collision_length), // // (i,) // ) // } // // # 3) Repeat step 2 until 2n/(k+1) bits remain // for (i = 1; i < k; i++) { // if (DEBUG) { // console.log('Round ' + i + ':') // } // // # 2a) Sort the list // if (DEBUG) { // console.log('- Sorting list') // } // X.sort(key = itemgetter(0)) // if (DEBUG && VERBOSE) { // for (var Xi in X) { // Xi in X[-32:]{ // console.log(print_hash(Xi[0]) + ' ' + Xi[1]) // } // } // if (DEBUG) { // console.log('- Finding collisions') // } // var Xc = [] // while (X.length > 0) { // // # 2b) Find next set of unordered pairs with collisions on first n/(k+1) bits // j = 1 // while (j < X.length) { // if (!has_collision(X[-1][0], X[-1 - j][0], i, collision_length)) { // break // } // j += 1 // } // // # 2c) Store tuples (X_i ^ X_j, (i, j)) on the table // for (var l = 0; l < j - 1; l++) { // for (var m = l + 1; m < j; m++) { // // # Check that there are no duplicate indices in tuples i and j // if (distinct_indices(X[-1 - l][1], X[-1 - m][1])) { // if (X[-1 - l][1][0] < X[-1 - m][1][0]) { // concat = X[-1 - l][1] + X[-1 - m][1] // } // else { // concat = X[-1 - m][1] + X[-1 - l][1] // } // Xc.append((xor(X[-1 - l][0], X[-1 - m][0]), concat)) // } // } // } // // # 2d) Drop this set // while (j > 0) { // X.pop(-1) // j -= 1 // } // } // // # 2e) Replace previous list with new list // X = Xc // } // // # k+1) Find a collision on last 2n(k+1) bits // if (DEBUG) { // console.log('Final round:') // console.log('- Sorting list') // } // X.sort(key = itemgetter(0)) // if (DEBUG && VERBOSE) { // for (var Xi in X) {//[-32:]: // console.log(print_hash(Xi[0]) + ' ' + Xi[1]) // } // } // if (DEBUG) { // console.log('- Finding collisions') // } // solns = [] // while (X.length > 0) { // var j = 1 // while (j < X.length) { // if (!(has_collision(X[-1][0], X[-1 - j][0], k, collision_length)) && // has_collision(X[-1][0], X[-1 - j][0], k + 1, collision_length)) { // break // } // j += 1 // } // for (var l = 0; l < j - 1; l++) { // for (var m = l + 1; l < j; l++) { // res = xor(X[-1 - l][0], X[-1 - m][0]) // if (count_zeroes(res) == 8 * hash_length && distinct_indices(X[-1 - l][1], X[-1 - m][1])) { // if (DEBUG && VERBOSE) { // console.log('Found solution:') // console.log('- ' + print_hash(X[-1 - l][0]) + ' ' + X[-1 - l][1]) // console.log('- ' + (print_hash(X[-1 - m][0]) + ' ' + X[-1 - m][1])) // } // if (X[-1 - l][1][0] < X[-1 - m][1][0]) { // solns.append(list(X[-1 - l][1] + X[-1 - m][1])) // } else { // solns.append(list(X[-1 - m][1] + X[-1 - l][1])) // } // } // } // } // // # 2d) Drop this set // while (j > 0) { // X.pop(-1) // j -= 1 // } // } // return //[get_minimal_from_indices(soln, collision_length+1) for soln in solns] // } // Ported gbp_validate = function (createDigest, minimal, n, k) { validate_params(n, k) var collision_length = n / (k + 1) var hash_length = (k + 1) * (Math.trunc((collision_length + 7) / 8)) var indices_per_hash_output = Math.trunc(512 / n) var solution_width = Math.trunc((1 << k) * (collision_length + 1) / 8) if (minimal.length != solution_width) { console.log('Invalid solution length: ' + minimal.length + ' (expected ' + solution_width + ')') return false } let X = [] let indices = get_indices_from_minimal(minimal, collision_length + 1) for (let m = 0; m < indices.length; m++) { let i = indices[m] let r = i % indices_per_hash_output // # X_i = H(I||V||x_i) let curr_digest = createDigest() hash_xi(curr_digest, Math.trunc(i / indices_per_hash_output)) let tmp_hash = curr_digest.digest() let slice = tmp_hash.slice(Math.trunc(r * n / 8), Math.trunc((r + 1) * n / 8)) X.push([ expand_array(slice, hash_length, collision_length), [i] ]) } for (let r = 1; r < k + 1; r++) { let Xc = [] for (let i = 0; i < X.length; i += 2) { if (!has_collision(X[i][0], X[i + 1][0], r, collision_length)) { console.log('Invalid solution: invalid collision length between StepRows') return false } if (X[i + 1][1][0] < X[i][1][0]) { console.log('Invalid solution: Index tree incorrectly ordered') return false } if (!distinct_indices(X[i][1], X[i + 1][1])) { console.log('Invalid solution: duplicate indices') return false } Xc.push([xor(X[i][0], X[i + 1][0]), _.union(X[i][1], X[i + 1][1])]) } X = Xc } if (X.length != 1) { console.log('Invalid solution: incorrect length after end of rounds: ' + X.length) return false } if (count_zeroes(X[0][0]) != 8 * hash_length) { console.log('Invalid solution: incorrect number of zeroes: ' + count_zeroes(X[0][0])) return false } return true }, // Ported zcash_person = function (person, n, k) { let buf = Buffer.alloc(16) buf.write(person, 0, 8, 'utf8') buf.writeUIntLE(n, 8, 4) buf.writeUIntLE(k, 12, 4) return buf; }, // function print_hash(h) { // if (type(h) == bytearray) { // return ''//.join('{0:02x}'.format(x, 'x') for x in h) // } else { // return ''//.join('{0:02x}'.format(ord(x), 'x') for x in h) // } // } // Ported validate_params = function (n, k) { if (k >= n) { throw new Error('n must be larger than k') } if (((n / (k + 1)) + 1) >= 32) { throw new Error('Parameters must satisfy n/(k+1)+1 < 32') } }, // Ported is_gbp_valid = function (header, nNonce, nSolution, n, k, person) { n = n || 48 k = k || 5 // # H(I||... let createDigest = function () { let digest = blake2b(Math.trunc((512 / n)) * Math.trunc(n / 8), null, null, zcash_person(person, n, k)) digest.update(header.slice(0, 108)) hash_nonce(digest, nNonce) return digest } return gbp_validate(createDigest, nSolution, n, k) }, verify = function (header, solution, nonce) { assert(Buffer.isBuffer(header), 'Header must be Buffer') assert(header.length >= 108, 'Header must be at least 108 long') assert(Buffer.isBuffer(solution), 'Solution must be Buffer') if (nonce) { assert(Buffer.isBuffer(nonce), 'Nonce must be Buffer') } else { assert(header.length >= 140, 'Header must contain nonce') var nonceHex = header.slice(140-32, 140).toString('hex').match(/.{2}/g).reverse().join("") nonce = Buffer.from(nonceHex, 'hex') } return is_gbp_valid(header, nonce, solution, this.network.n, this.network.k, this.network.person) } return { verify: verify } }() module.exports = Equihash