From 5e0f58bc4f8fda1d3f85ee10143ecb3b22de68fc Mon Sep 17 00:00:00 2001 From: zebra-lucky Date: Fri, 29 Jun 2018 01:36:50 +0300 Subject: [PATCH] add overwinter suport --- contrib/requirements/requirements.txt | 1 + contrib/zcash/requirements.txt | 2 +- lib/constants.py | 6 +- lib/transaction.py | 136 ++++++++++++++++++++++++-- 4 files changed, 132 insertions(+), 13 deletions(-) diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 227ec1cd..dd6cc1db 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -7,3 +7,4 @@ protobuf dnspython jsonrpclib-pelix PySocks>=1.6.6 +pyblake2 diff --git a/contrib/zcash/requirements.txt b/contrib/zcash/requirements.txt index 28a2c6f8..5cb8a59b 100644 --- a/contrib/zcash/requirements.txt +++ b/contrib/zcash/requirements.txt @@ -12,4 +12,4 @@ qrcode==5.3 requests==2.18.4 six==1.11.0 urllib3==1.22 -x11_hash==1.4 +pyblake2==1.1.2 diff --git a/lib/constants.py b/lib/constants.py index 8caa2ba7..c4be3184 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -54,8 +54,7 @@ class BitcoinMainnet: XPUB_HEADERS = { 'standard': 0x0488b21e, # xpub } - DRKV_HEADER = 0x02fe52f8 # drkv - DRKP_HEADER = 0x02fe52cc # drkp + OVERWINTER_HEIGHT = 347500 class BitcoinTestnet: @@ -75,8 +74,7 @@ class BitcoinTestnet: XPUB_HEADERS = { 'standard': 0x043587cf, # tpub } - DRKV_HEADER = 0x3a8061a0 # DRKV - DRKP_HEADER = 0x3a805837 # DRKP + OVERWINTER_HEIGHT = 207500 class BitcoinRegtest(BitcoinTestnet): diff --git a/lib/transaction.py b/lib/transaction.py index 436a0215..43a50e57 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -39,8 +39,15 @@ import sys # Workalike python implementation of Bitcoin's CDataStream class. # from .keystore import xpubkey_to_address, xpubkey_to_pubkey +from pyblake2 import blake2b NO_SIGNATURE = 'ff' +OVERWINTERED_VERSION_GROUP_ID = 0x03C48270 +OVERWINTER_BRANCH_ID = 0x5BA81B19 + + +class TransactionVersionError(Exception): + """ Thrown when there's a problem with transaction versioning """ class SerializationError(Exception): @@ -450,17 +457,61 @@ def parse_output(vds, i): return d +def parse_join_split(vds): + d = {} + d['vpub_old'] = vds.read_uint64() + d['vpub_new'] = vds.read_uint64() + d['anchor'] = vds.read_bytes(32) + d['nullifiers'] = vds.read_bytes(64) + d['commitments'] = vds.read_bytes(64) + d['ephemeralKey'] = vds.read_bytes(32) + d['randomSeed'] = vds.read_bytes(32) + d['vmacs'] = vds.read_bytes(64) + d['zkproof'] = vds.read_bytes(296) + d['encCiphertexts'] = vds.read_bytes(1202) + return d + + def deserialize(raw): + len_raw = len(raw) / 2 vds = BCDataStream() vds.write(bfh(raw)) d = {} start = vds.read_cursor - d['version'] = vds.read_int32() + + header = vds.read_uint32() + overwintered = True if header & 0x80000000 else False + version = header & 0x7FFFFFFF + + if overwintered: + if version != 3: + raise TransactionVersionError('Overwintered transaction' + ' with invalid version=%d' % version) + ver_group_id = vds.read_uint32() + if ver_group_id != OVERWINTERED_VERSION_GROUP_ID: + raise TransactionVersionError('Overwintered transaction with wrong' + ' versionGroupId=%X' % ver_group_id) + d['versionGroupId'] = ver_group_id + + d['overwintered'] = overwintered + d['version'] = version + n_vin = vds.read_compact_size() d['inputs'] = [parse_input(vds) for i in range(n_vin)] n_vout = vds.read_compact_size() d['outputs'] = [parse_output(vds, i) for i in range(n_vout)] d['lockTime'] = vds.read_uint32() + + if overwintered: + d['expiryHeight'] = vds.read_uint32() + + if version >= 2 and len_raw > vds.read_cursor: + n_js = vds.read_compact_size() + if n_js > 0: + d['joinSplits'] = [parse_join_split(vds) for i in range(n_js)] + d['joinSplitPubKey'] = vds.read_bytes(32) + d['joinSplitSig'] = vds.read_bytes(64) + return d @@ -499,7 +550,13 @@ class Transaction: self._inputs = None self._outputs = None self.locktime = 0 - self.version = 1 + self.version = 3 + self.overwintered = True + self.versionGroupId = OVERWINTERED_VERSION_GROUP_ID + self.expiryHeight = 0 + self.joinSplits = None + self.joinSplitPubKey = None + self.joinSplitSig = None def update(self, raw): self.raw = raw @@ -570,6 +627,12 @@ class Transaction: self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] self.locktime = d['lockTime'] self.version = d['version'] + self.overwintered = d['overwintered'] + self.versionGroupId = d.get('versionGroupId') + self.expiryHeight = d.get('expiryHeight') + self.joinSplits = d.get('joinSplits') + self.joinSplitPubKey = d.get('joinSplitPubKey') + self.joinSplitSig = d.get('joinSplitSig') return d @classmethod @@ -721,17 +784,63 @@ class Transaction: s += script return s + def serialize_join_split(self, js): + s = int_to_hex(js['vpub_old'], 8) + s += int_to_hex(js['vpub_new'], 8) + s += js['anchor'] + s += js['nullifiers'] + s += js['commitments'] + s += js['ephemeralKey'] + s += js['randomSeed'] + s += js['vmacs'] + s += js['zkproof'] + s += js['encCiphertexts'] + return s + def serialize_preimage(self, i): - nVersion = int_to_hex(self.version, 4) + overwintered = self.overwintered + version = self.version nHashType = int_to_hex(1, 4) nLocktime = int_to_hex(self.locktime, 4) inputs = self.inputs() outputs = self.outputs() txin = inputs[i] # TODO: py3 hex - txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs)) - txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) - preimage = nVersion + txins + txouts + nLocktime + nHashType + if overwintered: + nHeader = int_to_hex(0x80000000 | version, 4) + nVersionGroupId = int_to_hex(self.versionGroupId, 4) + s_prevouts = bfh(''.join(self.serialize_outpoint(txin) for txin in inputs)) + hashPrevouts = blake2b(s_prevouts, digest_size=32, person=b'ZcashPrevoutHash').hexdigest() + s_sequences = bfh(''.join(int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) for txin in inputs)) + hashSequence = blake2b(s_sequences, digest_size=32, person=b'ZcashSequencHash').hexdigest() + s_outputs = bfh(''.join(self.serialize_output(o) for o in outputs)) + hashOutputs = blake2b(s_outputs, digest_size=32, person=b'ZcashOutputsHash').hexdigest() + joinSplits = self.joinSplits + if joinSplits is None: + hashJoinSplits = '00'*32 + else: + s_joinSplits = bfh(''.join(self.serialize_join_split(j) for j in joinSplits)) + s_joinSplits += self.joinSplitPubKey + hashJoinSplits = blake2b(s_joinSplits, digest_size=32, person=b'ZcashJSplitsHash').hexdigest() + nExpiryHeight = int_to_hex(self.expiryHeight, 4) + + txin = inputs[i] + + preimage_script = self.get_preimage_script(txin) + scriptCode = var_int(len(preimage_script) // 2) + preimage_script + preimage = ( + nHeader + nVersionGroupId + hashPrevouts + hashSequence + hashOutputs + + hashJoinSplits + nLocktime + nExpiryHeight + nHashType + + self.serialize_outpoint(txin) + + scriptCode + + int_to_hex(txin['value'], 8) + + int_to_hex(txin.get('sequence', 0xffffffff - 1), 4) + ) + else: + nVersion = int_to_hex(version, 4) + txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs)) + txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) + preimage = nVersion + txins + txouts + nLocktime + nHashType return preimage def serialize(self, estimate_size=False): @@ -741,7 +850,13 @@ class Transaction: outputs = self.outputs() txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs) txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) - return nVersion + txins + txouts + nLocktime + if self.overwintered: + nVersion = int_to_hex(0x80000000 | self.version, 4) + nVersionGroupId = int_to_hex(self.versionGroupId, 4) + nExpiryHeight = int_to_hex(self.expiryHeight, 4) + return nVersion + nVersionGroupId + txins + txouts + nLocktime + nExpiryHeight + '00' + else: + return nVersion + txins + txouts + nLocktime def hash(self): print("warning: deprecated tx.hash()") @@ -845,7 +960,12 @@ class Transaction: sec, compressed = keypairs.get(x_pubkey) pubkey = public_key_from_private_key(sec, compressed) # add signature - pre_hash = Hash(bfh(self.serialize_preimage(i))) + if self.overwintered: + data = bfh(self.serialize_preimage(i)) + person = b'ZcashSigHash' + OVERWINTER_BRANCH_ID.to_bytes(4, 'little') + pre_hash = blake2b(data, digest_size=32, person=person).digest() + else: + pre_hash = Hash(bfh(self.serialize_preimage(i))) pkey = regenerate_key(sec) secexp = pkey.secret private_key = bitcoin.MySigningKey.from_secret_exponent(secexp, curve = SECP256k1)