What changed, and why it matters
This commit adds a new menu option that lets COLDCARD users export their public wallet information in a format compatible with Blue Wallet. It reuses the existing Electrum export code and only changes labels and filenames. There is no indication of a security bug or vulnerability.
No security action required. This is a routine feature addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch generalizes the existing Electrum skeleton-export routine so it can also produce a ‘Blue Wallet’ export. It parameterizes the menu item title and filename pattern (e.g., ‘new-blue.json’) and adds a ‘Blue Wallet’ entry to the export menu. No cryptographic, parsing, or access-control changes are introduced.
Changed components
shared/actions.pyshared/flow.pytesting/test_export.pyInspect captured patch +23 / −18
diff --git a/releases/Next-ChangeLog.md b/releases/Next-ChangeLog.md
index fde4965..5ca6c5a 100644
--- a/releases/Next-ChangeLog.md
+++ b/releases/Next-ChangeLog.md
@@ -15,6 +15,7 @@ This lists the new changes that have not yet been published in a normal release.
- Bugfix: Replace `/` with `-` in exported file names of multisig wallet export artifacts.
- Enhancement: Show the BIP-39 passphrase on-screen (must scroll down) once new key is in effect.
- Enhancement: New "Buried Settings" menu, inside Settings menu, for rarely-applied settings.
+- Enhancement: Add `Blue Wallet` option to `Export Wallet`
# Mk Specific Changes
diff --git a/shared/actions.py b/shared/actions.py
index c2da101..4e1d793 100644
--- a/shared/actions.py
+++ b/shared/actions.py
@@ -1122,19 +1122,21 @@ async def export_xpub(label, _2, item):
await show_qr_code(xpub, False)
-def electrum_export_story(background=False):
+def electrum_export_story(noun="Electrum", background=False):
# saves memory being in a function
return ('''\
-This saves a skeleton Electrum wallet file. \
-You can then open that file in Electrum without ever connecting this Coldcard to a computer.\n
-'''
+This saves a skeleton %s wallet file. \
+You can then open that file in the wallet without ever connecting this Coldcard to a computer.\n
+''' % noun
+ (background or 'Choose an address type for the wallet on the next screen.'+PICK_ACCOUNT)
+ SENSITIVE_NOT_SECRET)
-async def electrum_skeleton(*a):
+async def electrum_skeleton(a, b, item):
# save xpub, and some other public details into a file: NOT MULTISIG
+ title = item.arg
+ fname_pat = "new-%s.json" % title.lower()
- ch = await ux_show_story(electrum_export_story(), escape='1')
+ ch = await ux_show_story(electrum_export_story(title), escape='1')
account_num = 0
if ch == '1':
@@ -1144,7 +1146,7 @@ async def electrum_skeleton(*a):
rv = [
MenuItem(chains.addr_fmt_label(af), f=electrum_skeleton_step2,
- arg=(af, account_num))
+ arg=(af, account_num, title, fname_pat))
for af in chains.SINGLESIG_AF
]
the_ux.push(MenuSystem(rv))
@@ -1289,10 +1291,10 @@ without ever connecting this Coldcard to a computer.\
async def electrum_skeleton_step2(_1, _2, item):
# pick a semi-random file name, render and save it.
- addr_fmt, account_num = item.arg
- await export_contents('Electrum wallet',
+ addr_fmt, account_num, title, fname_pat = item.arg
+ await export_contents(title + " wallet",
lambda: generate_electrum_wallet(addr_fmt, account_num),
- "new-electrum.json", is_json=True)
+ fname_pat, is_json=True)
async def _generic_export(prompt, label, f_pattern):
# like the Multisig export, make a single JSON file with
diff --git a/shared/flow.py b/shared/flow.py
index 72eb72b..f1b07f6 100644
--- a/shared/flow.py
+++ b/shared/flow.py
@@ -227,14 +227,15 @@ WalletExportMenu = [
MenuItem("Nunchuk", f=named_generic_skeleton, arg="Nunchuk"),
MenuItem("Bull Bitcoin", f=ss_descriptor_skeleton,
arg=(True, [AF_P2WPKH], "", "bull-bitcoin.txt", KEY_QR)),
- MenuItem("Zeus", f=ss_descriptor_skeleton,
- arg=(True, [AF_P2WPKH, AF_P2WPKH_P2SH], "Zeus Wallet", "zeus-export.txt", None)),
- MenuItem("Electrum Wallet", f=electrum_skeleton),
+ MenuItem("Blue Wallet", f=electrum_skeleton, arg="Blue"),
+ MenuItem("Electrum Wallet", f=electrum_skeleton, arg="Electrum"),
MenuItem("Wasabi Wallet", f=wasabi_skeleton),
MenuItem("Fully Noded", f=named_generic_skeleton, arg="Fully Noded"),
MenuItem("Unchained", f=unchained_capital_export),
MenuItem("Theya", f=named_generic_skeleton, arg="Theya"),
MenuItem("Bitcoin Safe", f=named_generic_skeleton, arg="Bitcoin Safe"),
+ MenuItem("Zeus", f=ss_descriptor_skeleton,
+ arg=(True, [AF_P2WPKH, AF_P2WPKH_P2SH], "Zeus Wallet", "zeus-export.txt", None)),
MenuItem("Samourai Postmix", f=samourai_post_mix_descriptor_export),
MenuItem("Samourai Premix", f=samourai_pre_mix_descriptor_export),
# MenuItem("Samourai BadBank", f=samourai_bad_bank_descriptor_export), # not released yet
diff --git a/testing/test_export.py b/testing/test_export.py
index a9f0235..ffb8895 100644
--- a/testing/test_export.py
+++ b/testing/test_export.py
@@ -231,12 +231,13 @@ def test_export_wasabi(way, dev, pick_menu_item, goto_home, cap_story, press_sel
@pytest.mark.parametrize('mode', [ "Classic P2PKH", "P2SH-Segwit", "Segwit P2WPKH"])
-@pytest.mark.parametrize('acct_num', [ None, '0', '9897'])
+@pytest.mark.parametrize('acct_num', [ None, '9897'])
@pytest.mark.parametrize('way', ["sd", "vdisk", "nfc", "qr"])
@pytest.mark.parametrize('testnet', [True, False])
+@pytest.mark.parametrize('name', ["Electrum"])#, "Blue"]) # same tests, not needed
def test_export_electrum(way, dev, mode, acct_num, pick_menu_item, goto_home, cap_story, need_keypress,
microsd_path, nfc_read_json, virtdisk_path, use_mainnet, testnet, load_export,
- press_select, skip_if_useless_way, expect_acctnum_captured):
+ press_select, skip_if_useless_way, expect_acctnum_captured, name):
# lightly test electrum wallet export
skip_if_useless_way(way)
@@ -252,12 +253,12 @@ def test_export_electrum(way, dev, mode, acct_num, pick_menu_item, goto_home, ca
pick_menu_item('Advanced/Tools')
pick_menu_item('File Management')
pick_menu_item('Export Wallet')
- pick_menu_item('Electrum Wallet')
+ pick_menu_item(f'{name} Wallet')
time.sleep(0.1)
title, story = cap_story()
- assert 'This saves a skeleton Electrum wallet' in story
+ assert f'This saves a skeleton {name} wallet' in story
assert 'Press (1) to' in story
if acct_num is not None:
@@ -273,7 +274,7 @@ def test_export_electrum(way, dev, mode, acct_num, pick_menu_item, goto_home, ca
expect_acctnum_captured(acct_num)
- obj = load_export(way, label="Electrum wallet", is_json=True, addr_fmt=af)
+ obj = load_export(way, label=f"{name} wallet", is_json=True, addr_fmt=af)
ks = obj['keystore']
assert ks['ckcc_xfp'] == simulator_fixed_xfp
Why this scored 15/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.