Browse Source

psbt: fix bug re witness_utxo serialization

master
SomberNight 6 years ago
parent
commit
90b190bbcd
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 2
      electrum/tests/test_transaction.py
  2. 42
      electrum/transaction.py
  3. 5
      electrum/wallet.py

2
electrum/tests/test_transaction.py

@ -795,7 +795,7 @@ class TestLegacyPartialTxFormat(TestCaseForTestnet):
wallet = WalletIntegrityHelper.create_multisig_wallet([ks1, ks2, ks3], '2of3', config=self.config) 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) tx.add_info_from_wallet(wallet)
raw_tx = serialize_tx_in_legacy_format(tx, wallet=wallet) raw_tx = serialize_tx_in_legacy_format(tx, wallet=wallet)
self.assertEqual('45505446ff000200000000010187c4646ca690b397e357b23b2137030691a90a068a6690834d340b4be84acd6a0100000000fdffffff03a0860100000000001600148bc9d947e4d0addc2f4c34b8371034eb47b3d305140c030000000000220020a6087c4f84a55dc39014729a18e08955139d4384559d1fd2a48a1d95d746a425400d0300000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788acfeffffffff20a10700000000000000050001ff47304402202f7be5fad398f1a3576293339f2274d227af17798690aa1ad00ff83d96725cc5022000cb2e9fff5be23a862718c665a5035f172783e9b3158eae83673f6c59adc3c00101fffd0201524c53ff02575483000000000000000000d644dcdd7a4acb432355e010ac4a5f955940c82a9767731b7d89dc42eb3cdac40272faa2f98b76cc9d655ef281c07acda75c30a990dc3a527b6cf92a8f5a6b8a80000001004c53ff0257548301d24cd59180000000b60a85089ad0850a6ffe8590a3e15064e63eefd020813df2e4a6b3209ce3df5f02c8b14f2917d557cd482970c114c3e3457e09d256397802277a2e4d1519ab9f8c000001004c53ff0257548301d24cd5918000000199c4d6c893fa1addec6b0121181137e25c38194333b8c57215b3cf1e6d7e7af102a23ddae698bb6095b8b8035bdd1e94479f66c29679d81931e0fe07aa436149ee0000010053ae132b1800', self.assertEqual('45505446ff000200000000010187c4646ca690b397e357b23b2137030691a90a068a6690834d340b4be84acd6a0100000000fdffffff03a0860100000000001600148bc9d947e4d0addc2f4c34b8371034eb47b3d305140c030000000000220020a6087c4f84a55dc39014729a18e08955139d4384559d1fd2a48a1d95d746a425400d0300000000001976a914c13fd6294d1be7b9410a5538f4b4ef10fc594ee788acfeffffffff20a10700000000000000050001ff47304402202f7be5fad398f1a3576293339f2274d227af17798690aa1ad00ff83d96725cc5022000cb2e9fff5be23a862718c665a5035f172783e9b3158eae83673f6c59adc3c00101fffd0201524c53ff02575483000000000000000000d644dcdd7a4acb432355e010ac4a5f955940c82a9767731b7d89dc42eb3cdac40272faa2f98b76cc9d655ef281c07acda75c30a990dc3a527b6cf92a8f5a6b8a80000001004c53ff0257548301d24cd59180000000b60a85089ad0850a6ffe8590a3e15064e63eefd020813df2e4a6b3209ce3df5f02c8b14f2917d557cd482970c114c3e3457e09d256397802277a2e4d1519ab9f8c000001004c53ff0257548301d24cd5918000000199c4d6c893fa1addec6b0121181137e25c38194333b8c57215b3cf1e6d7e7af102a23ddae698bb6095b8b8035bdd1e94479f66c29679d81931e0fe07aa436149ee0000010053ae132b1800',

42
electrum/transaction.py

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

5
electrum/wallet.py

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

Loading…
Cancel
Save