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.
trezor-firmware/core/src/apps/management/reset_device.py

181 lines
6.4 KiB

from trezor import config, ui, wire
from trezor.crypto import bip39, hashlib, random, slip39
from trezor.messages import ButtonRequestType, ResetDeviceBackupType
from trezor.messages.EntropyAck import EntropyAck
from trezor.messages.EntropyRequest import EntropyRequest
from trezor.messages.Success import Success
from trezor.pin import pin_to_int
from trezor.ui.loader import LoadingAnimation
from trezor.ui.text import Text
from apps.common import mnemonic, storage
from apps.common.confirm import require_confirm
from apps.management.change_pin import request_pin_confirm
from apps.management.common import layout
if __debug__:
from apps import debug
if False:
from trezor.messages.ResetDevice import ResetDevice
_DEFAULT_BACKUP_TYPE = ResetDeviceBackupType.Bip39
async def reset_device(ctx: wire.Context, msg: ResetDevice) -> Success:
# validate parameters and device state
_validate_reset_device(msg)
is_slip39_simple = msg.backup_type == ResetDeviceBackupType.Slip39_Single_Group
# make sure user knows he's setting up a new wallet
await _show_reset_device_warning(ctx, is_slip39_simple)
# request new PIN
if msg.pin_protection:
newpin = await request_pin_confirm(ctx)
else:
newpin = ""
# generate and display internal entropy
int_entropy = random.bytes(32)
if __debug__:
debug.reset_internal_entropy = int_entropy
if msg.display_random:
await layout.show_internal_entropy(ctx, int_entropy)
# request external entropy and compute the master secret
entropy_ack = await ctx.call(EntropyRequest(), EntropyAck)
ext_entropy = entropy_ack.entropy
# For SLIP-39 this is the Encrypted Master Secret
secret = _compute_secret_from_entropy(int_entropy, ext_entropy, msg.strength)
if is_slip39_simple:
storage.device.set_slip39_identifier(slip39.generate_random_identifier())
storage.device.set_slip39_iteration_exponent(slip39.DEFAULT_ITERATION_EXPONENT)
# should we back up the wallet now?
if not msg.no_backup and not msg.skip_backup:
if not await layout.confirm_backup(ctx):
if not await layout.confirm_backup_again(ctx):
msg.skip_backup = True
# generate and display backup information for the master secret
if not msg.no_backup and not msg.skip_backup:
if is_slip39_simple:
await backup_slip39_wallet(ctx, secret)
else:
await backup_bip39_wallet(ctx, secret)
# write PIN into storage
if not config.change_pin(pin_to_int(""), pin_to_int(newpin)):
raise wire.ProcessError("Could not change PIN")
# write settings and master secret into storage
storage.device.load_settings(
label=msg.label, use_passphrase=msg.passphrase_protection
)
if is_slip39_simple:
storage.device.store_mnemonic_secret(
secret, # this is the EMS in SLIP-39 terminology
mnemonic.TYPE_SLIP39,
needs_backup=msg.skip_backup,
no_backup=msg.no_backup,
)
else:
# in BIP-39 we store mnemonic string instead of the secret
storage.device.store_mnemonic_secret(
bip39.from_data(secret).encode(),
mnemonic.TYPE_BIP39,
needs_backup=msg.skip_backup,
no_backup=msg.no_backup,
)
# if we backed up the wallet, show success message
if not msg.no_backup and not msg.skip_backup:
await layout.show_backup_success(ctx)
return Success(message="Initialized")
async def backup_slip39_wallet(
ctx: wire.Context, encrypted_master_secret: bytes
) -> None:
# get number of shares
await layout.slip39_show_checklist_set_shares(ctx)
shares_count = await layout.slip39_prompt_number_of_shares(ctx)
# get threshold
await layout.slip39_show_checklist_set_threshold(ctx, shares_count)
threshold = await layout.slip39_prompt_threshold(ctx, shares_count)
# generate the mnemonics
mnemonics = slip39.generate_single_group_mnemonics_from_data(
encrypted_master_secret,
storage.device.get_slip39_identifier(),
threshold,
shares_count,
storage.device.get_slip39_iteration_exponent(),
)
# show and confirm individual shares
await layout.slip39_show_checklist_show_shares(ctx, shares_count, threshold)
await layout.slip39_show_and_confirm_shares(ctx, mnemonics)
async def backup_bip39_wallet(ctx: wire.Context, secret: bytes) -> None:
mnemonic = bip39.from_data(secret)
await layout.bip39_show_and_confirm_mnemonic(ctx, mnemonic)
def _validate_reset_device(msg: ResetDevice) -> None:
msg.backup_type = msg.backup_type or _DEFAULT_BACKUP_TYPE
if msg.backup_type not in (
ResetDeviceBackupType.Bip39,
ResetDeviceBackupType.Slip39_Single_Group,
):
raise wire.ProcessError("Backup type not implemented.")
if msg.strength not in (128, 256):
if msg.backup_type == ResetDeviceBackupType.Slip39_Single_Group:
raise wire.ProcessError("Invalid strength (has to be 128 or 256 bits)")
elif msg.strength != 192:
raise wire.ProcessError("Invalid strength (has to be 128, 192 or 256 bits)")
if msg.display_random and (msg.skip_backup or msg.no_backup):
raise wire.ProcessError("Can't show internal entropy when backup is skipped")
if storage.is_initialized():
raise wire.UnexpectedMessage("Already initialized")
def _compute_secret_from_entropy(
int_entropy: bytes, ext_entropy: bytes, strength_in_bytes: int
) -> bytes:
# combine internal and external entropy
ehash = hashlib.sha256()
ehash.update(int_entropy)
ehash.update(ext_entropy)
entropy = ehash.digest()
# take a required number of bytes
strength = strength_in_bytes // 8
secret = entropy[:strength]
return secret
async def _show_reset_device_warning(ctx, use_slip39: bool):
text = Text("Create new wallet", ui.ICON_RESET, new_lines=False)
if use_slip39:
text.bold("Create a new wallet")
text.br()
text.bold("with Shamir Backup?")
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()