Reject out-of-range txn version on PSBTv2 path
What changed, and why it matters
This fix closes a gap where COLDCARD would approve and sign a Bitcoin transaction with an invalid version number if the transaction was packaged in the newer PSBTv2 format. For PSBTv0, such bad versions were already rejected, but the PSBTv2 code path skipped that check. The result could be a signed transaction that the Bitcoin network refuses to relay, potentially trapping funds or breaking wallet workflows. The patch adds the same version-number sanity check to the shared validation routine so both PSBT versions are protected.
Users should upgrade to a firmware release containing this commit. Until then, avoid signing PSBTv2 transactions from untrusted sources and verify that produced transactions use standard nVersion values (1, 2, or 3).
Security signals we found
Missing input validation on alternate code path
Signed output could be non-relayable / invalid on network
Regression test demonstrates pre-patch bypass reached 'OK TO SEND?'
Fix aligns validation logic across PSBTv0 and PSBTv2 paths
Evidence from the diff
In shared/psbt.py, parse_txn() enforces txn_version ∈ {0,1,2,3} for PSBTv0, but read_psbt() bypasses parse_txn() for PSBTv2, leaving PSBT_GLOBAL_TX_VERSION unvalidated for ordinary transactions. validate() already restricted Proof-of-Reserves/BIP-322 transactions to versions {0,2} but imposed no upper bound for non-PoR transactions. The patch adds an else branch asserting txn_version ∈ {1,2,3} for non-PoR transactions in validate(), ensuring both v0 and v2 paths reject out-of-range versions while leaving the BIP-322 path unchanged. A regression test verifies versions 4, -1, and 100 are rejected on the v2 path.
Changed components
shared/psbt.py: PSBT validation and signing logicPSBTv2 transaction parsing pathTransaction version enforcementInspect captured patch +21 / −0
### releases/Next-ChangeLog.md
@@ -28,6 +28,9 @@ This lists the new changes that have not yet been published in a normal release.
`psram_copy_file`/`psram_mmap_file` that allowed out-of-bounds PSRAM writes, reads,
and mappings from a compromised USB host.
- Bugfix: Hide Change Main PIN while a temporary seed or BIP-39 passphrase wallet is active.
+- Bugfix: Reject PSBTv2 transactions with an out-of-range transaction version, matching
+ the PSBTv0 parser. Previously a v2 PSBT with an invalid `nVersion` could be approved
+ and signed, producing a transaction the network will not relay.
- Bugfix: Reject firmware images that extend past the world-checksum-covered
flash region.
- Security hardening: Remove the unused USB CDC/VCP serial interface from normal
### shared/psbt.py
@@ -1550,6 +1550,11 @@ async def validate(self):
if self.por322:
assert self.txn_version in {0, 2}, TX_VER_ERR
+ else:
+ # reject out-of-range versions on both v0 and v2 paths (the
+ # parse_txn() check only runs for v0); keeps v2 from signing
+ # transactions the network will not relay
+ assert self.txn_version in {1, 2, 3}, TX_VER_ERR
# time based relative locks
tb_rel_locks = []
### testing/test_sign.py
@@ -2621,6 +2621,19 @@ def hacker(psbt, way):
assert "failed" in story or "Invalid PSBT" in story or "Network fee bigger" in story
+@pytest.mark.parametrize("bad_ver", [4, -1, 100])
+def test_psbt_v2_bad_txn_version(bad_ver, fake_txn, start_sign, cap_story):
+ # PSBTv2 must reject an out-of-range transaction version, same as the v0 parser
+ # (parse_txn's `bad txn version` check only runs for v0). Otherwise a v2 PSBT with
+ # an invalid nVersion could be approved and signed into an unrelayable transaction.
+ # negative values are stored two's-complement (device parses txn_version as <i)
+ psbt = fake_txn(1, 1, segwit_in=True, psbt_v2=True,
+ psbt_hacker=lambda p: setattr(p, 'txn_version', bad_ver & 0xffffffff))
+ start_sign(psbt)
+ title, story = cap_story()
+ assert "bad txn version" in story, story
+
+
@pytest.mark.bitcoind
@pytest.mark.parametrize("locktime", [
0, # zero defaultWhy this scored 64/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.