psbt: fix bug re witness_utxo serialization

This commit is contained in:
SomberNight 2019-11-04 20:00:32 +01:00
parent c8c1ea9c86
commit 90b190bbcd
No known key found for this signature in database
GPG key ID: B33B5F232C6271E9
3 changed files with 28 additions and 21 deletions

View file

@ -795,7 +795,7 @@ class TestLegacyPartialTxFormat(TestCaseForTestnet):
wallet = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2, ks3], '2of3', config=self.config)
tx = tx_from_any('cHNidP8BAJ8CAAAAAYfEZGymkLOX41eyOyE3AwaRqQoGimaQg000C0voSs1qAQAAAAD9////A6CGAQAAAAAAFgAUi8nZR+TQrdwvTDS4NxA060ez0wUUDAMAAAAAACIAIKYIfE+EpV3DkBRymhjgiVUTnUOEVZ0f0qSKHZXXRqQlQA0DAAAAAAAZdqkUwT/WKU0b57lBClU49LTvEPxZTueIrBMrGABPAQJXVIMAAAAAAAAAAADWRNzdekrLQyNV4BCsSl+VWUDIKpdncxt9idxC6zzaxAJy+qL5i3bMnWVe8oHAes2nXDCpkNw6Unts+SqPWmuKgARL8hIJTwECV1SDAdJM1ZGAAAABmcTWyJP6Gt3sawEhGBE34lw4GUMzuMVyFbPPHm1+evECoj3a5pi7YJW4uANb3R6UR59mwpZ52Bkx4P4HqkNhSe4I0kzVkQEAAIBPAQJXVIMB0kzVkYAAAAC2CoUImtCFCm/+hZCj4VBk5j7v0CCBPfLkprMgnOPfXwLIsU8pF9VXzUgpcMEUw+NFfgnSVjl4Aid6Lk0VGaufjAjSTNWRAAAAgAABASogoQcAAAAAAAAgqUjX+mq7uX4xd5rlQ4MBK0E9U4Icf9OUkA9rRDxh3u4iAgMHo8QdB+2XbWXiE+gj0ChAk3R15wm0ElPoXpcOPLFmdEcwRAIgL3vl+tOY8aNXYpMznyJ00ievF3mGkKoa0A/4PZZyXMUCIADLLp//W+I6hicYxmWlA18XJ4PpsxWOroNnP2xZrcPAAQEFaVIhAqnctXDoKAx0HwkDLBWAlbeqOwzkAa2gMPLUe5mfAgYGIQMHo8QdB+2XbWXiE+gj0ChAk3R15wm0ElPoXpcOPLFmdCEDUhsKReBC8IzNA69H/Yi7IHtUFODjC7h5n8oxGgYyOhlTriIGAwejxB0H7ZdtZeIT6CPQKECTdHXnCbQSU+helw48sWZ0ENJM1ZEAAACAAAAAAAEAAAAiBgNSGwpF4ELwjM0Dr0f9iLsge1QU4OMLuHmfyjEaBjI6GRDSTNWRAQAAgAAAAAABAAAAIgYCqdy1cOgoDHQfCQMsFYCVt6o7DOQBraAw8tR7mZ8CBgYMS/ISCQAAAAABAAAAAAABAWlSIQIFgp+VIldxIsqcqb62f5TPL+CtDRfgUqEQcBz0Eo0znCECLMooLP3ZzB84fTCYZh1ovJyKOaxr1yww21eaeFfQhZshAtkOPIlzhEucwLOElMSFFaMZ7sOsJIn5b29V5qppEqYIU64iAgLZDjyJc4RLnMCzhJTEhRWjGe7DrCSJ+W9vVeaqaRKmCBDSTNWRAAAAgAEAAAAAAAAAIgICLMooLP3ZzB84fTCYZh1ovJyKOaxr1yww21eaeFfQhZsQ0kzVkQEAAIABAAAAAAAAACICAgWCn5UiV3EiypypvrZ/lM8v4K0NF+BSoRBwHPQSjTOcDEvyEgkBAAAAAAAAAAAA')
tx = tx_from_any('70736274ff01009f020000000187c4646ca690b397e357b23b2137030691a90a068a6690834d340b4be84acd6a0100000000fdffffff03a0860100000000001600148bc9d947e4d0addc2f4c34b8371034eb47b3d305140c030000000000220020a6087c4f84a55dc39014729a18e08955139d4384559d1fd2a48a1d95d746a425400d0300000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788ac132b18004f0102575483000000000000000000d644dcdd7a4acb432355e010ac4a5f955940c82a9767731b7d89dc42eb3cdac40272faa2f98b76cc9d655ef281c07acda75c30a990dc3a527b6cf92a8f5a6b8a80044bf212094f010257548301d24cd5918000000199c4d6c893fa1addec6b0121181137e25c38194333b8c57215b3cf1e6d7e7af102a23ddae698bb6095b8b8035bdd1e94479f66c29679d81931e0fe07aa436149ee08d24cd591010000804f010257548301d24cd59180000000b60a85089ad0850a6ffe8590a3e15064e63eefd020813df2e4a6b3209ce3df5f02c8b14f2917d557cd482970c114c3e3457e09d256397802277a2e4d1519ab9f8c08d24cd591000000800001012b20a1070000000000220020a948d7fa6abbb97e31779ae54383012b413d53821c7fd394900f6b443c61deee22020307a3c41d07ed976d65e213e823d02840937475e709b41253e85e970e3cb1667447304402202f7be5fad398f1a3576293339f2274d227af17798690aa1ad00ff83d96725cc5022000cb2e9fff5be23a862718c665a5035f172783e9b3158eae83673f6c59adc3c001010569522102a9dcb570e8280c741f09032c158095b7aa3b0ce401ada030f2d47b999f020606210307a3c41d07ed976d65e213e823d02840937475e709b41253e85e970e3cb166742103521b0a45e042f08ccd03af47fd88bb207b5414e0e30bb8799fca311a06323a1953ae22060307a3c41d07ed976d65e213e823d02840937475e709b41253e85e970e3cb1667410d24cd591000000800000000001000000220603521b0a45e042f08ccd03af47fd88bb207b5414e0e30bb8799fca311a06323a1910d24cd591010000800000000001000000220602a9dcb570e8280c741f09032c158095b7aa3b0ce401ada030f2d47b999f0206060c4bf212090000000001000000000001016952210205829f9522577122ca9ca9beb67f94cf2fe0ad0d17e052a110701cf4128d339c21022cca282cfdd9cc1f387d3098661d68bc9c8a39ac6bd72c30db579a7857d0859b2102d90e3c8973844b9cc0b38494c48515a319eec3ac2489f96f6f55e6aa6912a60853ae220202d90e3c8973844b9cc0b38494c48515a319eec3ac2489f96f6f55e6aa6912a60810d24cd5910000008001000000000000002202022cca282cfdd9cc1f387d3098661d68bc9c8a39ac6bd72c30db579a7857d0859b10d24cd59101000080010000000000000022020205829f9522577122ca9ca9beb67f94cf2fe0ad0d17e052a110701cf4128d339c0c4bf2120901000000000000000000')
tx.add_info_from_wallet(wallet)
raw_tx = serialize_tx_in_legacy_format(tx, wallet=wallet)
self.assertEqual('45505446ff000200000000010187c4646ca690b397e357b23b2137030691a90a068a6690834d340b4be84acd6a0100000000fdffffff03a0860100000000001600148bc9d947e4d0addc2f4c34b8371034eb47b3d305140c030000000000220020a6087c4f84a55dc39014729a18e08955139d4384559d1fd2a48a1d95d746a425400d0300000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788acfeffffffff20a10700000000000000050001ff47304402202f7be5fad398f1a3576293339f2274d227af17798690aa1ad00ff83d96725cc5022000cb2e9fff5be23a862718c665a5035f172783e9b3158eae83673f6c59adc3c00101fffd0201524c53ff02575483000000000000000000d644dcdd7a4acb432355e010ac4a5f955940c82a9767731b7d89dc42eb3cdac40272faa2f98b76cc9d655ef281c07acda75c30a990dc3a527b6cf92a8f5a6b8a80000001004c53ff0257548301d24cd59180000000b60a85089ad0850a6ffe8590a3e15064e63eefd020813df2e4a6b3209ce3df5f02c8b14f2917d557cd482970c114c3e3457e09d256397802277a2e4d1519ab9f8c000001004c53ff0257548301d24cd5918000000199c4d6c893fa1addec6b0121181137e25c38194333b8c57215b3cf1e6d7e7af102a23ddae698bb6095b8b8035bdd1e94479f66c29679d81931e0fe07aa436149ee0000010053ae132b1800',

View file

@ -96,6 +96,22 @@ class TxOutput:
return cls(scriptpubkey=bfh(bitcoin.address_to_script(address)),
value=value)
def serialize_to_network(self) -> bytes:
buf = int.to_bytes(self.value, 8, byteorder="little", signed=False)
script = self.scriptpubkey
buf += bfh(var_int(len(script.hex()) // 2))
buf += script
return buf
@classmethod
def from_network_bytes(cls, raw: bytes) -> 'TxOutput':
vds = BCDataStream()
vds.write(raw)
txout = parse_output(vds)
if vds.can_read_more():
raise SerializationError('extra junk at the end of TxOutput bytes')
return txout
def to_legacy_tuple(self) -> Tuple[int, str, Union[int, str]]:
if self.address:
return TYPE_ADDRESS, self.address, self.value
@ -686,20 +702,12 @@ class Transaction:
s += int_to_hex(txin.nsequence, 4)
return s
@classmethod
def serialize_output(cls, output: TxOutput) -> str:
s = int_to_hex(output.value, 8)
script = output.scriptpubkey.hex()
s += var_int(len(script)//2)
s += script
return s
def _calc_bip143_shared_txdigest_fields(self) -> BIP143SharedTxDigestFields:
inputs = self.inputs()
outputs = self.outputs()
hashPrevouts = bh2u(sha256d(b''.join(txin.prevout.serialize_to_network() for txin in inputs)))
hashSequence = bh2u(sha256d(bfh(''.join(int_to_hex(txin.nsequence, 4) for txin in inputs))))
hashOutputs = bh2u(sha256d(bfh(''.join(self.serialize_output(o) for o in outputs))))
hashOutputs = bh2u(sha256d(bfh(''.join(o.serialize_to_network().hex() for o in outputs))))
return BIP143SharedTxDigestFields(hashPrevouts=hashPrevouts,
hashSequence=hashSequence,
hashOutputs=hashOutputs)
@ -738,7 +746,7 @@ class Transaction:
return ''
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, create_script_sig(txin))
for txin in inputs)
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
txouts = var_int(len(outputs)) + ''.join(o.serialize_to_network().hex() for o in outputs)
use_segwit_ser_for_estimate_size = estimate_size and self.is_segwit(guess_for_address=True)
use_segwit_ser_for_actual_use = not estimate_size and self.is_segwit()
@ -996,7 +1004,7 @@ class PartialTxInput(TxInput, PSBTSection):
def __init__(self, *args, **kwargs):
TxInput.__init__(self, *args, **kwargs)
self.utxo = None # type: Optional[Transaction]
self.witness_utxo = None # type: Optional[bytes]
self.witness_utxo = None # type: Optional[TxOutput]
self.part_sigs = {} # type: Dict[bytes, bytes] # pubkey -> sig
self.sighash = None # type: Optional[int]
self.bip32_paths = {} # type: Dict[bytes, Tuple[bytes, Sequence[int]]] # pubkey -> (xpub_fingerprint, path)
@ -1020,7 +1028,7 @@ class PartialTxInput(TxInput, PSBTSection):
'value_sats': self.value_sats(),
'address': self.address,
'utxo': str(self.utxo) if self.utxo else None,
'witness_utxo': self.witness_utxo.hex() if self.witness_utxo else None,
'witness_utxo': self.witness_utxo.serialize_to_network().hex() if self.witness_utxo else None,
'sighash': self.sighash,
'redeem_script': self.redeem_script.hex() if self.redeem_script else None,
'witness_script': self.witness_script.hex() if self.witness_script else None,
@ -1081,7 +1089,7 @@ class PartialTxInput(TxInput, PSBTSection):
raise SerializationError(f"duplicate key: {repr(kt)}")
if self.utxo is not None:
raise SerializationError(f"PSBT input cannot have both PSBT_IN_NON_WITNESS_UTXO and PSBT_IN_WITNESS_UTXO")
self.witness_utxo = val
self.witness_utxo = TxOutput.from_network_bytes(val)
if key: raise SerializationError(f"key for {repr(kt)} must be empty")
elif kt == PSBTInputType.PARTIAL_SIG:
if key in self.part_sigs:
@ -1131,7 +1139,7 @@ class PartialTxInput(TxInput, PSBTSection):
def serialize_psbt_section_kvs(self, wr):
if self.witness_utxo:
wr(PSBTInputType.WITNESS_UTXO, self.witness_utxo)
wr(PSBTInputType.WITNESS_UTXO, self.witness_utxo.serialize_to_network())
elif self.utxo:
wr(PSBTInputType.NON_WITNESS_UTXO, bfh(self.utxo.serialize_to_network(include_sigs=True)))
for pk, val in sorted(self.part_sigs.items()):
@ -1159,7 +1167,7 @@ class PartialTxInput(TxInput, PSBTSection):
out_idx = self.prevout.out_idx
return self.utxo.outputs()[out_idx].value
if self.witness_utxo:
return int.from_bytes(self.witness_utxo[:8], byteorder="little", signed=True)
return self.witness_utxo.value
return None
@property
@ -1179,7 +1187,7 @@ class PartialTxInput(TxInput, PSBTSection):
out_idx = self.prevout.out_idx
return self.utxo.outputs()[out_idx].scriptpubkey
if self.witness_utxo:
return self.witness_utxo[8:]
return self.witness_utxo.scriptpubkey
return None
def is_complete(self) -> bool:
@ -1625,7 +1633,7 @@ class PartialTransaction(Transaction):
else:
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, preimage_script if txin_index==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(o.serialize_to_network().hex() for o in outputs)
preimage = nVersion + txins + txouts + nLocktime + nHashType
return preimage

View file

@ -58,7 +58,7 @@ from .keystore import load_keystore, Hardware_KeyStore, KeyStore
from .util import multisig_type
from .storage import StorageEncryptionVersion, WalletStorage
from . import transaction, bitcoin, coinchooser, paymentrequest, ecc, bip32
from .transaction import (Transaction, TxInput, UnknownTxinType,
from .transaction import (Transaction, TxInput, UnknownTxinType, TxOutput,
PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint)
from .plugin import run_hook
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
@ -1240,8 +1240,7 @@ class Abstract_Wallet(AddressSynchronizer):
item = received.get(txin.prevout.to_str())
if item:
txin_value = item[1]
txin_value_bytes = txin_value.to_bytes(8, byteorder="little", signed=True)
txin.witness_utxo = txin_value_bytes + bfh(bitcoin.address_to_script(address))
txin.witness_utxo = TxOutput.from_address_and_value(address, txin_value)
else: # legacy input
if txin.utxo is None:
# note: for hw wallets, for legacy inputs, ignore_network_issues used to be False