1
0

change partial txn format: add header. only parse inputs and witness for partial txns.

This commit is contained in:
SomberNight
2018-06-04 19:01:23 +02:00
parent 89040de758
commit a0ba5a7962
5 changed files with 101 additions and 88 deletions

View File

@@ -44,6 +44,7 @@ import sys
from .keystore import xpubkey_to_address, xpubkey_to_pubkey
NO_SIGNATURE = 'ff'
PARTIAL_TXN_HEADER_MAGIC = b'EPTF\xff'
class SerializationError(Exception):
@@ -383,15 +384,6 @@ def parse_scriptSig(d, _bytes):
bh2u(_bytes))
def _revise_txin_type_guess_for_txin(txin):
_type = txin.get('type', 'unknown')
# fix incorrect guess of p2sh-segwit
we_guessed_segwit_input_type = Transaction.is_segwit_inputtype(_type)
has_zero_witness = txin.get('witness', '00') in ('00', None)
if we_guessed_segwit_input_type and has_zero_witness:
txin['type'] = 'unknown'
def parse_redeemScript_multisig(redeem_script: bytes):
dec2 = [ x for x in script_GetOp(redeem_script) ]
try:
@@ -443,7 +435,7 @@ def get_address_from_output_script(_bytes, *, net=None):
return TYPE_SCRIPT, bh2u(_bytes)
def parse_input(vds):
def parse_input(vds, full_parse: bool):
d = {}
prevout_hash = hash_encode(vds.read_bytes(32))
prevout_n = vds.read_uint32()
@@ -451,23 +443,22 @@ def parse_input(vds):
sequence = vds.read_uint32()
d['prevout_hash'] = prevout_hash
d['prevout_n'] = prevout_n
d['scriptSig'] = bh2u(scriptSig)
d['sequence'] = sequence
d['type'] = 'unknown' if prevout_hash != '00'*32 else 'coinbase'
d['address'] = None
d['num_sig'] = 0
if not full_parse:
return d
d['x_pubkeys'] = []
d['pubkeys'] = []
d['signatures'] = {}
d['address'] = None
d['num_sig'] = 0
d['scriptSig'] = bh2u(scriptSig)
if prevout_hash == '00'*32:
d['type'] = 'coinbase'
else:
d['type'] = 'unknown'
if scriptSig:
try:
parse_scriptSig(d, scriptSig)
except BaseException:
traceback.print_exc(file=sys.stderr)
print_error('failed to parse scriptSig', bh2u(scriptSig))
if d['type'] != 'coinbase' and scriptSig:
try:
parse_scriptSig(d, scriptSig)
except BaseException:
traceback.print_exc(file=sys.stderr)
print_error('failed to parse scriptSig', bh2u(scriptSig))
return d
@@ -483,27 +474,22 @@ def construct_witness(items: Sequence[Union[str, int, bytes]]) -> str:
return witness
def parse_witness(vds, txin):
def parse_witness(vds, txin, full_parse: bool):
n = vds.read_compact_size()
if n == 0:
txin['witness'] = '00'
return
if n == 0xffffffff:
txin['value'] = vds.read_uint64()
n = vds.read_compact_size()
# now 'n' is the number of items in the witness
w = list(bh2u(vds.read_bytes(vds.read_compact_size())) for i in range(n))
txin['witness'] = construct_witness(w)
if not full_parse:
return
# FIXME: witness version > 0 will probably fail here.
# For native segwit, we would need the scriptPubKey of the parent txn
# to determine witness program version, and properly parse the witness.
# In case of p2sh-segwit, we can tell based on the scriptSig in this txn.
# The code below assumes witness version 0.
# p2sh-segwit should work in that case; for native segwit we need to tell
# between p2wpkh and p2wsh; we do this based on number of witness items,
# hence (FIXME) p2wsh with n==2 (maybe n==1 ?) will probably fail.
# If v==0 and n==2, we need parent scriptPubKey to distinguish between p2wpkh and p2wsh.
# NOTE: when witness version 1 comes, if we need to distinguish it for partial txns here,
# we could put the witness version in our partial format, e.g. after value
try:
if txin['type'] == 'coinbase':
pass
@@ -533,7 +519,6 @@ def parse_witness(vds, txin):
raise UnknownTxinType()
except UnknownTxinType:
txin['type'] = 'unknown'
# FIXME: GUI might show 'unknown' address (e.g. for a non-multisig p2wsh)
except BaseException:
txin['type'] = 'unknown'
traceback.print_exc(file=sys.stderr)
@@ -550,11 +535,21 @@ def parse_output(vds, i):
return d
def deserialize(raw):
vds = BCDataStream()
vds.write(bfh(raw))
def deserialize(raw: str, force_full_parse=False) -> dict:
raw_bytes = bfh(raw)
d = {}
start = vds.read_cursor
if raw_bytes[:5] == PARTIAL_TXN_HEADER_MAGIC:
d['partial'] = is_partial = True
partial_format_version = raw_bytes[5]
if partial_format_version != 0:
raise SerializationError('unknown tx partial serialization format version: {}'
.format(partial_format_version))
raw_bytes = raw_bytes[6:]
else:
d['partial'] = is_partial = False
full_parse = force_full_parse or is_partial
vds = BCDataStream()
vds.write(raw_bytes)
d['version'] = vds.read_int32()
n_vin = vds.read_compact_size()
is_segwit = (n_vin == 0)
@@ -563,17 +558,15 @@ def deserialize(raw):
if marker != b'\x01':
raise ValueError('invalid txn marker byte: {}'.format(marker))
n_vin = vds.read_compact_size()
d['inputs'] = [parse_input(vds) for i in range(n_vin)]
d['segwit_ser'] = is_segwit
d['inputs'] = [parse_input(vds, full_parse=full_parse) for i in range(n_vin)]
n_vout = vds.read_compact_size()
d['outputs'] = [parse_output(vds, i) for i in range(n_vout)]
if is_segwit:
for i in range(n_vin):
txin = d['inputs'][i]
parse_witness(vds, txin)
parse_witness(vds, txin, full_parse=full_parse)
d['lockTime'] = vds.read_uint32()
for i in range(n_vin):
txin = d['inputs'][i]
_revise_txin_type_guess_for_txin(txin)
return d
@@ -613,6 +606,10 @@ class Transaction:
self._outputs = None
self.locktime = 0
self.version = 1
# by default we assume this is a partial txn;
# this value will get properly set when deserializing
self.is_partial_originally = True
self._segwit_ser = None # None means "don't know"
def update(self, raw):
self.raw = raw
@@ -645,7 +642,9 @@ class Transaction:
def update_signatures(self, raw):
"""Add new signatures to a transaction"""
d = deserialize(raw)
if self.is_complete():
return
d = deserialize(raw, force_full_parse=True)
for i, txin in enumerate(self.inputs()):
pubkeys, x_pubkeys = self.get_sorted_pubkeys(txin)
sigs1 = txin.get('signatures')
@@ -689,6 +688,8 @@ class Transaction:
self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
self.locktime = d['lockTime']
self.version = d['version']
self.is_partial_originally = d['partial']
self._segwit_ser = d['segwit_ser']
return d
@classmethod
@@ -842,6 +843,8 @@ class Transaction:
if txin['type'] == 'coinbase':
return True
num_sig = txin.get('num_sig', 1)
if num_sig == 0:
return True
x_signatures = txin['signatures']
signatures = list(filter(None, x_signatures))
return len(signatures) == num_sig
@@ -932,9 +935,21 @@ class Transaction:
return preimage
def is_segwit(self):
if not self.is_partial_originally:
return self._segwit_ser
return any(self.is_segwit_input(x) for x in self.inputs())
def serialize(self, estimate_size=False, witness=True):
network_ser = self.serialize_to_network(estimate_size, witness)
if estimate_size:
return network_ser
if self.is_partial_originally and not self.is_complete():
partial_format_version = '00'
return bh2u(PARTIAL_TXN_HEADER_MAGIC) + partial_format_version + network_ser
else:
return network_ser
def serialize_to_network(self, estimate_size=False, witness=True):
nVersion = int_to_hex(self.version, 4)
nLocktime = int_to_hex(self.locktime, 4)
inputs = self.inputs()
@@ -1056,6 +1071,8 @@ class Transaction:
return s, r
def is_complete(self):
if not self.is_partial_originally:
return True
s, r = self.signature_count()
return r == s