psbt: assume final sequence when PSBT_IN_SEQUENCE is omitted
What changed, and why it matters
This commit fixes a crash in the PSBT (Partially Signed Bitcoin Transaction) handling code. When a transaction input did not specify a sequence number, the software would crash with an assertion failure instead of assuming the standard default value (0xffffffff). The fix makes the code follow the BIP 370 specification correctly, allowing it to handle minimal PSBTv2 files without crashing.
Apply the patch. It is a low-risk correctness fix that improves robustness when processing externally supplied PSBTv2 files. No immediate security incident response is indicated, but users parsing untrusted PSBTs should update.
Security signals we found
Denial-of-service vector: assertion failure on malformed/minimal PSBT input
Non-compliance with BIP 370 default sequence semantics
Crash triggered by external PSBT data (parser robustness issue)
Evidence from the diff
In hwilib/psbt.py, get_unsigned_tx() previously asserted that psbt_in.sequence is not None. BIP 370 states that when PSBT_IN_SEQUENCE is omitted, the sequence number should be assumed to be the final sequence number (0xffffffff). The patch removes the assertion and substitutes 0xffffffff when sequence is None. A test case using the minimal BIP 370 PSBTv2 test vector is added to verify the behavior.
Changed components
hwilib/psbt.pytest/test_psbt.pyInspect captured patch +11 / −2
### hwilib/psbt.py
@@ -1122,9 +1122,10 @@ def get_unsigned_tx(self) -> CTransaction:
for psbt_in in self.inputs:
assert psbt_in.prev_txid is not None
assert psbt_in.prev_out is not None
- assert psbt_in.sequence is not None
- txin = CTxIn(COutPoint(uint256_from_str(psbt_in.prev_txid), psbt_in.prev_out), b"", psbt_in.sequence)
+ # If omitted, the sequence number is assumed to be the final sequence number
+ sequence = psbt_in.sequence if psbt_in.sequence is not None else 0xffffffff
+ txin = CTxIn(COutPoint(uint256_from_str(psbt_in.prev_txid), psbt_in.prev_out), b"", sequence)
tx.vin.append(txin)
for psbt_out in self.outputs:
### test/test_psbt.py
@@ -42,5 +42,13 @@ def test_convert_to_v0(self):
self.assertEqual(len(psbt.tx.vout), 2)
self.assertEqual(psbt.tx.nLockTime, 10000)
+ # BIP 370 vector "1 input, 2 output PSBTv2, required fields only",
+ # which omits PSBT_IN_SEQUENCE
+ psbt = PSBT()
+ psbt.deserialize("cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==")
+ psbt.convert_to_v0()
+
+ self.assertEqual(psbt.tx.vin[0].nSequence, 0xffffffff)
+
if __name__ == "__main__":
unittest.main()Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.