SFT-5689: prevented suspicious change alert for transactions from super-accounts
What changed, and why it matters
This firmware update changes how Passport checks whether a Bitcoin transaction's change output looks suspicious. The device previously compared the full beginning portion of the derivation path, which caused false alarms when a user combined Segwit and Taproot accounts under one 'super-account' (where only the first path number, the purpose, differs). The patch ignores that first purpose number when deciding if outputs belong to the same wallet, so legitimate change from mixed-account setups no longer triggers a warning. It is a user-experience fix rather than a clear security bug, but any relaxation of a warning rule deserves review to ensure it cannot hide a real attack.
Review whether ignoring the purpose component could allow a malicious or misconstructed PSBT to route change to a different script type under the same super-account without raising the alert. Verify that the remaining checks (path length, hardening pattern, change/index values, and gap limit) are sufficient to prevent cross-account change theft in all supported wallet policies. Consider adding a regression test with mixed-purpose super-account inputs and outputs.
Security signals we found
Relaxation of change-output path-prefix validation
Explicit handling of multi-purpose 'super-account' wallets
Warning message still displays the full original prefix for user visibility
No change to gap limit, hardening, or change/index checks
Small, targeted diff in PSBT parsing code
Evidence from the diff
In ports/stm32/boards/Passport/modules/psbt.py, the suspicious-change detection logic is adjusted for ‘super-accounts’. path_prefix is now computed from in_paths[0][1:-2] instead of [0:-2], and the comparison against each output path uses path[1:-2] rather than path[0:len(path_prefix)]. A separate full_path_prefix is kept only for formatting the expected-path string in the warning message. This means the first BIP-44/49/84/86 purpose component (e.g., 44’, 49’, 84’, 86’) is no longer required to match across inputs and change outputs. The change is narrowly scoped to the prefix check and does not alter length, hardening, change/index, or gap-limit checks.
Changed components
Passport firmware PSBT transaction validationports/stm32/boards/Passport/modules/psbt.pySuspicious change-output alert logicInspect captured patch +7 / −3
diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py
index 1dbdaf0..7cd7040 100644
--- a/ports/stm32/boards/Passport/modules/psbt.py
+++ b/ports/stm32/boards/Passport/modules/psbt.py
@@ -1320,8 +1320,12 @@ class psbtObject(psbtProxy):
# Assumption: common wallets modulate the last two components only
# of the path. Typically m/.../change/index where change is {0, 1}
# and index changes slowly over lifetime of wallet (increasing)
+ # With the use of super-accounts that combine taproot and segwit
+ # 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][0:-2]
+ path_prefix = in_paths[0][1:-2]
+ full_path_prefix = in_paths[0][0:-2]
idx_max = max(i[-1] & 0x7fffffff for i in in_paths) + 200
hard_pattern = hard_bits(in_paths[0])
@@ -1344,7 +1348,7 @@ class psbtObject(psbtProxy):
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[0:len(path_prefix)] != path_prefix:
+ elif path[1:-2] != path_prefix:
iss = "goes to different path prefix"
elif (path[-2] & 0x7fffffff) not in {0, 1}:
iss = "second last component not 0 or 1"
@@ -1356,7 +1360,7 @@ class psbtObject(psbtProxy):
probs.append("Output #%d: %s: %s not %s/{0~1}%s/{0~%d}%s expected"
% (nout, iss, keypath_to_str(path, skip=0),
- keypath_to_str(path_prefix, skip=0),
+ keypath_to_str(full_path_prefix, skip=0),
"'" if hard_pattern[-2] else "",
idx_max, "'" if hard_pattern[-1] else "",
))
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.