add overwinter suport
This commit is contained in:
parent
db76b3862a
commit
5e0f58bc4f
|
@ -7,3 +7,4 @@ protobuf
|
||||||
dnspython
|
dnspython
|
||||||
jsonrpclib-pelix
|
jsonrpclib-pelix
|
||||||
PySocks>=1.6.6
|
PySocks>=1.6.6
|
||||||
|
pyblake2
|
||||||
|
|
|
@ -12,4 +12,4 @@ qrcode==5.3
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
urllib3==1.22
|
urllib3==1.22
|
||||||
x11_hash==1.4
|
pyblake2==1.1.2
|
||||||
|
|
|
@ -54,8 +54,7 @@ class BitcoinMainnet:
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x0488b21e, # xpub
|
'standard': 0x0488b21e, # xpub
|
||||||
}
|
}
|
||||||
DRKV_HEADER = 0x02fe52f8 # drkv
|
OVERWINTER_HEIGHT = 347500
|
||||||
DRKP_HEADER = 0x02fe52cc # drkp
|
|
||||||
|
|
||||||
|
|
||||||
class BitcoinTestnet:
|
class BitcoinTestnet:
|
||||||
|
@ -75,8 +74,7 @@ class BitcoinTestnet:
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x043587cf, # tpub
|
'standard': 0x043587cf, # tpub
|
||||||
}
|
}
|
||||||
DRKV_HEADER = 0x3a8061a0 # DRKV
|
OVERWINTER_HEIGHT = 207500
|
||||||
DRKP_HEADER = 0x3a805837 # DRKP
|
|
||||||
|
|
||||||
|
|
||||||
class BitcoinRegtest(BitcoinTestnet):
|
class BitcoinRegtest(BitcoinTestnet):
|
||||||
|
|
|
@ -39,8 +39,15 @@ import sys
|
||||||
# Workalike python implementation of Bitcoin's CDataStream class.
|
# Workalike python implementation of Bitcoin's CDataStream class.
|
||||||
#
|
#
|
||||||
from .keystore import xpubkey_to_address, xpubkey_to_pubkey
|
from .keystore import xpubkey_to_address, xpubkey_to_pubkey
|
||||||
|
from pyblake2 import blake2b
|
||||||
|
|
||||||
NO_SIGNATURE = 'ff'
|
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):
|
class SerializationError(Exception):
|
||||||
|
@ -450,17 +457,61 @@ def parse_output(vds, i):
|
||||||
return d
|
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):
|
def deserialize(raw):
|
||||||
|
len_raw = len(raw) / 2
|
||||||
vds = BCDataStream()
|
vds = BCDataStream()
|
||||||
vds.write(bfh(raw))
|
vds.write(bfh(raw))
|
||||||
d = {}
|
d = {}
|
||||||
start = vds.read_cursor
|
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()
|
n_vin = vds.read_compact_size()
|
||||||
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
|
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
|
||||||
n_vout = vds.read_compact_size()
|
n_vout = vds.read_compact_size()
|
||||||
d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
|
d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
|
||||||
d['lockTime'] = vds.read_uint32()
|
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
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
@ -499,7 +550,13 @@ class Transaction:
|
||||||
self._inputs = None
|
self._inputs = None
|
||||||
self._outputs = None
|
self._outputs = None
|
||||||
self.locktime = 0
|
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):
|
def update(self, raw):
|
||||||
self.raw = raw
|
self.raw = raw
|
||||||
|
@ -570,6 +627,12 @@ class Transaction:
|
||||||
self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
|
self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
|
||||||
self.locktime = d['lockTime']
|
self.locktime = d['lockTime']
|
||||||
self.version = d['version']
|
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
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -721,14 +784,60 @@ class Transaction:
|
||||||
s += script
|
s += script
|
||||||
return s
|
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):
|
def serialize_preimage(self, i):
|
||||||
nVersion = int_to_hex(self.version, 4)
|
overwintered = self.overwintered
|
||||||
|
version = self.version
|
||||||
nHashType = int_to_hex(1, 4)
|
nHashType = int_to_hex(1, 4)
|
||||||
nLocktime = int_to_hex(self.locktime, 4)
|
nLocktime = int_to_hex(self.locktime, 4)
|
||||||
inputs = self.inputs()
|
inputs = self.inputs()
|
||||||
outputs = self.outputs()
|
outputs = self.outputs()
|
||||||
txin = inputs[i]
|
txin = inputs[i]
|
||||||
# TODO: py3 hex
|
# TODO: py3 hex
|
||||||
|
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))
|
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)
|
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
|
||||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||||
|
@ -741,6 +850,12 @@ class Transaction:
|
||||||
outputs = self.outputs()
|
outputs = self.outputs()
|
||||||
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs)
|
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)
|
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
|
||||||
|
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
|
return nVersion + txins + txouts + nLocktime
|
||||||
|
|
||||||
def hash(self):
|
def hash(self):
|
||||||
|
@ -845,6 +960,11 @@ class Transaction:
|
||||||
sec, compressed = keypairs.get(x_pubkey)
|
sec, compressed = keypairs.get(x_pubkey)
|
||||||
pubkey = public_key_from_private_key(sec, compressed)
|
pubkey = public_key_from_private_key(sec, compressed)
|
||||||
# add signature
|
# add signature
|
||||||
|
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)))
|
pre_hash = Hash(bfh(self.serialize_preimage(i)))
|
||||||
pkey = regenerate_key(sec)
|
pkey = regenerate_key(sec)
|
||||||
secexp = pkey.secret
|
secexp = pkey.secret
|
||||||
|
|
Loading…
Reference in New Issue