From 240fc65775d802deb562fe54bcea1131553c8a5b Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 21 Nov 2019 11:12:48 -0500 Subject: [PATCH] Show QR code when exploring addresses --- shared/address_explorer.py | 151 +++++++++++++++++++++++--- shared/uQR.py | 13 ++- unix/frozen-modules/sim_quickstart.py | 5 +- 3 files changed, 145 insertions(+), 24 deletions(-) diff --git a/shared/address_explorer.py b/shared/address_explorer.py index eaed25ab..75496d22 100644 --- a/shared/address_explorer.py +++ b/shared/address_explorer.py @@ -9,6 +9,7 @@ import chains, stash from ux import ux_show_story, the_ux, ux_confirm from actions import goto_top_menu from menu import MenuSystem, MenuItem, start_chooser +from public_constants import AFC_BECH32 SCREEN_CHAR_WIDTH = const(16) @@ -71,11 +72,13 @@ async def choose_first_address(*a): async def show_n_addresses(path, addr_fmt, start, n): # Displays n addresses from start from main import dis + import version def make_msg(start): msg = "Press 1 to save to MicroSD.\n\n" msg += "Addresses %d..%d:\n\n" % (start, start + n - 1) + addrs = [] chain = chains.current_chain() dis.fullscreen('Wait...') @@ -85,38 +88,48 @@ async def show_n_addresses(path, addr_fmt, start, n): for idx in range(start, start + n): subpath = path.format(account=0, change=0, idx=idx) node = sv.derive_path(subpath, register=False) - msg += "%s =>\n%s\n\n" % (subpath, chain.address(node, addr_fmt)) + addr = chain.address(node, addr_fmt) + addrs.append(addr) + + msg += "%s =>\n%s\n\n" % (subpath, addr) dis.progress_bar_show(idx/n) stash.blank_object(node) + if version.has_fatram: + msg += "Press 4 to view QR Code. " + msg += "Press 9 to see next group, 7 to go back. X to quit." - return msg + return msg, addrs - msg = make_msg(start) + msg, addrs = make_msg(start) while 1: - ch = await ux_show_story(msg, escape='179') + ch = await ux_show_story(msg, escape='1479') - if ch == '1': - # save addresses to MicroSD signal - await make_address_summary_file(path, addr_fmt) - # .. continue on same screen in case they want to write to multiple cards + if ch == '1': + # save addresses to MicroSD signal + await make_address_summary_file(path, addr_fmt) + # .. continue on same screen in case they want to write to multiple cards - if ch == 'x': - return + if ch == 'x': + return - if ch == '7' and start>0: - # go backwards in explorer - start -= n - msg = make_msg(start) + if ch == '4': + if not version.has_fatram: continue + await show_address_qr(addrs, (addr_fmt & AFC_BECH32), start) + continue - if ch == '9': - # go forwards - start += n - msg = make_msg(start) + if ch == '7' and start>0: + # go backwards in explorer + start -= n + elif ch == '9': + # go forwards + start += n + + msg, addrs = make_msg(start) def generate_address_csv(path, addr_fmt, n): # Produce CSV file contents as a generator @@ -200,4 +213,106 @@ Press 4 to start.''', escape='4') await show_n_addresses(path, addr_fmt, 0, 10) + +async def show_address_qr(addrs, is_segwit, start_n): + # show a QR code for the address. can only work on Mk3 + # Version 2 would be nice, but can't hold what we need, even at min error correction, + # so we are forced into version 3 = 29x29 pixels + # - see + # - to display 29x29 pixels, we have to double them up: 58x58 + # - not really providing enough space around it + # - inverted QR (black/white swap) still readable by scanners, altho wrong + + from utils import imported + from display import FontSmall, FontTiny + import uQR as uqr + from main import dis + + idx = 0 # start with first address + invert = False # looks better, but neither mode is ideal + + addr = addrs[idx] + + def render(addr): + dis.busy_bar(True) + with imported('uQR') as uqr: + if is_segwit: + # targeting 'alpha numeric' mode, typical len is 42 + ec = uqr.ERROR_CORRECT_Q + assert len(addr) <= 47 + else: + # has to be 'binary' mode, altho shorter msg, typical 34-36 + ec = uqr.ERROR_CORRECT_M + assert len(addr) <= 42 + + q = uqr.QRCode(version=3, box_size=1, border=0, mask_pattern=3, error_correction=ec) + if is_segwit: + here = uqr.QRData(addr.upper().encode('ascii'), + mode=uqr.MODE_ALPHA_NUM, check_data=False) + else: + here = uqr.QRData(addr.encode('ascii'), mode=uqr.MODE_8BIT_BYTE, check_data=False) + q.add_data(here) + q.make(fit=False) + + return q.get_matrix() + + data = render(addr) + + def redraw(): + dis.clear() + + w = 29 # because version=3 + XO,YO = 7, 3 # offsets + + if not invert: + dis.dis.fill_rect(XO-YO, 0, 64, 64, 1) + + for x in range(w): + for y in range(w): + px = data[x][y] + X = (x*2) + XO + Y = (y*2) + YO + dis.dis.fill_rect(X,Y, 2,2, px if invert else (not px)) + + x, y = 73, 0 if is_segwit else 2 + ll = 7 # per line + for i in range(0, len(addr), ll): + dis.text(x, y, addr[i:i+ll], FontSmall) + y += 10 if is_segwit else 12 + + if not invert: + # show path number, very tiny + ai = str(start_n + idx) + if len(ai) == 1: + dis.text(0, 30, ai[0], FontTiny) + else: + dis.text(0, 27, ai[0], FontTiny) + dis.text(0, 27+7, ai[1], FontTiny) + + dis.busy_bar(False) # includes show + + redraw() + + from ux import ux_wait_keyup + + while 1: + ch = await ux_wait_keyup() + + if ch == '1': + invert = not invert + redraw() + continue + elif ch in 'xy': + return + if ch == '5' or ch == '7': + if idx > 0: + idx -= 1 + elif ch == '8' or ch == '9': + if idx != len(addrs)-1: + idx += 1 + + addr = addrs[idx] + data = render(addr) + redraw() + # EOF diff --git a/shared/uQR.py b/shared/uQR.py index a2050545..4da3fadb 100644 --- a/shared/uQR.py +++ b/shared/uQR.py @@ -1,4 +1,7 @@ # from https://github.com/JASchilz/uQR/blob/master/uQR.py @ 0d105634841368ef0b1bb210a63c48e4b50a9a94 +# +# Please see for BSD-style license. +# import ure as re """ @@ -17,10 +20,10 @@ Formerly in constants.py """ # QR error correct levels -ERROR_CORRECT_L = 1 -ERROR_CORRECT_M = 0 -ERROR_CORRECT_Q = 3 -ERROR_CORRECT_H = 2 +ERROR_CORRECT_L = const(1) +ERROR_CORRECT_M = const(0) +ERROR_CORRECT_Q = const(3) +ERROR_CORRECT_H = const(2) """ LUT @@ -1022,7 +1025,7 @@ def create_data(version, error_correction, data_list): bit_limit += block.data_count * 8 if len(buffer) > bit_limit: - raise exceptions.DataOverflowError( + raise DataOverflowError( "Code length overflow. Data size (%s) > size available (%s)" % (len(buffer), bit_limit)) diff --git a/unix/frozen-modules/sim_quickstart.py b/unix/frozen-modules/sim_quickstart.py index dae3d6b4..c4f44c1d 100644 --- a/unix/frozen-modules/sim_quickstart.py +++ b/unix/frozen-modules/sim_quickstart.py @@ -18,15 +18,17 @@ if '-s' in sys.argv: numpad.inject('4') numpad.inject('y') -if '-a' in sys.argv: +if '--addr' in sys.argv: # Address Explorer from main import numpad numpad.inject('4') numpad.inject('y') numpad.inject('4') numpad.inject('8') + numpad.inject('8') numpad.inject('y') numpad.inject('y') + numpad.inject('4') # skips warning! if '--dz' in sys.argv: # Enter the "Danger Zone" @@ -36,6 +38,7 @@ if '--dz' in sys.argv: numpad.inject('4') numpad.inject('8') numpad.inject('8') + numpad.inject('8') numpad.inject('y') if '--xw' in sys.argv: