diff --git a/releases/Next-ChangeLog.md b/releases/Next-ChangeLog.md index d8b3dc82..ad26563d 100644 --- a/releases/Next-ChangeLog.md +++ b/releases/Next-ChangeLog.md @@ -7,6 +7,7 @@ This lists the new changes that have not yet been published in a normal release. - New Feature: Export [BIP-380](https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki) extended key expression. Navigate to `Advanced/Tools -> Export Wallet -> Key Expression` - New Feature: Support for v3 transactions +- New Feature: Send keystrokes with all derived BIP-85 secrets # Mk4 Specific Changes diff --git a/shared/auth.py b/shared/auth.py index cc66df3a..54aa5cad 100644 --- a/shared/auth.py +++ b/shared/auth.py @@ -548,8 +548,9 @@ class ApproveTransaction(UserAuthorizedAction): dis.fullscreen('Co-Signing...') gc.collect() CCCFeature.sign_psbt(self.psbt) - else: - # maybe capture new min-height for velocity limit + + if SSSPFeature.is_enabled(): + # capture new min-height for velocity limit SSSPFeature.update_last_signed(self.psbt) except FraudulentChangeOutput as exc: @@ -883,7 +884,8 @@ async def done_signing(psbt, tx_req, input_method=None, filename=None, # In that case this would just return dict and keep producing signed # files on SD infinitely (would never actually prompt). ch = await import_export_prompt(noun, intro="\n\n".join(intro), offer_kt=offer_kt, - txid=txid, title=title, force_prompt=not first_time, + key6="for QR Code of TXID", title=title, + force_prompt=not first_time, no_qr=not version.has_qwerty or not allow_qr) if ch == KEY_CANCEL: UserAuthorizedAction.cleanup() @@ -964,7 +966,7 @@ async def _save_to_disk(psbt, txid, save_options, is_complete, data_len, output_ del_after = settings.get('del', 0) - def _chunk_write(file_d, ofs, chunk=2048): + def _chunk_write(file_d, ofs, chunk=4096): written = 0 while written < data_len: if (written + chunk) > data_len: diff --git a/shared/drv_entro.py b/shared/drv_entro.py index 8f52d94e..76107678 100644 --- a/shared/drv_entro.py +++ b/shared/drv_entro.py @@ -205,14 +205,12 @@ async def drv_entro_step2(_1, picked, _2, just_pick=False): if new_secret: msg += '\n\nRaw Entropy:\n' + str(b2a_hex(new_secret), 'ascii') - # Add the standard export prompt at the end, with extra (5) option sometimes. - + key6 = 'to type %s over USB' % s_mode key0 = None if encoded is not None: key0 = 'to switch to derived secret' - elif s_mode == 'pw': - key0 = 'to type password over USB' - prompt, escape = export_prompt_builder('data', key0=key0, + + prompt, escape = export_prompt_builder('data', key0=key0, key6=key6, no_qr=(not qr), force_prompt=True) title = None if node: @@ -224,7 +222,9 @@ async def drv_entro_step2(_1, picked, _2, just_pick=False): ch = await ux_show_story(msg+'\n\n'+prompt, title=title, escape=escape, strict_escape=True, sensitive=True) choice = import_export_prompt_decode(ch) - if isinstance(choice, dict): + if choice == KEY_CANCEL: + break + elif isinstance(choice, dict): # write to SD card or Virtual Disk: simple text file dis.fullscreen("Saving...") try: @@ -247,27 +247,27 @@ async def drv_entro_step2(_1, picked, _2, just_pick=False): story = "Filename is:\n\n%s" % out_fn story += "\n\nSignature filename is:\n\n%s" % sig_nice await ux_show_story(story, title='Saved') - elif choice == KEY_CANCEL: - break + elif choice == KEY_QR: from ux import show_qr_code await show_qr_code(qr, qr_alnum, is_secret=True) - elif choice == '0': - if s_mode == 'pw': - # gets confirmation then types it - await single_send_keystrokes(qr, path) - elif encoded is not None: - # switch over to new secret! - dis.fullscreen("Applying...") - from actions import goto_top_menu - from glob import settings - xfp_str = xfp2str(settings.get("xfp", 0)) - await seed.set_ephemeral_seed( - encoded, - origin='BIP85 Derived from [%s], index=%d' % (xfp_str, index) - ) - goto_top_menu() - break + + elif (choice == '0') and (encoded is not None): + # switch over to new secret! + dis.fullscreen("Applying...") + from actions import goto_top_menu + from glob import settings + xfp_str = xfp2str(settings.get("xfp", 0)) + await seed.set_ephemeral_seed( + encoded, + origin='BIP85 Derived from [%s], index=%d' % (xfp_str, index) + ) + goto_top_menu() + break + + elif choice == "6": + # gets confirmation then types it + await single_send_keystrokes(qr, path) elif NFC and choice == KEY_NFC: # Share any of these over NFC diff --git a/shared/ux.py b/shared/ux.py index 36646ee6..5fffd425 100644 --- a/shared/ux.py +++ b/shared/ux.py @@ -394,14 +394,14 @@ def _import_prompt_builder(title, no_qr, no_nfc, slot_b_only=False): def export_prompt_builder(what_it_is, no_qr=False, no_nfc=False, key0=None, offer_kt=False, - force_prompt=False, txid=None): + force_prompt=False, key6=None): # Build the prompt for export # - key0 can be for special stuff from glob import NFC, VD prompt, escape = None, KEY_CANCEL+"x" - if (NFC or VD) or (num_sd_slots>1) or key0 or force_prompt or offer_kt or txid or (not no_qr): + if (NFC or VD) or (num_sd_slots>1) or key0 or force_prompt or offer_kt or key6 or (not no_qr): # no need to spam with another prompt, only option is SD card prompt = "Press (1) to save %s to SD Card" % what_it_is @@ -431,10 +431,6 @@ def export_prompt_builder(what_it_is, no_qr=False, no_nfc=False, key0=None, offe prompt += ", (4) to show QR code" escape += '4' - if txid: - prompt += ", (6) for QR Code of TXID" - escape += "6" - if offer_kt: prompt += ", (T) to " + offer_kt escape += 't' @@ -443,6 +439,10 @@ def export_prompt_builder(what_it_is, no_qr=False, no_nfc=False, key0=None, offe prompt += ', (0) ' + key0 escape += '0' + if key6: + prompt += ", (6) " + key6 + escape += "6" + prompt += "." return prompt, escape @@ -481,7 +481,7 @@ def import_export_prompt_decode(ch): async def import_export_prompt(what_it_is, is_import=False, no_qr=False, no_nfc=False, title=None, intro='', footnotes='', offer_kt=False, slot_b_only=False, force_prompt=False, - txid=None): + key0=None, key6=None): # Show story allowing user to select source for importing/exporting # - return either str(mode) OR dict(file_args) @@ -494,7 +494,7 @@ async def import_export_prompt(what_it_is, is_import=False, no_qr=False, if is_import: prompt, escape = _import_prompt_builder(what_it_is, no_qr, no_nfc, slot_b_only) else: - prompt, escape = export_prompt_builder(what_it_is, no_qr, no_nfc, txid=txid, + prompt, escape = export_prompt_builder(what_it_is, no_qr, no_nfc, key6=key6, key0=key0, force_prompt=force_prompt, offer_kt=offer_kt) # TODO: detect if we're only asking A or B, when just one card is inserted diff --git a/testing/test_drv_entro.py b/testing/test_drv_entro.py index 8daa4b3a..fb5fde64 100644 --- a/testing/test_drv_entro.py +++ b/testing/test_drv_entro.py @@ -77,6 +77,7 @@ def derive_bip85_secret(goto_home, press_select, pick_menu_item, cap_story, ente seed = Mnemonic.to_seed(' '.join(got)) node = BIP32Node.from_master_secret(seed) assert node.fingerprint().hex().upper() in title + assert "(6) to type words over USB" in story elif 'XPRV' in mode: assert 'Derived XPRV:' in story @@ -87,12 +88,14 @@ def derive_bip85_secret(goto_home, press_select, pick_menu_item, cap_story, ente node = BIP32Node.from_hwif(story.split("\n\n")[0].split("\n")[-1]) assert node.fingerprint().hex().upper() in title + assert "(6) to type xprv over USB" in story elif 'WIF' in mode: assert 'WIF (privkey)' in story assert f"m/83696968h/2h/{index}h" in story if expect: assert expect in story + assert "(6) to type wif over USB" in story elif 'bytes hex' in mode: width = int(mode.split('-')[0]) @@ -101,13 +104,14 @@ def derive_bip85_secret(goto_home, press_select, pick_menu_item, cap_story, ente assert f"m/83696968h/128169h/{width}h/{index}h" in story if expect: assert expect in story + assert "(6) to type hex over USB" in story elif 'Passwords' == mode: assert "Password:" in story assert f"m/83696968h/707764h/21h/{index}h" in story if expect: assert expect in story - assert "(0) to type password over USB" in story + assert "(6) to type pw over USB" in story else: raise ValueError(mode)