1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-16 19:38: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:
Tomas Susanka 2019-03-07 14:49:23 +01:00
parent d86abc473f
commit 5c37e8dae2
9 changed files with 116 additions and 49 deletions

View File

@ -2,7 +2,7 @@ from trezor import wire
from trezor.crypto import bip32 from trezor.crypto import bip32
from apps.cardano import SEED_NAMESPACE 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 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: if passphrase is None:
passphrase = await protect_by_passphrase(ctx) passphrase = await protect_by_passphrase(ctx)
cache.set_passphrase(passphrase) 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 # derive the namespaced root node
for i in SEED_NAMESPACE[0]: for i in SEED_NAMESPACE[0]:

View 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()

View File

@ -1,7 +1,7 @@
from trezor import ui, wire 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 from apps.common.request_passphrase import protect_by_passphrase
allow = list allow = list
@ -66,32 +66,17 @@ async def _compute_seed(ctx: wire.Context) -> bytes:
if passphrase is None: if passphrase is None:
passphrase = await protect_by_passphrase(ctx) passphrase = await protect_by_passphrase(ctx)
cache.set_passphrase(passphrase) cache.set_passphrase(passphrase)
_start_bip39_progress() seed = mnemonic.get_seed(passphrase)
seed = bip39.seed(storage.get_mnemonic(), passphrase, _render_bip39_progress)
cache.set_seed(seed) cache.set_seed(seed)
return 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( def derive_node_without_passphrase(
path: list, curve_name: str = "secp256k1" path: list, curve_name: str = "secp256k1"
) -> bip32.HDNode: ) -> bip32.HDNode:
if not storage.is_initialized(): if not storage.is_initialized():
raise Exception("Device is not 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 = bip32.from_seed(seed, curve_name)
node.derive_path(path) node.derive_path(path)
return node return node

View File

@ -18,7 +18,7 @@ _COUNTER_TAIL_LEN = 8
_APP = const(0x01) # app namespace _APP = const(0x01) # app namespace
_DEVICE_ID = const(0x00) # bytes _DEVICE_ID = const(0x00) # bytes
_VERSION = const(0x01) # int _VERSION = const(0x01) # int
_MNEMONIC = const(0x02) # str _MNEMONIC_SECRET = const(0x02) # bytes
_LANGUAGE = const(0x03) # str _LANGUAGE = const(0x03) # str
_LABEL = const(0x04) # str _LABEL = const(0x04) # str
_USE_PASSPHRASE = const(0x05) # bool (0x01 or empty) _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) _UNFINISHED_BACKUP = const(0x0B) # bool (0x01 or empty)
_AUTOLOCK_DELAY_MS = const(0x0C) # int _AUTOLOCK_DELAY_MS = const(0x0C) # int
_NO_BACKUP = const(0x0D) # bool (0x01 or empty) _NO_BACKUP = const(0x0D) # bool (0x01 or empty)
_MNEMONIC_TYPE = const(0x0E) # int
# fmt: on # 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 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: def _new_device_id() -> str:
return hexlify(random.bytes(12)).decode().upper() return hexlify(random.bytes(12)).decode().upper()
@ -67,11 +79,15 @@ def get_label() -> str:
return label.decode() return label.decode()
def get_mnemonic() -> str: def get_mnemonic_secret() -> bytes:
mnemonic = config.get(_APP, _MNEMONIC) mnemonic = config.get(_APP, _MNEMONIC_SECRET)
if mnemonic is None: if mnemonic is None:
return None return None
return mnemonic.decode() return mnemonic
def get_mnemonic_type() -> int:
return _get_uint8(_APP, _MNEMONIC_TYPE)
def has_passphrase() -> bool: def has_passphrase() -> bool:
@ -82,8 +98,11 @@ def get_homescreen() -> bytes:
return config.get(_APP, _HOMESCREEN, True) # public return config.get(_APP, _HOMESCREEN, True) # public
def load_mnemonic(mnemonic: str, needs_backup: bool, no_backup: bool) -> None: def store_mnemonic(
config.set(_APP, _MNEMONIC, mnemonic.encode()) 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) config.set(_APP, _VERSION, _STORAGE_VERSION)
_set_bool(_APP, _NO_BACKUP, no_backup) _set_bool(_APP, _NO_BACKUP, no_backup)
if not no_backup: if not no_backup:

View File

@ -9,7 +9,7 @@ if __debug__:
from trezor.messages.DebugLinkState import DebugLinkState from trezor.messages.DebugLinkState import DebugLinkState
from trezor.ui import confirm, swipe from trezor.ui import confirm, swipe
from trezor.wire import register, protobuf_workflow from trezor.wire import register, protobuf_workflow
from apps.common import storage from apps.common import storage, mnemonic
reset_internal_entropy = None reset_internal_entropy = None
reset_current_words = None reset_current_words = None
@ -29,7 +29,7 @@ if __debug__:
async def dispatch_DebugLinkGetState(ctx, msg): async def dispatch_DebugLinkGetState(ctx, msg):
m = DebugLinkState() m = DebugLinkState()
m.mnemonic = storage.get_mnemonic() m.mnemonic_secret, m.mnemonic_type = mnemonic.get()
m.passphrase_protection = storage.has_passphrase() m.passphrase_protection = storage.has_passphrase()
m.reset_word_pos = reset_word_index m.reset_word_pos = reset_word_index
m.reset_entropy = reset_internal_entropy m.reset_entropy = reset_internal_entropy

View File

@ -1,7 +1,7 @@
from trezor import wire from trezor import wire
from trezor.messages.Success import Success from trezor.messages.Success import Success
from apps.common import storage from apps.common import mnemonic, storage
from apps.management.reset_device import ( from apps.management.reset_device import (
check_mnemonic, check_mnemonic,
show_mnemonic, show_mnemonic,
@ -16,7 +16,7 @@ async def backup_device(ctx, msg):
if not storage.needs_backup(): if not storage.needs_backup():
raise wire.ProcessError("Seed already backed up") raise wire.ProcessError("Seed already backed up")
mnemonic = storage.get_mnemonic() words = mnemonic.restore()
# warn user about mnemonic safety # warn user about mnemonic safety
await show_warning(ctx) await show_warning(ctx)
@ -26,8 +26,8 @@ async def backup_device(ctx, msg):
while True: while True:
# show mnemonic and require confirmation of a random word # show mnemonic and require confirmation of a random word
await show_mnemonic(ctx, mnemonic) await show_mnemonic(ctx, words)
if await check_mnemonic(ctx, mnemonic): if await check_mnemonic(ctx, words):
break break
await show_wrong_entry(ctx) await show_wrong_entry(ctx)

View File

@ -4,7 +4,7 @@ from trezor.messages.Success import Success
from trezor.pin import pin_to_int from trezor.pin import pin_to_int
from trezor.ui.text import Text from trezor.ui.text import Text
from apps.common import storage from apps.common import mnemonic, storage
from apps.common.confirm import require_confirm 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!") text.normal("Continue only if you", "know what you are doing!")
await require_confirm(ctx, text) 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) storage.load_settings(use_passphrase=msg.passphrase_protection, label=msg.label)
if msg.pin: if msg.pin:
config.change_pin(pin_to_int(""), pin_to_int(msg.pin)) config.change_pin(pin_to_int(""), pin_to_int(msg.pin))

View File

@ -15,7 +15,7 @@ from trezor.ui.text import Text
from trezor.ui.word_select import WordSelector from trezor.ui.word_select import WordSelector
from trezor.utils import consteq, format_ordinal 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.common.confirm import require_confirm
from apps.management.change_pin import request_pin_ack, request_pin_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) wordcount = await request_wordcount(ctx)
# ask for mnemonic words one by one # ask for mnemonic words one by one
mnemonic = await request_mnemonic(ctx, wordcount) words = await request_mnemonic(ctx, wordcount)
# check mnemonic validity # check mnemonic validity
if msg.enforce_wordlist or msg.dry_run: 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") raise wire.ProcessError("Mnemonic is not valid")
# ask for pin repeatedly # ask for pin repeatedly
@ -60,10 +60,13 @@ async def recovery_device(ctx, msg):
else: else:
newpin = "" newpin = ""
secret = mnemonic.process([words], mnemonic.TYPE_BIP39)
# dry run # dry run
if msg.dry_run: if msg.dry_run:
digest_input = sha256(mnemonic).digest() digest_input = sha256(secret).digest()
digest_stored = sha256(storage.get_mnemonic()).digest() stored, _ = mnemonic.get()
digest_stored = sha256(stored).digest()
if consteq(digest_stored, digest_input): if consteq(digest_stored, digest_input):
return Success( return Success(
message="The seed is valid and matches the one in the device" 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)) config.change_pin(pin_to_int(""), pin_to_int(newpin))
storage.set_u2f_counter(msg.u2f_counter) storage.set_u2f_counter(msg.u2f_counter)
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) 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") return Success(message="Device recovered")

