ENV-2282: fixed taproot versions of component hashes when coming after segwit
What changed, and why it matters
This firmware update fixes a bug in how Passport hardware wallets compute Taproot Bitcoin transaction signatures. Previously, when a transaction contained both SegWit and Taproot inputs, the wallet could accidentally reuse cached SegWit-style hashes for the Taproot signing math instead of computing fresh Taproot-specific hashes. Because Taproot and SegWit use different hashing rules, this could lead to an invalid signature or, in some edge cases, a signature that does not match what the Bitcoin network expects. The fix creates separate Taproot hash fields so the two paths no longer share state.
Users should upgrade to the firmware version containing this commit before signing Taproot transactions, especially transactions that mix SegWit and Taproot inputs. Developers should audit other shared-state caches in the PSBT module for similar cross-script-type contamination.
Security signals we found
Incorrect cryptographic hash reuse across different signing modes
Violation of BIP-341 Taproot sighash semantics
Potential for invalid or malleable Taproot transaction signatures
State contamination between SegWit and Taproot PSBT processing
Evidence from the diff
The patch introduces dedicated Taproot cache variables (tap_hashPrevouts, tap_hashSequence, tap_hashOutputs) in psbt.py and updates taproot_sign_input to populate and consume them instead of the legacy SegWit hashPrevouts/hashSequence/hashOutputs fields. The bug occurred because the SegWit and Taproot code paths shared the same cached hash variables; if a SegWit input was processed first, the cached values would be reused for a later Taproot input, violating BIP-341’s requirement that Taproot sighashes use distinct tagged hashes and serialization. The fix ensures Taproot sighash computation is isolated from SegWit state.
Changed components
ports/stm32/boards/Passport/modules/psbt.pyTaproot transaction signing pathPSBT input processingSighash computation cacheInspect captured patch +10 / −7
diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py
index f33883a..80eb248 100644
--- a/ports/stm32/boards/Passport/modules/psbt.py
+++ b/ports/stm32/boards/Passport/modules/psbt.py
@@ -959,6 +959,9 @@ class psbtObject(psbtProxy):
self.hashOutputs = None
# taproot additions to reused segwit hashes
+ self.tap_hashPrevouts = None
+ self.tap_hashSequence = None
+ self.tap_hashOutputs = None
self.hashAmounts = None
self.hashScriptPubkeys = None
@@ -1678,7 +1681,7 @@ class psbtObject(psbtProxy):
assert sighash_type == SIGHASH_DEFAULT
- if self.hashPrevouts is None or self.hashAmounts is None or self.hashScriptPubkeys is None:
+ if self.tap_hashPrevouts is None:
# First time thru, we'll need to hash up this stuff.
prevouts = trezorcrypto.sha256()
@@ -1694,10 +1697,10 @@ class psbtObject(psbtProxy):
script_pubkeys.update(ser_string(utxo.scriptPubKey))
sequences.update(pack("<I", txi.nSequence))
- self.hashPrevouts = prevouts.digest()
+ self.tap_hashPrevouts = prevouts.digest()
self.hashAmounts = amounts.digest()
self.hashScriptPubkeys = script_pubkeys.digest()
- self.hashSequence = sequences.digest()
+ self.tap_hashSequence = sequences.digest()
del prevouts, amounts, script_pubkeys, sequences, txi
@@ -1706,7 +1709,7 @@ class psbtObject(psbtProxy):
for out_idx, txo in self.output_iter():
outputs.update(txo.serialize())
- self.hashOutputs = outputs.digest()
+ self.tap_hashOutputs = outputs.digest()
del outputs, txo
gc.collect()
@@ -1714,11 +1717,11 @@ class psbtObject(psbtProxy):
data = bytes([sighash_type])
data += pack('<i', self.txn_version)
data += pack('<I', self.lock_time)
- data += self.hashPrevouts
+ data += self.tap_hashPrevouts
data += self.hashAmounts
data += self.hashScriptPubkeys
- data += self.hashSequence
- data += self.hashOutputs
+ data += self.tap_hashSequence
+ data += self.tap_hashOutputs
spend_type = (2 * ext_flag) + (1 if annex is not None else 0)
data += pack('B', spend_type)
Why this scored 71/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.