mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-03 03:50:58 +00:00
feat(core): derive Cardano seed together with normal seed, if requested
This commit is contained in:
parent
387466e073
commit
f818f4bc23
@ -1,10 +1,10 @@
|
||||
from storage import cache, device
|
||||
from trezor import wire
|
||||
from trezor.crypto import bip32
|
||||
from trezor.crypto import bip32, cardano
|
||||
|
||||
from apps.common import mnemonic
|
||||
from apps.common.passphrase import get as get_passphrase
|
||||
from apps.common.seed import get_seed
|
||||
from apps.common.seed import get_seed, derive_and_store_roots
|
||||
|
||||
from .helpers import paths
|
||||
|
||||
@ -24,12 +24,19 @@ class Keychain:
|
||||
"""
|
||||
|
||||
def __init__(self, root: bip32.HDNode) -> None:
|
||||
self.byron_root = derive_path_cardano(root, paths.BYRON_ROOT)
|
||||
self.shelley_root = derive_path_cardano(root, paths.SHELLEY_ROOT)
|
||||
self.multisig_root = derive_path_cardano(root, paths.MULTISIG_ROOT)
|
||||
self.minting_root = derive_path_cardano(root, paths.MINTING_ROOT)
|
||||
self.byron_root = self._derive_path(root, paths.BYRON_ROOT)
|
||||
self.shelley_root = self._derive_path(root, paths.SHELLEY_ROOT)
|
||||
self.multisig_root = self._derive_path(root, paths.MULTISIG_ROOT)
|
||||
self.minting_root = self._derive_path(root, paths.MINTING_ROOT)
|
||||
root.__del__()
|
||||
|
||||
@staticmethod
|
||||
def _derive_path(root: bip32.HDNode, path: Bip32Path) -> bip32.HDNode:
|
||||
"""Clone and derive path from the root."""
|
||||
node = root.clone()
|
||||
node.derive_path(path)
|
||||
return node
|
||||
|
||||
def verify_path(self, path: Bip32Path) -> None:
|
||||
if not self.is_in_keychain(path):
|
||||
raise wire.DataError("Forbidden key path")
|
||||
@ -67,7 +74,7 @@ class Keychain:
|
||||
suffix = node_path[len(paths.SHELLEY_ROOT) :]
|
||||
|
||||
# derive child node from the root
|
||||
return derive_path_cardano(path_root, suffix)
|
||||
return self._derive_path(path_root, suffix)
|
||||
|
||||
# XXX the root node remains in session cache so we should not delete it
|
||||
# def __del__(self) -> None:
|
||||
@ -90,28 +97,35 @@ def is_minting_path(path: Bip32Path) -> bool:
|
||||
return path[: len(paths.MINTING_ROOT)] == paths.MINTING_ROOT
|
||||
|
||||
|
||||
def derive_path_cardano(root: bip32.HDNode, path: Bip32Path) -> bip32.HDNode:
|
||||
node = root.clone()
|
||||
for i in path:
|
||||
node.derive_cardano(i)
|
||||
return node
|
||||
def derive_and_store_secret(passphrase: str) -> None:
|
||||
assert device.is_initialized()
|
||||
assert cache.get(cache.APP_COMMON_DERIVE_CARDANO)
|
||||
|
||||
if not mnemonic.is_bip39():
|
||||
# nothing to do for SLIP-39, where we can derive the root from the main seed
|
||||
return
|
||||
|
||||
icarus_trezor_secret = mnemonic.derive_cardano_icarus_trezor(passphrase)
|
||||
cache.set(cache.APP_CARDANO_SECRET, icarus_trezor_secret)
|
||||
|
||||
|
||||
@cache.stored_async(cache.APP_CARDANO_PASSPHRASE)
|
||||
async def _get_passphrase(ctx: wire.Context) -> bytes:
|
||||
return (await get_passphrase(ctx)).encode()
|
||||
@cache.stored_async(cache.APP_CARDANO_SECRET)
|
||||
async def _get_secret(ctx: wire.Context) -> bytes:
|
||||
await derive_and_store_roots(ctx)
|
||||
secret = cache.get(cache.APP_CARDANO_SECRET)
|
||||
assert secret is not None
|
||||
return secret
|
||||
|
||||
|
||||
async def _get_keychain_bip39(ctx: wire.Context) -> Keychain:
|
||||
if not device.is_initialized():
|
||||
raise wire.NotInitialized("Device is not initialized")
|
||||
|
||||
# ask for passphrase, loading from cache if necessary
|
||||
passphrase = await _get_passphrase(ctx)
|
||||
# derive the root node from mnemonic and passphrase via Cardano Icarus algorithm
|
||||
secret_bytes = mnemonic.get_secret()
|
||||
assert secret_bytes is not None
|
||||
root = bip32.from_mnemonic_cardano(secret_bytes.decode(), passphrase.decode())
|
||||
if not cache.get(cache.APP_COMMON_DERIVE_CARDANO):
|
||||
raise wire.ProcessError("Cardano derivation is not enabled for this session")
|
||||
|
||||
secret = await _get_secret(ctx)
|
||||
root = cardano.from_secret(secret)
|
||||
return Keychain(root)
|
||||
|
||||
|
||||
@ -121,7 +135,7 @@ async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||
else:
|
||||
# derive the root node via SLIP-0023 https://github.com/satoshilabs/slips/blob/master/slip-0022.md
|
||||
seed = await get_seed(ctx)
|
||||
return Keychain(bip32.from_seed(seed, "ed25519 cardano seed"))
|
||||
return Keychain(cardano.from_seed_slip23(seed))
|
||||
|
||||
|
||||
def with_keychain(func: HandlerWithKeychain[MsgIn, MsgOut]) -> Handler[MsgIn, MsgOut]:
|
||||
|
@ -26,7 +26,7 @@ def is_bip39() -> bool:
|
||||
def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
mnemonic_secret = get_secret()
|
||||
if mnemonic_secret is None:
|
||||
raise ValueError("Mnemonic not set")
|
||||
raise ValueError # Mnemonic not set
|
||||
|
||||
render_func = None
|
||||
if progress_bar and not utils.DISABLE_ANIMATION:
|
||||
@ -57,6 +57,30 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
return seed
|
||||
|
||||
|
||||
if not utils.BITCOIN_ONLY:
|
||||
|
||||
def derive_cardano_icarus_trezor(
|
||||
passphrase: str = "", progress_bar: bool = True
|
||||
) -> bytes:
|
||||
if not is_bip39():
|
||||
raise ValueError # should not be called for SLIP-39
|
||||
|
||||
mnemonic_secret = get_secret()
|
||||
if mnemonic_secret is None:
|
||||
raise ValueError("Mnemonic not set")
|
||||
|
||||
render_func = None
|
||||
if progress_bar and not utils.DISABLE_ANIMATION:
|
||||
_start_progress()
|
||||
render_func = _render_progress
|
||||
|
||||
from trezor.crypto import cardano
|
||||
|
||||
return cardano.derive_icarus_trezor(
|
||||
mnemonic_secret, passphrase, render_func
|
||||
)
|
||||
|
||||
|
||||
def _start_progress() -> None:
|
||||
from trezor.ui.layouts import draw_simple_text
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from storage import cache, device
|
||||
from trezor import wire
|
||||
from trezor import utils, wire
|
||||
from trezor.crypto import bip32, hmac
|
||||
|
||||
from . import mnemonic
|
||||
@ -40,10 +40,48 @@ class Slip21Node:
|
||||
return Slip21Node(data=self.data)
|
||||
|
||||
|
||||
@cache.stored_async(cache.APP_COMMON_SEED)
|
||||
async def get_seed(ctx: wire.Context) -> bytes:
|
||||
if not utils.BITCOIN_ONLY:
|
||||
# === Cardano variant ===
|
||||
# We want to derive both the normal seed and the Cardano seed together, AND
|
||||
# expose a method for Cardano to do the same
|
||||
|
||||
async def derive_and_store_roots(ctx: wire.Context) -> None:
|
||||
if not device.is_initialized():
|
||||
raise wire.NotInitialized("Device is not initialized")
|
||||
|
||||
need_seed = not cache.is_set(cache.APP_COMMON_SEED)
|
||||
need_cardano_secret = cache.get(
|
||||
cache.APP_COMMON_DERIVE_CARDANO
|
||||
) and not cache.is_set(cache.APP_CARDANO_SECRET)
|
||||
|
||||
if not need_seed and not need_cardano_secret:
|
||||
return
|
||||
|
||||
passphrase = await get_passphrase(ctx)
|
||||
|
||||
if need_seed:
|
||||
common_seed = mnemonic.get_seed(passphrase)
|
||||
cache.set(cache.APP_COMMON_SEED, common_seed)
|
||||
|
||||
if need_cardano_secret:
|
||||
from apps.cardano.seed import derive_and_store_secret
|
||||
|
||||
derive_and_store_secret(passphrase)
|
||||
|
||||
@cache.stored_async(cache.APP_COMMON_SEED)
|
||||
async def get_seed(ctx: wire.Context) -> bytes:
|
||||
await derive_and_store_roots(ctx)
|
||||
common_seed = cache.get(cache.APP_COMMON_SEED)
|
||||
assert common_seed is not None
|
||||
return common_seed
|
||||
|
||||
|
||||
else:
|
||||
# === Bitcoin-only variant ===
|
||||
# We use the simple version of `get_seed` that never needs to derive anything else.
|
||||
|
||||
@cache.stored_async(cache.APP_COMMON_SEED)
|
||||
async def get_seed(ctx: wire.Context) -> bytes:
|
||||
passphrase = await get_passphrase(ctx)
|
||||
return mnemonic.get_seed(passphrase)
|
||||
|
||||
|
@ -21,7 +21,7 @@ _SESSION_ID_LENGTH = 32
|
||||
# Traditional cache keys
|
||||
APP_COMMON_SEED = 0
|
||||
APP_COMMON_DERIVE_CARDANO = 1
|
||||
APP_CARDANO_XPRV = 2
|
||||
APP_CARDANO_SECRET = 2
|
||||
APP_MONERO_LIVE_REFRESH = 3
|
||||
APP_COMMON_AUTHORIZATION_TYPE = 4
|
||||
APP_COMMON_AUTHORIZATION_DATA = 5
|
||||
@ -69,11 +69,15 @@ class DataCache:
|
||||
...
|
||||
|
||||
def get(self, key: int, default: T | None = None) -> bytes | T | None: # noqa: F811
|
||||
utils.ensure(key < len(self.fields), f"failed to load key {key}")
|
||||
utils.ensure(key < len(self.fields))
|
||||
if self.data[key][0] != 1:
|
||||
return default
|
||||
return bytes(self.data[key][1:])
|
||||
|
||||
def is_set(self, key: int) -> bool:
|
||||
utils.ensure(key < len(self.fields))
|
||||
return self.data[key][0] == 1
|
||||
|
||||
def delete(self, key: int) -> None:
|
||||
utils.ensure(key < len(self.fields))
|
||||
self.data[key][:] = b"\x00"
|
||||
@ -89,7 +93,7 @@ class SessionCache(DataCache):
|
||||
self.fields = (
|
||||
64, # APP_COMMON_SEED
|
||||
1, # APP_COMMON_DERIVE_CARDANO
|
||||
96, # APP_CARDANO_XPRV
|
||||
96, # APP_CARDANO_SECRET
|
||||
1, # APP_MONERO_LIVE_REFRESH
|
||||
2, # APP_COMMON_AUTHORIZATION_TYPE
|
||||
128, # APP_COMMON_AUTHORIZATION_DATA
|
||||
@ -227,6 +231,14 @@ def get(key: int, default: T | None = None) -> bytes | T | None: # noqa: F811
|
||||
return _SESSIONS[_active_session_idx].get(key, default)
|
||||
|
||||
|
||||
def is_set(key: int) -> bool:
|
||||
if key & _SESSIONLESS_FLAG:
|
||||
return _SESSIONLESS_CACHE.is_set(key ^ _SESSIONLESS_FLAG)
|
||||
if _active_session_idx is None:
|
||||
raise InvalidSessionError
|
||||
return _SESSIONS[_active_session_idx].is_set(key)
|
||||
|
||||
|
||||
def delete(key: int) -> None:
|
||||
if key & _SESSIONLESS_FLAG:
|
||||
return _SESSIONLESS_CACHE.delete(key ^ _SESSIONLESS_FLAG)
|
||||
|
Loading…
Reference in New Issue
Block a user