mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-29 02:48:18 +00:00
core: rework cache and fix cardano caching
This commit is contained in:
parent
aa6988a556
commit
b96d7cafbb
@ -1,4 +1,5 @@
|
|||||||
import storage
|
import storage
|
||||||
|
from storage import cache
|
||||||
from trezor import wire
|
from trezor import wire
|
||||||
from trezor.crypto import bip32
|
from trezor.crypto import bip32
|
||||||
|
|
||||||
@ -6,11 +7,6 @@ from apps.cardano import CURVE, SEED_NAMESPACE
|
|||||||
from apps.common import mnemonic
|
from apps.common import mnemonic
|
||||||
from apps.common.passphrase import get as get_passphrase
|
from apps.common.passphrase import get as get_passphrase
|
||||||
|
|
||||||
if False:
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
_cached_root = None # type: Optional[bytes]
|
|
||||||
|
|
||||||
|
|
||||||
class Keychain:
|
class Keychain:
|
||||||
def __init__(self, path: list, root: bip32.HDNode):
|
def __init__(self, path: list, root: bip32.HDNode):
|
||||||
@ -35,25 +31,30 @@ class Keychain:
|
|||||||
|
|
||||||
|
|
||||||
async def get_keychain(ctx: wire.Context) -> Keychain:
|
async def get_keychain(ctx: wire.Context) -> Keychain:
|
||||||
global _cached_root
|
root = cache.get(cache.APP_CARDANO_ROOT)
|
||||||
|
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise wire.NotInitialized("Device is not initialized")
|
raise wire.NotInitialized("Device is not initialized")
|
||||||
|
|
||||||
if _cached_root is None:
|
if root is None:
|
||||||
passphrase = await get_passphrase(ctx)
|
passphrase = await get_passphrase(ctx)
|
||||||
if mnemonic.is_bip39():
|
if mnemonic.is_bip39():
|
||||||
# derive the root node from mnemonic and passphrase
|
# derive the root node from mnemonic and passphrase
|
||||||
_cached_root = bip32.from_mnemonic_cardano(
|
root = bip32.from_mnemonic_cardano(
|
||||||
mnemonic.get_secret().decode(), passphrase
|
mnemonic.get_secret().decode(), passphrase
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
seed = mnemonic.get_seed(passphrase)
|
seed = mnemonic.get_seed(passphrase)
|
||||||
_cached_root = bip32.from_seed(seed, "ed25519 cardano seed")
|
root = bip32.from_seed(seed, "ed25519 cardano seed")
|
||||||
|
|
||||||
|
storage.cache.set(cache.APP_CARDANO_ROOT, root)
|
||||||
|
|
||||||
|
# let's not modify the one in the cache
|
||||||
|
root = root.clone()
|
||||||
|
|
||||||
# derive the namespaced root node
|
# derive the namespaced root node
|
||||||
for i in SEED_NAMESPACE:
|
for i in SEED_NAMESPACE:
|
||||||
_cached_root.derive_cardano(i)
|
root.derive_cardano(i)
|
||||||
|
|
||||||
keychain = Keychain(SEED_NAMESPACE, _cached_root)
|
keychain = Keychain(SEED_NAMESPACE, root)
|
||||||
return keychain
|
return keychain
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import storage
|
import storage
|
||||||
import storage.cache
|
from storage import cache
|
||||||
from trezor import wire
|
from trezor import wire
|
||||||
from trezor.crypto import bip32, hashlib, hmac
|
from trezor.crypto import bip32, hashlib, hmac
|
||||||
from trezor.crypto.curve import secp256k1
|
from trezor.crypto.curve import secp256k1
|
||||||
@ -112,11 +112,11 @@ class Keychain:
|
|||||||
async def get_keychain(ctx: wire.Context, namespaces: list) -> Keychain:
|
async def get_keychain(ctx: wire.Context, namespaces: list) -> Keychain:
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise wire.NotInitialized("Device is not initialized")
|
raise wire.NotInitialized("Device is not initialized")
|
||||||
seed = storage.cache.get_seed()
|
seed = cache.get(cache.APP_COMMON_SEED)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
passphrase = await get_passphrase(ctx)
|
passphrase = await get_passphrase(ctx)
|
||||||
seed = mnemonic.get_seed(passphrase)
|
seed = mnemonic.get_seed(passphrase)
|
||||||
storage.cache.set_seed(seed)
|
cache.set(cache.APP_COMMON_SEED, seed)
|
||||||
keychain = Keychain(seed, namespaces)
|
keychain = Keychain(seed, namespaces)
|
||||||
return keychain
|
return keychain
|
||||||
|
|
||||||
@ -126,10 +126,10 @@ def derive_node_without_passphrase(
|
|||||||
) -> 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 = storage.cache.get_seed_without_passphrase()
|
seed = cache.get(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
seed = mnemonic.get_seed(progress_bar=False)
|
seed = mnemonic.get_seed(progress_bar=False)
|
||||||
storage.cache.set_seed_without_passphrase(seed)
|
cache.set(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE, 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
|
||||||
@ -138,10 +138,10 @@ def derive_node_without_passphrase(
|
|||||||
def derive_slip21_node_without_passphrase(path: list) -> Slip21Node:
|
def derive_slip21_node_without_passphrase(path: list) -> Slip21Node:
|
||||||
if not storage.is_initialized():
|
if not storage.is_initialized():
|
||||||
raise Exception("Device is not initialized")
|
raise Exception("Device is not initialized")
|
||||||
seed = storage.cache.get_seed_without_passphrase()
|
seed = cache.get(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
seed = mnemonic.get_seed(progress_bar=False)
|
seed = mnemonic.get_seed(progress_bar=False)
|
||||||
storage.cache.set_seed_without_passphrase(seed)
|
cache.set(cache.APP_COMMON_SEED_WITHOUT_PASSPHRASE, seed)
|
||||||
node = Slip21Node(seed)
|
node = Slip21Node(seed)
|
||||||
node.derive_path(path)
|
node.derive_path(path)
|
||||||
return node
|
return node
|
||||||
|
@ -3,39 +3,35 @@ from trezor.crypto import random
|
|||||||
if False:
|
if False:
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
_cached_seed = None # type: Optional[bytes]
|
APP_COMMON_SEED = 0
|
||||||
_cached_seed_without_passphrase = None # type: Optional[bytes] # Needed for SLIP-21
|
APP_COMMON_SEED_WITHOUT_PASSPHRASE = 1
|
||||||
_cached_session_id = None # type: Optional[bytes]
|
APP_CARDANO_ROOT = 2
|
||||||
|
|
||||||
|
_cache_session_id = None # type: Optional[bytes]
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
if False:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def get_session_id() -> bytes:
|
def get_session_id() -> bytes:
|
||||||
global _cached_session_id
|
global _cache_session_id
|
||||||
if not _cached_session_id:
|
if not _cache_session_id:
|
||||||
_cached_session_id = random.bytes(32)
|
_cache_session_id = random.bytes(32)
|
||||||
return _cached_session_id
|
return _cache_session_id
|
||||||
|
|
||||||
|
|
||||||
def set_seed(seed: Optional[bytes]) -> None:
|
def set(key: int, value: Any) -> None:
|
||||||
global _cached_seed
|
global _cache
|
||||||
_cached_seed = seed
|
_cache[key] = value
|
||||||
|
|
||||||
|
|
||||||
def get_seed() -> Optional[bytes]:
|
def get(key: int) -> Any:
|
||||||
return _cached_seed
|
return _cache.get(key)
|
||||||
|
|
||||||
|
|
||||||
def set_seed_without_passphrase(seed: Optional[bytes]) -> None:
|
|
||||||
global _cached_seed_without_passphrase
|
|
||||||
_cached_seed_without_passphrase = seed
|
|
||||||
|
|
||||||
|
|
||||||
def get_seed_without_passphrase() -> Optional[bytes]:
|
|
||||||
return _cached_seed_without_passphrase
|
|
||||||
|
|
||||||
|
|
||||||
def clear() -> None:
|
def clear() -> None:
|
||||||
global _cached_session_id
|
global _cache_session_id
|
||||||
_cached_session_id = None
|
global _cache
|
||||||
|
_cache_session_id = None
|
||||||
set_seed(None)
|
_cache.clear()
|
||||||
set_seed_without_passphrase(None)
|
|
||||||
|
@ -22,6 +22,7 @@ from trezorlib.tools import parse_path
|
|||||||
|
|
||||||
XPUB_PASSPHRASE_A = "xpub6CekxGcnqnJ6osfY4Rrq7W5ogFtR54KUvz4H16XzaQuukMFZCGebEpVznfq4yFcKEmYyShwj2UKjL7CazuNSuhdkofF4mHabHkLxCMVvsqG"
|
XPUB_PASSPHRASE_A = "xpub6CekxGcnqnJ6osfY4Rrq7W5ogFtR54KUvz4H16XzaQuukMFZCGebEpVznfq4yFcKEmYyShwj2UKjL7CazuNSuhdkofF4mHabHkLxCMVvsqG"
|
||||||
XPUB_PASSPHRASE_NONE = "xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy"
|
XPUB_PASSPHRASE_NONE = "xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy"
|
||||||
|
XPUB_CARDANO_PASSPHRASE_B = "d80e770f6dfc3edb58eaab68aa091b2c27b08a47583471e93437ac5f8baa61880c7af4938a941c084c19731e6e57a5710e6ad1196263291aea297ce0eec0f177"
|
||||||
|
|
||||||
|
|
||||||
def _get_xpub(client, passphrase):
|
def _get_xpub(client, passphrase):
|
||||||
@ -222,3 +223,52 @@ def test_passphrase_missing(client):
|
|||||||
response = client.call_raw(messages.PassphraseAck(passphrase=None, on_device=False))
|
response = client.call_raw(messages.PassphraseAck(passphrase=None, on_device=False))
|
||||||
assert isinstance(response, messages.Failure)
|
assert isinstance(response, messages.Failure)
|
||||||
assert response.code == FailureType.DataError
|
assert response.code == FailureType.DataError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip_ui
|
||||||
|
@pytest.mark.skip_t1
|
||||||
|
@pytest.mark.altcoin
|
||||||
|
@pytest.mark.setup_client(passphrase=True)
|
||||||
|
def test_cardano_passphrase(client):
|
||||||
|
# Cardano uses a variation of BIP-39 so we need to ask for the passphrase again.
|
||||||
|
|
||||||
|
response = client.call_raw(messages.Initialize())
|
||||||
|
assert isinstance(response, messages.Features)
|
||||||
|
session_id = response.session_id
|
||||||
|
assert len(session_id) == 32
|
||||||
|
|
||||||
|
# GetPublicKey requires passphrase and since it is not cached,
|
||||||
|
# Trezor will prompt for it.
|
||||||
|
xpub = _get_xpub(client, passphrase="A")
|
||||||
|
assert xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# The passphrase is now cached for non-Cardano coins.
|
||||||
|
xpub = _get_xpub(client, passphrase=None)
|
||||||
|
assert xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Cardano will prompt for it again.
|
||||||
|
response = client.call_raw(
|
||||||
|
messages.CardanoGetPublicKey(address_n=parse_path("44'/1815'/0'/0/0"))
|
||||||
|
)
|
||||||
|
assert isinstance(response, messages.PassphraseRequest)
|
||||||
|
response = client.call_raw(messages.PassphraseAck(passphrase="B"))
|
||||||
|
assert response.xpub == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
|
||||||
|
# But now also Cardano has it cached.
|
||||||
|
response = client.call_raw(
|
||||||
|
messages.CardanoGetPublicKey(address_n=parse_path("44'/1815'/0'/0/0"))
|
||||||
|
)
|
||||||
|
assert response.xpub == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
|
||||||
|
# And others behaviour did not change.
|
||||||
|
xpub = _get_xpub(client, passphrase=None)
|
||||||
|
assert xpub == XPUB_PASSPHRASE_A
|
||||||
|
|
||||||
|
# Initialize with the session id does not destroy the state
|
||||||
|
client.call_raw(messages.Initialize(session_id=session_id))
|
||||||
|
xpub = _get_xpub(client, passphrase=None)
|
||||||
|
assert xpub == XPUB_PASSPHRASE_A
|
||||||
|
response = client.call_raw(
|
||||||
|
messages.CardanoGetPublicKey(address_n=parse_path("44'/1815'/0'/0/0"))
|
||||||
|
)
|
||||||
|
assert response.xpub == XPUB_CARDANO_PASSPHRASE_B
|
||||||
|
Loading…
Reference in New Issue
Block a user