def sign_transaction(psbt_len, flags=0x0, psbt_sha=None): # transaction (binary) loaded into sflash already, checksum checked UserAuthorizedAction.check_busy(ApproveTransaction) UserAuthorizedAction.active_request = ApproveTransaction(psbt_len, flags, psbt_sha=psbt_sha) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)
def start_show_p2sh_address(M, N, addr_format, xfp_paths, witdeem_script): # Show P2SH address to user, also returns it. # - first need to find appropriate multisig wallet associated # - they must provide full redeem script, and we will re-verify it and check pubkeys inside it import ustruct from multisig import MultisigWallet, MultisigOutOfSpace try: assert addr_format in SUPPORTED_ADDR_FORMATS assert addr_format & AFC_SCRIPT except: raise AssertionError('Unknown/unsupported addr format') # Search for matching multisig wallet that we must already know about xfps = [i[0] for i in xfp_paths] idx = MultisigWallet.find_match(M, N, xfps) assert idx >= 0, 'Multisig wallet with those fingerprints not found' ms = MultisigWallet.get_by_idx(idx) assert ms assert ms.M == M assert ms.N == N global active_request UserAuthorizedAction.check_busy(ShowAddressBase) active_request = ShowP2SHAddress(ms, addr_format, xfp_paths, witdeem_script) # kill any menu stack, and put our thing at the top abort_and_goto(active_request) # provide the value back to attached desktop return active_request.address
def maybe_enroll_xpub(sf_len=None, config=None, name=None, ux_reset=False): # Offer to import (enroll) a new multisig wallet. Allow reject by user. global active_request from multisig import MultisigWallet UserAuthorizedAction.cleanup() if sf_len: with SFFile(TXN_INPUT_OFFSET, length=sf_len) as fd: config = fd.read(sf_len).decode() # this call will raise on parsing errors, so let them rise up # and be shown on screen/over usb ms = MultisigWallet.from_file(config, name=name) active_request = NewEnrollRequest(ms) if ux_reset: # for USB case, and import from PSBT # kill any menu stack, and put our thing at the top abort_and_goto(active_request) else: # menu item case: add to stack from ux import the_ux the_ux.push(active_request)
def start_bip39_passphrase(pw): # new passphrase has come in via USB. offer to switch to it. UserAuthorizedAction.cleanup() UserAuthorizedAction.active_request = NewPassphrase(pw) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)
def sign_transaction(psbt_len, do_finalize=False): # transaction (binary) loaded into sflash already, checksum checked global active_request UserAuthorizedAction.check_busy(ApproveTransaction) active_request = ApproveTransaction(psbt_len, do_finalize) # kill any menu stack, and put our thing at the top abort_and_goto(active_request)
def start_remote_backup(): # tell the local user the secret words, and then save to SPI flash # USB caller has to come back and download encrypted contents. UserAuthorizedAction.cleanup() UserAuthorizedAction.active_request = RemoteBackup() # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)
def authorize_upgrade(hdr, length): # final USB write has come in, get buy-in # Do some verification before we even show to the local user UserAuthorizedAction.check_busy() UserAuthorizedAction.active_request = FirmwareUpgradeRequest(hdr, length) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)
def start_bip39_passphrase(pw): # tell the local user the secret words, and then save to SPI flash # USB caller has to come back and download encrypted contents. global active_request UserAuthorizedAction.cleanup() active_request = NewPassphrase(pw) # kill any menu stack, and put our thing at the top abort_and_goto(active_request)
async def sign_psbt_buf_OLD(psbt_buf): # sign a PSBT string from common import dis from sram4 import tmp_buf from utils import HexStreamer, Base64Streamer, HexWriter, Base64Writer UserAuthorizedAction.cleanup() # Determine encoding used psbt_len = len(psbt_buf) taste = psbt_buf[0:10] print('sign_psbt_buf: 1') if taste[0:5] == b'psbt\xff': print('sign_psbt_buf: 2') print("sign 1") decoder = None def output_encoder(x): return x elif taste[0:10] == b'70736274ff': print("sign 2") decoder = HexStreamer() output_encoder = HexWriter psbt_len //= 2 elif taste[0:6] == b'cHNidP': print("sign 3") decoder = Base64Streamer() output_encoder = Base64Writer psbt_len = (psbt_len * 3 // 4) + 10 print('sign_psbt_buf: 3') async def done(psbt): if psbt.is_complete(): psbt.finalize(fd) else: psbt.serialize(fd) ch = await ux_show_signed_transaction() await ux_show_story(msg, title='PSBT Signed') UserAuthorizedAction.cleanup() print('sign_psbt_buf: 4') UserAuthorizedAction.active_request = ApproveTransaction( psbt_len, approved_cb=done, psbt_buf=psbt_buf) print('sign_psbt_buf: 5') # Kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request) print('sign_psbt_buf: 6')
def start_show_address(addr_format, subpath): try: assert addr_format in SUPPORTED_ADDR_FORMATS assert not (addr_format & AFC_SCRIPT) except: raise AssertionError('Unknown/unsupported addr format') # require a path to a key subpath = cleanup_deriv_path(subpath) # serAuthorizedAction.check_busy(ShowAddressBase) UserAuthorizedAction.active_request = ShowPKHAddress(addr_format, subpath) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request) # provide the value back to attached desktop return UserAuthorizedAction.active_request.address
def start_show_address(subpath, addr_format): # Show address to user, also returns it. try: subpath = str(subpath, 'ascii') except UnicodeError: raise AssertionError('must be ascii') try: assert addr_format in SUPPORTED_ADDR_FORMATS except: raise AssertionError('Unknown/unsupported addr format') global active_request UserAuthorizedAction.check_busy(ShowAddress) active_request = ShowAddress(subpath, addr_format) # kill any menu stack, and put our thing at the top abort_and_goto(active_request) # provide the value back to attached desktop too! return active_request.address
def sign_msg(text, subpath, addr_fmt): # Convert to strings try: text = str(text,'ascii') subpath = str(subpath, 'ascii') except UnicodeError: raise AssertionError('must be ascii') try: assert addr_fmt in SUPPORTED_ADDR_FORMATS assert not (addr_fmt & AFC_SCRIPT) except: raise AssertionError('Unknown/unsupported addr format') # Do some verification before we even show to the local user ApproveMessageSign.validate(text) global active_request UserAuthorizedAction.check_busy() active_request = ApproveMessageSign(text, subpath, addr_fmt) # kill any menu stack, and put our thing at the top abort_and_goto(active_request)
def start_show_address(addr_format, subpath): try: assert addr_format in SUPPORTED_ADDR_FORMATS assert not (addr_format & AFC_SCRIPT) except: raise AssertionError('Unknown/unsupported addr format') # text path expected try: subpath = str(subpath, 'ascii') ms = None subpaths = None except UnicodeError: raise AssertionError('must be ascii') global active_request UserAuthorizedAction.check_busy(ShowAddressBase) active_request = ShowPKHAddress(addr_format, subpath) # kill any menu stack, and put our thing at the top abort_and_goto(active_request) # provide the value back to attached desktop return active_request.address
def sign_psbt_file(filename): # sign a PSBT file found on a MicroSD card from files import CardSlot, CardMissingError from main import dis from sram2 import tmp_buf global active_request UserAuthorizedAction.cleanup() #print("sign: %s" % filename) # copy file into our spiflash # - can't work in-place on the card because we want to support writing out to different card with CardSlot() as card: with open(filename, 'rb') as fd: dis.fullscreen('Reading...') # see how long it is psbt_len = fd.seek(0, 2) fd.seek(0) total = 0 with SFFile(TXN_INPUT_OFFSET, max_size=psbt_len) as out: # blank flash await out.erase() while 1: n = fd.readinto(tmp_buf) if not n: break if n == len(tmp_buf): out.write(tmp_buf) else: out.write(memoryview(tmp_buf)[0:n]) total += n dis.progress_bar_show(total / psbt_len) assert total == psbt_len, repr([total, psbt_len]) async def done(psbt): orig_path, basename = filename.rsplit('/', 1) orig_path += '/' base = basename.rsplit('.', 1)[0] out2_fn = None out_fn = None while 1: # try to put back into same spot, but also do top-of-card is_comp = psbt.is_complete() if not is_comp: # keep the filename under control during multiple passes target_fname = base.replace('-part', '')+'-part.psbt' else: # add -signed to end. We won't offer to sign again. target_fname = base+'-signed.psbt' for path in [orig_path, None]: try: with CardSlot() as card: out_full, out_fn = card.pick_filename(target_fname, path) out_path = path if out_full: break except CardMissingError: prob = 'Missing card.\n\n' out_fn = None if not out_fn: # need them to insert a card prob = '' else: # attempt write-out try: with CardSlot() as card: with open(out_full, 'wb') as fd: # save as updated PSBT psbt.serialize(fd) if is_comp: # write out as hex too, if it's final out2_full, out2_fn = card.pick_filename(base+'-final.txn', out_path) if out2_full: with HexWriter(open(out2_full, 'wt')) as fd: # save transaction, in hex psbt.finalize(fd) # success and done! break except OSError as exc: prob = 'Failed to write!\n\n%s\n\n' % exc sys.print_exception(exc) # fall thru to try again # prompt them to input another card? ch = await ux_show_story(prob+"Please insert an SDCard to receive signed transaction, " "and press OK.", title="Need Card") if ch == 'x': await ux_aborted() return # done. msg = "Updated PSBT is:\n\n%s" % out_fn if out2_fn: msg += '\n\nFinalized transaction (ready for broadcast):\n\n%s' % out2_fn await ux_show_story(msg, title='PSBT Signed') UserAuthorizedAction.cleanup() active_request = ApproveTransaction(psbt_len, approved_cb=done) # kill any menu stack, and put our thing at the top abort_and_goto(active_request)
async def start_hsm_approval(sf_len=0, usb_mode=False, startup_mode=False): # Show details of the proposed HSM policy (or saved one) # If approved, go into HSM mode and never come back to normal. UserAuthorizedAction.cleanup() is_new = True if sf_len: with SFFile(0, length=sf_len) as fd: json = fd.read(sf_len).decode() else: try: json = open(POLICY_FNAME, 'rt').read() except: raise ValueError("No existing policy") is_new = False # parse as JSON cant_fail = False try: try: js_policy = ujson.loads(json) except: raise ValueError("JSON parse fail") cant_fail = bool(js_policy.get('boot_to_hsm', False)) # parse the policy policy = HSMPolicy() policy.load(js_policy) except BaseException as exc: err = "HSM Policy invalid: %s: %s" % (problem_file_line(exc), str(exc)) if usb_mode: raise ValueError(err) # What to do in a menu case? Shouldn't happen anyway, but # maybe they upgraded the firmware, and so old policy file # isn't suitable anymore. # - or maybe the settings have been f-ed with. print(err) if startup_mode and cant_fail: # die as a brick here, not safe to proceed w/o HSM active import callgate, ux ux.show_fatal_error(err.replace(': ', ':\n ')) callgate.show_logout(1) # die w/ it visible # not reached await ux_show_story("Cannot start HSM.\n\n%s" % err) return # Boot-to-HSM feature: don't ask, just start policy immediately if startup_mode and policy.boot_to_hsm: msg = uio.StringIO() policy.explain(msg) policy.activate(False) the_ux.reset(hsm_ux_obj) return None ar = ApproveHSMPolicy(policy, is_new) UserAuthorizedAction.active_request = ar if startup_mode: return ar if usb_mode: # for USB case, kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request) else: # menu item case: add to stack, so we can still back out from ux import the_ux the_ux.push(UserAuthorizedAction.active_request) return ar
def sign_psbt_buf(psbt_buf): # sign a PSBT file found on a microSD card from uio import BytesIO from common import dis from sram4 import tmp_buf from utils import HexStreamer, Base64Streamer, HexWriter, Base64Writer UserAuthorizedAction.cleanup() # copy buffer into SPI Flash # - accepts hex or base64 encoding, but binary prefered with BytesIO(psbt_buf) as fd: dis.fullscreen('Reading...') # see how long it is psbt_len = fd.seek(0, 2) fd.seek(0) # determine encoding used, altho we prefer binary taste = fd.read(10) fd.seek(0) if taste[0:5] == b'psbt\xff': print('tastes like text PSBT') decoder = None def output_encoder(x): return x elif taste[0:10] == b'70736274ff': print('tastes like binary PSBT') decoder = HexStreamer() output_encoder = HexWriter psbt_len //= 2 elif taste[0:6] == b'cHNidP': print('tastes like Base64 PSBT') decoder = Base64Streamer() output_encoder = Base64Writer psbt_len = (psbt_len * 3 // 4) + 10 else: return total = 0 with SFFile(TXN_INPUT_OFFSET, max_size=psbt_len) as out: print('sign 1') # blank flash await out.erase() print('sign 2') while 1: n = fd.readinto(tmp_buf) print('sign copy to SPI flash 1: n={}'.format(n)) if not n: break if n == len(tmp_buf): abuf = tmp_buf else: abuf = memoryview(tmp_buf)[0:n] if not decoder: out.write(abuf) total += n else: for here in decoder.more(abuf): out.write(here) total += len(here) print('sign copy to SPI flash 2: {}/{} = {}'.format(total, psbt_len, total/psbt_len)) dis.progress_bar_show(total / psbt_len) print('sign 3') # might have been whitespace inflating initial estimate of PSBT size assert total <= psbt_len psbt_len = total print('sign 4') # Create a new BytesIO() to hold the result async def done(psbt): print('sign 5: done') signed_bytes = None with BytesIO() as bfd: with output_encoder(bfd) as fd: print('sign 6: done') if psbt.is_complete(): print('sign 7: done') psbt.finalize(fd) print('sign 8: done') else: print('sign 9: done') psbt.serialize(fd) print('sign 10: done') bfd.seek(0) signed_bytes = bfd.read() print('signed_bytes={}'.format(signed_bytes)) print('sign 11: done') gc.collect() from ur1.encode_ur import encode_ur from ubinascii import hexlify signed_str = hexlify(signed_bytes) print('signed_str={}'.format(signed_str)) from ux import DisplayURCode o = DisplayURCode('Signed Txn', 'Scan to Wallet', signed_str) await o.interact_bare() UserAuthorizedAction.cleanup() print('sign 12: done') UserAuthorizedAction.active_request = ApproveTransaction(psbt_len, approved_cb=done) print('sign 13: done') # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request) print('sign 14: done')
def sign_psbt_file(filename): # sign a PSBT file found on a MicroSD card from files import CardSlot, CardMissingError, securely_blank_file from main import dis from sram2 import tmp_buf from utils import HexStreamer, Base64Streamer, HexWriter, Base64Writer UserAuthorizedAction.cleanup() #print("sign: %s" % filename) # copy file into our spiflash # - can't work in-place on the card because we want to support writing out to different card # - accepts hex or base64 encoding, but binary prefered with CardSlot() as card: with open(filename, 'rb') as fd: dis.fullscreen('Reading...') # see how long it is psbt_len = fd.seek(0, 2) fd.seek(0) # determine encoding used, altho we prefer binary taste = fd.read(10) fd.seek(0) if taste[0:5] == b'psbt\xff': decoder = None output_encoder = lambda x: x elif taste[0:10] == b'70736274ff': decoder = HexStreamer() output_encoder = HexWriter psbt_len //= 2 elif taste[0:6] == b'cHNidP': decoder = Base64Streamer() output_encoder = Base64Writer psbt_len = (psbt_len * 3 // 4) + 10 total = 0 with SFFile(TXN_INPUT_OFFSET, max_size=psbt_len) as out: # blank flash await out.erase() while 1: n = fd.readinto(tmp_buf) if not n: break if n == len(tmp_buf): abuf = tmp_buf else: abuf = memoryview(tmp_buf)[0:n] if not decoder: out.write(abuf) total += n else: for here in decoder.more(abuf): out.write(here) total += len(here) dis.progress_bar_show(total / psbt_len) # might have been whitespace inflating initial estimate of PSBT size assert total <= psbt_len psbt_len = total async def done(psbt): orig_path, basename = filename.rsplit('/', 1) orig_path += '/' base = basename.rsplit('.', 1)[0] out2_fn = None out_fn = None txid = None from main import settings import os del_after = settings.get('del', 0) while 1: # try to put back into same spot, but also do top-of-card is_comp = psbt.is_complete() if not is_comp: # keep the filename under control during multiple passes target_fname = base.replace('-part', '')+'-part.psbt' else: # add -signed to end. We won't offer to sign again. target_fname = base+'-signed.psbt' for path in [orig_path, None]: try: with CardSlot() as card: out_full, out_fn = card.pick_filename(target_fname, path) out_path = path if out_full: break except CardMissingError: prob = 'Missing card.\n\n' out_fn = None if not out_fn: # need them to insert a card prob = '' else: # attempt write-out try: with CardSlot() as card: if is_comp and del_after: # don't write signed PSBT if we'd just delete it anyway out_fn = None else: with output_encoder(open(out_full, 'wb')) as fd: # save as updated PSBT psbt.serialize(fd) if is_comp: # write out as hex too, if it's final out2_full, out2_fn = card.pick_filename( base+'-final.txn' if not del_after else 'tmp.txn', out_path) if out2_full: with HexWriter(open(out2_full, 'w+t')) as fd: # save transaction, in hex txid = psbt.finalize(fd) if del_after: # rename it now that we know the txid after_full, out2_fn = card.pick_filename( txid+'.txn', out_path, overwrite=True) os.rename(out2_full, after_full) if del_after: # this can do nothing if they swapped SDCard between steps, which is ok, # but if the original file is still there, this blows it away. # - if not yet final, the foo-part.psbt file stays try: securely_blank_file(filename) except: pass # success and done! break except OSError as exc: prob = 'Failed to write!\n\n%s\n\n' % exc sys.print_exception(exc) # fall thru to try again # prompt them to input another card? ch = await ux_show_story(prob+"Please insert an SDCard to receive signed transaction, " "and press OK.", title="Need Card") if ch == 'x': await ux_aborted() return # done. if out_fn: msg = "Updated PSBT is:\n\n%s" % out_fn if out2_fn: msg += '\n\n' else: # del_after is probably set msg = '' if out2_fn: msg += 'Finalized transaction (ready for broadcast):\n\n%s' % out2_fn if txid and not del_after: msg += '\n\nFinal TXID:\n'+txid await ux_show_story(msg, title='PSBT Signed') UserAuthorizedAction.cleanup() UserAuthorizedAction.active_request = ApproveTransaction(psbt_len, approved_cb=done) # kill any menu stack, and put our thing at the top abort_and_goto(UserAuthorizedAction.active_request)