fix bypass_tmp return to master secret with xprv type
What changed, and why it matters
This commit fixes a bug in how the COLDCARD wallet creates encrypted backups when a temporary seed is being bypassed. Previously, if the main secret was stored as an extended private key ('xprv') rather than as BIP39 seed words, the backup code could incorrectly encode the secret and potentially return to or expose the master secret in the wrong form. The fix ensures the correct secret type is used when restoring settings from the main seed during backup generation.
Review whether any released firmware versions shipped with this bug and assess whether backups generated under bypass_tmp with xprv-type master secrets could be malformed or leak sensitive state. Consider adding regression tests covering both 'words' and 'xprv' master secret types during temporary-seed backup flows.
Security signals we found
Incorrect secret encoding when bypassing temporary seed
Potential master secret exposure or backup corruption due to type mismatch
Missing branch for xprv-type master secret in backup path
Fix uses explicit mode assertion to prevent silent fallback
Evidence from the diff
In shared/backups.py, render_backup_contents() handles two secret modes: ‘words’ (BIP39 mnemonic) and ‘master’ (raw BIP32 master key). The first hunk changes the second ‘if’ to ‘elif’, making the modes mutually exclusive. The second hunk fixes the bypass_tmp path: when bypassing a temporary seed, the code temporarily clears pa.tmp_value and re-encodes the main secret to load its settings. Previously it always called SecretStash.encode(seed_phrase=sv.raw), which is only valid when sv.mode == ‘words’. For ‘master’ secrets, sv.raw is the raw BIP32 master key bytes and sv.node holds the HDNode object; encoding it as a seed phrase would produce an incorrect stash value. The fix branches: encode from seed_phrase for ‘words’, and from xprv=sv.node for ‘xprv’ mode, with an assertion that the mode is ‘xprv’.
Changed components
shared/backups.pyrender_backup_contents()SecretStash.encode()Temporary seed bypass logicInspect captured patch +7 / −2
diff --git a/shared/backups.py b/shared/backups.py
index 07fb2f5..4f508cd 100644
--- a/shared/backups.py
+++ b/shared/backups.py
@@ -49,7 +49,7 @@ def render_backup_contents(bypass_tmp=False):
if sv.mode == 'words':
ADD('mnemonic', bip39.b2a_words(sv.raw))
- if sv.mode == 'master':
+ elif sv.mode == 'master':
ADD('bip32_master_key', b2a_hex(sv.raw))
ADD('chain', chain.ctype)
@@ -76,7 +76,12 @@ def render_backup_contents(bypass_tmp=False):
current_tmp = pa.tmp_value[:]
pa.tmp_value = None
# we also need correct settings from main seed
- nv = stash.SecretStash.encode(seed_phrase=sv.raw)
+ if sv.mode == 'words':
+ nv = stash.SecretStash.encode(seed_phrase=sv.raw)
+ else:
+ assert sv.mode == "xprv"
+ nv = stash.SecretStash.encode(xprv=sv.node)
+
settings.set_key(nv)
settings.load()
stash.blank_object(nv)
Why this scored 61/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.