mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-01 01:35:20 +00:00
transaction: allow PSBT input to have both UTXO and WITNESS_UTXO
- make sure they are consistent - only keep one of them internally (UTXO), and only serialise with UTXO (not both) fixes #6429
This commit is contained in:
parent
52f8aafb60
commit
73cf007048
2 changed files with 39 additions and 7 deletions
|
@ -90,6 +90,12 @@ class TestValidPSBT(TestCaseForTestnet):
|
||||||
finally:
|
finally:
|
||||||
constants.set_testnet()
|
constants.set_testnet()
|
||||||
|
|
||||||
|
def test_valid_psbt__input_with_both_witness_utxo_and_nonwitness_utxo(self):
|
||||||
|
# Case: PSBT where an input has both WITNESS_UTXO and UTXO.
|
||||||
|
# test it does not raise
|
||||||
|
tx = tx_from_any(bytes.fromhex('70736274ff0100710100000001626bbbb7a4ad82dbf7f6bd64ac3f40d0e2695b606d7953f2802b9ea426ea080a0000000000fdffffff02a025260000000000160014e5bddbfee3883729b48fe3385216e64e6035f6eb585d720000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a1c3914000001011f8096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a0100fd200101000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400000000'))
|
||||||
|
self.assertEqual(1, len(tx.inputs()))
|
||||||
|
|
||||||
|
|
||||||
class TestInvalidPSBT(TestCaseForTestnet):
|
class TestInvalidPSBT(TestCaseForTestnet):
|
||||||
# test cases from BIP-0174
|
# test cases from BIP-0174
|
||||||
|
@ -220,6 +226,11 @@ class TestInvalidPSBT(TestCaseForTestnet):
|
||||||
with self.assertRaises(SerializationError):
|
with self.assertRaises(SerializationError):
|
||||||
tx2 = tx_from_any('cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A')
|
tx2 = tx_from_any('cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A')
|
||||||
|
|
||||||
|
def test_invalid_psbt__input_with_both_witness_utxo_and_nonwitness_utxo_that_are_inconsistent(self):
|
||||||
|
# Case: PSBT where an input has both WITNESS_UTXO and UTXO but which are inconsistent.
|
||||||
|
with self.assertRaises(PSBTInputConsistencyFailure):
|
||||||
|
tx = tx_from_any(bytes.fromhex('70736274ff0100710100000001626bbbb7a4ad82dbf7f6bd64ac3f40d0e2695b606d7953f2802b9ea426ea080a0000000000fdffffff02a025260000000000160014e5bddbfee3883729b48fe3385216e64e6035f6eb585d720000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a1c3914000001011f8096990000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a0100fd200101000000000101197a89cff51096b9dd4214cdee0eb90cb27a25477e739521d728a679724042730100000000fdffffff048096980000000000160014dab37af8fefbbb31887a0a5f9b2698f4a7b45f6a80969800000000001976a91405a20074ef7eb42c7c6fcd4f499faa699742783288ac809698000000000017a914b808938a8007bc54509cd946944c479c0fa6554f87131b2c0400000000160014a04dfdb9a9aeac3b3fada6f43c2a66886186e2440247304402204f5dbb9dda65eab26179f1ca7c37c8baf028153815085dd1bbb2b826296e3b870220379fcd825742d6e2bdff772f347b629047824f289a5499a501033f6c3495594901210363c9c98740fe0455c646215cea9b13807b758791c8af7b74e62968bef57ff8ae1e391400000000'))
|
||||||
|
|
||||||
|
|
||||||
class TestPSBTSignerChecks(TestCaseForTestnet):
|
class TestPSBTSignerChecks(TestCaseForTestnet):
|
||||||
# test cases from BIP-0174
|
# test cases from BIP-0174
|
||||||
|
|
|
@ -1126,8 +1126,8 @@ class PSBTSection:
|
||||||
class PartialTxInput(TxInput, PSBTSection):
|
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[TxOutput]
|
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)
|
||||||
|
@ -1145,6 +1145,26 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||||
self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown
|
self._is_p2sh_segwit = None # type: Optional[bool] # None means unknown
|
||||||
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
||||||
|
|
||||||
|
@property
|
||||||
|
def utxo(self):
|
||||||
|
return self._utxo
|
||||||
|
|
||||||
|
@utxo.setter
|
||||||
|
def utxo(self, value: Optional[Transaction]):
|
||||||
|
self._utxo = value
|
||||||
|
self.validate_data()
|
||||||
|
self.ensure_there_is_only_one_utxo()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def witness_utxo(self):
|
||||||
|
return self._witness_utxo
|
||||||
|
|
||||||
|
@witness_utxo.setter
|
||||||
|
def witness_utxo(self, value: Optional[TxOutput]):
|
||||||
|
self._witness_utxo = value
|
||||||
|
self.validate_data()
|
||||||
|
self.ensure_there_is_only_one_utxo()
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
d = super().to_json()
|
d = super().to_json()
|
||||||
d.update({
|
d.update({
|
||||||
|
@ -1177,6 +1197,10 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||||
if self.prevout.txid.hex() != self.utxo.txid():
|
if self.prevout.txid.hex() != self.utxo.txid():
|
||||||
raise PSBTInputConsistencyFailure(f"PSBT input validation: "
|
raise PSBTInputConsistencyFailure(f"PSBT input validation: "
|
||||||
f"If a non-witness UTXO is provided, its hash must match the hash specified in the prevout")
|
f"If a non-witness UTXO is provided, its hash must match the hash specified in the prevout")
|
||||||
|
if self.witness_utxo:
|
||||||
|
if self.utxo.outputs()[self.prevout.out_idx] != self.witness_utxo:
|
||||||
|
raise PSBTInputConsistencyFailure(f"PSBT input validation: "
|
||||||
|
f"If both non-witness UTXO and witness UTXO are provided, they must be consistent")
|
||||||
# The following test is disabled, so we are willing to sign non-segwit inputs
|
# The following test is disabled, so we are willing to sign non-segwit inputs
|
||||||
# without verifying the input amount. This means, given a maliciously modified PSBT,
|
# without verifying the input amount. This means, given a maliciously modified PSBT,
|
||||||
# for non-segwit inputs, we might end up burning coins as miner fees.
|
# for non-segwit inputs, we might end up burning coins as miner fees.
|
||||||
|
@ -1208,16 +1232,12 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||||
if kt == PSBTInputType.NON_WITNESS_UTXO:
|
if kt == PSBTInputType.NON_WITNESS_UTXO:
|
||||||
if self.utxo is not None:
|
if self.utxo is not None:
|
||||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
raise SerializationError(f"duplicate key: {repr(kt)}")
|
||||||
if self.witness_utxo is not None:
|
|
||||||
raise SerializationError(f"PSBT input cannot have both PSBT_IN_NON_WITNESS_UTXO and PSBT_IN_WITNESS_UTXO")
|
|
||||||
self.utxo = Transaction(val)
|
self.utxo = Transaction(val)
|
||||||
self.utxo.deserialize()
|
self.utxo.deserialize()
|
||||||
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.WITNESS_UTXO:
|
elif kt == PSBTInputType.WITNESS_UTXO:
|
||||||
if self.witness_utxo is not None:
|
if self.witness_utxo is not None:
|
||||||
raise SerializationError(f"duplicate key: {repr(kt)}")
|
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 = TxOutput.from_network_bytes(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:
|
||||||
|
@ -1266,9 +1286,10 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||||
self._unknown[full_key] = val
|
self._unknown[full_key] = val
|
||||||
|
|
||||||
def serialize_psbt_section_kvs(self, wr):
|
def serialize_psbt_section_kvs(self, wr):
|
||||||
|
self.ensure_there_is_only_one_utxo()
|
||||||
if self.witness_utxo:
|
if self.witness_utxo:
|
||||||
wr(PSBTInputType.WITNESS_UTXO, self.witness_utxo.serialize_to_network())
|
wr(PSBTInputType.WITNESS_UTXO, self.witness_utxo.serialize_to_network())
|
||||||
elif self.utxo:
|
if 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()):
|
||||||
wr(PSBTInputType.PARTIAL_SIG, val, pk)
|
wr(PSBTInputType.PARTIAL_SIG, val, pk)
|
||||||
|
|
Loading…
Add table
Reference in a new issue