new address format for UX display

(cherry picked from commit 1a18258b5a)
This commit is contained in:
scgbckbone 2025-01-28 11:42:22 +01:00
parent dafb475ee5
commit b2fc03a4da
23 changed files with 184 additions and 87 deletions

View File

@ -74,4 +74,9 @@ special_chars = dict(small=[
x x x x x
'''),
# thin space
('\u2009', dict(y=0, w=5), '''\
'''),
])

View File

@ -8,6 +8,7 @@ This lists the new changes that have not yet been published in a normal release.
- New Feature: Sign message from note text, or password note
- New Feature: Sign message with key resulting from positive ownership check. Press (0) + enter/scan message text
- New Feature: Sign message with key selected from Address Explorer Custom Path menu. Press (2) + enter/scan message text
- Enhancement: New address display format improves address verification on screen
- Enhancement: Hide Secure Notes & Passwords in Deltamode. Wipe seed if notes menu accessed.
- Enhancement: Hide Seed Vault in Deltamode. Wipe seed if Seed Vault menu accessed.
- Enhancement: Add ability to switch between BIP-32 xpub, and obsolete

View File

@ -17,6 +17,7 @@ from glob import settings
from auth import write_sig_file
from charcodes import KEY_QR, KEY_NFC, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_HOME, KEY_LEFT, KEY_RIGHT
from charcodes import KEY_CANCEL
from utils import show_single_address, problem_file_line
def truncate_address(addr):
# Truncates address to width of screen, replacing middle chars
@ -306,7 +307,7 @@ Press (3) if you really understand and accept these risks.
for idx, addr, deriv in main.yield_addresses(start, n, change if allow_change else None):
addrs.append(addr)
msg += "%s =>\n%s\n\n" % (deriv, addr)
msg += "%s =>\n%s\n\n" % (deriv, show_single_address(addr))
dis.progress_sofar(idx-start+1, n or 1)
# export options
@ -353,10 +354,10 @@ Press (3) if you really understand and accept these risks.
# continue on same screen in case they want to write to multiple cards
elif choice == KEY_QR:
# switch into a mode that shows them as QR codes
from ux import show_qr_codes
addr_fmt = addr_fmt or ms_wallet.addr_fmt
is_alnum = bool(addr_fmt & (AFC_BECH32 | AFC_BECH32M))
await show_qr_codes(addrs, is_alnum, start)
await show_qr_codes(addrs, is_alnum, start, is_addrs=True)
continue
elif NFC and (choice == KEY_NFC):
@ -480,7 +481,6 @@ async def make_address_summary_file(path, addr_fmt, ms_wallet, account_num,
await needs_microsd()
return
except Exception as e:
from utils import problem_file_line
await ux_show_story('Failed to write!\n\n\n%s\n%s' % (e, problem_file_line(e)))
return

View File

@ -16,7 +16,7 @@ from ux import ux_aborted, ux_show_story, abort_and_goto, ux_dramatic_pause, ux_
from ux import show_qr_code, OK, X, ux_input_text, ux_enter_bip32_index
from usb import CCBusyError
from utils import HexWriter, xfp2str, problem_file_line, cleanup_deriv_path
from utils import B2A, to_ascii_printable
from utils import B2A, to_ascii_printable, show_single_address
from psbt import psbtObject, FatalPSBTIssue, FraudulentChangeOutput
from files import CardSlot, CardMissingError, needs_microsd
from exceptions import HSMDenied
@ -372,14 +372,14 @@ class ApproveMessageSign(UserAuthorizedAction):
if hsm_active:
ch = await hsm_active.approve_msg_sign(self.text, self.address, self.subpath)
else:
story = MSG_SIG_TEMPLATE.format(msg=self.text, addr=self.address, subpath=self.subpath)
story = MSG_SIG_TEMPLATE.format(msg=self.text, addr=show_single_address(self.address),
subpath=self.subpath)
ch = await ux_show_story(story)
if ch != 'y':
# they don't want to!
self.refused = True
else:
# perform signing (progress bar shown)
digest = chains.current_chain().hash_message(self.text.encode())
self.result = sign_message_digest(digest, self.subpath, "Signing...", self.addr_fmt)[0]
@ -665,7 +665,7 @@ async def verify_armored_signed_msg(contents, digest_check=True):
title = "CORRECT"
warn_msg = ""
err_msg = ""
story = "Good signature by address:\n %s" % addr
story = "Good signature by address:\n%s" % show_single_address(addr)
if digest_check:
digest_prob = verify_signed_file_digest(msg)
@ -748,7 +748,7 @@ class ApproveTransaction(UserAuthorizedAction):
try:
dest = self.chain.render_address(o.scriptPubKey)
return '%s\n - to address -\n%s\n' % (val, dest)
return '%s\n - to address -\n%s\n' % (val, show_single_address(dest))
except ValueError:
pass
@ -1050,8 +1050,10 @@ class ApproveTransaction(UserAuthorizedAction):
msg = make_msg(start, n)
async def save_visualization(self, msg, sign_text=False):
# write text into spi flash, maybe signing it as we go
# write story text out, maybe signing it as we go
# - return length and checksum
from charcodes import OUT_CTRL_ADDRESS
txt_len = msg.seek(0, 2)
msg.seek(0)
@ -1059,7 +1061,8 @@ class ApproveTransaction(UserAuthorizedAction):
with SFFile(TXN_OUTPUT_OFFSET, max_size=txt_len+300, message="Visualizing...") as fd:
while 1:
blk = msg.read(256).encode('ascii')
# replace with empty space, to keep correct txt_len - already hashed
blk = msg.read(256).replace(OUT_CTRL_ADDRESS, ' ').encode('ascii')
if not blk: break
if chk:
chk.update(blk)
@ -1072,7 +1075,7 @@ class ApproveTransaction(UserAuthorizedAction):
fd.write(b2a_base64(sig).decode('ascii').strip())
fd.write('\n')
return (fd.tell(), fd.checksum.digest())
return fd.tell(), fd.checksum.digest()
def output_summary_text(self, msg):
# Produce text report of where their cash is going. This is what
@ -1150,13 +1153,13 @@ class ApproveTransaction(UserAuthorizedAction):
visible_change_sum = 0
if len(largest_change) == 1:
visible_change_sum += largest_change[0][0]
msg.write(' - to address -\n%s\n' % largest_change[0][1])
msg.write(' - to address -\n%s\n' % show_single_address(largest_change[0][1]))
else:
msg.write(' - to addresses -\n')
for val, addr in largest_change:
visible_change_sum += val
msg.write(addr)
msg.write('\n')
msg.write(show_single_address(addr))
msg.write('\n\n')
left_c = self.psbt.num_change_outputs - len(largest_change)
if left_c:
@ -1543,7 +1546,8 @@ class ShowPKHAddress(ShowAddressBase):
self.address = sv.chain.address(node, addr_fmt)
def get_msg(self):
return '''{addr}\n\n= {sp}''' .format(addr=self.address, sp=self.subpath)
return '''{addr}\n\n= {sp}''' .format(addr=show_single_address(self.address),
sp=self.subpath)
class ShowP2SHAddress(ShowAddressBase):
@ -1570,8 +1574,8 @@ Wallet:
Paths:
{sp}'''.format(addr=self.address, name=self.ms.name,
M=self.ms.M, N=self.ms.N, sp='\n\n'.join(self.subpath_help))
{sp}'''.format(addr=show_single_address(self.address), name=self.ms.name,
M=self.ms.M, N=self.ms.N, sp='\n\n'.join(self.subpath_help))
class ShowMiniscriptAddress(ShowAddressBase):

View File

@ -107,4 +107,9 @@ if has_qwerty:
assert DECODER[KEYNUM_SYMBOL] == KEY_SYMBOL
assert DECODER[KEYNUM_LAMP] == KEY_LAMP
# These affect how 'ux stories' are rendered; they are control
# characters on the output side of things, not input.
OUT_CTRL_TITLE = '\x01' # must be first char in line: be a title line
OUT_CTRL_ADDRESS = '\x02' # must be first char in line: it's a payment address
# EOF

View File

@ -7,6 +7,7 @@ from ssd1306 import SSD1306_SPI
from version import is_devmode, is_edge
import framebuf
from graphics_mk4 import Graphics
from charcodes import OUT_CTRL_TITLE, OUT_CTRL_ADDRESS
# we support 4 fonts
from zevvpeep import FontSmall, FontLarge, FontTiny
@ -310,9 +311,14 @@ class Display:
for ln in lines:
if ln == 'EOT':
self.hline(y+3)
elif ln and ln[0] == '\x01':
elif ln and ln[0] == OUT_CTRL_TITLE:
self.text(0, y, ln[1:], FontLarge)
y += 21
elif ln and ln[0] == OUT_CTRL_ADDRESS:
from utils import chunk_address
fmt = '\u2009'.join(chunk_address(ln[1:]))
self.text(14, y, fmt) # fixed indent, to be centered
y += 15 # a bit extra vertical line height
else:
self.text(0, y, ln)
@ -328,9 +334,10 @@ class Display:
# no status bar on Mk4
return
def draw_qr_display(self, qr_data, msg, is_alnum, sidebar, idx_hint, invert):
def draw_qr_display(self, qr_data, msg, is_alnum, sidebar, idx_hint, invert, is_addr=False):
# 'sidebar' is a pre-formated obj to show to right of QR -- oled life
# - 'msg' will appear to right if very short, else under in tiny
# - ignores "is_addr" because exactly zero space to do anything special
from utils import word_wrap
self.clear()

View File

@ -3,11 +3,11 @@
# lcd_display.py - LCD rendering for Q1's 320x240 pixel *colour* display!
#
import machine, uzlib, utime, array
from uasyncio import sleep_ms
from graphics_q1 import Graphics
from st7788 import ST7788
from utils import xfp2str, word_wrap
from utils import xfp2str, word_wrap, chunk_address
from ucollections import namedtuple
from charcodes import OUT_CTRL_TITLE, OUT_CTRL_ADDRESS
# the one font: fixed-width (except for a few double-width chars)
from font_iosevka import CELL_W, CELL_H, TEXT_PALETTES, COL_TEXT, COL_DARK_TEXT, COL_SCROLL_DARK
@ -612,25 +612,50 @@ class Display:
self.clear()
y=0
prev_x = None
for ln in lines:
if ln == 'EOT':
self.text(0, y, ''*CHARS_W, dark=True)
continue
elif ln and ln[0] == '\x01':
elif ln and ln[0] == OUT_CTRL_TITLE:
# title ... but we have no special font? Inverse!
self.text(0, y, ' '+ln[1:]+' ', invert=True)
if hint_icons:
# maybe show that [QR] can do something
self.text(-1, y, hint_icons, dark=True)
elif ln and ln[0] == OUT_CTRL_ADDRESS:
# we can assume this will be a single line for our display
# thanks to code in utils.word_wrap
prev_x = self._draw_addr(y, ln[1:], prev_x=prev_x)
else:
self.text(0, y, ln)
prev_x = None
y += 1
self.scroll_bar(top, num_lines, CHARS_H)
self.show()
def draw_qr_display(self, qr_data, msg, is_alnum, sidebar, idx_hint, invert, partial_bar=None):
def _draw_addr(self, y, addr, prev_x=None):
# Draw a single-line of an address
# - use prev_x=0 to start centered
if prev_x is None:
# left justify (for stories)
prev_x = x = 1
elif prev_x == 0:
# center first line, following line will be left-justified to match that
prev_x = x = max(((CHARS_W - (len(addr) * 5) // 4) // 2), 0)
else:
x = prev_x
self.text(x, y, ' '+' '.join(chunk_address(addr))+' ', invert=1)
return prev_x
def draw_qr_display(self, qr_data, msg, is_alnum, sidebar, idx_hint, invert, partial_bar=None, is_addr=False):
# Show a QR code on screen w/ some text under it
# - invert not supported on Q1
# - sidebar not supported here (see users.py)
@ -643,9 +668,11 @@ class Display:
parts = [msg]
elif ' ' not in msg and (len(msg) <= CHARS_W*2):
# fits in two lines, but has no spaces (ie. payment addr)
# so split nicely, and shift off center
# - so split nicely in middle and/or at mod4 if address
hh = len(msg) // 2
parts = [msg[0:hh] + ' ', ' '+msg[hh:]]
if is_addr:
hh = (hh + 3) & ~0x3
parts = [msg[0:hh], msg[hh:]]
else:
# do word wrap
parts = list(word_wrap(msg, CHARS_W))
@ -723,8 +750,12 @@ class Display:
if num_lines:
# centered text under that
y = CHARS_H - num_lines
prev_x = 0
for line in parts:
self.text(None, y, line)
if not is_addr:
self.text(None, y, line)
else:
prev_x = self._draw_addr(y, line, prev_x=prev_x)
y += 1
if idx_hint:

View File

@ -77,6 +77,7 @@ from utils import call_later_ms
# cd_pin = [<=mk3] pin code which enables "countdown to brick" mode
# kbtn = (1 char str) button will wipe seed during login process (mk4+, Q)
# terms_ok = customer has signed-off on the terms of sale
# msas = multisig address show (do not censor multisig addresses)
# settings linked to seed
# LINKED_SETTINGS = ["multisig","miniscript", "tp", "ovc", "xfp", "xpub", "words"]

View File

@ -7,8 +7,8 @@ from glob import settings
from ucollections import namedtuple
from ubinascii import hexlify as b2a_hex
from exceptions import UnknownAddressExplained
from utils import problem_file_line, show_single_address
from public_constants import AFC_SCRIPT, AF_P2WPKH_P2SH, AF_P2SH, AF_P2WSH_P2SH, AF_P2TR
from utils import problem_file_line
# Track many addresses, but in compressed form
# - map from random Bech32/Base58 payment address to (wallet) + keypath
@ -324,7 +324,7 @@ class OwnershipCache:
is_complex = isinstance(wallet, MultisigWallet) or isinstance(wallet, MiniScriptWallet)
sp = None
msg = addr
msg = show_single_address(addr)
msg += '\n\nFound in wallet:\n ' + wallet.name
if hasattr(wallet, "render_path"):
sp = wallet.render_path(*subpath)
@ -358,7 +358,7 @@ class OwnershipCache:
break
except UnknownAddressExplained as exc:
await ux_show_story(addr + '\n\n' + str(exc), title="Unknown Address")
await ux_show_story(show_single_address(addr) + '\n\n' + str(exc), title="Unknown Address")
except Exception as e:
await ux_show_story('Ownership search failed.\n\n%s\n%s' % (e, problem_file_line(e)))

View File

@ -17,13 +17,14 @@ MAX_V11_CHAR_LIMIT = const(321)
class QRDisplaySingle(UserInteraction):
# Show a single QR code for (typically) a list of addresses, or a single value.
def __init__(self, addrs, is_alnum, start_n=0, sidebar=None, msg=None):
def __init__(self, addrs, is_alnum, start_n=0, sidebar=None, msg=None, is_addrs=False):
self.is_alnum = is_alnum
self.idx = 0 # start with first address
self.invert = False # looks better, but neither mode is ideal
self.addrs = addrs
self.sidebar = sidebar
self.start_n = start_n
self.is_addrs = is_addrs
self.msg = msg
self.qr_data = None
@ -73,7 +74,7 @@ class QRDisplaySingle(UserInteraction):
# draw display
dis.busy_bar(False)
dis.draw_qr_display(self.qr_data, self.msg or body, self.is_alnum,
self.sidebar, self.idx_hint(), self.invert)
self.sidebar, self.idx_hint(), self.invert, is_addr=self.is_addrs)
async def interact_bare(self):
from glob import NFC, dis

View File

@ -6,6 +6,7 @@ import gc, sys, ustruct, ngu, chains, ure, time, version, uos, uio, bip39
from ubinascii import unhexlify as a2b_hex
from ubinascii import hexlify as b2a_hex
from ubinascii import a2b_base64, b2a_base64
from charcodes import OUT_CTRL_ADDRESS
from uhashlib import sha256
from public_constants import AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR, MAX_PATH_DEPTH
from public_constants import AF_P2WSH, AF_P2WSH_P2SH
@ -495,11 +496,26 @@ def word_wrap(ln, w):
return
while ln:
# find a space in (width) first part of remainder
sp = ln.rfind(' ', 0, w-1)
if sp == -1:
if ln[0] == OUT_CTRL_ADDRESS:
# special handling for lines w/ payment address in them
# - add same marker to newly split lines
addr = ln[1:]
if version.has_qwerty:
# - do line break in middle, on mod4 boundry
# - will not work if addresses are > 2 lines long (34*2 chars)
aw = ((len(addr) // 2) + 3) & ~3
else:
# - simply 3 4-char groups on Mk4
aw = 12
pos = 0
while pos < len(addr):
yield OUT_CTRL_ADDRESS + addr[pos:pos+aw]
pos += aw
return
# bad-break the line
sp = min(txtlen(ln), w)
nsp = sp
@ -799,4 +815,13 @@ def truncate_address(addr):
def encode_seed_qr(words):
return ''.join('%04d' % bip39.get_word_index(w) for w in words)
def show_single_address(addr):
# insert some metadata so display layer can do special rendering
# of addresses (based on hardware capabilities)
return OUT_CTRL_ADDRESS + addr
def chunk_address(addr):
# useful to show payment addresses specially
return [addr[i:i+4] for i in range(0, len(addr), 4)]
# EOF

View File

@ -7,7 +7,8 @@ from queues import QueueEmpty
import utime, gc, version
from utils import word_wrap
from charcodes import (KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_HOME, KEY_NFC, KEY_QR,
KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_ENTER, KEY_CANCEL)
KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_ENTER, KEY_CANCEL, OUT_CTRL_TITLE)
from exceptions import AbortInteraction
DEFAULT_IDLE_TIMEOUT = const(4*3600) # (seconds) 4 hours
@ -181,8 +182,8 @@ async def ux_show_story(msg, title=None, escape=None, sensitive=False,
lines = []
if title:
# kinda weak rendering but it works.
lines.append('\x01' + title)
# render the title line specially, see display/lcd_display.py
lines.append(OUT_CTRL_TITLE + title)
if version.has_qwerty:
# big screen always needs blank after title
@ -321,14 +322,14 @@ def abort_and_push(m):
the_ux.push(m)
numpad.abort_ux()
async def show_qr_codes(addrs, is_alnum, start_n):
async def show_qr_codes(addrs, is_alnum, start_n, **kw):
from qrs import QRDisplaySingle
o = QRDisplaySingle(addrs, is_alnum, start_n, sidebar=None)
o = QRDisplaySingle(addrs, is_alnum, start_n, **kw)
await o.interact_bare()
async def show_qr_code(data, is_alnum=False, msg=None):
async def show_qr_code(data, is_alnum=False, msg=None, **kw):
from qrs import QRDisplaySingle
o = QRDisplaySingle([data], is_alnum, msg=msg)
o = QRDisplaySingle([data], is_alnum, msg=msg, **kw)
await o.interact_bare()
async def ux_enter_bip32_index(prompt, can_cancel=False, unlimited=False):

View File

@ -14,7 +14,7 @@ from ubinascii import hexlify as b2a_hex
from ubinascii import unhexlify as a2b_hex
from ubinascii import b2a_base64
from utils import problem_file_line
from utils import problem_file_line, show_single_address
from public_constants import MSG_SIGNING_MAX_LENGTH
from glob import numpad # may be None depending on import order, careful
@ -1085,7 +1085,7 @@ async def ux_visualize_bip21(proto, addr, args):
# - validate address ownership on request
from ux import ux_show_story
msg = addr + '\n\n'
msg = show_single_address(addr) + '\n\n'
args = args or {}
if 'amount' in args:

View File

@ -36,8 +36,8 @@ class FontSmall(FontBase):
_bboxes = [None, (0, -3, 7, 14, 0), (0, -3, 7, 14, 4), (0, -3, 7,
14, 5), (0, -3, 7, 14, 7), (0, -3, 7, 14, 9), (0, -3, 7, 14, 10),
(0, -3, 7, 14, 11), (0, -3, 7, 14, 12), (0, -3, 7, 14, 13), (0, -3,
7, 14, 14), (0, 0, 8, 8, 8), (0, 0, 11, 8, 16), (0, 0, 11, 9, 18),
(0, 0, 14, 10, 20)]
7, 14, 14), (0, 0, 5, 2, 2), (0, 0, 8, 8, 8), (0, 0, 11, 8, 16), (0,
0, 11, 9, 18), (0, 0, 14, 10, 20)]
_code_points = [
(range(32, 127), [1, 2, 14, 20, 31, 43, 55, 67, 73, 87, 101, 111, 122,
@ -48,11 +48,11 @@ class FontSmall(FontBase):
755, 767, 779, 791, 803, 815, 827, 842, 854, 866, 880, 892, 904,
916, 928, 940, 954, 968, 980, 992, 1004, 1016, 1028, 1040, 1052,
1067, 1079, 1093, 1106, 1120]),
(range(8226, 8227), [1126]), #
(range(8592, 8593), [1145]), # ←
(range(8594, 8595), [1166]), #
(range(8627, 8628), [1187]), # ↳
(range(8943, 8944), [1208]), # ⋯
(range(8201, 8202), [1126]), #
(range(8226, 8227), [1129]), # •
(range(8592, 8595), [1148, 0, 1169]), # ←
(range(8627, 8628), [1190]), # ↳
(range(8943, 8944), [1211]), # ⋯
]
_bitmaps = b"""\
@ -63,7 +63,7 @@ class FontSmall(FontBase):
\x10\x09\x04\x08\x10\x10\x20\x20\x20\x20\x20\x10\x10\x08\x04\x09\x20\x10\
\x08\x08\x04\x04\x04\x04\x04\x08\x08\x10\x20\x05\x00\x00\x00\x00\x24\x18\
\x7e\x18\x24\x06\x00\x00\x00\x08\x08\x08\x3e\x08\x08\x08\x09\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x18\x30\x20\x40\x0b\x00\x00\x00\x00\x00\x00\x3e\
\x00\x00\x00\x00\x00\x00\x18\x30\x20\x40\x0c\x00\x00\x00\x00\x00\x00\x3e\
\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x38\x10\x08\x02\x02\x04\
\x04\x08\x08\x10\x10\x20\x20\x40\x40\x07\x00\x18\x24\x42\x42\x4a\x52\x42\
\x42\x24\x18\x07\x00\x08\x18\x28\x48\x08\x08\x08\x08\x08\x08\x07\x00\x3c\
@ -118,13 +118,13 @@ class FontSmall(FontBase):
\x3a\x02\x02\x42\x3c\x07\x00\x00\x00\x00\x7e\x02\x04\x08\x10\x20\x7e\x09\
\x06\x08\x08\x08\x08\x08\x30\x08\x08\x08\x08\x08\x06\x08\x10\x10\x10\x10\
\x10\x10\x10\x10\x10\x10\x10\x10\x09\x60\x10\x10\x10\x10\x10\x0c\x10\x10\
\x10\x10\x10\x60\x03\x00\x00\x32\x4a\x44\x0d\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x03\x80\x03\x80\x03\x80\x00\x00\x0e\x00\x00\x00\x00\x00\x00\
\x00\x00\x08\x00\x18\x00\x3f\xf8\x18\x00\x08\x00\x00\x00\x0e\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x20\x00\x30\x3f\xf8\x00\x30\x00\x20\x00\x00\x0e\
\x00\x00\x10\x00\x10\x00\x10\x00\x10\x20\x10\x30\x1f\xf8\x00\x30\x00\x20\
\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x2a\xa0\x00\
\x00\
\x10\x10\x10\x60\x03\x00\x00\x32\x4a\x44\x0b\x00\x00\x0e\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x03\x80\x03\x80\x03\x80\x00\x00\x0f\x00\x00\x00\
\x00\x00\x00\x00\x00\x08\x00\x18\x00\x3f\xf8\x18\x00\x08\x00\x00\x00\x0f\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x30\x3f\xf8\x00\x30\x00\x20\
\x00\x00\x0f\x00\x00\x10\x00\x10\x00\x10\x00\x10\x20\x10\x30\x1f\xf8\x00\
\x30\x00\x20\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x2a\xa0\x00\x00\
"""

View File

@ -3,7 +3,7 @@
import pytest, time, sys, random, re, ndef, os, glob, hashlib, json, functools, io, math, bech32, pdb
from subprocess import check_output
from ckcc.protocol import CCProtocolPacker
from helpers import B2A, U2SAT, hash160, taptweak
from helpers import B2A, U2SAT, hash160, taptweak, addr_from_display_format
from base58 import decode_base58_checksum
from bip32 import BIP32Node
from msg import verify_message
@ -2159,6 +2159,7 @@ def txout_explorer(cap_story, press_cancel, need_keypress, is_q1):
assert f"Output {i}:" == sa
txt_amount, _, addr = sb.split("\n")
addr = addr_from_display_format(addr)
assert txt_amount == f'{amount / 100000000:.8f} {chain}'
if af == "p2pkh":
if chain == "BTC":

View File

@ -35,7 +35,10 @@ if 'destinations' in expect:
for (val, addr), (idx, txo) in zip(expect['destinations'], p.output_iter()):
assert val == txo.nValue
txt = active_request.render_output(txo)
assert addr in txt
# normalize from display format
address = txt.split("\n")[-2]
assert address[0] == "\x02"
assert addr == address[1:]
assert '%.8f'%(val/1E8) in txt
if 'sw_inputs' in expect:

View File

@ -103,8 +103,11 @@ def xfp2str(xfp):
from struct import pack
return b2a_hex(pack('<I', xfp)).decode('ascii').upper()
def parse_change_back(story):
def addr_from_display_format(dis_addr):
assert dis_addr[0] == '\x02' # OUT_CTRL_ADDRESS
return dis_addr[1:]
def parse_change_back(story):
lines = story.split('\n')
s = lines.index('Change back:')
assert s > 3
@ -113,8 +116,12 @@ def parse_change_back(story):
assert 'address' in lines[s+2]
addrs = []
for y in range(s+3, len(lines)):
if not lines[y]: break
addrs.append(lines[y])
line = lines[y].strip()
if line:
if line[0] == "\x02":
addrs.append(addr_from_display_format(line))
if line.startswith(("3","2","1","m","n","tb1","bc1","bcrt")):
addrs.append(line)
if len(addrs) >= 2:
assert 'to addresses' in lines[s+2]

View File

@ -10,6 +10,7 @@ from ckcc_protocol.protocol import CCProtocolPacker
from ckcc_protocol.constants import *
from charcodes import KEY_QR
from constants import msg_sign_unmap_addr_fmt
from helpers import addr_from_display_format
@pytest.mark.parametrize('path', [ 'm', "m/1/2", "m/1'/100'"])
@pytest.mark.parametrize('addr_fmt', [ AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR ])
@ -45,8 +46,7 @@ def test_show_addr_displayed(dev, need_keypress, addr_vs_path, path, addr_fmt,
else:
assert path in story
assert addr in story
assert addr in story.split('\n')
assert addr in addr_from_display_format(story.split("\n\n")[0])
# check expected addr was used
addr_vs_path(addr, path, addr_fmt)
@ -137,7 +137,7 @@ def test_show_addr_nfc(path, str_addr_fmt, nfc_write_text, nfc_read_text, pick_m
_, story = cap_story()
split_story = story.split("\n\n")
story_addr = split_story[0]
story_addr = addr_from_display_format(split_story[0])
story_path = split_story[1][2:] # remove "= "
if not is_q1:
assert "Press (3) to share via NFC" in story

View File

@ -8,7 +8,7 @@ import pytest, time, io, csv, bech32
from ckcc_protocol.constants import *
from bip32 import BIP32Node
from base58 import decode_base58_checksum
from helpers import detruncate_address, hash160
from helpers import detruncate_address, hash160, addr_from_display_format
from charcodes import KEY_QR, KEY_LEFT, KEY_RIGHT
from constants import MAX_BIP32_IDX
@ -53,7 +53,7 @@ def parse_display_screen(cap_story, is_mark3):
d = dict()
for path_raw, addr, empty in zip(*[iter(raw_addrs)]*3):
path = path_raw.split(" =>")[0]
d[path] = addr
d[path] = addr_from_display_format(addr)
assert len(d) == n
return d
return doit
@ -469,7 +469,7 @@ def test_custom_path(path_sidx, which_fmt, addr_vs_path, pick_menu_item, goto_ad
assert 'Showing single addr' in body
assert path in body
addr = body.split("\n")[3]
addr = addr_from_display_format(body.split("\n")[3])
addr_vs_path(addr, path, addr_fmt=which_fmt)

View File

@ -10,6 +10,7 @@ from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError, CCUserRefused
from ckcc_protocol.constants import *
from constants import addr_fmt_names, msg_sign_unmap_addr_fmt
from charcodes import KEY_QR, KEY_NFC
from helpers import addr_from_display_format
def default_derivation_by_af(addr_fmt, testnet=True):
@ -77,7 +78,7 @@ def verify_msg_sign_story():
assert 'Using the key associated' in story
if addr:
assert addr in story
assert addr == addr_from_display_format(story.split("\n\n")[2].split("\n")[-1])
if not subpath:
assert 'm =>' not in story
@ -648,7 +649,7 @@ def test_verify_signature_file(way, addr_fmt, path, msg, sign_on_microsd, goto_h
title, story = verify_armored_signature(way, fname, should)
assert title == "CORRECT"
assert "Good signature" in story
assert addr in story
assert addr == addr_from_display_format(story.split("\n")[-1])
if (addr_fmt == "p2pkh") and (chain != "BTC"):
res = bitcoind.rpc.verifymessage(addr, sig, msg)
assert res is True
@ -999,5 +1000,6 @@ def test_verify_scanned_signed_msg(msg, scan_a_qr, need_keypress, goto_home, cap
title, story = cap_story()
assert title == "CORRECT"
assert "Good signature by address" in story
assert addr == addr_from_display_format(story.split("\n")[-1])
# EOF

View File

@ -12,7 +12,7 @@ from ckcc.protocol import CCProtocolPacker, MAX_TXN_LEN
from pprint import pprint
from base64 import b64encode, b64decode
from base58 import encode_base58_checksum
from helpers import B2A, fake_dest_addr, xfp2str
from helpers import B2A, fake_dest_addr, xfp2str, addr_from_display_format
from helpers import path_to_str, str_to_path, slip132undo, swab32, hash160
from struct import unpack, pack
from constants import *
@ -519,7 +519,7 @@ def test_ms_show_addr(dev, cap_story, press_select, addr_vs_path, bitcoind_p2sh,
#print(story)
if not has_ms_checks:
assert got_addr in story
assert got_addr == addr_from_display_format(story.split("\n\n")[0])
assert all((xfp2str(xfp) in story) for xfp,_,_ in keys)
if bip45:
for i in range(len(keys)):
@ -2184,7 +2184,7 @@ def test_ms_addr_explorer(change, M_N, addr_fmt, start_idx, clear_ms, cap_menu,
for ln in story.split('\n'):
if '=>' not in ln: continue
path,chk,addr = ln.split()
path,chk,addr = ln.split(" ", 2)
assert chk == '=>'
assert '/' in path
path = path.replace("[", "").replace("]", "")

View File

@ -5,7 +5,7 @@
import pytest, time, io, csv, json
from txn import fake_address
from base58 import encode_base58_checksum
from helpers import hash160, taptweak
from helpers import hash160, taptweak, addr_from_display_format
from bip32 import BIP32Node
from constants import AF_P2WSH, AF_P2SH, AF_P2WSH_P2SH, AF_CLASSIC, AF_P2WPKH, AF_P2WPKH_P2SH, AF_P2TR
from constants import simulator_fixed_xprv, simulator_fixed_tprv, addr_fmt_names
@ -208,7 +208,7 @@ def test_ux(valid, testnet, method,
title, story = cap_story()
assert addr in story
assert addr == addr_from_display_format(story.split("\n\n")[0])
assert '(1) to verify ownership' in story
need_keypress('1')
@ -233,8 +233,7 @@ def test_ux(valid, testnet, method,
time.sleep(1)
title, story = cap_story()
assert addr in story
assert addr == addr_from_display_format(story.split("\n\n")[0])
if title == 'Unknown Address' and not testnet:
assert 'That address is not valid on Bitcoin Testnet' in story
@ -322,7 +321,7 @@ def test_address_explorer_saver(af, wipe_cache, settings_set, goto_address_explo
time.sleep(1)
title, story = cap_story()
assert addr in story
assert addr == addr_from_display_format(story.split("\n\n")[0])
assert title == 'Verified Address'
assert 'Found in wallet' in story
assert 'Derivation path' in story

View File

@ -4,22 +4,22 @@
#
import time, pytest, os, random, pdb, struct, base64, binascii, itertools, datetime
from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError, MAX_TXN_LEN, CCUserRefused
from ckcc_protocol.protocol import CCProtocolPacker, CCProtoError
from binascii import b2a_hex, a2b_hex
from psbt import BasicPSBT, BasicPSBTInput, BasicPSBTOutput, PSBT_IN_REDEEM_SCRIPT
from io import BytesIO
from pprint import pprint, pformat
from pprint import pprint
from decimal import Decimal
from base64 import b64encode, b64decode
from base58 import encode_base58_checksum
from helpers import B2A, fake_dest_addr, parse_change_back
from helpers import B2A, fake_dest_addr, parse_change_back, addr_from_display_format
from helpers import xfp2str, seconds2human_readable, hash160
from msg import verify_message
from bip32 import BIP32Node
from constants import ADDR_STYLES, ADDR_STYLES_SINGLE, SIGHASH_MAP, simulator_fixed_tpub
from constants import ADDR_STYLES, ADDR_STYLES_SINGLE, SIGHASH_MAP
from txn import *
from ctransaction import CTransaction, CTxOut, CTxIn, COutPoint
from ckcc_protocol.constants import STXN_FINALIZE, STXN_VISUALIZE, STXN_SIGNED
from ckcc_protocol.constants import STXN_VISUALIZE, STXN_SIGNED
from charcodes import KEY_QR, KEY_RIGHT
@ -560,7 +560,9 @@ def test_change_case(start_sign, use_regtest, end_sign, check_against_bitcoind,
time.sleep(.1)
_, story = cap_story()
assert chg_addr in story
split_sory = story.split("\n\n")[3].split("\n")
assert split_sory[0] == "Change back:"
assert chg_addr == addr_from_display_format(split_sory[-1])
b4 = BasicPSBT().parse(psbt)
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[1,])
@ -630,7 +632,7 @@ def test_change_fraud_path(start_sign, use_regtest, end_sign, case, check_agains
time.sleep(.1)
_, story = cap_story()
assert chg_addr in story
assert chg_addr == addr_from_display_format(story.split("\n\n")[3].split("\n")[-1])
assert 'Change back:' not in story
end_sign(True)
@ -738,7 +740,9 @@ def test_change_p2sh_p2wpkh(start_sign, end_sign, check_against_bitcoind, use_re
check_against_bitcoind(B2A(b4.txn), Decimal('0.00000294'), change_outs=[1,],
dests=[(1, expect_addr)])
assert expect_addr in story
split_sory = story.split("\n\n")[3].split("\n")
assert split_sory[0] == "Change back:"
assert expect_addr == addr_from_display_format(split_sory[-1])
assert parse_change_back(story) == (Decimal('1.09997082'), [expect_addr])
end_sign(True)