|
|
|
@ -9,17 +9,22 @@ from . import recover
|
|
|
|
|
|
|
|
|
|
from apps.common import mnemonic, storage
|
|
|
|
|
from apps.common.layout import show_success
|
|
|
|
|
from apps.common.storage import (
|
|
|
|
|
device as storage_device,
|
|
|
|
|
recovery as storage_recovery,
|
|
|
|
|
recovery_shares as storage_recovery_shares,
|
|
|
|
|
)
|
|
|
|
|
from apps.management import backup_types
|
|
|
|
|
from apps.management.recovery_device import layout
|
|
|
|
|
|
|
|
|
|
if False:
|
|
|
|
|
from typing import Optional, Tuple
|
|
|
|
|
from typing import Optional, Tuple, cast
|
|
|
|
|
from trezor.messages.ResetDevice import EnumTypeBackupType
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def recovery_homescreen() -> None:
|
|
|
|
|
# recovery process does not communicate on the wire
|
|
|
|
|
ctx = wire.DummyContext()
|
|
|
|
|
ctx = cast(wire.Context, wire.DummyContext()) # TODO
|
|
|
|
|
try:
|
|
|
|
|
await recovery_process(ctx)
|
|
|
|
|
finally:
|
|
|
|
@ -33,9 +38,9 @@ async def recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
try:
|
|
|
|
|
result = await _continue_recovery_process(ctx)
|
|
|
|
|
except recover.RecoveryAborted:
|
|
|
|
|
dry_run = storage.recovery.is_dry_run()
|
|
|
|
|
dry_run = storage_recovery.is_dry_run()
|
|
|
|
|
if dry_run:
|
|
|
|
|
storage.recovery.end_progress()
|
|
|
|
|
storage_recovery.end_progress()
|
|
|
|
|
else:
|
|
|
|
|
storage.wipe()
|
|
|
|
|
raise wire.ActionCancelled("Cancelled")
|
|
|
|
@ -44,7 +49,7 @@ async def recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
|
|
|
|
|
async def _continue_recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
# gather the current recovery state from storage
|
|
|
|
|
dry_run = storage.recovery.is_dry_run()
|
|
|
|
|
dry_run = storage_recovery.is_dry_run()
|
|
|
|
|
word_count, backup_type = recover.load_slip39_state()
|
|
|
|
|
|
|
|
|
|
# Both word_count and backup_type are derived from the same data. Both will be
|
|
|
|
@ -54,6 +59,7 @@ async def _continue_recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
is_first_step = backup_type is None
|
|
|
|
|
|
|
|
|
|
if not is_first_step:
|
|
|
|
|
assert word_count is not None
|
|
|
|
|
# If we continue recovery, show starting screen with word count immediately.
|
|
|
|
|
await _request_share_first_screen(ctx, word_count)
|
|
|
|
|
|
|
|
|
@ -64,6 +70,7 @@ async def _continue_recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
word_count = await _request_word_count(ctx, dry_run)
|
|
|
|
|
# ...and only then show the starting screen with word count.
|
|
|
|
|
await _request_share_first_screen(ctx, word_count)
|
|
|
|
|
assert word_count is not None
|
|
|
|
|
|
|
|
|
|
# ask for mnemonic words one by one
|
|
|
|
|
words = await layout.request_mnemonic(ctx, word_count, backup_type)
|
|
|
|
@ -81,6 +88,7 @@ async def _continue_recovery_process(ctx: wire.Context) -> Success:
|
|
|
|
|
except MnemonicError:
|
|
|
|
|
await layout.show_invalid_mnemonic(ctx, word_count)
|
|
|
|
|
|
|
|
|
|
assert backup_type is not None
|
|
|
|
|
if dry_run:
|
|
|
|
|
result = await _finish_recovery_dry_run(ctx, secret, backup_type)
|
|
|
|
|
else:
|
|
|
|
@ -104,17 +112,17 @@ async def _finish_recovery_dry_run(
|
|
|
|
|
# Check that the identifier and iteration exponent match as well
|
|
|
|
|
if is_slip39:
|
|
|
|
|
result &= (
|
|
|
|
|
storage.device.get_slip39_identifier()
|
|
|
|
|
== storage.recovery.get_slip39_identifier()
|
|
|
|
|
storage_device.get_slip39_identifier()
|
|
|
|
|
== storage_recovery.get_slip39_identifier()
|
|
|
|
|
)
|
|
|
|
|
result &= (
|
|
|
|
|
storage.device.get_slip39_iteration_exponent()
|
|
|
|
|
== storage.recovery.get_slip39_iteration_exponent()
|
|
|
|
|
storage_device.get_slip39_iteration_exponent()
|
|
|
|
|
== storage_recovery.get_slip39_iteration_exponent()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await layout.show_dry_run_result(ctx, result, is_slip39)
|
|
|
|
|
|
|
|
|
|
storage.recovery.end_progress()
|
|
|
|
|
storage_recovery.end_progress()
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
return Success("The seed is valid and matches the one in the device")
|
|
|
|
@ -128,21 +136,21 @@ async def _finish_recovery(
|
|
|
|
|
if backup_type is None:
|
|
|
|
|
raise RuntimeError
|
|
|
|
|
|
|
|
|
|
storage.device.store_mnemonic_secret(
|
|
|
|
|
storage_device.store_mnemonic_secret(
|
|
|
|
|
secret, backup_type, needs_backup=False, no_backup=False
|
|
|
|
|
)
|
|
|
|
|
if backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced):
|
|
|
|
|
identifier = storage.recovery.get_slip39_identifier()
|
|
|
|
|
exponent = storage.recovery.get_slip39_iteration_exponent()
|
|
|
|
|
identifier = storage_recovery.get_slip39_identifier()
|
|
|
|
|
exponent = storage_recovery.get_slip39_iteration_exponent()
|
|
|
|
|
if identifier is None or exponent is None:
|
|
|
|
|
# Identifier and exponent need to be stored in storage at this point
|
|
|
|
|
raise RuntimeError
|
|
|
|
|
storage.device.set_slip39_identifier(identifier)
|
|
|
|
|
storage.device.set_slip39_iteration_exponent(exponent)
|
|
|
|
|
storage_device.set_slip39_identifier(identifier)
|
|
|
|
|
storage_device.set_slip39_iteration_exponent(exponent)
|
|
|
|
|
|
|
|
|
|
await show_success(ctx, ("You have successfully", "recovered your wallet."))
|
|
|
|
|
|
|
|
|
|
storage.recovery.end_progress()
|
|
|
|
|
storage_recovery.end_progress()
|
|
|
|
|
return Success(message="Device recovered")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -162,12 +170,13 @@ async def _process_words(
|
|
|
|
|
|
|
|
|
|
share = None
|
|
|
|
|
if not is_slip39: # BIP-39
|
|
|
|
|
secret = recover.process_bip39(words)
|
|
|
|
|
secret = recover.process_bip39(words) # type: Optional[bytes]
|
|
|
|
|
else:
|
|
|
|
|
secret, share = recover.process_slip39(words)
|
|
|
|
|
|
|
|
|
|
backup_type = backup_types.infer_backup_type(is_slip39, share)
|
|
|
|
|
if secret is None:
|
|
|
|
|
if secret is None: # SLIP-39
|
|
|
|
|
assert share is not None
|
|
|
|
|
if share.group_count and share.group_count > 1:
|
|
|
|
|
await layout.show_group_share_success(ctx, share.index, share.group_index)
|
|
|
|
|
await _request_share_next_screen(ctx)
|
|
|
|
@ -177,7 +186,7 @@ async def _process_words(
|
|
|
|
|
|
|
|
|
|
async def _request_share_first_screen(ctx: wire.Context, word_count: int) -> None:
|
|
|
|
|
if backup_types.is_slip39_word_count(word_count):
|
|
|
|
|
remaining = storage.recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
remaining = storage_recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
if remaining:
|
|
|
|
|
await _request_share_next_screen(ctx)
|
|
|
|
|
else:
|
|
|
|
@ -193,8 +202,8 @@ async def _request_share_first_screen(ctx: wire.Context, word_count: int) -> Non
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def _request_share_next_screen(ctx: wire.Context) -> None:
|
|
|
|
|
remaining = storage.recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
group_count = storage.recovery.get_slip39_group_count()
|
|
|
|
|
remaining = storage_recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
group_count = storage_recovery.get_slip39_group_count()
|
|
|
|
|
if not remaining:
|
|
|
|
|
# 'remaining' should be stored at this point
|
|
|
|
|
raise RuntimeError
|
|
|
|
@ -217,7 +226,9 @@ async def _show_remaining_groups_and_shares(ctx: wire.Context) -> None:
|
|
|
|
|
"""
|
|
|
|
|
Show info dialog for Slip39 Advanced - what shares are to be entered.
|
|
|
|
|
"""
|
|
|
|
|
shares_remaining = storage.recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
shares_remaining = storage_recovery.fetch_slip39_remaining_shares()
|
|
|
|
|
# should be stored at this point
|
|
|
|
|
assert shares_remaining
|
|
|
|
|
|
|
|
|
|
groups = set()
|
|
|
|
|
first_entered_index = -1
|
|
|
|
@ -228,17 +239,18 @@ async def _show_remaining_groups_and_shares(ctx: wire.Context) -> None:
|
|
|
|
|
share = None
|
|
|
|
|
for index, remaining in enumerate(shares_remaining):
|
|
|
|
|
if 0 <= remaining < slip39.MAX_SHARE_COUNT:
|
|
|
|
|
m = storage.recovery_shares.fetch_group(index)[0]
|
|
|
|
|
m = storage_recovery_shares.fetch_group(index)[0]
|
|
|
|
|
if not share:
|
|
|
|
|
share = slip39.decode_mnemonic(m)
|
|
|
|
|
identifier = m.split(" ")[0:3]
|
|
|
|
|
groups.add((remaining, tuple(identifier)))
|
|
|
|
|
elif remaining == slip39.MAX_SHARE_COUNT: # no shares yet
|
|
|
|
|
identifier = storage.recovery_shares.fetch_group(first_entered_index)[
|
|
|
|
|
identifier = storage_recovery_shares.fetch_group(first_entered_index)[
|
|
|
|
|
0
|
|
|
|
|
].split(" ")[0:2]
|
|
|
|
|
groups.add((remaining, tuple(identifier)))
|
|
|
|
|
|
|
|
|
|
assert share # share needs to be set
|
|
|
|
|
return await layout.show_remaining_shares(
|
|
|
|
|
ctx, groups, shares_remaining, share.group_threshold
|
|
|
|
|
)
|
|
|
|
|