mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-16 03:18:09 +00:00
common: introduce mnemonic module
To provide another layer between seed and mnemonic. First step to introduce SLIP-39
This commit is contained in:
parent
d86abc473f
commit
5c37e8dae2
@ -2,7 +2,7 @@ from trezor import wire
|
||||
from trezor.crypto import bip32
|
||||
|
||||
from apps.cardano import SEED_NAMESPACE
|
||||
from apps.common import cache, storage
|
||||
from apps.common import cache, mnemonic, storage
|
||||
from apps.common.request_passphrase import protect_by_passphrase
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
if passphrase is None:
|
||||
passphrase = await protect_by_passphrase(ctx)
|
||||
cache.set_passphrase(passphrase)
|
||||
root = bip32.from_mnemonic_cardano(storage.get_mnemonic(), passphrase)
|
||||
root = bip32.from_mnemonic_cardano(mnemonic.restore(), passphrase)
|
||||
|
||||
# derive the namespaced root node
|
||||
for i in SEED_NAMESPACE[0]:
|
||||
|
46
src/apps/common/mnemonic.py
Normal file
46
src/apps/common/mnemonic.py
Normal file
@ -0,0 +1,46 @@
|
||||
from trezor import ui
|
||||
from trezor.crypto import bip39
|
||||
|
||||
from apps.common import storage
|
||||
|
||||
TYPE_BIP39 = 0
|
||||
|
||||
|
||||
def get() -> (bytes, int):
|
||||
mnemonic_secret = storage.get_mnemonic_secret()
|
||||
mnemonic_type = storage.get_mnemonic_type()
|
||||
return mnemonic_secret, mnemonic_type
|
||||
|
||||
|
||||
def get_seed(passphrase: str = ""):
|
||||
secret, mnemonic_type = get()
|
||||
_start_progress()
|
||||
if mnemonic_type == TYPE_BIP39:
|
||||
return bip39.seed(secret.decode(), passphrase, _render_progress)
|
||||
|
||||
|
||||
def process(mnemonics: list, mnemonic_type: int):
|
||||
if mnemonic_type == TYPE_BIP39:
|
||||
return mnemonics[0].encode()
|
||||
else:
|
||||
raise RuntimeError("Unknown mnemonic type")
|
||||
|
||||
|
||||
def restore() -> str:
|
||||
secret, mnemonic_type = get()
|
||||
if mnemonic_type == TYPE_BIP39:
|
||||
return secret.decode()
|
||||
|
||||
|
||||
def _start_progress():
|
||||
ui.backlight_slide_sync(ui.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
ui.header("Please wait")
|
||||
ui.display.refresh()
|
||||
ui.backlight_slide_sync(ui.BACKLIGHT_NORMAL)
|
||||
|
||||
|
||||
def _render_progress(progress: int, total: int):
|
||||
p = int(1000 * progress / total)
|
||||
ui.display.loader(p, 18, ui.WHITE, ui.BG)
|
||||
ui.display.refresh()
|
@ -1,7 +1,7 @@
|
||||
from trezor import ui, wire
|
||||
from trezor.crypto import bip32, bip39
|
||||
from trezor.crypto import bip32
|
||||
|
||||
from apps.common import cache, storage
|
||||
from apps.common import cache, mnemonic, storage
|
||||
from apps.common.request_passphrase import protect_by_passphrase
|
||||
|
||||
allow = list
|
||||
@ -66,32 +66,17 @@ async def _compute_seed(ctx: wire.Context) -> bytes:
|
||||
if passphrase is None:
|
||||
passphrase = await protect_by_passphrase(ctx)
|
||||
cache.set_passphrase(passphrase)
|
||||
_start_bip39_progress()
|
||||
seed = bip39.seed(storage.get_mnemonic(), passphrase, _render_bip39_progress)
|
||||
seed = mnemonic.get_seed(passphrase)
|
||||
cache.set_seed(seed)
|
||||
return seed
|
||||
|
||||
|
||||
def _start_bip39_progress():
|
||||
ui.backlight_slide_sync(ui.BACKLIGHT_DIM)
|
||||
ui.display.clear()
|
||||
ui.header("Please wait")
|
||||
ui.display.refresh()
|
||||
ui.backlight_slide_sync(ui.BACKLIGHT_NORMAL)
|
||||
|
||||
|
||||
def _render_bip39_progress(progress: int, total: int):
|
||||
p = int(1000 * progress / total)
|
||||
ui.display.loader(p, 18, ui.WHITE, ui.BG)
|
||||
ui.display.refresh()
|
||||
|
||||
|
||||
def derive_node_without_passphrase(
|
||||
path: list, curve_name: str = "secp256k1"
|
||||
) -> bip32.HDNode:
|
||||
if not storage.is_initialized():
|
||||
raise Exception("Device is not initialized")
|
||||
seed = bip39.seed(storage.get_mnemonic(), "")
|
||||
seed = mnemonic.get_seed()
|
||||
node = bip32.from_seed(seed, curve_name)
|
||||
node.derive_path(path)
|
||||
return node
|
||||
|
@ -18,7 +18,7 @@ _COUNTER_TAIL_LEN = 8
|
||||
_APP = const(0x01) # app namespace
|
||||
_DEVICE_ID = const(0x00) # bytes
|
||||
_VERSION = const(0x01) # int
|
||||
_MNEMONIC = const(0x02) # str
|
||||
_MNEMONIC_SECRET = const(0x02) # bytes
|
||||
_LANGUAGE = const(0x03) # str
|
||||
_LABEL = const(0x04) # str
|
||||
_USE_PASSPHRASE = const(0x05) # bool (0x01 or empty)
|
||||
@ -30,6 +30,7 @@ _PASSPHRASE_SOURCE = const(0x0A) # int
|
||||
_UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
|
||||
_AUTOLOCK_DELAY_MS = const(0x0C) # int
|
||||
_NO_BACKUP = const(0x0D) # bool (0x01 or empty)
|
||||
_MNEMONIC_TYPE = const(0x0E) # int
|
||||
# fmt: on
|
||||
|
||||
|
||||
@ -44,6 +45,17 @@ def _get_bool(app: int, key: int, public: bool = False) -> bool:
|
||||
return config.get(app, key, public) == _TRUE_BYTE
|
||||
|
||||
|
||||
def _set_uint8(app: int, key: int, val: int):
|
||||
config.set(app, key, val.to_bytes(1, "big"))
|
||||
|
||||
|
||||
def _get_uint8(app: int, key: int) -> int:
|
||||
val = config.get(app, key)
|
||||
if not val:
|
||||
return None
|
||||
return int.from_bytes(val, "big")
|
||||
|
||||
|
||||
def _new_device_id() -> str:
|
||||
return hexlify(random.bytes(12)).decode().upper()
|
||||
|
||||
@ -67,11 +79,15 @@ def get_label() -> str:
|
||||
return label.decode()
|
||||
|
||||
|
||||
def get_mnemonic() -> str:
|
||||
mnemonic = config.get(_APP, _MNEMONIC)
|
||||
def get_mnemonic_secret() -> bytes:
|
||||
mnemonic = config.get(_APP, _MNEMONIC_SECRET)
|
||||
if mnemonic is None:
|
||||
return None
|
||||
return mnemonic.decode()
|
||||
return mnemonic
|
||||
|
||||
|
||||
def get_mnemonic_type() -> int:
|
||||
return _get_uint8(_APP, _MNEMONIC_TYPE)
|
||||
|
||||
|
||||
def has_passphrase() -> bool:
|
||||
@ -82,8 +98,11 @@ def get_homescreen() -> bytes:
|
||||
return config.get(_APP, _HOMESCREEN, True) # public
|
||||
|
||||
|
||||
def load_mnemonic(mnemonic: str, needs_backup: bool, no_backup: bool) -> None:
|
||||
config.set(_APP, _MNEMONIC, mnemonic.encode())
|
||||
def store_mnemonic(
|
||||
secret: bytes, mnemonic_type: int, needs_backup: bool, no_backup: bool
|
||||
) -> None:
|
||||
config.set(_APP, _MNEMONIC_SECRET, secret)
|
||||
_set_uint8(_APP, _MNEMONIC_TYPE, mnemonic_type)
|
||||
config.set(_APP, _VERSION, _STORAGE_VERSION)
|
||||
_set_bool(_APP, _NO_BACKUP, no_backup)
|
||||
if not no_backup:
|
||||
|
@ -9,7 +9,7 @@ if __debug__:
|
||||
from trezor.messages.DebugLinkState import DebugLinkState
|
||||
from trezor.ui import confirm, swipe
|
||||
from trezor.wire import register, protobuf_workflow
|
||||
from apps.common import storage
|
||||
from apps.common import storage, mnemonic
|
||||
|
||||
reset_internal_entropy = None
|
||||
reset_current_words = None
|
||||
@ -29,7 +29,7 @@ if __debug__:
|
||||
|
||||
async def dispatch_DebugLinkGetState(ctx, msg):
|
||||
m = DebugLinkState()
|
||||
m.mnemonic = storage.get_mnemonic()
|
||||
m.mnemonic_secret, m.mnemonic_type = mnemonic.get()
|
||||
m.passphrase_protection = storage.has_passphrase()
|
||||
m.reset_word_pos = reset_word_index
|
||||
m.reset_entropy = reset_internal_entropy
|
||||
|
@ -1,7 +1,7 @@
|
||||
from trezor import wire
|
||||
from trezor.messages.Success import Success
|
||||
|
||||
from apps.common import storage
|
||||
from apps.common import mnemonic, storage
|
||||
from apps.management.reset_device import (
|
||||
check_mnemonic,
|
||||
show_mnemonic,
|
||||
@ -16,7 +16,7 @@ async def backup_device(ctx, msg):
|
||||
if not storage.needs_backup():
|
||||
raise wire.ProcessError("Seed already backed up")
|
||||
|
||||
mnemonic = storage.get_mnemonic()
|
||||
words = mnemonic.restore()
|
||||
|
||||
# warn user about mnemonic safety
|
||||
await show_warning(ctx)
|
||||
@ -26,8 +26,8 @@ async def backup_device(ctx, msg):
|
||||
|
||||
while True:
|
||||
# show mnemonic and require confirmation of a random word
|
||||
await show_mnemonic(ctx, mnemonic)
|
||||
if await check_mnemonic(ctx, mnemonic):
|
||||
await show_mnemonic(ctx, words)
|
||||
if await check_mnemonic(ctx, words):
|
||||
break
|
||||
await show_wrong_entry(ctx)
|
||||
|
||||
|
@ -4,7 +4,7 @@ from trezor.messages.Success import Success
|
||||
from trezor.pin import pin_to_int
|
||||
from trezor.ui.text import Text
|
||||
|
||||
from apps.common import storage
|
||||
from apps.common import mnemonic, storage
|
||||
from apps.common.confirm import require_confirm
|
||||
|
||||
|
||||
@ -24,7 +24,13 @@ async def load_device(ctx, msg):
|
||||
text.normal("Continue only if you", "know what you are doing!")
|
||||
await require_confirm(ctx, text)
|
||||
|
||||
storage.load_mnemonic(mnemonic=msg.mnemonic, needs_backup=True, no_backup=False)
|
||||
secret = mnemonic.process([msg.mnemonic], mnemonic.TYPE_BIP39)
|
||||
storage.store_mnemonic(
|
||||
secret=secret,
|
||||
mnemonic_type=mnemonic.TYPE_BIP39,
|
||||
needs_backup=True,
|
||||
no_backup=False,
|
||||
)
|
||||
storage.load_settings(use_passphrase=msg.passphrase_protection, label=msg.label)
|
||||
if msg.pin:
|
||||
config.change_pin(pin_to_int(""), pin_to_int(msg.pin))
|
||||
|
@ -15,7 +15,7 @@ from trezor.ui.text import Text
|
||||
from trezor.ui.word_select import WordSelector
|
||||
from trezor.utils import consteq, format_ordinal
|
||||
|
||||
from apps.common import storage
|
||||
from apps.common import mnemonic, storage
|
||||
from apps.common.confirm import require_confirm
|
||||
from apps.management.change_pin import request_pin_ack, request_pin_confirm
|
||||
|
||||
@ -47,11 +47,11 @@ async def recovery_device(ctx, msg):
|
||||
wordcount = await request_wordcount(ctx)
|
||||
|
||||
# ask for mnemonic words one by one
|
||||
mnemonic = await request_mnemonic(ctx, wordcount)
|
||||
words = await request_mnemonic(ctx, wordcount)
|
||||
|
||||
# check mnemonic validity
|
||||
if msg.enforce_wordlist or msg.dry_run:
|
||||
if not bip39.check(mnemonic):
|
||||
if not bip39.check(words):
|
||||
raise wire.ProcessError("Mnemonic is not valid")
|
||||
|
||||
# ask for pin repeatedly
|
||||
@ -60,10 +60,13 @@ async def recovery_device(ctx, msg):
|
||||
else:
|
||||
newpin = ""
|
||||
|
||||
secret = mnemonic.process([words], mnemonic.TYPE_BIP39)
|
||||
|
||||
# dry run
|
||||
if msg.dry_run:
|
||||
digest_input = sha256(mnemonic).digest()
|
||||
digest_stored = sha256(storage.get_mnemonic()).digest()
|
||||
digest_input = sha256(secret).digest()
|
||||
stored, _ = mnemonic.get()
|
||||
digest_stored = sha256(stored).digest()
|
||||
if consteq(digest_stored, digest_input):
|
||||
return Success(
|
||||
message="The seed is valid and matches the one in the device"
|
||||
@ -78,7 +81,12 @@ async def recovery_device(ctx, msg):
|
||||
config.change_pin(pin_to_int(""), pin_to_int(newpin))
|
||||
storage.set_u2f_counter(msg.u2f_counter)
|
||||
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection)
|
||||
storage.load_mnemonic(mnemonic=mnemonic, needs_backup=False, no_backup=False)
|
||||
storage.store_mnemonic(
|
||||
secret=secret,
|
||||
mnemonic_type=mnemonic.TYPE_BIP39,
|
||||
needs_backup=False,
|
||||
no_backup=False,
|
||||
)
|
||||
|
||||
return Success(message="Device recovered")
|
||||
|
||||
|
@ -14,7 +14,7 @@ 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 import mnemonic, storage
|
||||
from apps.common.confirm import require_confirm
|
||||
from apps.management.change_pin import request_pin_confirm
|
||||
|
||||
@ -59,7 +59,7 @@ async def reset_device(ctx, msg):
|
||||
|
||||
# request external entropy and compute mnemonic
|
||||
ent_ack = await ctx.call(EntropyRequest(), MessageType.EntropyAck)
|
||||
mnemonic = generate_mnemonic(msg.strength, internal_ent, ent_ack.entropy)
|
||||
words = generate_mnemonic(msg.strength, internal_ent, ent_ack.entropy)
|
||||
|
||||
if not msg.skip_backup and not msg.no_backup:
|
||||
# require confirmation of the mnemonic safety
|
||||
@ -67,8 +67,8 @@ async def reset_device(ctx, msg):
|
||||
|
||||
# show mnemonic and require confirmation of a random word
|
||||
while True:
|
||||
await show_mnemonic(ctx, mnemonic)
|
||||
if await check_mnemonic(ctx, mnemonic):
|
||||
await show_mnemonic(ctx, words)
|
||||
if await check_mnemonic(ctx, words):
|
||||
break
|
||||
await show_wrong_entry(ctx)
|
||||
|
||||
@ -77,10 +77,14 @@ async def reset_device(ctx, msg):
|
||||
if not config.change_pin(pin_to_int(""), pin_to_int(newpin)):
|
||||
raise wire.ProcessError("Could not change PIN")
|
||||
|
||||
secret = mnemonic.process([words], mnemonic.TYPE_BIP39)
|
||||
# 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, no_backup=msg.no_backup
|
||||
storage.store_mnemonic(
|
||||
secret=secret,
|
||||
mnemonic_type=mnemonic.TYPE_BIP39,
|
||||
needs_backup=msg.skip_backup,
|
||||
no_backup=msg.no_backup,
|
||||
)
|
||||
|
||||
# show success message. if we skipped backup, it's possible that homescreen
|
||||
@ -98,8 +102,7 @@ def generate_mnemonic(strength: int, int_entropy: bytes, ext_entropy: bytes) ->
|
||||
ehash.update(int_entropy)
|
||||
ehash.update(ext_entropy)
|
||||
entropy = ehash.digest()
|
||||
mnemonic = bip39.from_data(entropy[: strength // 8])
|
||||
return mnemonic
|
||||
return bip39.from_data(entropy[: strength // 8])
|
||||
|
||||
|
||||
async def show_warning(ctx):
|
||||
|
Loading…
Reference in New Issue
Block a user