|
|
|
@ -1,14 +1,14 @@
|
|
|
|
|
import ubinascii
|
|
|
|
|
from micropython import const
|
|
|
|
|
|
|
|
|
|
from trezor import ui, utils
|
|
|
|
|
from trezor.crypto import random
|
|
|
|
|
from trezor.messages import ButtonRequestType
|
|
|
|
|
from trezor.messages import BackupType, ButtonRequestType
|
|
|
|
|
from trezor.ui.button import Button, ButtonDefault
|
|
|
|
|
from trezor.ui.checklist import Checklist
|
|
|
|
|
from trezor.ui.info import InfoConfirm
|
|
|
|
|
from trezor.ui.loader import LoadingAnimation
|
|
|
|
|
from trezor.ui.num_input import NumInput
|
|
|
|
|
from trezor.ui.scroll import Paginated
|
|
|
|
|
from trezor.ui.shamir import NumInput
|
|
|
|
|
from trezor.ui.text import Text
|
|
|
|
|
|
|
|
|
|
from apps.common.confirm import confirm, hold_to_confirm, require_confirm
|
|
|
|
@ -27,6 +27,7 @@ async def show_internal_entropy(ctx, entropy: bytes):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def confirm_backup(ctx):
|
|
|
|
|
# First prompt
|
|
|
|
|
text = Text("Success", ui.ICON_CONFIRM, ui.GREEN, new_lines=False)
|
|
|
|
|
text.bold("New wallet created")
|
|
|
|
|
text.br()
|
|
|
|
@ -36,17 +37,17 @@ async def confirm_backup(ctx):
|
|
|
|
|
text.normal("You should back up your")
|
|
|
|
|
text.br()
|
|
|
|
|
text.normal("new wallet right now.")
|
|
|
|
|
return await confirm(
|
|
|
|
|
if await confirm(
|
|
|
|
|
ctx,
|
|
|
|
|
text,
|
|
|
|
|
ButtonRequestType.ResetDevice,
|
|
|
|
|
cancel="Skip",
|
|
|
|
|
confirm="Back up",
|
|
|
|
|
major_confirm=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
async def confirm_backup_again(ctx):
|
|
|
|
|
# If the user selects Skip, ask again
|
|
|
|
|
text = Text("Warning", ui.ICON_WRONG, ui.RED, new_lines=False)
|
|
|
|
|
text.bold("Are you sure you want")
|
|
|
|
|
text.br()
|
|
|
|
@ -66,15 +67,93 @@ async def confirm_backup_again(ctx):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _show_share_words(ctx, share_words, share_index=None, group_index=None):
|
|
|
|
|
first, chunks, last = _split_share_into_pages(share_words)
|
|
|
|
|
|
|
|
|
|
if share_index is None:
|
|
|
|
|
header_title = "Recovery seed"
|
|
|
|
|
elif group_index is None:
|
|
|
|
|
header_title = "Recovery share #%s" % (share_index + 1)
|
|
|
|
|
else:
|
|
|
|
|
header_title = "Group %s - Share %s" % ((group_index + 1), (share_index + 1))
|
|
|
|
|
header_icon = ui.ICON_RESET
|
|
|
|
|
pages = [] # ui page components
|
|
|
|
|
shares_words_check = [] # check we display correct data
|
|
|
|
|
|
|
|
|
|
# first page
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
text.bold("Write down these")
|
|
|
|
|
text.bold("%s words:" % len(share_words))
|
|
|
|
|
text.br_half()
|
|
|
|
|
for index, word in first:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# middle pages
|
|
|
|
|
for chunk in chunks:
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
for index, word in chunk:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# last page
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
for index, word in last:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
text.br_half()
|
|
|
|
|
text.bold("I wrote down all %s" % len(share_words))
|
|
|
|
|
text.bold("words in order.")
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# pagination
|
|
|
|
|
paginated = Paginated(pages)
|
|
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
|
|
|
|
|
|
word_pages = [first] + chunks + [last]
|
|
|
|
|
|
|
|
|
|
def export_displayed_words():
|
|
|
|
|
# export currently displayed mnemonic words into debuglink
|
|
|
|
|
words = [w for _, w in word_pages[paginated.page]]
|
|
|
|
|
debug.reset_current_words.publish(words)
|
|
|
|
|
|
|
|
|
|
paginated.on_change = export_displayed_words
|
|
|
|
|
export_displayed_words()
|
|
|
|
|
|
|
|
|
|
# make sure we display correct data
|
|
|
|
|
utils.ensure(share_words == shares_words_check)
|
|
|
|
|
|
|
|
|
|
# confirm the share
|
|
|
|
|
await hold_to_confirm(ctx, paginated, ButtonRequestType.ResetDevice)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _split_share_into_pages(share_words):
|
|
|
|
|
share = list(enumerate(share_words)) # we need to keep track of the word indices
|
|
|
|
|
first = share[:2] # two words on the first page
|
|
|
|
|
length = len(share_words)
|
|
|
|
|
if length == 12 or length == 20 or length == 24:
|
|
|
|
|
middle = share[2:-2]
|
|
|
|
|
last = share[-2:] # two words on the last page
|
|
|
|
|
elif length == 33:
|
|
|
|
|
middle = share[2:]
|
|
|
|
|
last = [] # no words at the last page, because it does not add up
|
|
|
|
|
else:
|
|
|
|
|
# Invalid number of shares. SLIP-39 allows 20 or 33 words, BIP-39 12 or 24
|
|
|
|
|
raise RuntimeError
|
|
|
|
|
|
|
|
|
|
chunks = utils.chunks(middle, 4) # 4 words on the middle pages
|
|
|
|
|
return first, list(chunks), last
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _confirm_share_words(ctx, share_index, share_words, group_index=None):
|
|
|
|
|
numbered = list(enumerate(share_words))
|
|
|
|
|
|
|
|
|
|
# check three words
|
|
|
|
|
third = len(numbered) // 3
|
|
|
|
|
# if the num of words is not dividable by 3 let's add 1
|
|
|
|
|
# to have more words at the beggining and to check all of them
|
|
|
|
|
if len(numbered) % 3:
|
|
|
|
|
third += 1
|
|
|
|
|
# divide list into thirds, rounding up, so that chunking by `third` always yields
|
|
|
|
|
# three parts (the last one might be shorter)
|
|
|
|
|
third = (len(numbered) + 2) // 3
|
|
|
|
|
|
|
|
|
|
for part in utils.chunks(numbered, third):
|
|
|
|
|
if not await _confirm_word(
|
|
|
|
@ -88,8 +167,8 @@ async def _confirm_share_words(ctx, share_index, share_words, group_index=None):
|
|
|
|
|
async def _confirm_word(
|
|
|
|
|
ctx, share_index, numbered_share_words, count, group_index=None
|
|
|
|
|
):
|
|
|
|
|
# TODO: duplicated words in the choice list
|
|
|
|
|
|
|
|
|
|
# TODO: duplicated words in the choice list
|
|
|
|
|
# shuffle the numbered seed half, slice off the choices we need
|
|
|
|
|
random.shuffle(numbered_share_words)
|
|
|
|
|
numbered_choices = numbered_share_words[: MnemonicWordSelect.NUM_OF_CHOICES]
|
|
|
|
@ -115,22 +194,24 @@ async def _confirm_word(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _show_confirmation_success(
|
|
|
|
|
ctx, share_index, num_of_shares=None, slip39=False, group_index=None
|
|
|
|
|
ctx, share_index=None, num_of_shares=None, group_index=None
|
|
|
|
|
):
|
|
|
|
|
if share_index is None or num_of_shares is None or share_index == num_of_shares - 1:
|
|
|
|
|
if slip39:
|
|
|
|
|
if group_index is None:
|
|
|
|
|
subheader = ("You have finished", "verifying your", "recovery shares.")
|
|
|
|
|
else:
|
|
|
|
|
subheader = (
|
|
|
|
|
"You have finished",
|
|
|
|
|
"verifying your",
|
|
|
|
|
"recovery shares",
|
|
|
|
|
"for group %s." % (group_index + 1),
|
|
|
|
|
)
|
|
|
|
|
if share_index is None: # it is a BIP39 backup
|
|
|
|
|
subheader = ("You have finished", "verifying your", "recovery seed.")
|
|
|
|
|
text = []
|
|
|
|
|
|
|
|
|
|
elif share_index == num_of_shares - 1:
|
|
|
|
|
if group_index is None:
|
|
|
|
|
subheader = ("You have finished", "verifying your", "recovery shares.")
|
|
|
|
|
else:
|
|
|
|
|
subheader = ("You have finished", "verifying your", "recovery seed.")
|
|
|
|
|
subheader = (
|
|
|
|
|
"You have finished",
|
|
|
|
|
"verifying your",
|
|
|
|
|
"recovery shares",
|
|
|
|
|
"for group %s." % (group_index + 1),
|
|
|
|
|
)
|
|
|
|
|
text = []
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
if group_index is None:
|
|
|
|
|
subheader = (
|
|
|
|
@ -198,123 +279,82 @@ async def bip39_show_and_confirm_mnemonic(ctx, mnemonic: str):
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
# display paginated mnemonic on the screen
|
|
|
|
|
await _bip39_show_mnemonic(ctx, words)
|
|
|
|
|
await _show_share_words(ctx, share_words=words)
|
|
|
|
|
|
|
|
|
|
# make the user confirm 2 words from the mnemonic
|
|
|
|
|
# make the user confirm some words from the mnemonic
|
|
|
|
|
if await _confirm_share_words(ctx, None, words):
|
|
|
|
|
await _show_confirmation_success(ctx, None)
|
|
|
|
|
await _show_confirmation_success(ctx)
|
|
|
|
|
break # this share is confirmed, go to next one
|
|
|
|
|
else:
|
|
|
|
|
await _show_confirmation_failure(ctx, None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _bip39_show_mnemonic(ctx, words: list):
|
|
|
|
|
# split mnemonic words into pages
|
|
|
|
|
PER_PAGE = const(4)
|
|
|
|
|
words = list(enumerate(words))
|
|
|
|
|
words = list(utils.chunks(words, PER_PAGE))
|
|
|
|
|
|
|
|
|
|
# display the pages, with a confirmation dialog on the last one
|
|
|
|
|
pages = [_get_mnemonic_page(page) for page in words]
|
|
|
|
|
paginated = Paginated(pages)
|
|
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
|
|
|
|
|
|
def export_displayed_words():
|
|
|
|
|
# export currently displayed mnemonic words into debuglink
|
|
|
|
|
debug.reset_current_words.publish([w for _, w in words[paginated.page]])
|
|
|
|
|
|
|
|
|
|
paginated.on_change = export_displayed_words
|
|
|
|
|
export_displayed_words()
|
|
|
|
|
|
|
|
|
|
await hold_to_confirm(ctx, paginated, ButtonRequestType.ResetDevice)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_mnemonic_page(words: list):
|
|
|
|
|
text = Text("Recovery seed", ui.ICON_RESET)
|
|
|
|
|
for index, word in words:
|
|
|
|
|
text.mono("%2d. %s" % (index + 1, word))
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# SLIP39
|
|
|
|
|
# ===
|
|
|
|
|
|
|
|
|
|
# TODO: yellow cancel style?
|
|
|
|
|
# TODO: loading animation style?
|
|
|
|
|
# TODO: smaller font or tighter rows to fit more text in
|
|
|
|
|
# TODO: icons in checklist
|
|
|
|
|
|
|
|
|
|
# SLIP 39 simple
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_show_checklist_set_shares(ctx):
|
|
|
|
|
async def slip39_show_checklist(ctx, step: int, backup_type: BackupType) -> None:
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of shares")
|
|
|
|
|
checklist.add("Set threshold")
|
|
|
|
|
checklist.add(("Write down and check", "all recovery shares"))
|
|
|
|
|
checklist.select(0)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
if backup_type is BackupType.Slip39_Basic:
|
|
|
|
|
checklist.add("Set number of shares")
|
|
|
|
|
checklist.add("Set threshold")
|
|
|
|
|
checklist.add(("Write down and check", "all recovery shares"))
|
|
|
|
|
elif backup_type is BackupType.Slip39_Advanced:
|
|
|
|
|
checklist.add("Set number of groups")
|
|
|
|
|
checklist.add("Set group threshold")
|
|
|
|
|
checklist.add(("Set size and threshold", "for each group"))
|
|
|
|
|
checklist.select(step)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_show_checklist_set_threshold(ctx, num_of_shares):
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of shares")
|
|
|
|
|
checklist.add("Set threshold")
|
|
|
|
|
checklist.add(("Write down and check", "all recovery shares"))
|
|
|
|
|
checklist.select(1)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_show_checklist_show_shares(ctx, num_of_shares, threshold):
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of shares")
|
|
|
|
|
checklist.add("Set threshold")
|
|
|
|
|
checklist.add(("Write down and check", "all recovery shares"))
|
|
|
|
|
checklist.select(2)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# SLIP 39 group
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_group_show_checklist_set_groups(ctx):
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of groups")
|
|
|
|
|
checklist.add("Set group threshold")
|
|
|
|
|
checklist.add(("Set size and threshold", "for each group"))
|
|
|
|
|
checklist.select(0)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def slip39_prompt_threshold(ctx, num_of_shares, group_id=None):
|
|
|
|
|
count = num_of_shares // 2 + 1
|
|
|
|
|
# min value of share threshold is 2 unless the number of shares is 1
|
|
|
|
|
# number of shares 1 is possible in advnaced slip39
|
|
|
|
|
min_count = min(2, num_of_shares)
|
|
|
|
|
max_count = num_of_shares
|
|
|
|
|
|
|
|
|
|
async def slip39_group_show_checklist_set_group_threshold(ctx, num_of_shares):
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of groups")
|
|
|
|
|
checklist.add("Set group threshold")
|
|
|
|
|
checklist.add(("Set size and threshold", "for each group"))
|
|
|
|
|
checklist.select(1)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
while True:
|
|
|
|
|
shares = Slip39NumInput(
|
|
|
|
|
Slip39NumInput.SET_THRESHOLD, count, min_count, max_count, group_id
|
|
|
|
|
)
|
|
|
|
|
confirmed = await confirm(
|
|
|
|
|
ctx,
|
|
|
|
|
shares,
|
|
|
|
|
ButtonRequestType.ResetDevice,
|
|
|
|
|
cancel="Info",
|
|
|
|
|
confirm="Continue",
|
|
|
|
|
major_confirm=True,
|
|
|
|
|
cancel_style=ButtonDefault,
|
|
|
|
|
)
|
|
|
|
|
count = shares.input.count
|
|
|
|
|
if confirmed:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if group_id is None:
|
|
|
|
|
info = InfoConfirm(
|
|
|
|
|
"The threshold sets the "
|
|
|
|
|
"number of shares "
|
|
|
|
|
"needed to recover your "
|
|
|
|
|
"wallet. Set it to %s and "
|
|
|
|
|
"you will need any %s "
|
|
|
|
|
"of your %s shares." % (count, count, num_of_shares)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
info = InfoConfirm(
|
|
|
|
|
"The threshold sets the "
|
|
|
|
|
"number of shares "
|
|
|
|
|
"needed to form a group. "
|
|
|
|
|
"Set it to %s and you will "
|
|
|
|
|
"need any %s of %s shares "
|
|
|
|
|
"to form Group %s." % (count, count, num_of_shares, group_id + 1)
|
|
|
|
|
)
|
|
|
|
|
await info
|
|
|
|
|
|
|
|
|
|
async def slip39_group_show_checklist_set_shares(ctx, num_of_shares, group_threshold):
|
|
|
|
|
checklist = Checklist("Backup checklist", ui.ICON_RESET)
|
|
|
|
|
checklist.add("Set number of groups")
|
|
|
|
|
checklist.add("Set group threshold")
|
|
|
|
|
checklist.add(("Set size and threshold", "for each group"))
|
|
|
|
|
checklist.select(2)
|
|
|
|
|
return await confirm(
|
|
|
|
|
ctx, checklist, ButtonRequestType.ResetDevice, cancel=None, confirm="Continue"
|
|
|
|
|
)
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_prompt_number_of_shares(ctx, group_id=None):
|
|
|
|
@ -326,8 +366,8 @@ async def slip39_prompt_number_of_shares(ctx, group_id=None):
|
|
|
|
|
max_count = 16
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
shares = ShamirNumInput(
|
|
|
|
|
ShamirNumInput.SET_SHARES, count, min_count, max_count, group_id
|
|
|
|
|
shares = Slip39NumInput(
|
|
|
|
|
Slip39NumInput.SET_SHARES, count, min_count, max_count, group_id
|
|
|
|
|
)
|
|
|
|
|
confirmed = await confirm(
|
|
|
|
|
ctx,
|
|
|
|
@ -365,13 +405,33 @@ async def slip39_prompt_number_of_shares(ctx, group_id=None):
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_prompt_number_of_groups(ctx):
|
|
|
|
|
async def slip39_basic_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
# warn user about mnemonic safety
|
|
|
|
|
await show_backup_warning(ctx, slip39=True)
|
|
|
|
|
|
|
|
|
|
for index, share in enumerate(shares):
|
|
|
|
|
share_words = share.split(" ")
|
|
|
|
|
while True:
|
|
|
|
|
# display paginated share on the screen
|
|
|
|
|
await _show_share_words(ctx, share_words, index)
|
|
|
|
|
|
|
|
|
|
# make the user confirm words from the share
|
|
|
|
|
if await _confirm_share_words(ctx, index, share_words):
|
|
|
|
|
await _show_confirmation_success(
|
|
|
|
|
ctx, share_index=index, num_of_shares=len(shares)
|
|
|
|
|
)
|
|
|
|
|
break # this share is confirmed, go to next one
|
|
|
|
|
else:
|
|
|
|
|
await _show_confirmation_failure(ctx, index)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_advanced_prompt_number_of_groups(ctx):
|
|
|
|
|
count = 5
|
|
|
|
|
min_count = 2
|
|
|
|
|
max_count = 16
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
shares = ShamirNumInput(ShamirNumInput.SET_GROUPS, count, min_count, max_count)
|
|
|
|
|
shares = Slip39NumInput(Slip39NumInput.SET_GROUPS, count, min_count, max_count)
|
|
|
|
|
confirmed = await confirm(
|
|
|
|
|
ctx,
|
|
|
|
|
shares,
|
|
|
|
@ -398,14 +458,14 @@ async def slip39_prompt_number_of_groups(ctx):
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_prompt_group_threshold(ctx, num_of_groups):
|
|
|
|
|
async def slip39_advanced_prompt_group_threshold(ctx, num_of_groups):
|
|
|
|
|
count = num_of_groups // 2 + 1
|
|
|
|
|
min_count = 1
|
|
|
|
|
max_count = num_of_groups
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
shares = ShamirNumInput(
|
|
|
|
|
ShamirNumInput.SET_GROUP_THRESHOLD, count, min_count, max_count
|
|
|
|
|
shares = Slip39NumInput(
|
|
|
|
|
Slip39NumInput.SET_GROUP_THRESHOLD, count, min_count, max_count
|
|
|
|
|
)
|
|
|
|
|
confirmed = await confirm(
|
|
|
|
|
ctx,
|
|
|
|
@ -431,72 +491,7 @@ async def slip39_prompt_group_threshold(ctx, num_of_groups):
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_prompt_threshold(ctx, num_of_shares, group_id=None):
|
|
|
|
|
count = num_of_shares // 2 + 1
|
|
|
|
|
min_count = min(2, num_of_shares)
|
|
|
|
|
max_count = num_of_shares
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
shares = ShamirNumInput(
|
|
|
|
|
ShamirNumInput.SET_THRESHOLD, count, min_count, max_count, group_id
|
|
|
|
|
)
|
|
|
|
|
confirmed = await confirm(
|
|
|
|
|
ctx,
|
|
|
|
|
shares,
|
|
|
|
|
ButtonRequestType.ResetDevice,
|
|
|
|
|
cancel="Info",
|
|
|
|
|
confirm="Continue",
|
|
|
|
|
major_confirm=True,
|
|
|
|
|
cancel_style=ButtonDefault,
|
|
|
|
|
)
|
|
|
|
|
count = shares.input.count
|
|
|
|
|
if confirmed:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if group_id is None:
|
|
|
|
|
info = InfoConfirm(
|
|
|
|
|
"The threshold sets the "
|
|
|
|
|
"number of shares "
|
|
|
|
|
"needed to recover your "
|
|
|
|
|
"wallet. Set it to %s and "
|
|
|
|
|
"you will need any %s "
|
|
|
|
|
"of your %s shares." % (count, count, num_of_shares)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
info = InfoConfirm(
|
|
|
|
|
"The threshold sets the "
|
|
|
|
|
"number of shares "
|
|
|
|
|
"needed to form a group. "
|
|
|
|
|
"Set it to %s and you will "
|
|
|
|
|
"need any %s of %s shares "
|
|
|
|
|
"to form Group %s." % (count, count, num_of_shares, group_id + 1)
|
|
|
|
|
)
|
|
|
|
|
await info
|
|
|
|
|
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
# warn user about mnemonic safety
|
|
|
|
|
await show_backup_warning(ctx, slip39=True)
|
|
|
|
|
|
|
|
|
|
for index, share in enumerate(shares):
|
|
|
|
|
share_words = share.split(" ")
|
|
|
|
|
while True:
|
|
|
|
|
# display paginated share on the screen
|
|
|
|
|
await _slip39_show_share_words(ctx, index, share_words)
|
|
|
|
|
|
|
|
|
|
# make the user confirm words from the share
|
|
|
|
|
if await _confirm_share_words(ctx, index, share_words):
|
|
|
|
|
await _show_confirmation_success(
|
|
|
|
|
ctx, index, num_of_shares=len(shares), slip39=True
|
|
|
|
|
)
|
|
|
|
|
break # this share is confirmed, go to next one
|
|
|
|
|
else:
|
|
|
|
|
await _show_confirmation_failure(ctx, index)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def slip39_group_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
async def slip39_advanced_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
# warn user about mnemonic safety
|
|
|
|
|
await show_backup_warning(ctx, slip39=True)
|
|
|
|
|
|
|
|
|
@ -505,9 +500,7 @@ async def slip39_group_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
share_words = share.split(" ")
|
|
|
|
|
while True:
|
|
|
|
|
# display paginated share on the screen
|
|
|
|
|
await _slip39_show_share_words(
|
|
|
|
|
ctx, share_index, share_words, group_index
|
|
|
|
|
)
|
|
|
|
|
await _show_share_words(ctx, share_words, share_index, group_index)
|
|
|
|
|
|
|
|
|
|
# make the user confirm words from the share
|
|
|
|
|
if await _confirm_share_words(
|
|
|
|
@ -515,9 +508,8 @@ async def slip39_group_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
):
|
|
|
|
|
await _show_confirmation_success(
|
|
|
|
|
ctx,
|
|
|
|
|
share_index,
|
|
|
|
|
share_index=share_index,
|
|
|
|
|
num_of_shares=len(shares),
|
|
|
|
|
slip39=True,
|
|
|
|
|
group_index=group_index,
|
|
|
|
|
)
|
|
|
|
|
break # this share is confirmed, go to next one
|
|
|
|
@ -525,88 +517,7 @@ async def slip39_group_show_and_confirm_shares(ctx, shares):
|
|
|
|
|
await _show_confirmation_failure(ctx, share_index)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _slip39_show_share_words(ctx, share_index, share_words, group_index=None):
|
|
|
|
|
first, chunks, last = _slip39_split_share_into_pages(share_words)
|
|
|
|
|
|
|
|
|
|
if share_index is None:
|
|
|
|
|
header_title = "Recovery seed"
|
|
|
|
|
elif group_index is None:
|
|
|
|
|
header_title = "Recovery share #%s" % (share_index + 1)
|
|
|
|
|
else:
|
|
|
|
|
header_title = "Group %s - Share %s" % ((group_index + 1), (share_index + 1))
|
|
|
|
|
header_icon = ui.ICON_RESET
|
|
|
|
|
pages = [] # ui page components
|
|
|
|
|
shares_words_check = [] # check we display correct data
|
|
|
|
|
|
|
|
|
|
# first page
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
text.bold("Write down these")
|
|
|
|
|
text.bold("%s words:" % len(share_words))
|
|
|
|
|
text.br_half()
|
|
|
|
|
for index, word in first:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# middle pages
|
|
|
|
|
for chunk in chunks:
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
for index, word in chunk:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# last page
|
|
|
|
|
text = Text(header_title, header_icon)
|
|
|
|
|
for index, word in last:
|
|
|
|
|
text.mono("%s. %s" % (index + 1, word))
|
|
|
|
|
shares_words_check.append(word)
|
|
|
|
|
text.br_half()
|
|
|
|
|
text.bold("I wrote down all %s" % len(share_words))
|
|
|
|
|
text.bold("words in order.")
|
|
|
|
|
pages.append(text)
|
|
|
|
|
|
|
|
|
|
# pagination
|
|
|
|
|
paginated = Paginated(pages)
|
|
|
|
|
|
|
|
|
|
if __debug__:
|
|
|
|
|
|
|
|
|
|
word_pages = [first] + chunks + [last]
|
|
|
|
|
|
|
|
|
|
def export_displayed_words():
|
|
|
|
|
# export currently displayed mnemonic words into debuglink
|
|
|
|
|
words = [w for _, w in word_pages[paginated.page]]
|
|
|
|
|
debug.reset_current_words.publish(words)
|
|
|
|
|
|
|
|
|
|
paginated.on_change = export_displayed_words
|
|
|
|
|
export_displayed_words()
|
|
|
|
|
|
|
|
|
|
# make sure we display correct data
|
|
|
|
|
utils.ensure(share_words == shares_words_check)
|
|
|
|
|
|
|
|
|
|
# confirm the share
|
|
|
|
|
await hold_to_confirm(ctx, paginated) # TODO: customize the loader here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _slip39_split_share_into_pages(share_words):
|
|
|
|
|
share = list(enumerate(share_words)) # we need to keep track of the word indices
|
|
|
|
|
first = share[:2] # two words on the first page
|
|
|
|
|
length = len(share_words)
|
|
|
|
|
if length == 20:
|
|
|
|
|
middle = share[2:-2]
|
|
|
|
|
last = share[-2:] # two words on the last page
|
|
|
|
|
elif length == 33:
|
|
|
|
|
middle = share[2:]
|
|
|
|
|
last = [] # no words at the last page, because it does not add up
|
|
|
|
|
else:
|
|
|
|
|
# Invalid number of shares. SLIP-39 allows 20 or 33 words.
|
|
|
|
|
raise RuntimeError
|
|
|
|
|
|
|
|
|
|
chunks = utils.chunks(middle, 4) # 4 words on the middle pages
|
|
|
|
|
return first, list(chunks), last
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ShamirNumInput(ui.Component):
|
|
|
|
|
class Slip39NumInput(ui.Component):
|
|
|
|
|
SET_SHARES = object()
|
|
|
|
|
SET_THRESHOLD = object()
|
|
|
|
|
SET_GROUPS = object()
|
|
|
|
@ -629,18 +540,18 @@ class ShamirNumInput(ui.Component):
|
|
|
|
|
count = self.input.count
|
|
|
|
|
|
|
|
|
|
# render the headline
|
|
|
|
|
if self.step is ShamirNumInput.SET_SHARES:
|
|
|
|
|
if self.step is Slip39NumInput.SET_SHARES:
|
|
|
|
|
header = "Set num. of shares"
|
|
|
|
|
elif self.step is ShamirNumInput.SET_THRESHOLD:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_THRESHOLD:
|
|
|
|
|
header = "Set threshold"
|
|
|
|
|
elif self.step is ShamirNumInput.SET_GROUPS:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_GROUPS:
|
|
|
|
|
header = "Set num. of groups"
|
|
|
|
|
elif self.step is ShamirNumInput.SET_GROUP_THRESHOLD:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_GROUP_THRESHOLD:
|
|
|
|
|
header = "Set group threshold"
|
|
|
|
|
ui.header(header, ui.ICON_RESET, ui.TITLE_GREY, ui.BG, ui.ORANGE_ICON)
|
|
|
|
|
|
|
|
|
|
# render the counter
|
|
|
|
|
if self.step is ShamirNumInput.SET_SHARES:
|
|
|
|
|
if self.step is Slip39NumInput.SET_SHARES:
|
|
|
|
|
if self.group_id is None:
|
|
|
|
|
first_line_text = "%s people or locations" % count
|
|
|
|
|
second_line_text = "will each hold one share."
|
|
|
|
@ -651,7 +562,7 @@ class ShamirNumInput(ui.Component):
|
|
|
|
|
12, 130, first_line_text, ui.NORMAL, ui.FG, ui.BG, ui.WIDTH - 12
|
|
|
|
|
)
|
|
|
|
|
ui.display.text(12, 156, second_line_text, ui.NORMAL, ui.FG, ui.BG)
|
|
|
|
|
elif self.step is ShamirNumInput.SET_THRESHOLD:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_THRESHOLD:
|
|
|
|
|
if self.group_id is None:
|
|
|
|
|
first_line_text = "For recovery you need"
|
|
|
|
|
second_line_text = "any %s of the shares." % count
|
|
|
|
@ -662,14 +573,14 @@ class ShamirNumInput(ui.Component):
|
|
|
|
|
ui.display.text(
|
|
|
|
|
12, 156, second_line_text, ui.NORMAL, ui.FG, ui.BG, ui.WIDTH - 12
|
|
|
|
|
)
|
|
|
|
|
elif self.step is ShamirNumInput.SET_GROUPS:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_GROUPS:
|
|
|
|
|
ui.display.text(
|
|
|
|
|
12, 130, "A group is made up of", ui.NORMAL, ui.FG, ui.BG
|
|
|
|
|
)
|
|
|
|
|
ui.display.text(
|
|
|
|
|
12, 156, "recovery shares.", ui.NORMAL, ui.FG, ui.BG, ui.WIDTH - 12
|
|
|
|
|
)
|
|
|
|
|
elif self.step is ShamirNumInput.SET_GROUP_THRESHOLD:
|
|
|
|
|
elif self.step is Slip39NumInput.SET_GROUP_THRESHOLD:
|
|
|
|
|
ui.display.text(
|
|
|
|
|
12, 130, "The required number of", ui.NORMAL, ui.FG, ui.BG
|
|
|
|
|
)
|
|
|
|
@ -722,3 +633,27 @@ class MnemonicWordSelect(ui.Layout):
|
|
|
|
|
raise ui.Result(word)
|
|
|
|
|
|
|
|
|
|
return fn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def show_reset_device_warning(ctx, backup_type: BackupType = BackupType.Bip39):
|
|
|
|
|
text = Text("Create new wallet", ui.ICON_RESET, new_lines=False)
|
|
|
|
|
if backup_type == BackupType.Slip39_Basic:
|
|
|
|
|
text.bold("Create a new wallet")
|
|
|
|
|
text.br()
|
|
|
|
|
text.bold("with Shamir Backup?")
|
|
|
|
|
elif backup_type == BackupType.Slip39_Advanced:
|
|
|
|
|
text.bold("Create a new wallet")
|
|
|
|
|
text.br()
|
|
|
|
|
text.bold("with Super Shamir?")
|
|
|
|
|
else:
|
|
|
|
|
text.bold("Do you want to create")
|
|
|
|
|
text.br()
|
|
|
|
|
text.bold("a new wallet?")
|
|
|
|
|
text.br()
|
|
|
|
|
text.br_half()
|
|
|
|
|
text.normal("By continuing you agree")
|
|
|
|
|
text.br()
|
|
|
|
|
text.normal("to")
|
|
|
|
|
text.bold("https://trezor.io/tos")
|
|
|
|
|
await require_confirm(ctx, text, ButtonRequestType.ResetDevice, major_confirm=True)
|
|
|
|
|
await LoadingAnimation()
|