View File

@ -14,7 +14,7 @@ from trezor.ui.scroll import Scrollpage, animate_swipe, paginate
from trezor.ui.text import Text from trezor.ui.text import Text
from trezor.utils import chunks, format_ordinal 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.common.confirm import require_confirm
from apps.management.change_pin import request_pin_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 # request external entropy and compute mnemonic
ent_ack = await ctx.call(EntropyRequest(), MessageType.EntropyAck) 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: if not msg.skip_backup and not msg.no_backup:
# require confirmation of the mnemonic safety # 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 # show mnemonic and require confirmation of a random word
while True: while True:
await show_mnemonic(ctx, mnemonic) await show_mnemonic(ctx, words)
if await check_mnemonic(ctx, mnemonic): if await check_mnemonic(ctx, words):
break break
await show_wrong_entry(ctx) 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)): if not config.change_pin(pin_to_int(""), pin_to_int(newpin)):
raise wire.ProcessError("Could not change PIN") raise wire.ProcessError("Could not change PIN")
secret = mnemonic.process([words], mnemonic.TYPE_BIP39)
# write settings and mnemonic into storage # write settings and mnemonic into storage
storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection) storage.load_settings(label=msg.label, use_passphrase=msg.passphrase_protection)
storage.load_mnemonic( storage.store_mnemonic(
mnemonic=mnemonic, needs_backup=msg.skip_backup, no_backup=msg.no_backup 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 # 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(int_entropy)
ehash.update(ext_entropy) ehash.update(ext_entropy)
entropy = ehash.digest() entropy = ehash.digest()
mnemonic = bip39.from_data(entropy[: strength // 8]) return bip39.from_data(entropy[: strength // 8])
return mnemonic
async def show_warning(ctx): async def show_warning(ctx):