SFT-6783: fixed check for fully signed transactions with taproot
What changed, and why it matters
This firmware update fixes how Passport detects a fully-signed Bitcoin transaction when using Taproot. Previously, the wallet could misjudge whether a Taproot transaction input was already signed, which could lead it to either skip needed signing or misreport the transaction state. The fix explicitly checks for the Taproot key-path signature rather than counting sub-paths.
Users relying on Taproot key-path spends should update to firmware containing this commit. Developers should verify that the new fully_signed check behaves correctly for both single-sig Taproot and multi-sig Tapscript cases, and add regression tests for partially-signed Taproot PSBTs.
Security signals we found
Incorrect fully-signed detection could cause a hardware wallet to skip signing a Taproot input
User-visible transaction state could be wrong, leading to signing workflow issues
Fix is narrowly scoped to Taproot key-path signature handling
Evidence from the diff
In psbt.py, the logic that decides if a PSBT input is ‘fully_signed’ was changed for Taproot key-path spends. Before, the code counted self.tap_subpaths or self.subpaths and compared against self.part_sig, which did not correctly account for a Taproot key-path signature (tap_key_sig). Now, if tap_subpaths exist, fully_signed is set directly based on whether self.tap_key_sig is present. Otherwise it falls back to the existing part_sig/subpath count logic.
Changed components
ports/stm32/boards/Passport/modules/psbt.pyPSBT input signing state determinationTaproot key-path transaction handlingInspect captured patch +11 / −7
diff --git a/ports/stm32/boards/Passport/modules/psbt.py b/ports/stm32/boards/Passport/modules/psbt.py
index 1dbdaf0..643c243 100644
--- a/ports/stm32/boards/Passport/modules/psbt.py
+++ b/ports/stm32/boards/Passport/modules/psbt.py
@@ -597,13 +597,17 @@ class psbtInputProxy(psbtProxy):
# - someday we will expand to other types, but not yet
raise FatalPSBTIssue('Can only do SIGHASH_ALL')
- if self.part_sig:
- # How complete is the set of signatures so far?
- # - assuming PSBT creator doesn't give us extra data not required
- # - seems harmless if they fool us into thinking already signed; we do nothing
- # - could also look at pubkey needed vs. sig provided
- # - could consider structure of MofN in p2sh cases
- num_subpaths = len(self.tap_subpaths) if len(self.tap_subpaths) > 0 else len(self.subpaths)
+ # How complete is the set of signatures so far?
+ # - assuming PSBT creator doesn't give us extra data not required
+ # - seems harmless if they fool us into thinking already signed; we do nothing
+ # - could also look at pubkey needed vs. sig provided
+ # - could consider structure of MofN in p2sh cases
+ num_subpaths = len(self.subpaths)
+
+ if len(self.tap_subpaths) > 0:
+ # For key-path taproot signing, presence of tap_key_sig means the input is signed.
+ self.fully_signed = self.tap_key_sig is not None
+ elif num_subpaths > 0:
self.fully_signed = len(self.part_sig) >= num_subpaths
else:
# No signatures at all yet for this input (typical non multisig)
Why this scored 58/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.