|
|
|
@ -1,15 +1,12 @@
|
|
|
|
|
import builtins
|
|
|
|
|
import gc
|
|
|
|
|
from micropython import const
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
from storage.cache_common import InvalidSessionError, SessionlessCache
|
|
|
|
|
from storage.cache_common import SESSIONLESS_FLAG, SessionlessCache
|
|
|
|
|
from trezor import utils
|
|
|
|
|
|
|
|
|
|
SESSIONLESS_FLAG = const(128)
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from typing import Callable, ParamSpec, TypeVar, overload
|
|
|
|
|
from typing import Callable, ParamSpec, TypeVar
|
|
|
|
|
|
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
P = ParamSpec("P")
|
|
|
|
@ -29,42 +26,6 @@ def check_thp_is_not_used(f: Callable[P, T]) -> Callable[P, T]:
|
|
|
|
|
return inner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Traditional cache keys
|
|
|
|
|
APP_COMMON_SEED = const(0)
|
|
|
|
|
APP_COMMON_AUTHORIZATION_TYPE = const(1)
|
|
|
|
|
APP_COMMON_AUTHORIZATION_DATA = const(2)
|
|
|
|
|
APP_COMMON_NONCE = const(3)
|
|
|
|
|
if not utils.BITCOIN_ONLY:
|
|
|
|
|
APP_COMMON_DERIVE_CARDANO = const(4)
|
|
|
|
|
APP_CARDANO_ICARUS_SECRET = const(5)
|
|
|
|
|
APP_CARDANO_ICARUS_TREZOR_SECRET = const(6)
|
|
|
|
|
APP_MONERO_LIVE_REFRESH = const(7)
|
|
|
|
|
|
|
|
|
|
# Keys that are valid across sessions
|
|
|
|
|
APP_COMMON_SEED_WITHOUT_PASSPHRASE = const(0 | SESSIONLESS_FLAG)
|
|
|
|
|
APP_COMMON_SAFETY_CHECKS_TEMPORARY = const(1 | SESSIONLESS_FLAG)
|
|
|
|
|
STORAGE_DEVICE_EXPERIMENTAL_FEATURES = const(2 | SESSIONLESS_FLAG)
|
|
|
|
|
APP_COMMON_REQUEST_PIN_LAST_UNLOCK = const(3 | SESSIONLESS_FLAG)
|
|
|
|
|
APP_COMMON_BUSY_DEADLINE_MS = const(4 | SESSIONLESS_FLAG)
|
|
|
|
|
APP_MISC_COSI_NONCE = const(5 | SESSIONLESS_FLAG)
|
|
|
|
|
APP_MISC_COSI_COMMITMENT = const(6 | SESSIONLESS_FLAG)
|
|
|
|
|
|
|
|
|
|
# === Homescreen storage ===
|
|
|
|
|
# This does not logically belong to the "cache" functionality, but the cache module is
|
|
|
|
|
# a convenient place to put this.
|
|
|
|
|
# When a Homescreen layout is instantiated, it checks the value of `homescreen_shown`
|
|
|
|
|
# to know whether it should render itself or whether the result of a previous instance
|
|
|
|
|
# is still on. This way we can avoid unnecessary fadeins/fadeouts when a workflow ends.
|
|
|
|
|
HOMESCREEN_ON = object()
|
|
|
|
|
LOCKSCREEN_ON = object()
|
|
|
|
|
BUSYSCREEN_ON = object()
|
|
|
|
|
homescreen_shown: object | None = None
|
|
|
|
|
|
|
|
|
|
# Timestamp of last autolock activity.
|
|
|
|
|
# Here to persist across main loop restart between workflows.
|
|
|
|
|
autolock_last_touch: int | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# XXX
|
|
|
|
|
# Allocation notes:
|
|
|
|
|
# Instantiation of a DataCache subclass should make as little garbage as possible, so
|
|
|
|
@ -90,16 +51,6 @@ _SESSIONLESS_CACHE.clear()
|
|
|
|
|
|
|
|
|
|
gc.collect()
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
|
def get(key: int) -> bytes | None: ...
|
|
|
|
|
|
|
|
|
|
@overload
|
|
|
|
|
def get(key: int, default: T) -> bytes | T: # noqa: F811
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Common functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -121,52 +72,8 @@ def get_int_all_sessions(key: int) -> builtins.set[int]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Sessionless functions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_sessionless(
|
|
|
|
|
key: int, default: T | None = None
|
|
|
|
|
) -> bytes | T | None: # noqa: F811
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
return _SESSIONLESS_CACHE.get(key ^ SESSIONLESS_FLAG, default)
|
|
|
|
|
raise ValueError("Argument 'key' does not have a sessionless flag")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_int_sessionless(
|
|
|
|
|
key: int, default: T | None = None
|
|
|
|
|
) -> int | T | None: # noqa: F811
|
|
|
|
|
encoded = get_sessionless(key)
|
|
|
|
|
if encoded is None:
|
|
|
|
|
return default
|
|
|
|
|
else:
|
|
|
|
|
return int.from_bytes(encoded, "big")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_set_sessionless(key: int) -> bool:
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
return _SESSIONLESS_CACHE.is_set(key ^ SESSIONLESS_FLAG)
|
|
|
|
|
raise ValueError("Argument 'key' does not have a sessionless flag")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_sessionless(key: int, value: bytes) -> None:
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
_SESSIONLESS_CACHE.set(key ^ SESSIONLESS_FLAG, value)
|
|
|
|
|
return
|
|
|
|
|
raise ValueError("Argument 'key' does not have a sessionless flag")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_int_sessionless(key: int, value: int) -> None:
|
|
|
|
|
|
|
|
|
|
if not key & SESSIONLESS_FLAG:
|
|
|
|
|
raise ValueError("Argument 'key' does not have a sessionless flag")
|
|
|
|
|
|
|
|
|
|
length = _SESSIONLESS_CACHE.fields[key ^ SESSIONLESS_FLAG]
|
|
|
|
|
encoded = value.to_bytes(length, "big")
|
|
|
|
|
|
|
|
|
|
# Ensure that the value fits within the length. Micropython's int.to_bytes()
|
|
|
|
|
# doesn't raise OverflowError.
|
|
|
|
|
assert int.from_bytes(encoded, "big") == value
|
|
|
|
|
|
|
|
|
|
set_sessionless(key, encoded)
|
|
|
|
|
def get_sessionless_cache() -> SessionlessCache:
|
|
|
|
|
return _SESSIONLESS_CACHE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Codec_v1 specific functions
|
|
|
|
@ -180,73 +87,3 @@ def start_session(received_session_id: bytes | None = None) -> bytes:
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def end_current_session() -> None:
|
|
|
|
|
cache_codec.end_current_session()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def delete(key: int) -> None:
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
return _SESSIONLESS_CACHE.delete(key ^ SESSIONLESS_FLAG)
|
|
|
|
|
active_session = cache_codec.get_active_session()
|
|
|
|
|
if active_session is None:
|
|
|
|
|
raise InvalidSessionError
|
|
|
|
|
return active_session.delete(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def get(key: int, default: T | None = None) -> bytes | T | None: # noqa: F811
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
return get_sessionless(key, default)
|
|
|
|
|
active_session = cache_codec.get_active_session()
|
|
|
|
|
if active_session is None:
|
|
|
|
|
raise InvalidSessionError
|
|
|
|
|
return active_session.get(key, default)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def get_int(key: int, default: T | None = None) -> int | T | None: # noqa: F811
|
|
|
|
|
encoded = get(key)
|
|
|
|
|
if encoded is None:
|
|
|
|
|
return default
|
|
|
|
|
else:
|
|
|
|
|
return int.from_bytes(encoded, "big")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def is_set(key: int) -> bool:
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
return _SESSIONLESS_CACHE.is_set(key ^ SESSIONLESS_FLAG)
|
|
|
|
|
active_session = cache_codec.get_active_session()
|
|
|
|
|
if active_session is None:
|
|
|
|
|
raise InvalidSessionError
|
|
|
|
|
return active_session.is_set(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def set(key: int, value: bytes) -> None:
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
_SESSIONLESS_CACHE.set(key ^ SESSIONLESS_FLAG, value)
|
|
|
|
|
return
|
|
|
|
|
active_session = cache_codec.get_active_session()
|
|
|
|
|
if active_session is None:
|
|
|
|
|
raise InvalidSessionError
|
|
|
|
|
active_session.set(key, value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@check_thp_is_not_used
|
|
|
|
|
def set_int(key: int, value: int) -> None:
|
|
|
|
|
active_session = cache_codec.get_active_session()
|
|
|
|
|
|
|
|
|
|
if key & SESSIONLESS_FLAG:
|
|
|
|
|
length = _SESSIONLESS_CACHE.fields[key ^ SESSIONLESS_FLAG]
|
|
|
|
|
elif active_session is None:
|
|
|
|
|
raise InvalidSessionError
|
|
|
|
|
else:
|
|
|
|
|
length = active_session.fields[key]
|
|
|
|
|
|
|
|
|
|
encoded = value.to_bytes(length, "big")
|
|
|
|
|
|
|
|
|
|
# Ensure that the value fits within the length. Micropython's int.to_bytes()
|
|
|
|
|
# doesn't raise OverflowError.
|
|
|
|
|
assert int.from_bytes(encoded, "big") == value
|
|
|
|
|
|
|
|
|
|
set(key, encoded)
|
|
|
|
|