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/common/seed.py

117 lines
3.6 KiB

from typing import TYPE_CHECKING
from storage import cache, device
from trezor import utils, wire
from trezor.crypto import bip32, hmac
from . import mnemonic
from .passphrase import get as get_passphrase
if TYPE_CHECKING:
from .paths import Bip32Path, Slip21Path
class Slip21Node:
"""
This class implements the SLIP-0021 hierarchical derivation of symmetric keys, see
https://github.com/satoshilabs/slips/blob/master/slip-0021.md.
"""
def __init__(self, seed: bytes | None = None, data: bytes | None = None) -> None:
assert seed is None or data is None, "Specify exactly one of: seed, data"
if data is not None:
self.data = data
elif seed is not None:
self.data = hmac(hmac.SHA512, b"Symmetric key seed", seed).digest()
else:
raise ValueError # neither seed nor data specified
def __del__(self) -> None:
del self.data
def derive_path(self, path: Slip21Path) -> None:
for label in path:
h = hmac(hmac.SHA512, self.data[0:32], b"\x00")
h.update(label)
self.data = h.digest()
def key(self) -> bytes:
return self.data[32:64]
def clone(self) -> "Slip21Node":
return Slip21Node(data=self.data)
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_ICARUS_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_secrets
derive_and_store_secrets(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)
@cache.stored(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE)
def _get_seed_without_passphrase() -> bytes:
if not device.is_initialized():
raise Exception("Device is not initialized")
return mnemonic.get_seed(progress_bar=False)
def derive_node_without_passphrase(
path: Bip32Path, curve_name: str = "secp256k1"
) -> bip32.HDNode:
seed = _get_seed_without_passphrase()
node = bip32.from_seed(seed, curve_name)
node.derive_path(path)
return node
def derive_slip21_node_without_passphrase(path: Slip21Path) -> Slip21Node:
seed = _get_seed_without_passphrase()
node = Slip21Node(seed)
node.derive_path(path)
return node
def remove_ed25519_prefix(pubkey: bytes) -> bytes:
# 0x01 prefix is not part of the actual public key, hence removed
return pubkey[1:]