What changed, and why it matters
This commit is a minor cleanup and documentation update for the COLDCARD firmware. It adds a short document explaining a BIP-21 wallet parameter, replaces a hardcoded number with a named constant, tweaks on-screen text to fit smaller screens, and updates tests to match the new display strings. There is no security fix or vulnerability here.
No security action required; treat as routine maintenance/documentation commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit titled ‘docs & nits’ contains only cosmetic and non-functional changes: (1) new markdown doc docs/bip-21-extensions.md describing the ‘wallet’ query parameter for ownership address checks; (2) comments added in address_explorer.py; (3) a hardcoded ‘20’ in ownership.py replaced by a named constant BONUS_AFTER_MATCH with identical behavior, plus simplification of fullscreen message construction; (4) wallet.py shortens wallet names for non-QWERTY (Mk4) tiny displays; (5) test_ownership.py updated to assert the shortened names on Mk4 and skip multisig-with-subaccount tests. No cryptographic, authorization, or memory-safety logic is altered.
Changed components
docs/bip-21-extensions.mdshared/address_explorer.pyshared/display.pyshared/ownership.pyshared/wallet.pytesting/test_ownership.pyInspect captured patch +48 / −24
diff --git a/docs/bip-21-extensions.md b/docs/bip-21-extensions.md
new file mode 100644
index 0000000..0fc2ab5
--- /dev/null
+++ b/docs/bip-21-extensions.md
@@ -0,0 +1,11 @@
+## `wallet` Ownership address check
+
+Address ownership allows to specify particular multisig wallet in which to search, allowing to skip
+useless searches in irrelevant wallets. `wallet` query parameter is provided via [BIP-21](https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki)
+
+#### Examples:
+```
+tb1q4d67p7stxml3kdudrgkg5mgaxsrgzcqzjrrj4gg62nxtvnsnvqjsxjkej0?wallet=my_wal
+
+'mtHSVByP9EYZmB26jASDdPVm19gvpecb5R?label=coldcard_purchase&amount=50&wallet=multi_wsh',
+```
\ No newline at end of file
diff --git a/shared/address_explorer.py b/shared/address_explorer.py
index 63026a9..0452a6c 100644
--- a/shared/address_explorer.py
+++ b/shared/address_explorer.py
@@ -429,6 +429,7 @@ def generate_address_csv(path, addr_fmt, ms_wallet, account_num, n, start=0, cha
+ ['Derivation (%d of %d)' % (i+1, ms_wallet.N) for i in range(ms_wallet.N)]
) + '"\n'
+ # saver will be None if we don't think it worth saving these addresses
saver = OWNERSHIP.saver(ms_wallet, change, start, n)
for (idx, addr, derivs, script) in ms_wallet.yield_addresses(start, n, change_idx=change):
@@ -453,6 +454,7 @@ def generate_address_csv(path, addr_fmt, ms_wallet, account_num, n, start=0, cha
from wallet import MasterSingleSigWallet
main = MasterSingleSigWallet(addr_fmt, path, account_num)
+ # saver will be None if we don't think it worth saving these addresses
saver = OWNERSHIP.saver(main, change, start, n)
yield '"Index","Payment Address","Derivation"\n'
diff --git a/shared/display.py b/shared/display.py
index ab1a2cc..53a394e 100644
--- a/shared/display.py
+++ b/shared/display.py
@@ -155,9 +155,8 @@ class Display:
self.text(None, y, msg, font=FontLarge)
if line2:
- y += FontLarge.height # add height of above
- y += FontTiny.height # add space of size FontTiny height
- self.text(None, y, line2, font=FontSmall)
+ # 21 + 6 ie. FontLarge.height of above text + FontTiny.height as space between
+ self.text(None, y + 27, line2, font=FontSmall)
if percent is not None:
self.progress_bar(percent)
diff --git a/shared/ownership.py b/shared/ownership.py
index eb3434d..3557855 100644
--- a/shared/ownership.py
+++ b/shared/ownership.py
@@ -38,6 +38,7 @@ OWNERSHIP_MAGIC = 0x10A0 # "Address Ownership" v1.0
# target 3 flash blocks, max file size => 764 addresses
MAX_ADDRS_STORED = const(764) # =((3*512) - OWNERSHIP_FILE_HDR_LEN) // HASH_ENC_LEN
+BONUS_AFTER_MATCH = const(20) # number of addresses to still generate after match found
def encode_addr(addr, salt):
# Convert text address to something we can store while preserving privacy.
@@ -161,7 +162,7 @@ class AddressCacheFile:
self.count += 1
if bonus:
- if bonus >= 20:
+ if bonus >= BONUS_AFTER_MATCH:
# do (at most) 20 more - limited by 'start_idx' & 'count'
break
bonus += 1
@@ -315,11 +316,8 @@ class OwnershipCache:
cachefs.append(AddressCacheFile(w, 1))
for cf in cachefs:
- msg, l2 = "Searching...", "(change)" if cf.change_idx else None
- if dis.has_lcd:
- msg, l2 = 'Searching wallet(s)...', cf.nice_name()
-
- dis.fullscreen(msg, line2=l2)
+ msg = "Searching wallet(s)..." if dis.has_lcd else "Searching..."
+ dis.fullscreen(msg, line2=cf.nice_name())
wallet, subpath = OWNERSHIP.search_wallet_cache(addr, cf)
if wallet:
# first arg from_cache=True
@@ -328,11 +326,8 @@ class OwnershipCache:
# nothing found in existing cache files
c = 0
for cf in cachefs:
- msg, l2 = "Generating...", "(change)" if cf.change_idx else None
- if dis.has_lcd:
- msg, l2 = 'Generating addresses...', cf.nice_name()
-
- dis.fullscreen(msg, line2=l2)
+ msg = "Generating addresses..." if dis.has_lcd else "Generating..."
+ dis.fullscreen(msg, line2=cf.nice_name())
wallet, subpath = OWNERSHIP.search_build_wallet(addr, cf)
c += cf.count
if wallet:
diff --git a/shared/wallet.py b/shared/wallet.py
index 97d321c..d3ef62a 100644
--- a/shared/wallet.py
+++ b/shared/wallet.py
@@ -2,7 +2,7 @@
#
# wallet.py - A place you find UTXO, addresses and descriptors.
#
-import chains
+import chains, version
from descriptor import Descriptor
from stash import SensitiveValues
@@ -41,6 +41,13 @@ class MasterSingleSigWallet(WalletABC):
# - path can be overriden when we come here via address explorer
n = chains.addr_fmt_label(addr_fmt)
+ if not version.has_qwerty:
+ # Mk4 tiny display
+ # Classic P2PKH -> P2PKH
+ # Segwit P2WPKH -> P2WPKH
+ # P2SH-Segwit -> no change (should not be used that much)
+ n = n.split(" ")[-1]
+
purpose = chains.af_to_bip44_purpose(addr_fmt)
prefix = path or 'm/%dh/{coin_type}h/{account}h' % purpose
@@ -50,12 +57,13 @@ class MasterSingleSigWallet(WalletABC):
self.chain = chains.current_chain()
if account_idx != 0:
- n += ' Account#%d' % account_idx
+ rv = " Account#%d" if version.has_qwerty else " Acct#%d"
+ n += rv % account_idx
if self.chain.ctype == 'XTN':
- n += ' (Testnet)'
+ n += ' (Testnet)' if version.has_qwerty else " XTN"
if self.chain.ctype == 'XRT':
- n += ' (Regtest)'
+ n += ' (Regtest)' if version.has_qwerty else " XRT"
self.name = n
diff --git a/testing/test_ownership.py b/testing/test_ownership.py
index 15a25a9..1e83363 100644
--- a/testing/test_ownership.py
+++ b/testing/test_ownership.py
@@ -57,15 +57,18 @@ def test_negative(addr_fmt, testnet, sim_exec):
@pytest.mark.parametrize('from_empty', [ True, False] )
def test_positive(addr_fmt, offset, subaccount, testnet, from_empty, change_idx,
sim_exec, wipe_cache, make_myself_wallet, use_testnet, goto_home, pick_menu_item,
- enter_number, press_cancel, settings_set, import_ms_wallet, clear_ms
+ enter_number, press_cancel, settings_set, import_ms_wallet, clear_ms, is_q1,
):
# API/Unit test, limited UX
-
- if not testnet and addr_fmt in { AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH }:
+ ms_addr_fmts = { AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH }
+ if not testnet and (addr_fmt in ms_addr_fmts):
# multisig jigs assume testnet
raise pytest.skip('testnet only')
+ if (addr_fmt in ms_addr_fmts) and subaccount:
+ raise pytest.skip('multisig with subaccount')
+
wipe_cache()
settings_set('accts', [])
use_testnet(testnet)
@@ -150,10 +153,16 @@ def test_positive(addr_fmt, offset, subaccount, testnet, from_empty, change_idx,
from_cache, got_name, got_path = lst
assert from_cache == (not from_empty)
- assert expect_name in got_name
- if subaccount and '...' not in path:
+ if is_q1:
+ assert expect_name in got_name
+ else:
+ assert expect_name.split(" ")[-1] in got_name
+ if subaccount:
# not expected for multisig, since we have proper wallet name
- assert f'Account#{subaccount}' in got_name
+ if is_q1:
+ assert f'Account#{subaccount}' in got_name
+ else:
+ assert f'Acct#{subaccount}' in got_name
assert got_path == (change_idx, offset)
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.