SFT-5689: improved path mismatch logic
What changed, and why it matters
This commit tightens how Passport checks that all inputs in a Bitcoin transaction belong to the same wallet account. Previously, the firmware only compared a partial derivation path prefix, which could have allowed mixing inputs from different wallet types (for example, Native SegWit and Taproot) that share the same account keys. The change now explicitly checks whether the wallet 'purpose' numbers differ and only allows that mismatch for known compatible types (84 for SegWit, 86 for Taproot). This is a defensive hardening fix rather than a clear exploit patch, because the commit message and diff do not describe a demonstrated attack.
Treat as a hardening fix and include in normal firmware release testing. Review whether the allowed purpose list (84, 86) matches the wallet types Passport officially supports, and verify that mixed-purpose PSBTs are rejected or handled safely in end-to-end tests. No urgent incident response is warranted based solely on this commit.
Security signals we found
Input validation change in PSBT signing path
New explicit purpose-field check for BIP-44/49/84/86 derivation paths
Defensive tightening of 'super-account' path-mismatch logic
No explicit security claim or exploit description in commit message
Evidence from the diff
In ports/stm32/boards/Passport/modules/psbt.py, the PSBT input-path validation was refactored. The old code compared path[1:-2] against a single path_prefix and did not explicitly reason about the BIP-44/49/84/86 purpose field at index 0. The new code introduces purpose_mismatch_allowed(), which returns true only for purposes 84 and 86 (with the hardening bit masked). It then computes partial_path_mismatch and purpose_mismatch separately, and flags a path mismatch unless the purpose mismatch is between two allowed purposes. This prevents two inputs from being treated as part of the same ‘super-account’ when their purposes differ and at least one is not 84/86. The change is small and appears to be hardening; no CVE, advisory, or researcher attribution is present in the supplied materials.
Changed components
ports/stm32/boards/Passport/modules/psbt.pyPSBT input derivation-path validationPassport firmware transaction signing flowInspect captured patch +14 / −2
diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py
index 7cd7040..0fa5bea 100644
--- a/ports/stm32/boards/Passport/modules/psbt.py
+++ b/ports/stm32/boards/Passport/modules/psbt.py
@@ -49,6 +49,10 @@ def seq_to_str(seq):
return ', '.join(str(i) for i in sorted(seq))
+def purpose_mismatch_allowed(purpose):
+ return (purpose & 0x7fffffff) in [84, 86]
+
+
def _skip_n_objs(fd, n, cls):
# skip N sized objects in the stream, for example a vectors of CTxIns
# - returns starting position
@@ -1324,8 +1328,8 @@ class psbtObject(psbtProxy):
# accounts of the same account index, the purpose (index 0)
# can mismatch and still be part of the same super-account.
path_len = shortest
- path_prefix = in_paths[0][1:-2]
full_path_prefix = in_paths[0][0:-2]
+ partial_path_prefix = in_paths[0][1:-2]
idx_max = max(i[-1] & 0x7fffffff for i in in_paths) + 200
hard_pattern = hard_bits(in_paths[0])
@@ -1344,11 +1348,19 @@ class psbtObject(psbtProxy):
continue # possible in p2sh case
path = path[1:]
+
+ partial_path_mismatch = partial_path_prefix != path[1:-2]
+ purpose_mismatch = full_path_prefix[0] != path[0]
+ mismatch_allowed = purpose_mismatch_allowed(full_path_prefix[0]) \
+ and purpose_mismatch_allowed(path[0])
+ path_mismatch = partial_path_mismatch \
+ or (purpose_mismatch and not mismatch_allowed)
+
if len(path) != path_len:
iss = "has wrong path length (%d not %d)" % (len(path), path_len)
elif hard_bits(path) != hard_pattern:
iss = "has different hardening pattern"
- elif path[1:-2] != path_prefix:
+ elif path_mismatch:
iss = "goes to different path prefix"
elif (path[-2] & 0x7fffffff) not in {0, 1}:
iss = "second last component not 0 or 1"
Why this scored 42/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.