diff --git a/.gitignore b/.gitignore
index dae35e75..5496f334 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,8 @@ bin/
# tox files
.cache/
.coverage
+
+# User-specific stuff:
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/dictionaries
diff --git a/.idea/electrum.iml b/.idea/electrum.iml
new file mode 100644
index 00000000..849be4f7
--- /dev/null
+++ b/.idea/electrum.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..98a50070
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..fba7c97b
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.rst b/README.rst
index 43d3257f..90feb11d 100644
--- a/README.rst
+++ b/README.rst
@@ -28,6 +28,7 @@ Electrum is a pure python application. If you want to use the
Qt interface, install the Qt dependencies::
sudo apt-get install python3-pyqt5
+ sudo pip2 install pyblake2
If you downloaded the official package (tar.gz), you can run
Electrum from its root directory, without installing it on your
diff --git a/lib/__init__.py b/lib/__init__.py
index 286e4b6a..4fb98550 100644
--- a/lib/__init__.py
+++ b/lib/__init__.py
@@ -9,6 +9,7 @@ from .simple_config import SimpleConfig, get_config, set_config
from . import bitcoin
from . import transaction
from . import daemon
+from . import equihash
from .transaction import Transaction
from .plugins import BasePlugin
from .commands import Commands, known_commands
diff --git a/lib/bitcoin.py b/lib/bitcoin.py
index 8c4e8726..c22cc8c0 100644
--- a/lib/bitcoin.py
+++ b/lib/bitcoin.py
@@ -29,6 +29,7 @@ import hmac
import os
import json
+import struct
import ecdsa
import pyaes
@@ -78,7 +79,8 @@ class NetworkConstants:
cls.ADDRTYPE_P2PKH = 0
cls.ADDRTYPE_P2SH = 5
cls.SEGWIT_HRP = "bc"
- cls.GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
+ cls.HEADERS_URL = "https://headers.electrum.org/blockchain_headers" #TODO
+ cls.GENESIS = "0007104ccda289427919efc39dc9e4d499804b7bebc22df55f8b834301260602"
cls.DEFAULT_PORTS = {'t': '50001', 's': '50002'}
cls.DEFAULT_SERVERS = read_json('servers.json', {})
cls.CHECKPOINTS = read_json('checkpoints.json', [])
@@ -237,6 +239,75 @@ def op_push(i):
def push_script(x):
return op_push(len(x)//2) + x
+# ZCASH specific utils methods
+# https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/mininode.py
+def ser_char_vector(l):
+ r = b''
+ if l is None:
+ l = ''
+ if len(l) < 253:
+ r = chr(len(l))
+ elif len(l) < 0x10000:
+ r = chr(253) + struct.pack(">= 32
+ return rs
+
+
+def uint256_from_str(s):
+ r = 0
+ t = struct.unpack(" target:
- raise BaseException("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))
+ if int('0x' + _powhash, 16) > target:
+ raise BaseException("insufficient proof of work: %s vs target %s" % (int('0x' + _powhash, 16), target))
+ if not is_gbp_valid(nonce, n_solution):
+ raise BaseException("Equihash invalid")
def verify_chunk(self, index, data):
- num = len(data) // 80
- prev_hash = self.get_hash(index * 2016 - 1)
- target = self.get_target(index-1)
+ num = len(data) / 1484
+ prev_header = None
+ if index != 0:
+ prev_header = self.read_header(index * 2016 - 1)
+ headers = {}
for i in range(num):
- raw_header = data[i*80:(i+1) * 80]
- header = deserialize_header(raw_header, index*2016 + i)
- self.verify_header(header, prev_hash, target)
- prev_hash = hash_header(header)
+ raw_header = data[i * 1484:(i + 1) * 1484]
+ header = self.deserialize_header(raw_header, index * 2016 + i)
+ headers[header.get('block_height')] = header
+ nonce, n_solution = headers.get('nonce'), header.get('n_solution')
+ bits, target = self.get_target(index * 2016 + i, headers)
+ self.verify_header(header, prev_header, bits, target, nonce, n_solution)
+ prev_header = header
def path(self):
d = util.get_headers_dir(self.config)
@@ -177,7 +199,7 @@ class Blockchain(util.PrintError):
def save_chunk(self, index, chunk):
filename = self.path()
- d = (index * 2016 - self.checkpoint) * 80
+ d = (index * 2016 - self.checkpoint) * 1484
if d < 0:
chunk = chunk[-d:]
d = 0
@@ -197,10 +219,10 @@ class Blockchain(util.PrintError):
with open(self.path(), 'rb') as f:
my_data = f.read()
with open(parent.path(), 'rb') as f:
- f.seek((checkpoint - parent.checkpoint)*80)
- parent_data = f.read(parent_branch_size*80)
+ f.seek((checkpoint - parent.checkpoint)*1484)
+ parent_data = f.read(parent_branch_size*1484)
self.write(parent_data, 0)
- parent.write(my_data, (checkpoint - parent.checkpoint)*80)
+ parent.write(my_data, (checkpoint - parent.checkpoint)*1484)
# store file path
for b in blockchains.values():
b.old_path = b.path()
@@ -222,7 +244,7 @@ class Blockchain(util.PrintError):
filename = self.path()
with self.lock:
with open(filename, 'rb+') as f:
- if truncate and offset != self._size*80:
+ if offset != self._size*1484:
f.seek(offset)
f.truncate()
f.seek(offset)
@@ -235,8 +257,8 @@ class Blockchain(util.PrintError):
delta = header.get('block_height') - self.checkpoint
data = bfh(serialize_header(header))
assert delta == self.size()
- assert len(data) == 80
- self.write(data, delta*80)
+ assert len(data) == 1484
+ self.write(data, delta*1484)
self.swap_with_parent()
def read_header(self, height):
@@ -247,15 +269,16 @@ class Blockchain(util.PrintError):
return self.parent().read_header(height)
if height > self.height():
return
- delta = height - self.checkpoint
+
+ idx, h = 0, None
name = self.path()
+
if os.path.exists(name):
- with open(name, 'rb') as f:
- f.seek(delta * 80)
- h = f.read(80)
- if h == bytes([0])*80:
- return None
- return deserialize_header(h, height)
+ while idx <= height:
+ f = open(name, 'rb')
+ h = deserialize_header(f, height)
+ idx += 1
+ return h
def get_hash(self, height):
if height == -1:
@@ -323,10 +346,11 @@ class Blockchain(util.PrintError):
return False
if prev_hash != header.get('prev_block_hash'):
return False
- target = self.get_target(height // 2016 - 1)
+ nonce, n_solution = headers.get('nonce'), header.get('n_solution')
+ bits, target = self.get_target(index * 2016 + i, headers)
try:
- self.verify_header(header, prev_hash, target)
- except BaseException as e:
+ self.verify_header(header, prev_header, bits, target, nonce, n_solution)
+ except:
return False
return True
diff --git a/lib/equihash.py b/lib/equihash.py
new file mode 100644
index 00000000..bd637889
--- /dev/null
+++ b/lib/equihash.py
@@ -0,0 +1,321 @@
+# ZCASH implementation: https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/equihash.py
+from pyblake2 import blake2b
+from operator import itemgetter
+import struct
+
+DEBUG = False
+VERBOSE = False
+
+
+word_size = 32
+word_mask = (1<= 8 and word_size >= 7+bit_len
+ bit_len_mask = (1<= bit_len:
+ acc_bits -= bit_len
+ for x in range(byte_pad, out_width):
+ 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
+
+
+def compress_array(inp, out_len, bit_len, byte_pad=0):
+ assert bit_len >= 8 and word_size >= 7+bit_len
+
+ in_width = (bit_len+7)/8 + byte_pad
+ assert out_len == bit_len*len(inp)/(8*in_width)
+ out = bytearray(out_len)
+
+ bit_len_mask = (1 << bit_len) - 1
+
+ # The acc_bits least-significant bits of acc_value represent a bit sequence
+ # in big-endian order.
+ acc_bits = 0;
+ acc_value = 0;
+
+ j = 0
+ for i in range(out_len):
+ # 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 x in range(byte_pad, in_width):
+ 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
+
+
+def get_indices_from_minimal(minimal, bit_len):
+ eh_index_size = 4
+ assert (bit_len+7)/8 <= eh_index_size
+ len_indices = 8*eh_index_size*len(minimal)/bit_len
+ byte_pad = eh_index_size - (bit_len+7)/8
+ expanded = expand_array(minimal, len_indices, bit_len, byte_pad)
+ return [struct.unpack('>I', expanded[i:i+4])[0] for i in range(0, len_indices, eh_index_size)]
+
+
+def get_minimal_from_indices(indices, bit_len):
+ eh_index_size = 4
+ assert (bit_len+7)/8 <= eh_index_size
+ len_indices = len(indices)*eh_index_size
+ min_len = bit_len*len_indices/(8*eh_index_size)
+ byte_pad = eh_index_size - (bit_len+7)/8
+ byte_indices = bytearray(''.join([struct.pack('>I', i) for i in indices]))
+ return compress_array(byte_indices, min_len, bit_len, byte_pad)
+
+
+def hash_nonce(digest, nonce):
+ for i in range(8):
+ digest.update(struct.pack('> (32*i)))
+
+
+def hash_xi(digest, xi):
+ digest.update(struct.pack(' 0:
+ # 2b) Find next set of unordered pairs with collisions on first n/(k+1) bits
+ j = 1
+ while j < len(X):
+ if not 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 l in range(0, j-1):
+ for m in range(l+1, j):
+ # 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:
+ print('Final round:')
+ print('- Sorting list')
+ X.sort(key=itemgetter(0))
+ if DEBUG and VERBOSE:
+ for Xi in X[-32:]:
+ print('%s %s' % (print_hash(Xi[0]), Xi[1]))
+ if DEBUG: print('- Finding collisions')
+ solns = []
+ while len(X) > 0:
+ j = 1
+ while j < len(X):
+ if not (has_collision(X[-1][0], X[-1-j][0], k, collision_length) and
+ has_collision(X[-1][0], X[-1-j][0], k+1, collision_length)):
+ break
+ j += 1
+
+ for l in range(0, j-1):
+ for m in range(l+1, j):
+ res = xor(X[-1-l][0], X[-1-m][0])
+ if count_zeroes(res) == 8*hash_length and distinct_indices(X[-1-l][1], X[-1-m][1]):
+ if DEBUG and VERBOSE:
+ print('Found solution:')
+ print('- %s %s' % (print_hash(X[-1-l][0]), X[-1-l][1]))
+ print('- %s %s' % (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]
+
+
+def gbp_validate(digest, minimal, n, k):
+ validate_params(n, k)
+ collision_length = n/(k+1)
+ hash_length = (k+1)*((collision_length+7)//8)
+ indices_per_hash_output = 512/n
+ solution_width = (1 << k)*(collision_length+1)//8
+
+ if len(minimal) != solution_width:
+ print('Invalid solution length: %d (expected %d)' % \
+ (len(minimal), solution_width))
+ return False
+
+ X = []
+ for i in get_indices_from_minimal(minimal, collision_length+1):
+ r = i % indices_per_hash_output
+ # X_i = H(I||V||x_i)
+ 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,)
+ ))
+
+ for r in range(1, k+1):
+ Xc = []
+ for i in range(0, len(X), 2):
+ if not has_collision(X[i][0], X[i+1][0], r, collision_length):
+ print('Invalid solution: invalid collision length between StepRows')
+ return False
+ if X[i+1][1][0] < X[i][1][0]:
+ print('Invalid solution: Index tree incorrectly ordered')
+ return False
+ if not distinct_indices(X[i][1], X[i+1][1]):
+ print('Invalid solution: duplicate indices')
+ return False
+ Xc.append((xor(X[i][0], X[i+1][0]), X[i][1] + X[i+1][1]))
+ X = Xc
+
+ if len(X) != 1:
+ print('Invalid solution: incorrect length after end of rounds: %d' % len(X))
+ return False
+
+ if count_zeroes(X[0][0]) != 8*hash_length:
+ print('Invalid solution: incorrect number of zeroes: %d' % count_zeroes(X[0][0]))
+ return False
+
+ return True
+
+
+def zcash_person(n, k):
+ return b'ZcashPoW' + struct.pack('= n):
+ raise ValueError('n must be larger than k')
+ if (((n/(k+1))+1) >= 32):
+ raise ValueError('Parameters must satisfy n/(k+1)+1 < 32')
+
+
+# a bit different from https://github.com/zcash/zcash/blob/master/qa/rpc-tests/test_framework/mininode.py#L747
+# since electrum is a SPV oriented and not a node
+def is_gbp_valid(nNonce, nSolution, n=48, k=5):
+ # H(I||...
+ digest = blake2b(digest_size=(512/n)*n/8, person=zcash_person(n, k))
+ digest.update(super(CBlock, self).serialize()[:108])
+ hash_nonce(digest, nNonce)
+ if not gbp_validate(nSolution, digest, n, k):
+ return False
+ return True
diff --git a/setup.py b/setup.py
index 5c95d4b6..50df4e72 100755
--- a/setup.py
+++ b/setup.py
@@ -44,6 +44,7 @@ setup(
'protobuf',
'dnspython',
'jsonrpclib-pelix',
+ 'pyblake2',
'PySocks>=1.6.6',
],
packages=[