new address format for UX display
This commit is contained in:
parent
38c92ef0c1
commit
1a18258b5a
@ -74,4 +74,9 @@ special_chars = dict(small=[
|
||||
x x x x x
|
||||
'''),
|
||||
|
||||
# thin space
|
||||
('\u2009', dict(y=0, w=5), '''\
|
||||
|
||||
'''),
|
||||
|
||||
])
|
||||
|
||||
@ -9,6 +9,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
|
||||
|
||||
@ -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
|
||||
@ -315,7 +316,7 @@ Press (3) if you really understand and accept these risks.
|
||||
else:
|
||||
msg += '⋯/%d/%d =>\n' % (change, idx)
|
||||
|
||||
msg += addr + '\n\n'
|
||||
msg += show_single_address(addr) + '\n\n'
|
||||
dis.progress_sofar(idx-start+1, n)
|
||||
|
||||
else:
|
||||
@ -328,7 +329,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
|
||||
@ -382,7 +383,7 @@ Press (3) if you really understand and accept these risks.
|
||||
if allow_qr:
|
||||
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
|
||||
|
||||
@ -522,7 +523,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
|
||||
|
||||
|
||||
@ -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
|
||||
@ -368,14 +368,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]
|
||||
@ -661,7 +661,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)
|
||||
@ -744,7 +744,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
|
||||
|
||||
@ -1046,8 +1046,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)
|
||||
|
||||
@ -1055,7 +1057,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)
|
||||
@ -1068,7 +1071,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
|
||||
@ -1146,13 +1149,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:
|
||||
@ -1539,7 +1542,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):
|
||||
@ -1566,8 +1570,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))
|
||||
|
||||
def start_show_p2sh_address(M, N, addr_format, xfp_paths, witdeem_script):
|
||||
# Show P2SH address to user, also returns it.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -7,6 +7,7 @@ from ssd1306 import SSD1306_SPI
|
||||
from version import is_devmode
|
||||
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
|
||||
@ -304,9 +305,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)
|
||||
|
||||
@ -322,9 +328,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()
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -22,7 +22,6 @@ TRUST_VERIFY = const(0)
|
||||
TRUST_OFFER = const(1)
|
||||
TRUST_PSBT = const(2)
|
||||
|
||||
|
||||
class MultisigOutOfSpace(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@ -76,6 +76,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", "tp", "ovc", "xfp", "xpub", "words"]
|
||||
|
||||
@ -7,7 +7,7 @@ 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
|
||||
from utils import problem_file_line, show_single_address
|
||||
|
||||
# Track many addresses, but in compressed form
|
||||
# - map from random Bech32/Base58 payment address to (wallet) + keypath
|
||||
@ -315,7 +315,7 @@ class OwnershipCache:
|
||||
is_ms = isinstance(wallet, MultisigWallet)
|
||||
sp = wallet.render_path(*subpath)
|
||||
|
||||
msg = addr
|
||||
msg = show_single_address(addr)
|
||||
msg += '\n\nFound in wallet:\n ' + wallet.name
|
||||
msg += '\nDerivation path:\n ' + sp
|
||||
if is_ms:
|
||||
@ -346,7 +346,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)))
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
#
|
||||
# utils.py - Misc utils. My favourite kind of source file.
|
||||
#
|
||||
import gc, sys, ustruct, ngu, chains, ure, time, bip39
|
||||
import gc, sys, ustruct, ngu, chains, ure, time, bip39, version
|
||||
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
|
||||
|
||||
B2A = lambda x: str(b2a_hex(x), 'ascii')
|
||||
@ -481,11 +482,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
|
||||
@ -502,6 +518,9 @@ def word_wrap(ln, w):
|
||||
# not clear when this would happen? final bit??
|
||||
left = left + ' ' + ln
|
||||
ln = ''
|
||||
else:
|
||||
if ln and ln[0] == ' ' and version.has_qwerty:
|
||||
ln = " " + ln
|
||||
|
||||
yield left
|
||||
|
||||
@ -669,4 +688,13 @@ def decode_bip21_text(got):
|
||||
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
|
||||
|
||||
15
shared/ux.py
15
shared/ux.py
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1084,7 +1084,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:
|
||||
|
||||
@ -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\
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import pytest, time, sys, random, re, ndef, os, glob, hashlib, json, functools, io, math, pdb
|
||||
from subprocess import check_output
|
||||
from ckcc.protocol import CCProtocolPacker
|
||||
from helpers import B2A, U2SAT, hash160
|
||||
from helpers import B2A, U2SAT, hash160, addr_from_display_format
|
||||
from base58 import decode_base58_checksum
|
||||
from bip32 import BIP32Node
|
||||
from msg import verify_message
|
||||
@ -2145,6 +2145,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":
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -101,8 +101,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
|
||||
@ -111,8 +114,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]
|
||||
|
||||
@ -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 ])
|
||||
@ -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)
|
||||
@ -122,7 +122,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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -52,7 +52,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
|
||||
@ -474,7 +474,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)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -15,7 +15,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)):
|
||||
@ -2222,7 +2222,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
|
||||
|
||||
@ -2259,6 +2259,7 @@ def test_ms_addr_explorer(change, M_N, addr_fmt, start_idx, clear_ms, cap_menu,
|
||||
assert int(subpath.split('/')[-1]) == idx
|
||||
#print('../0/%s => \n %s' % (idx, B2A(script)))
|
||||
|
||||
addr = addr_from_display_format(addr)
|
||||
if msas:
|
||||
assert addr == expect == qr_addrs[c]
|
||||
else:
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import pytest, time, io, csv
|
||||
from txn import fake_address
|
||||
from base58 import encode_base58_checksum
|
||||
from helpers import hash160
|
||||
from helpers import hash160, 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
|
||||
from constants import simulator_fixed_xprv, simulator_fixed_tprv, addr_fmt_names
|
||||
@ -193,7 +193,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')
|
||||
|
||||
@ -218,8 +218,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
|
||||
@ -299,7 +298,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
|
||||
|
||||
@ -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, U2SAT, prandom, fake_dest_addr, make_change_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
|
||||
|
||||
|
||||
@ -553,7 +553,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,])
|
||||
@ -623,7 +625,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)
|
||||
|
||||
@ -731,7 +733,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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user