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/recovery_device/recover.py

118 lines
4.3 KiB

import storage.recovery
import storage.recovery_shares
from trezor.crypto import bip39, slip39
from trezor.errors import MnemonicError
from .. import backup_types
if False:
from trezor.messages.ResetDevice import EnumTypeBackupType
from typing import Union
class RecoveryAborted(Exception):
pass
def process_bip39(words: str) -> bytes:
"""
Receives single mnemonic and processes it. Returns what is then stored
in the storage, which is the mnemonic itself for BIP-39.
"""
if not bip39.check(words):
raise MnemonicError
return words.encode()
def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
"""
Processes a single mnemonic share. Returns the encrypted master secret
(or None if more shares are needed) and the share's group index and member index.
"""
share = slip39.decode_mnemonic(words)
remaining = storage.recovery.fetch_slip39_remaining_shares()
# if this is the first share, parse and store metadata
if not remaining:
storage.recovery.set_slip39_group_count(share.group_count)
storage.recovery.set_slip39_iteration_exponent(share.iteration_exponent)
storage.recovery.set_slip39_identifier(share.identifier)
storage.recovery.set_slip39_remaining_shares(
share.threshold - 1, share.group_index
)
storage.recovery_shares.set(share.index, share.group_index, words)
# if share threshold and group threshold are 1
# we can calculate the secret right away
if share.threshold == 1 and share.group_threshold == 1:
identifier, iteration_exponent, secret = slip39.recover_ems([words])
return secret, share
else:
# we need more shares
return None, share
# These should be checked by UI before so it's a Runtime exception otherwise
if share.identifier != storage.recovery.get_slip39_identifier():
raise RuntimeError("Slip39: Share identifiers do not match")
if share.iteration_exponent != storage.recovery.get_slip39_iteration_exponent():
raise RuntimeError("Slip39: Share exponents do not match")
if storage.recovery_shares.get(share.index, share.group_index):
raise RuntimeError("Slip39: This mnemonic was already entered")
if share.group_count != storage.recovery.get_slip39_group_count():
raise RuntimeError("Slip39: Group count does not match")
remaining_for_share = (
storage.recovery.get_slip39_remaining_shares(share.group_index)
or share.threshold
)
storage.recovery.set_slip39_remaining_shares(
remaining_for_share - 1, share.group_index
)
remaining[share.group_index] = remaining_for_share - 1
storage.recovery_shares.set(share.index, share.group_index, words)
if remaining.count(0) < share.group_threshold:
# we need more shares
return None, share
if share.group_count > 1:
mnemonics = []
for i, r in enumerate(remaining):
# if we have multiple groups pass only the ones with threshold reached
if r == 0:
group = storage.recovery_shares.fetch_group(i)
mnemonics.extend(group)
else:
# in case of slip39 basic we only need the first and only group
mnemonics = storage.recovery_shares.fetch_group(0)
identifier, iteration_exponent, secret = slip39.recover_ems(mnemonics)
return secret, share
if False:
Slip39State = Union[tuple[int, EnumTypeBackupType], tuple[None, None]]
def load_slip39_state() -> Slip39State:
previous_mnemonics = fetch_previous_mnemonics()
if not previous_mnemonics:
return None, None
# let's get the first mnemonic and decode it to find out the metadata
mnemonic = next(p[0] for p in previous_mnemonics if p)
share = slip39.decode_mnemonic(mnemonic)
word_count = len(mnemonic.split(" "))
return word_count, backup_types.infer_backup_type(True, share)
def fetch_previous_mnemonics() -> list[list[str]] | None:
mnemonics = []
if not storage.recovery.get_slip39_group_count():
return None
for i in range(storage.recovery.get_slip39_group_count()):
mnemonics.append(storage.recovery_shares.fetch_group(i))
if not any(p for p in mnemonics):
return None
return mnemonics