What changed, and why it matters
This commit fixes how Krux prints a wallet's backup words to a small thermal printer. Previously, long BIP39 words could run into the next column and make the printed backup hard or impossible to read correctly. The change also switches word-only backups from three columns to two columns and uses a fixed 12-character column width, so longer words no longer overlap. It also fixes a related device error when showing backups as word numbers. There is no evidence this is a remotely exploitable security flaw; it is a reliability/usability fix for physical backups.
Treat as a routine bug-fix / reliability patch. Reviewers may verify the new printed layout renders correctly on the supported thermal printers and that numeric backups no longer crash on device. No security-specific response is indicated by the supplied materials.
Security signals we found
Physical backup readability fix (non-exploitable)
Corrects argument-passing bug that could crash numeric backup display
No input validation, parsing, cryptography, or privilege changes observed
No references to CVEs, advisories, or security researchers in commit or supplied materials
Evidence from the diff
The patch refactors print_mnemonic_text() in src/krux/pages/print_page.py. It now chooses 3 columns for decimal/hex/octal number backups and 2 columns for plain-word backups, pads every column except the last to a fixed width of 12 characters, and lays out words row-by-row instead of the previous column-major order. Constants BASE_DEC_SUFFIX, BASE_HEX_SUFFIX, BASE_OCT_SUFFIX were moved from Utils to the pages package and are now imported where needed. A call to display_mnemonic() in mnemonic_backup.py was corrected to use keyword arguments (suffix=…, display_mnemonic=…), fixing a positional-argument mismatch that caused a device error when displaying numeric backups. Tests were updated to match the new 2-column word layout and wider 12-character padding.
Changed components
src/krux/pages/print_page.pysrc/krux/pages/home_pages/mnemonic_backup.pysrc/krux/pages/mnemonic_loader.pysrc/krux/pages/__init__.pysrc/krux/pages/utils.pytests/pages/home_pages/test_mnemonic_backup.pyInspect captured patch +70 / −58
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05eb602..6b10603 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ The Maix Bit device has long been discouraged due to its poor-quality camera. St
- Remove "Reboot" option and status bar, when empty, from Login menu
- Optimized Datum show: total pages are no longer visible, and navigation no longer wraps from the first page to the last or vice versa; better memory management and handling for large (~100K) files
- Enabled swipe up/down gestures on keypads, menus, and QR transcribe
+- Fix mnemonic thermal printing when long words span multiple lines
# Changelog 25.09.0 - September 2025
diff --git a/src/krux/pages/__init__.py b/src/krux/pages/__init__.py
index ac88f9b..b3c7f25 100644
--- a/src/krux/pages/__init__.py
+++ b/src/krux/pages/__init__.py
@@ -81,6 +81,10 @@ EXTRA_MNEMONIC_LENGTH_FLAG = 48
SWIPE_R_CHAR = "»"
SWIPE_L_CHAR = "«"
+BASE_DEC_SUFFIX = "DEC"
+BASE_HEX_SUFFIX = "HEX"
+BASE_OCT_SUFFIX = "OCT"
+
class Page:
"""Represents a page in the app, with helper methods for common display and
diff --git a/src/krux/pages/home_pages/mnemonic_backup.py b/src/krux/pages/home_pages/mnemonic_backup.py
index 582c13b..a0581ca 100644
--- a/src/krux/pages/home_pages/mnemonic_backup.py
+++ b/src/krux/pages/home_pages/mnemonic_backup.py
@@ -95,7 +95,9 @@ class MnemonicsView(Page):
def show_mnemonic(self, mnemonic, suffix="", display_mnemonic=None):
"""Displays only the mnemonic words or indexes"""
- self.display_mnemonic(mnemonic, suffix, display_mnemonic)
+ self.display_mnemonic(
+ mnemonic, suffix=suffix, display_mnemonic=display_mnemonic
+ )
self.ctx.input.wait_for_button()
# Avoid printing text on a cnc
@@ -115,6 +117,7 @@ class MnemonicsView(Page):
def display_mnemonic_numbers(self):
"""Handler for the 'numbers' menu item"""
from ..utils import Utils
+ from .. import BASE_DEC_SUFFIX, BASE_HEX_SUFFIX, BASE_OCT_SUFFIX
submenu = Menu(
self.ctx,
@@ -123,7 +126,7 @@ class MnemonicsView(Page):
t("Decimal"),
lambda: self.show_mnemonic(
self.ctx.wallet.key.mnemonic,
- Utils.BASE_DEC_SUFFIX,
+ BASE_DEC_SUFFIX,
Utils.get_mnemonic_numbers(
self.ctx.wallet.key.mnemonic, Utils.BASE_DEC
),
@@ -133,7 +136,7 @@ class MnemonicsView(Page):
t("Hexadecimal"),
lambda: self.show_mnemonic(
self.ctx.wallet.key.mnemonic,
- Utils.BASE_HEX_SUFFIX,
+ BASE_HEX_SUFFIX,
Utils.get_mnemonic_numbers(
self.ctx.wallet.key.mnemonic, Utils.BASE_HEX
),
@@ -143,7 +146,7 @@ class MnemonicsView(Page):
t("Octal"),
lambda: self.show_mnemonic(
self.ctx.wallet.key.mnemonic,
- Utils.BASE_OCT_SUFFIX,
+ BASE_OCT_SUFFIX,
Utils.get_mnemonic_numbers(
self.ctx.wallet.key.mnemonic, Utils.BASE_OCT
),
diff --git a/src/krux/pages/mnemonic_loader.py b/src/krux/pages/mnemonic_loader.py
index b2bd0e3..a0c09b8 100644
--- a/src/krux/pages/mnemonic_loader.py
+++ b/src/krux/pages/mnemonic_loader.py
@@ -29,6 +29,9 @@ from . import (
MENU_CONTINUE,
ESC_KEY,
LETTERS,
+ BASE_DEC_SUFFIX,
+ BASE_HEX_SUFFIX,
+ BASE_OCT_SUFFIX,
)
from ..display import BOTTOM_PROMPT_LINE
from ..qr import FORMAT_UR
@@ -468,15 +471,15 @@ class MnemonicLoader(Page):
DIGITS_OCT: Utils.BASE_OCT,
}
suffix_dict = {
- DIGITS: Utils.BASE_DEC_SUFFIX,
- DIGITS_HEX: Utils.BASE_HEX_SUFFIX,
- DIGITS_OCT: Utils.BASE_OCT_SUFFIX,
+ DIGITS: BASE_DEC_SUFFIX,
+ DIGITS_HEX: BASE_HEX_SUFFIX,
+ DIGITS_OCT: BASE_OCT_SUFFIX,
}
numbers_str = Utils.get_mnemonic_numbers(mnemonic, charset_type[charset])
self.display_mnemonic(
mnemonic,
- suffix_dict[charset],
- numbers_str,
+ suffix=suffix_dict[charset],
+ display_mnemonic=numbers_str,
fingerprint=Key.extract_fingerprint(mnemonic),
)
if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE):
diff --git a/src/krux/pages/print_page.py b/src/krux/pages/print_page.py
index 9ab0a92..81daeaa 100644
--- a/src/krux/pages/print_page.py
+++ b/src/krux/pages/print_page.py
@@ -73,23 +73,24 @@ class PrintPage(Page):
def print_mnemonic_text(self, mnemonic, suffix=""):
"""Prints Mnemonics words as text"""
+ from . import BASE_DEC_SUFFIX, BASE_HEX_SUFFIX, BASE_OCT_SUFFIX
+
self.ctx.display.clear()
self.ctx.display.draw_hcentered_text(
t("Printing…"), self.ctx.display.height() // 2
)
self.printer.print_string("BIP39" + " " + suffix + "\n\n")
words = mnemonic.split(" ")
- lines = len(words) // 3
+ cols = 3 if suffix in (BASE_DEC_SUFFIX, BASE_HEX_SUFFIX, BASE_OCT_SUFFIX) else 2
+ lines = len(words) // cols
for i in range(lines):
- index = i + 1
- string = str(index) + ":" + words[index - 1] + " "
- while len(string) < 10:
- string += " "
- index += lines
- string += str(index) + ":" + words[index - 1] + " "
- while len(string) < 21:
- string += " "
- index += lines
- string += str(index) + ":" + words[index - 1] + "\n"
- self.printer.print_string(string)
+ parts = []
+ for c in range(cols):
+ index = i + 1 + c * lines
+ part = str(index) + ":" + words[index - 1]
+ # pad all but last column
+ if c < cols - 1:
+ part = part + " " * (12 - len(part)) # fixed width of 12
+ parts.append(part)
+ self.printer.print_string("".join(parts) + "\n")
self.printer.feed(4)
diff --git a/src/krux/pages/utils.py b/src/krux/pages/utils.py
index 356bf66..73447e8 100644
--- a/src/krux/pages/utils.py
+++ b/src/krux/pages/utils.py
@@ -32,10 +32,6 @@ class Utils(Page):
BASE_HEX = 16
BASE_OCT = 8
- BASE_DEC_SUFFIX = "DEC"
- BASE_HEX_SUFFIX = "HEX"
- BASE_OCT_SUFFIX = "OCT"
-
def __init__(self, ctx):
super().__init__(ctx, None)
diff --git a/tests/pages/home_pages/test_mnemonic_backup.py b/tests/pages/home_pages/test_mnemonic_backup.py
index d654f0f..31ee7c9 100644
--- a/tests/pages/home_pages/test_mnemonic_backup.py
+++ b/tests/pages/home_pages/test_mnemonic_backup.py
@@ -89,7 +89,7 @@ def test_mnemonic_words(mocker, m5stickv, tdata):
mnemonics.mnemonic()
mnemonics.display_mnemonic.assert_called_with(
- ctx.wallet.key.mnemonic, "Mnemonic", None
+ ctx.wallet.key.mnemonic, suffix="Mnemonic", display_mnemonic=None
)
assert ctx.input.wait_for_button.call_count == len(case[2])
@@ -681,14 +681,18 @@ def test_print_mnemonic_other_words(mocker, amigo, tdata):
# brush badge sing still venue panther kitchen please help panel bundle excess sign couch stove increase human once effort candy goat top tiny major"
printer.print_string.assert_has_calls(
[
- mocker.call("1:brush 9:help 17:human\n"),
- mocker.call("2:badge 10:panel 18:once\n"),
- mocker.call("3:sing 11:bundle 19:effort\n"),
- mocker.call("4:still 12:excess 20:candy\n"),
- mocker.call("5:venue 13:sign 21:goat\n"),
- mocker.call("6:panther 14:couch 22:top\n"),
- mocker.call("7:kitchen 15:stove 23:tiny\n"),
- mocker.call("8:please 16:increase 24:major\n"),
+ mocker.call("1:brush 13:sign\n"),
+ mocker.call("2:badge 14:couch\n"),
+ mocker.call("3:sing 15:stove\n"),
+ mocker.call("4:still 16:increase\n"),
+ mocker.call("5:venue 17:human\n"),
+ mocker.call("6:panther 18:once\n"),
+ mocker.call("7:kitchen 19:effort\n"),
+ mocker.call("8:please 20:candy\n"),
+ mocker.call("9:help 21:goat\n"),
+ mocker.call("10:panel 22:top\n"),
+ mocker.call("11:bundle 23:tiny\n"),
+ mocker.call("12:excess 24:major\n"),
]
)
@@ -729,14 +733,14 @@ def test_print_mnemonic_numbers_decimal(mocker, amigo, tdata):
# brush badge sing still venue panther kitchen please help panel bundle excess sign couch stove increase human once effort candy goat top tiny major"
printer.print_string.assert_has_calls(
[
- mocker.call("1:234 9:857 17:887\n"),
- mocker.call("2:140 10:1277 18:1237\n"),
- mocker.call("3:1611 11:243 19:566\n"),
- mocker.call("4:1711 12:629 20:267\n"),
- mocker.call("5:1940 13:1603 21:801\n"),
- mocker.call("6:1279 14:392 22:1832\n"),
- mocker.call("7:985 15:1718 23:1812\n"),
- mocker.call("8:1332 16:918 24:1076\n"),
+ mocker.call("1:234 9:857 17:887\n"),
+ mocker.call("2:140 10:1277 18:1237\n"),
+ mocker.call("3:1611 11:243 19:566\n"),
+ mocker.call("4:1711 12:629 20:267\n"),
+ mocker.call("5:1940 13:1603 21:801\n"),
+ mocker.call("6:1279 14:392 22:1832\n"),
+ mocker.call("7:985 15:1718 23:1812\n"),
+ mocker.call("8:1332 16:918 24:1076\n"),
]
)
@@ -779,14 +783,14 @@ def test_print_mnemonic_numbers_hex(mocker, amigo, tdata):
# brush badge sing still venue panther kitchen please help panel bundle excess sign couch stove increase human once effort candy goat top tiny major"
printer.print_string.assert_has_calls(
[
- mocker.call("1:EA 9:359 17:377\n"),
- mocker.call("2:8C 10:4FD 18:4D5\n"),
- mocker.call("3:64B 11:F3 19:236\n"),
- mocker.call("4:6AF 12:275 20:10B\n"),
- mocker.call("5:794 13:643 21:321\n"),
- mocker.call("6:4FF 14:188 22:728\n"),
- mocker.call("7:3D9 15:6B6 23:714\n"),
- mocker.call("8:534 16:396 24:434\n"),
+ mocker.call("1:EA 9:359 17:377\n"),
+ mocker.call("2:8C 10:4FD 18:4D5\n"),
+ mocker.call("3:64B 11:F3 19:236\n"),
+ mocker.call("4:6AF 12:275 20:10B\n"),
+ mocker.call("5:794 13:643 21:321\n"),
+ mocker.call("6:4FF 14:188 22:728\n"),
+ mocker.call("7:3D9 15:6B6 23:714\n"),
+ mocker.call("8:534 16:396 24:434\n"),
]
)
@@ -829,14 +833,14 @@ def test_print_mnemonic_numbers_oct(mocker, amigo, tdata):
# brush badge sing still venue panther kitchen please help panel bundle excess sign couch stove increase human once effort candy goat top tiny major"
printer.print_string.assert_has_calls(
[
- mocker.call("1:352 9:1531 17:1567\n"),
- mocker.call("2:214 10:2375 18:2325\n"),
- mocker.call("3:3113 11:363 19:1066\n"),
- mocker.call("4:3257 12:1165 20:413\n"),
- mocker.call("5:3624 13:3103 21:1441\n"),
- mocker.call("6:2377 14:610 22:3450\n"),
- mocker.call("7:1731 15:3266 23:3424\n"),
- mocker.call("8:2464 16:1626 24:2064\n"),
+ mocker.call("1:352 9:1531 17:1567\n"),
+ mocker.call("2:214 10:2375 18:2325\n"),
+ mocker.call("3:3113 11:363 19:1066\n"),
+ mocker.call("4:3257 12:1165 20:413\n"),
+ mocker.call("5:3624 13:3103 21:1441\n"),
+ mocker.call("6:2377 14:610 22:3450\n"),
+ mocker.call("7:1731 15:3266 23:3424\n"),
+ mocker.call("8:2464 16:1626 24:2064\n"),
]
)
Why this scored 19/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.