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

158 lines
5.0 KiB

from trezor import wire
from trezor.crypto import bip32, hashlib, hmac
from trezor.crypto.curve import secp256k1
from apps.common import HARDENED, cache, mnemonic, storage
from apps.common.request_passphrase import protect_by_passphrase
if False:
from typing import List, Union
class Slip21Node:
def __init__(self, seed: bytes = None) -> None:
if seed is not None:
self.data = hmac.new(b"Symmetric key seed", seed, hashlib.sha512).digest()
else:
self.data = b""
def __del__(self) -> None:
del self.data
def derive_path(self, path: list) -> None:
for label in path:
h = hmac.new(self.data[0:32], b"\x00", hashlib.sha512)
h.update(label)
self.data = h.digest()
def key(self) -> bytes:
return self.data[32:64]
def clone(self) -> "Slip21Node":
node = Slip21Node()
node.data = self.data
return node
class Keychain:
"""
Keychain provides an API for deriving HD keys from previously allowed
key-spaces.
"""
def __init__(self, seed: bytes, namespaces: list):
self.seed = seed
self.namespaces = namespaces
self.roots = [None] * len(
namespaces
) # type: List[Union[bip32.HDNode, Slip21Node, None]]
def __del__(self) -> None:
for root in self.roots:
if root is not None and hasattr(root, "__del__"):
root.__del__()
del self.roots
del self.seed
def validate_path(self, checked_path: list, checked_curve: str) -> None:
for curve, *path in self.namespaces:
if path == checked_path[: len(path)] and curve == checked_curve:
if "ed25519" in curve and not _path_hardened(checked_path):
break
return
raise wire.DataError("Forbidden key path")
def derive(
self, node_path: list, curve_name: str = "secp256k1"
) -> Union[bip32.HDNode, Slip21Node]:
if "ed25519" in curve_name and not _path_hardened(node_path):
raise wire.DataError("Forbidden key path")
# find the root node index
root_index = 0
for curve, *path in self.namespaces:
prefix = node_path[: len(path)]
suffix = node_path[len(path) :]
if curve == curve_name and path == prefix:
break
root_index += 1
else:
raise wire.DataError("Forbidden key path")
# create the root node if not cached
root = self.roots[root_index]
if root is None:
if curve_name != "slip21":
root = bip32.from_seed(self.seed, curve_name)
else:
root = Slip21Node(self.seed)
root.derive_path(path)
self.roots[root_index] = root
# derive child node from the root
node = root.clone()
node.derive_path(suffix)
return node
def derive_slip77_blinding_private_key(self, script: bytes) -> bytes:
"""Following the derivation by Elements/Liquid."""
master_node = self.derive(node_path=[b"SLIP-0077"], curve_name="slip21")
assert isinstance(master_node, Slip21Node)
return hmac.new(
key=master_node.key(), msg=script, digestmod=hashlib.sha256
).digest()
def derive_slip77_blinding_public_key(self, script: bytes) -> bytes:
private_key = self.derive_slip77_blinding_private_key(script)
return secp256k1.publickey(private_key)
async def get_keychain(ctx: wire.Context, namespaces: list) -> Keychain:
if not storage.is_initialized():
raise wire.NotInitialized("Device is not initialized")
seed = cache.get_seed()
if seed is None:
passphrase = cache.get_passphrase()
if passphrase is None:
passphrase = await protect_by_passphrase(ctx)
cache.set_passphrase(passphrase)
seed = mnemonic.get_seed(passphrase)
cache.set_seed(seed)
keychain = Keychain(seed, namespaces)
return keychain
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 = cache.get_seed_without_passphrase()
if seed is None:
seed = mnemonic.get_seed(progress_bar=False)
cache.set_seed_without_passphrase(seed)
node = bip32.from_seed(seed, curve_name)
node.derive_path(path)
return node
def derive_slip21_node_without_passphrase(path: list) -> Slip21Node:
if not storage.is_initialized():
raise Exception("Device is not initialized")
seed = cache.get_seed_without_passphrase()
if seed is None:
seed = mnemonic.get_seed(progress_bar=False)
cache.set_seed_without_passphrase(seed)
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:]
def _path_hardened(path: list) -> bool:
return all(i & HARDENED for i in path)