You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.8 KiB
182 lines
5.8 KiB
from micropython import const
|
|
from ubinascii import hexlify
|
|
|
|
from trezor import config, ui, wire, workflow
|
|
from trezor.crypto import bip39, hashlib, random
|
|
from trezor.messages import ButtonRequestType, MessageType
|
|
from trezor.messages.ButtonRequest import ButtonRequest
|
|
from trezor.messages.EntropyRequest import EntropyRequest
|
|
from trezor.messages.Success import Success
|
|
from trezor.pin import pin_to_int
|
|
from trezor.ui.confirm import HoldToConfirmDialog
|
|
from trezor.ui.mnemonic import MnemonicKeyboard
|
|
from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
|
|
from trezor.ui.text import Text
|
|
from trezor.utils import chunks, format_ordinal
|
|
|
|
from apps.common import storage
|
|
from apps.common.confirm import require_confirm
|
|
from apps.management.change_pin import request_pin_confirm
|
|
|
|
if __debug__:
|
|
from apps import debug
|
|
|
|
|
|
async def reset_device(ctx, msg):
|
|
# validate parameters and device state
|
|
if msg.strength not in (128, 192, 256):
|
|
raise wire.ProcessError("Invalid strength (has to be 128, 192 or 256 bits)")
|
|
if storage.is_initialized():
|
|
raise wire.UnexpectedMessage("Already initialized")
|
|
|
|
# request new PIN
|
|
if msg.pin_protection:
|
|
newpin = await request_pin_confirm(ctx)
|
|
else:
|
|
newpin = ""
|
|
|
|
# generate and display internal entropy
|
|
internal_ent = random.bytes(32)
|
|
if __debug__:
|
|
debug.reset_internal_entropy = internal_ent
|
|
if msg.display_random:
|
|
await show_entropy(ctx, internal_ent)
|
|
|
|
# request external entropy and compute mnemonic
|
|
ent_ack = await ctx.call(EntropyRequest(), MessageType.EntropyAck)
|
|
mnemonic = generate_mnemonic(msg.strength, internal_ent, ent_ack.entropy)
|
|
|
|
if not msg.skip_backup:
|
|
# require confirmation of the mnemonic safety
|
|
await show_warning(ctx)
|
|
|
|
# show mnemonic and require confirmation of a random word
|
|
while True:
|
|
await show_mnemonic(ctx, mnemonic)
|
|
if await check_mnemonic(ctx, mnemonic):
|
|
break
|
|
await show_wrong_entry(ctx)
|
|
|
|
# write PIN into storage
|
|
if not config.change_pin(pin_to_int(""), pin_to_int(newpin), None):
|
|
raise wire.ProcessError("Could not change PIN")
|
|
|
|
# write settings and mnemonic into storage
|
|
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection)
|
|
storage.load_mnemonic(mnemonic=mnemonic, needs_backup=msg.skip_backup)
|
|
|
|
# show success message. if we skipped backup, it's possible that homescreen
|
|
# is still running, uninterrupted. restart it to pick up new label.
|
|
if not msg.skip_backup:
|
|
await show_success(ctx)
|
|
else:
|
|
workflow.restartdefault()
|
|
|
|
return Success(message="Initialized")
|
|
|
|
|
|
def generate_mnemonic(strength: int, int_entropy: bytes, ext_entropy: bytes) -> bytes:
|
|
ehash = hashlib.sha256()
|
|
ehash.update(int_entropy)
|
|
ehash.update(ext_entropy)
|
|
entropy = ehash.digest()
|
|
mnemonic = bip39.from_data(entropy[: strength // 8])
|
|
return mnemonic
|
|
|
|
|
|
async def show_warning(ctx):
|
|
text = Text("Backup your seed", ui.ICON_NOCOPY)
|
|
text.normal(
|
|
"Never make a digital",
|
|
"copy of your recovery",
|
|
"seed and never upload",
|
|
"it online!",
|
|
)
|
|
await require_confirm(
|
|
ctx, text, ButtonRequestType.ResetDevice, confirm="I understand", cancel=None
|
|
)
|
|
|
|
|
|
async def show_wrong_entry(ctx):
|
|
text = Text("Wrong entry!", ui.ICON_WRONG, icon_color=ui.RED)
|
|
text.normal("You have entered", "wrong seed word.", "Please check again.")
|
|
await require_confirm(
|
|
ctx, text, ButtonRequestType.ResetDevice, confirm="Check again", cancel=None
|
|
)
|
|
|
|
|
|
async def show_success(ctx):
|
|
text = Text("Backup is done!", ui.ICON_CONFIRM, icon_color=ui.GREEN)
|
|
text.normal(
|
|
"Never make a digital",
|
|
"copy of your recovery",
|
|
"seed and never upload",
|
|
"it online!",
|
|
)
|
|
await require_confirm(
|
|
ctx, text, ButtonRequestType.ResetDevice, confirm="Finish setup", cancel=None
|
|
)
|
|
|
|
|
|
async def show_entropy(ctx, entropy: bytes):
|
|
entropy_str = hexlify(entropy).decode()
|
|
lines = chunks(entropy_str, 16)
|
|
text = Text("Internal entropy", ui.ICON_RESET)
|
|
text.mono(*lines)
|
|
await require_confirm(ctx, text, ButtonRequestType.ResetDevice)
|
|
|
|
|
|
async def show_mnemonic(ctx, mnemonic: str):
|
|
await ctx.call(
|
|
ButtonRequest(code=ButtonRequestType.ResetDevice), MessageType.ButtonAck
|
|
)
|
|
first_page = const(0)
|
|
words_per_page = const(4)
|
|
words = list(enumerate(mnemonic.split()))
|
|
pages = list(chunks(words, words_per_page))
|
|
paginator = paginate(show_mnemonic_page, len(pages), first_page, pages)
|
|
await ctx.wait(paginator)
|
|
|
|
|
|
@ui.layout
|
|
async def show_mnemonic_page(page: int, page_count: int, pages: list):
|
|
if __debug__:
|
|
debug.reset_current_words = [word for _, word in pages[page]]
|
|
|
|
lines = ["%2d. %s" % (wi + 1, word) for wi, word in pages[page]]
|
|
text = Text("Recovery seed", ui.ICON_RESET)
|
|
text.mono(*lines)
|
|
content = Scrollpage(text, page, page_count)
|
|
|
|
if page + 1 == page_count:
|
|
await HoldToConfirmDialog(content)
|
|
else:
|
|
content.render()
|
|
await animate_swipe()
|
|
|
|
|
|
async def check_mnemonic(ctx, mnemonic: str) -> bool:
|
|
words = mnemonic.split()
|
|
|
|
# check a word from the first half
|
|
index = random.uniform(len(words) // 2)
|
|
if not await check_word(ctx, words, index):
|
|
return False
|
|
|
|
# check a word from the second half
|
|
index = random.uniform(len(words) // 2) + len(words) // 2
|
|
if not await check_word(ctx, words, index):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
@ui.layout
|
|
async def check_word(ctx, words: list, index: int):
|
|
if __debug__:
|
|
debug.reset_word_index = index
|
|
|
|
keyboard = MnemonicKeyboard("Type the %s word:" % format_ordinal(index + 1))
|
|
result = await ctx.wait(keyboard)
|
|
return result == words[index]
|