common: introduce mnemonic module

To provide another layer between seed and mnemonic. First step to
introduce SLIP-39
pull/25/head
Tomas Susanka 5 years ago
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]:

@ -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…
Cancel
Save