mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-03-16 08:06:05 +00:00
Merge pull request #852 from trezor/tsusanka/multiple-passphrases
Allow multiple sessions/passphrases
This commit is contained in:
commit
e568faa862
@ -72,15 +72,16 @@ def get_features() -> Features:
|
||||
f.sd_card_present = sdcard.is_present()
|
||||
f.sd_protection = storage.sd_salt.is_enabled()
|
||||
f.wipe_code_protection = config.has_wipe_code()
|
||||
f.session_id = cache.get_session_id()
|
||||
f.passphrase_always_on_device = storage.device.get_passphrase_always_on_device()
|
||||
return f
|
||||
|
||||
|
||||
async def handle_Initialize(ctx: wire.Context, msg: Initialize) -> Features:
|
||||
if msg.session_id is None or msg.session_id != cache.get_session_id():
|
||||
cache.clear()
|
||||
return get_features()
|
||||
features = get_features()
|
||||
if msg.session_id:
|
||||
msg.session_id = bytes(msg.session_id)
|
||||
features.session_id = cache.start_session(msg.session_id)
|
||||
return features
|
||||
|
||||
|
||||
async def handle_GetFeatures(ctx: wire.Context, msg: GetFeatures) -> Features:
|
||||
|
@ -12,7 +12,7 @@ def is_initialized() -> bool:
|
||||
|
||||
def wipe() -> None:
|
||||
config.wipe()
|
||||
cache.clear()
|
||||
cache.clear_all()
|
||||
|
||||
|
||||
def init_unlocked() -> None:
|
||||
|
@ -1,36 +1,82 @@
|
||||
from trezor.crypto import random
|
||||
|
||||
if False:
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
_MAX_SESSIONS_COUNT = 10
|
||||
_SESSIONLESS_FLAG = 128
|
||||
|
||||
# Traditional cache keys
|
||||
APP_COMMON_SEED = 0
|
||||
APP_COMMON_SEED_WITHOUT_PASSPHRASE = 1
|
||||
APP_CARDANO_ROOT = 2
|
||||
APP_MONERO_LIVE_REFRESH = 3
|
||||
APP_CARDANO_ROOT = 1
|
||||
APP_MONERO_LIVE_REFRESH = 2
|
||||
|
||||
_cache_session_id = None # type: Optional[bytes]
|
||||
_cache = {}
|
||||
# Keys that are valid across sessions
|
||||
APP_COMMON_SEED_WITHOUT_PASSPHRASE = 1 | _SESSIONLESS_FLAG
|
||||
|
||||
|
||||
_active_session_id = None # type: Optional[bytes]
|
||||
_caches = {} # type: Dict[bytes, Dict[int, Any]]
|
||||
_session_ids = [] # type: List[bytes]
|
||||
_sessionless_cache = {} # type: Dict[int, Any]
|
||||
|
||||
if False:
|
||||
from typing import Any
|
||||
|
||||
|
||||
def get_session_id() -> bytes:
|
||||
global _cache_session_id
|
||||
if not _cache_session_id:
|
||||
_cache_session_id = random.bytes(32)
|
||||
return _cache_session_id
|
||||
def _move_session_ids_queue(session_id: bytes) -> None:
|
||||
# Move the LRU session ids queue.
|
||||
if session_id in _session_ids:
|
||||
_session_ids.remove(session_id)
|
||||
|
||||
while len(_session_ids) >= _MAX_SESSIONS_COUNT:
|
||||
remove_session_id = _session_ids.pop()
|
||||
del _caches[remove_session_id]
|
||||
|
||||
_session_ids.insert(0, session_id)
|
||||
|
||||
|
||||
def start_session(received_session_id: bytes = None) -> bytes:
|
||||
if received_session_id and received_session_id in _session_ids:
|
||||
session_id = received_session_id
|
||||
else:
|
||||
session_id = random.bytes(32)
|
||||
_caches[session_id] = {}
|
||||
|
||||
global _active_session_id
|
||||
_active_session_id = session_id
|
||||
_move_session_ids_queue(session_id)
|
||||
return _active_session_id
|
||||
|
||||
|
||||
def is_session_started() -> bool:
|
||||
return _active_session_id is not None
|
||||
|
||||
|
||||
def set(key: int, value: Any) -> None:
|
||||
_cache[key] = value
|
||||
if key & _SESSIONLESS_FLAG:
|
||||
_sessionless_cache[key] = value
|
||||
return
|
||||
if _active_session_id is None:
|
||||
raise RuntimeError # no session active
|
||||
_caches[_active_session_id][key] = value
|
||||
|
||||
|
||||
def get(key: int) -> Any:
|
||||
return _cache.get(key)
|
||||
if key & _SESSIONLESS_FLAG:
|
||||
return _sessionless_cache.get(key)
|
||||
if _active_session_id is None:
|
||||
raise RuntimeError # no session active
|
||||
return _caches[_active_session_id].get(key)
|
||||
|
||||
|
||||
def clear() -> None:
|
||||
global _cache_session_id
|
||||
_cache_session_id = None
|
||||
_cache.clear()
|
||||
def clear_all() -> None:
|
||||
global _active_session_id
|
||||
global _caches
|
||||
global _session_ids
|
||||
global _sessionless_cache
|
||||
|
||||
_active_session_id = None
|
||||
_caches.clear()
|
||||
_session_ids.clear()
|
||||
_sessionless_cache.clear()
|
||||
|
@ -1,41 +1,54 @@
|
||||
from common import *
|
||||
from mock import patch
|
||||
from mock_storage import mock_storage
|
||||
|
||||
import storage
|
||||
from storage import cache
|
||||
from trezor.messages.Initialize import Initialize
|
||||
from trezor.messages.ClearSession import ClearSession
|
||||
from trezor.wire import DUMMY_CONTEXT
|
||||
|
||||
from apps.homescreen import handle_Initialize, handle_ClearSession
|
||||
from apps.homescreen import handle_Initialize
|
||||
|
||||
KEY = 99
|
||||
|
||||
|
||||
class TestStorageCache(unittest.TestCase):
|
||||
def test_session_id(self):
|
||||
session_id_a = cache.get_session_id()
|
||||
def test_start_session(self):
|
||||
session_id_a = cache.start_session()
|
||||
self.assertIsNotNone(session_id_a)
|
||||
session_id_b = cache.get_session_id()
|
||||
self.assertEqual(session_id_a, session_id_b)
|
||||
session_id_b = cache.start_session()
|
||||
self.assertNotEqual(session_id_a, session_id_b)
|
||||
|
||||
cache.clear()
|
||||
session_id_c = cache.get_session_id()
|
||||
self.assertIsNotNone(session_id_c)
|
||||
self.assertNotEqual(session_id_a, session_id_c)
|
||||
cache.clear_all()
|
||||
with self.assertRaises(RuntimeError):
|
||||
cache.set(KEY, "something")
|
||||
with self.assertRaises(RuntimeError):
|
||||
cache.get(KEY)
|
||||
|
||||
def test_session_queue(self):
|
||||
session_id = cache.start_session()
|
||||
self.assertEqual(cache.start_session(session_id), session_id)
|
||||
cache.set(KEY, "A")
|
||||
for i in range(cache._MAX_SESSIONS_COUNT):
|
||||
cache.start_session()
|
||||
self.assertNotEqual(cache.start_session(session_id), session_id)
|
||||
self.assertIsNone(cache.get(KEY))
|
||||
|
||||
def test_get_set(self):
|
||||
value = cache.get(KEY)
|
||||
self.assertIsNone(value)
|
||||
|
||||
session_id1 = cache.start_session()
|
||||
cache.set(KEY, "hello")
|
||||
value = cache.get(KEY)
|
||||
self.assertEqual(value, "hello")
|
||||
self.assertEqual(cache.get(KEY), "hello")
|
||||
|
||||
cache.clear()
|
||||
value = cache.get(KEY)
|
||||
self.assertIsNone(value)
|
||||
session_id2 = cache.start_session()
|
||||
cache.set(KEY, "world")
|
||||
self.assertEqual(cache.get(KEY), "world")
|
||||
|
||||
cache.start_session(session_id2)
|
||||
self.assertEqual(cache.get(KEY), "world")
|
||||
cache.start_session(session_id1)
|
||||
self.assertEqual(cache.get(KEY), "hello")
|
||||
|
||||
cache.clear_all()
|
||||
with self.assertRaises(RuntimeError):
|
||||
cache.get(KEY)
|
||||
|
||||
@mock_storage
|
||||
def test_Initialize(self):
|
||||
@ -44,38 +57,32 @@ class TestStorageCache(unittest.TestCase):
|
||||
return await_result(handle_Initialize(DUMMY_CONTEXT, msg))
|
||||
|
||||
# calling Initialize without an ID allocates a new one
|
||||
session_id = cache.get_session_id()
|
||||
session_id = cache.start_session()
|
||||
features = call_Initialize()
|
||||
new_session_id = cache.get_session_id()
|
||||
self.assertNotEqual(session_id, new_session_id)
|
||||
self.assertEqual(new_session_id, features.session_id)
|
||||
self.assertNotEqual(session_id, features.session_id)
|
||||
|
||||
# calling Initialize with the current ID does not allocate a new one
|
||||
features = call_Initialize(session_id=new_session_id)
|
||||
same_session_id = cache.get_session_id()
|
||||
self.assertEqual(new_session_id, same_session_id)
|
||||
self.assertEqual(same_session_id, features.session_id)
|
||||
features = call_Initialize(session_id=session_id)
|
||||
self.assertEqual(session_id, features.session_id)
|
||||
|
||||
call_Initialize()
|
||||
# calling Initialize with a non-current ID returns a different one
|
||||
features = call_Initialize(session_id=new_session_id)
|
||||
self.assertNotEqual(new_session_id, features.session_id)
|
||||
|
||||
# allocating a new session ID clears the cache
|
||||
# store "hello"
|
||||
cache.set(KEY, "hello")
|
||||
# check that it is cleared
|
||||
features = call_Initialize()
|
||||
session_id = features.session_id
|
||||
self.assertIsNone(cache.get(KEY))
|
||||
|
||||
# resuming a session does not clear the cache
|
||||
# store "hello" again
|
||||
cache.set(KEY, "hello")
|
||||
call_Initialize(session_id=features.session_id)
|
||||
self.assertEqual(cache.get(KEY), "hello")
|
||||
|
||||
# supplying a different session ID clears the cache
|
||||
self.assertNotEqual(new_session_id, features.session_id)
|
||||
call_Initialize(session_id=new_session_id)
|
||||
# supplying a different session ID starts a new cache
|
||||
call_Initialize(session_id=b"A")
|
||||
self.assertIsNone(cache.get(KEY))
|
||||
|
||||
# but resuming a session loads the previous one
|
||||
call_Initialize(session_id=session_id)
|
||||
self.assertEqual(cache.get(KEY), "hello")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
@ -81,6 +81,8 @@ static const uint32_t META_MAGIC_V10 = 0xFFFFFFFF;
|
||||
#define KEY_U2F_ROOT (17 | APP | FLAG_PUBLIC_SHIFTED) // node
|
||||
#define KEY_DEBUG_LINK_PIN (255 | APP | FLAG_PUBLIC_SHIFTED) // string(10)
|
||||
|
||||
#define MAX_SESSIONS_COUNT 10
|
||||
|
||||
// The PIN value corresponding to an empty PIN.
|
||||
static const uint32_t PIN_EMPTY = 1;
|
||||
|
||||
@ -120,11 +122,23 @@ be added to the storage u2f_counter to get the real counter value.
|
||||
* storage.u2f_counter + config_u2f_offset.
|
||||
* This corresponds to the number of cleared bits in the U2FAREA.
|
||||
*/
|
||||
static secbool sessionSeedCached;
|
||||
static uint8_t CONFIDENTIAL sessionSeed[64];
|
||||
|
||||
static secbool sessionIdCached;
|
||||
static uint8_t sessionId[32];
|
||||
// Session management
|
||||
typedef struct {
|
||||
uint8_t id[32];
|
||||
uint32_t last_use;
|
||||
uint8_t seed[64];
|
||||
secbool seedCached;
|
||||
} Session;
|
||||
|
||||
static void session_clearCache(Session *session);
|
||||
static uint8_t session_findLeastRecent(void);
|
||||
static uint8_t session_findSession(const uint8_t *sessionId);
|
||||
|
||||
static CONFIDENTIAL Session sessionsCache[MAX_SESSIONS_COUNT];
|
||||
static Session *activeSessionCache;
|
||||
|
||||
static uint32_t sessionUseCounter = 0;
|
||||
|
||||
#define autoLockDelayMsDefault (10 * 60 * 1000U) // 10 minutes
|
||||
static secbool autoLockDelayMsCached = secfalse;
|
||||
@ -402,19 +416,30 @@ void config_init(void) {
|
||||
}
|
||||
data2hex(config_uuid, sizeof(config_uuid), config_uuid_str);
|
||||
|
||||
session_clear(false);
|
||||
|
||||
usbTiny(oldTiny);
|
||||
}
|
||||
|
||||
void session_clear(bool lock) {
|
||||
sessionSeedCached = secfalse;
|
||||
memzero(&sessionSeed, sizeof(sessionSeed));
|
||||
sessionIdCached = secfalse;
|
||||
memzero(&sessionId, sizeof(sessionId));
|
||||
for (uint8_t i = 0; i < MAX_SESSIONS_COUNT; i++) {
|
||||
session_clearCache(sessionsCache + i);
|
||||
}
|
||||
activeSessionCache = NULL;
|
||||
if (lock) {
|
||||
storage_lock();
|
||||
config_lockDevice();
|
||||
}
|
||||
}
|
||||
|
||||
void session_clearCache(Session *session) {
|
||||
session->last_use = 0;
|
||||
memzero(session->id, sizeof(session->id));
|
||||
memzero(session->seed, sizeof(session->seed));
|
||||
session->seedCached = false;
|
||||
}
|
||||
|
||||
void config_lockDevice(void) { storage_lock(); }
|
||||
|
||||
static void get_u2froot_callback(uint32_t iter, uint32_t total) {
|
||||
layoutProgress(_("Updating"), 1000 * iter / total);
|
||||
}
|
||||
@ -422,11 +447,11 @@ static void get_u2froot_callback(uint32_t iter, uint32_t total) {
|
||||
static void config_compute_u2froot(const char *mnemonic,
|
||||
StorageHDNode *u2froot) {
|
||||
static CONFIDENTIAL HDNode node;
|
||||
static CONFIDENTIAL uint8_t seed[64];
|
||||
char oldTiny = usbTiny(1);
|
||||
mnemonic_to_seed(mnemonic, "", sessionSeed,
|
||||
get_u2froot_callback); // BIP-0039
|
||||
mnemonic_to_seed(mnemonic, "", seed, get_u2froot_callback); // BIP-0039
|
||||
usbTiny(oldTiny);
|
||||
hdnode_from_seed(sessionSeed, 64, NIST256P1_NAME, &node);
|
||||
hdnode_from_seed(seed, 64, NIST256P1_NAME, &node);
|
||||
hdnode_private_ckd(&node, U2F_KEY_PATH);
|
||||
u2froot->depth = node.depth;
|
||||
u2froot->child_num = U2F_KEY_PATH;
|
||||
@ -437,6 +462,7 @@ static void config_compute_u2froot(const char *mnemonic,
|
||||
memcpy(u2froot->private_key.bytes, node.private_key,
|
||||
sizeof(node.private_key));
|
||||
memzero(&node, sizeof(node));
|
||||
memzero(&seed, sizeof(seed));
|
||||
session_clear(false); // invalidate seed cache
|
||||
}
|
||||
|
||||
@ -551,8 +577,9 @@ static void get_root_node_callback(uint32_t iter, uint32_t total) {
|
||||
|
||||
const uint8_t *config_getSeed(void) {
|
||||
// root node is properly cached
|
||||
if (sectrue == sessionSeedCached) {
|
||||
return sessionSeed;
|
||||
if ((activeSessionCache != NULL) &&
|
||||
(activeSessionCache->seedCached == sectrue)) {
|
||||
return activeSessionCache->seed;
|
||||
}
|
||||
|
||||
// if storage has mnemonic, convert it to node and use it
|
||||
@ -575,13 +602,17 @@ const uint8_t *config_getSeed(void) {
|
||||
}
|
||||
}
|
||||
char oldTiny = usbTiny(1);
|
||||
mnemonic_to_seed(mnemonic, passphrase, sessionSeed,
|
||||
if (activeSessionCache == NULL) {
|
||||
// this should not happen if the Host behaves and sends Initialize first
|
||||
session_startSession(NULL);
|
||||
}
|
||||
mnemonic_to_seed(mnemonic, passphrase, activeSessionCache->seed,
|
||||
get_root_node_callback); // BIP-0039
|
||||
memzero(mnemonic, sizeof(mnemonic));
|
||||
memzero(passphrase, sizeof(passphrase));
|
||||
usbTiny(oldTiny);
|
||||
sessionSeedCached = sectrue;
|
||||
return sessionSeed;
|
||||
activeSessionCache->seedCached = sectrue;
|
||||
return activeSessionCache->seed;
|
||||
} else {
|
||||
fsm_sendFailure(FailureType_Failure_NotInitialized,
|
||||
_("Device not initialized"));
|
||||
@ -772,12 +803,51 @@ bool config_changeWipeCode(const char *pin, const char *wipe_code) {
|
||||
return sectrue == ret;
|
||||
}
|
||||
|
||||
const uint8_t *session_getSessionId(void) {
|
||||
if (!sessionIdCached) {
|
||||
random_buffer(sessionId, 32);
|
||||
uint8_t session_findLeastRecent(void) {
|
||||
uint8_t least_recent_index = MAX_SESSIONS_COUNT;
|
||||
uint32_t least_recent_use = sessionUseCounter;
|
||||
for (uint8_t i = 0; i < MAX_SESSIONS_COUNT; i++) {
|
||||
if (sessionsCache[i].last_use == 0) {
|
||||
return i;
|
||||
}
|
||||
if (sessionsCache[i].last_use <= least_recent_use) {
|
||||
least_recent_use = sessionsCache[i].last_use;
|
||||
least_recent_index = i;
|
||||
}
|
||||
}
|
||||
sessionIdCached = sectrue;
|
||||
return sessionId;
|
||||
ensure(sectrue * (least_recent_index < MAX_SESSIONS_COUNT), NULL);
|
||||
return least_recent_index;
|
||||
}
|
||||
|
||||
uint8_t session_findSession(const uint8_t *sessionId) {
|
||||
for (uint8_t i = 0; i < MAX_SESSIONS_COUNT; i++) {
|
||||
if (sessionsCache[i].last_use != 0) {
|
||||
if (memcmp(sessionsCache[i].id, sessionId, 32) == 0) { // session found
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return MAX_SESSIONS_COUNT;
|
||||
}
|
||||
|
||||
uint8_t *session_startSession(const uint8_t *received_session_id) {
|
||||
int session_index = MAX_SESSIONS_COUNT;
|
||||
|
||||
if (received_session_id != NULL) {
|
||||
session_index = session_findSession(received_session_id);
|
||||
}
|
||||
|
||||
if (session_index == MAX_SESSIONS_COUNT) {
|
||||
// Session not found in cache. Use an empty one or the least recently used.
|
||||
session_index = session_findLeastRecent();
|
||||
session_clearCache(sessionsCache + session_index);
|
||||
random_buffer(sessionsCache[session_index].id, 32);
|
||||
}
|
||||
|
||||
sessionUseCounter++;
|
||||
sessionsCache[session_index].last_use = sessionUseCounter;
|
||||
activeSessionCache = sessionsCache + session_index;
|
||||
return activeSessionCache->id;
|
||||
}
|
||||
|
||||
bool session_isUnlocked(void) { return sectrue == storage_is_unlocked(); }
|
||||
|
@ -88,6 +88,7 @@ extern Storage configUpdate;
|
||||
|
||||
void config_init(void);
|
||||
void session_clear(bool lock);
|
||||
void config_lockDevice(void);
|
||||
|
||||
void config_loadDevice(const LoadDevice *msg);
|
||||
|
||||
@ -108,7 +109,7 @@ bool config_getPassphraseProtection(bool *passphrase_protection);
|
||||
bool config_getHomescreen(uint8_t *dest, uint16_t dest_size);
|
||||
void config_setHomescreen(const uint8_t *data, uint32_t size);
|
||||
|
||||
const uint8_t *session_getSessionId(void);
|
||||
uint8_t *session_startSession(const uint8_t *received_session_id);
|
||||
|
||||
bool config_setMnemonic(const char *mnemonic);
|
||||
bool config_containsMnemonic(const char *mnemonic);
|
||||
|
@ -17,31 +17,7 @@
|
||||
* along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
void fsm_msgInitialize(const Initialize *msg) {
|
||||
recovery_abort();
|
||||
signing_abort();
|
||||
if (msg && msg->has_session_id && msg->session_id.size == 32) {
|
||||
if (0 != memcmp(session_getSessionId(), msg->session_id.bytes, 32)) {
|
||||
// If session id was specified but does not match -> clear the cache.
|
||||
session_clear(false); // do not lock
|
||||
}
|
||||
} else {
|
||||
// If session id was not specified -> clear the cache.
|
||||
session_clear(false); // do not lock
|
||||
}
|
||||
layoutHome();
|
||||
fsm_msgGetFeatures(0);
|
||||
}
|
||||
|
||||
void fsm_msgGetFeatures(const GetFeatures *msg) {
|
||||
(void)msg;
|
||||
RESP_INIT(Features);
|
||||
|
||||
resp->has_session_id = true;
|
||||
memcpy(resp->session_id.bytes, session_getSessionId(),
|
||||
sizeof(resp->session_id.bytes));
|
||||
resp->session_id.size = sizeof(resp->session_id.bytes);
|
||||
|
||||
bool get_features(Features *resp) {
|
||||
resp->has_vendor = true;
|
||||
strlcpy(resp->vendor, "trezor.io", sizeof(resp->vendor));
|
||||
resp->has_major_version = true;
|
||||
@ -103,7 +79,35 @@ void fsm_msgGetFeatures(const GetFeatures *msg) {
|
||||
resp->capabilities[6] = Capability_Capability_Stellar;
|
||||
resp->capabilities[7] = Capability_Capability_U2F;
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
|
||||
void fsm_msgInitialize(const Initialize *msg) {
|
||||
recovery_abort();
|
||||
signing_abort();
|
||||
|
||||
uint8_t *session_id;
|
||||
if (msg && msg->has_session_id) {
|
||||
session_id = session_startSession(msg->session_id.bytes);
|
||||
} else {
|
||||
session_id = session_startSession(NULL);
|
||||
}
|
||||
|
||||
RESP_INIT(Features);
|
||||
get_features(resp);
|
||||
|
||||
resp->has_session_id = true;
|
||||
memcpy(resp->session_id.bytes, session_id, sizeof(resp->session_id.bytes));
|
||||
resp->session_id.size = sizeof(resp->session_id.bytes);
|
||||
|
||||
layoutHome();
|
||||
msg_write(MessageType_MessageType_Features, resp);
|
||||
}
|
||||
|
||||
void fsm_msgGetFeatures(const GetFeatures *msg) {
|
||||
(void)msg;
|
||||
RESP_INIT(Features);
|
||||
get_features(resp);
|
||||
msg_write(MessageType_MessageType_Features, resp);
|
||||
}
|
||||
|
||||
@ -343,7 +347,9 @@ void fsm_msgCancel(const Cancel *msg) {
|
||||
|
||||
void fsm_msgClearSession(const ClearSession *msg) {
|
||||
(void)msg;
|
||||
session_clear(true); // clear PIN as well
|
||||
// we do not actually clear the session, we just lock it
|
||||
// TODO: the message should be called LockSession see #819
|
||||
config_lockDevice();
|
||||
layoutScreensaver();
|
||||
fsm_sendSuccess(_("Session cleared"));
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ void check_lock_screen(void) {
|
||||
|
||||
if (button.YesUp) {
|
||||
// lock the screen
|
||||
session_clear(true);
|
||||
config_lockDevice();
|
||||
layoutScreensaver();
|
||||
} else {
|
||||
// resume homescreen
|
||||
@ -85,7 +85,7 @@ void check_lock_screen(void) {
|
||||
if ((timer_ms() - system_millis_lock_start) >=
|
||||
config_getAutoLockDelayMs()) {
|
||||
// lock the screen
|
||||
session_clear(true);
|
||||
config_lockDevice();
|
||||
layoutScreensaver();
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,34 @@
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
import random
|
||||
|
||||
import pytest
|
||||
|
||||
from trezorlib import messages
|
||||
from trezorlib.messages import FailureType
|
||||
from trezorlib.tools import parse_path
|
||||
|
||||
XPUB_PASSPHRASE_A = "xpub6CekxGcnqnJ6osfY4Rrq7W5ogFtR54KUvz4H16XzaQuukMFZCGebEpVznfq4yFcKEmYyShwj2UKjL7CazuNSuhdkofF4mHabHkLxCMVvsqG"
|
||||
XPUB_PASSPHRASES = {
|
||||
"A": "xpub6CekxGcnqnJ6osfY4Rrq7W5ogFtR54KUvz4H16XzaQuukMFZCGebEpVznfq4yFcKEmYyShwj2UKjL7CazuNSuhdkofF4mHabHkLxCMVvsqG",
|
||||
"B": "xpub6CFxuyQpgryoR64QC38w42dLgDv5P4qWXhn1fbaN62UYzu1wJXZyrYqGnkq5d8xPUK68RXtXFBiqp3rfLGpeQ57zLtx675ZZn5ezKMAWQfu",
|
||||
"C": "xpub6BhJMNFwCjGKyRb9RUcnuHhJ2TgcnurfUrQszrmZ1rg8aadsMXLySF6LY3qf4pR7bY4vwpd1VwLPQvuCRr7BPTs8wvqrv2gexxViwj96czT",
|
||||
"D": "xpub6DK1vnTBe9EkhLACJRvovv8RSUC3MSiEV64opM7XUqrowxQ8J5C2WpA6n4vt5LS3bs618aKzi7k5w7VzNCv3SfqEeSepvvHaPhRoTvRqR5u",
|
||||
"E": "xpub6CqbQjHN7r68GHh7RsiAyrdAmyiZQgWvDxQtba2NxZHumvfMK31U6emVQSexYrTAHWQeLygRD1yXZQLsCs1LLJtaeSxMAnh2YUmP3ov6EQz",
|
||||
"F": "xpub6CRDxB1aHVNHfqjPeYhnPBhBfkQb4b4K581uYKxwv4KnkiVsRttBCXSkZM5jtP1Vv2v3wr5FxfzqWWDApLCbutBLnfwYpkWpZUmZSp6hqg5",
|
||||
"G": "xpub6DGKmAKYDF44KQEaqXY3bbJNufEDi6QPnahV4JdBxFbFCN9Vg7ZfUHxPv3uhjeeJEtPe2PjFKWRsUrEF3RDttnXf9wXq3BfYBZemwKipJ24",
|
||||
"H": "xpub6Bg8zbY94d1cBbAGT2crZL7C1UM8JWCP5CCtiHMnV4tB1pE9oCfjvZxRRFLi6EiamBDyCs3ARaHwU2FLx76YYCPFRVc1YyJi6depNtWRnoJ",
|
||||
"I": "xpub6DMpHuTZTTN64eEHcNpyeQwehXgWTrY668ZkRWnRfkFEGKpNv2uPR3js1dJgcFRksSmrdtpHqFDPTzFsR1HqvzNdgZwXmk9vCLt1ypwUzA3",
|
||||
"J": "xpub6CVeYPTG57D4tm9BvwCcakppwGJstbXyK8Yd611agusZuHmx7og3dNvr6pjMN6e4BoaNc5MZA4TjMLjMT2h2vJRU8rYLvHFUwrEL9zDbuqe",
|
||||
}
|
||||
XPUB_PASSPHRASE_NONE = "xpub6BiVtCpG9fQPxnPmHXG8PhtzQdWC2Su4qWu6XW9tpWFYhxydCLJGrWBJZ5H6qTAHdPQ7pQhtpjiYZVZARo14qHiay2fvrX996oEP42u8wZy"
|
||||
XPUB_CARDANO_PASSPHRASE_B = "d80e770f6dfc3edb58eaab68aa091b2c27b08a47583471e93437ac5f8baa61880c7af4938a941c084c19731e6e57a5710e6ad1196263291aea297ce0eec0f177"
|
||||
|
||||
ADDRESS_N = parse_path("44h/0h/0h")
|
||||
XPUB_REQUEST = messages.GetPublicKey(address_n=ADDRESS_N, coin_name="Bitcoin")
|
||||
|
||||
SESSIONS_STORED = 10
|
||||
|
||||
|
||||
def _init_session(client, session_id=None):
|
||||
"""Call Initialize, check and return the session ID."""
|
||||
@ -54,24 +69,143 @@ def test_session_with_passphrase(client):
|
||||
|
||||
# GetPublicKey requires passphrase and since it is not cached,
|
||||
# Trezor will prompt for it.
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# Call Initialize again, this time with the received session id and then call
|
||||
# GetPublicKey. The passphrase should be cached now so Trezor must
|
||||
# not ask for it again, whilst returning the same xpub.
|
||||
new_session_id = _init_session(client, session_id=session_id)
|
||||
assert new_session_id == session_id
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# If we set session id in Initialize to None, the cache will be cleared
|
||||
# and Trezor will ask for the passphrase again.
|
||||
new_session_id = _init_session(client)
|
||||
assert new_session_id != session_id
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# Unknown session id is the same as setting it to None.
|
||||
_init_session(client, session_id=b"X" * 32)
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.timeout(300)
|
||||
@pytest.mark.setup_client(passphrase=True)
|
||||
def test_multiple_sessions(client):
|
||||
# start SESSIONS_STORED sessions
|
||||
session_ids = []
|
||||
for _ in range(SESSIONS_STORED):
|
||||
session_ids.append(_init_session(client))
|
||||
|
||||
# Resume each session
|
||||
for session_id in session_ids:
|
||||
new_session_id = _init_session(client, session_id)
|
||||
assert session_id == new_session_id
|
||||
|
||||
# Creating a new session replaces the least-recently-used session
|
||||
_init_session(client)
|
||||
|
||||
# Resuming session 1 through SESSIONS_STORED will still work
|
||||
for session_id in session_ids[1:]:
|
||||
new_session_id = _init_session(client, session_id)
|
||||
assert session_id == new_session_id
|
||||
|
||||
# Resuming session 0 will not work
|
||||
new_session_id = _init_session(client, session_ids[0])
|
||||
assert new_session_id != session_ids[0]
|
||||
|
||||
# New session bumped out the least-recently-used anonymous session.
|
||||
# Resuming session 1 through SESSIONS_STORED will still work
|
||||
for session_id in session_ids[1:]:
|
||||
new_session_id = _init_session(client, session_id)
|
||||
assert session_id == new_session_id
|
||||
|
||||
# Creating a new session replaces session_ids[0] again
|
||||
_init_session(client)
|
||||
|
||||
# Resuming all sessions one by one will in turn bump out the previous session.
|
||||
for session_id in session_ids:
|
||||
new_session_id = _init_session(client, session_id)
|
||||
assert session_id != new_session_id
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.setup_client(passphrase=True)
|
||||
def test_multiple_passphrases(client):
|
||||
# start a session
|
||||
session_a = _init_session(client)
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
# start it again wit the same session id
|
||||
new_session_id = _init_session(client, session_id=session_a)
|
||||
# session is the same
|
||||
assert new_session_id == session_a
|
||||
# passphrase is not prompted
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# start a second session
|
||||
session_b = _init_session(client)
|
||||
# new session -> new session id and passphrase prompt
|
||||
assert _get_xpub(client, passphrase="B") == XPUB_PASSPHRASES["B"]
|
||||
|
||||
# provide the same session id -> must not ask for passphrase again.
|
||||
new_session_id = _init_session(client, session_id=session_b)
|
||||
assert new_session_id == session_b
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["B"]
|
||||
|
||||
# provide the first session id -> must not ask for passphrase again and return the same result.
|
||||
new_session_id = _init_session(client, session_id=session_a)
|
||||
assert new_session_id == session_a
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# provide the second session id -> must not ask for passphrase again and return the same result.
|
||||
new_session_id = _init_session(client, session_id=session_b)
|
||||
assert new_session_id == session_b
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["B"]
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.timeout(600)
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.setup_client(passphrase=True)
|
||||
def test_max_sessions_with_passphrases(client):
|
||||
# for the following tests, we are using as many passphrases as there are available sessions
|
||||
assert len(XPUB_PASSPHRASES) == SESSIONS_STORED
|
||||
|
||||
# start as many sessions as the limit is
|
||||
session_ids = {}
|
||||
for passphrase, xpub in XPUB_PASSPHRASES.items():
|
||||
session_id = _init_session(client)
|
||||
assert session_id not in session_ids.values()
|
||||
session_ids[passphrase] = session_id
|
||||
assert _get_xpub(client, passphrase=passphrase) == xpub
|
||||
|
||||
# passphrase is not prompted for the started the sessions, regardless the order
|
||||
# let's try 20 different orderings
|
||||
passphrases = list(XPUB_PASSPHRASES.keys())
|
||||
shuffling = passphrases[:]
|
||||
for _ in range(20):
|
||||
random.shuffle(shuffling)
|
||||
for passphrase in shuffling:
|
||||
session_id = _init_session(client, session_id=session_ids[passphrase])
|
||||
assert session_id == session_ids[passphrase]
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES[passphrase]
|
||||
|
||||
# make sure the usage order is the reverse of the creation order
|
||||
for passphrase in reversed(passphrases):
|
||||
session_id = _init_session(client, session_id=session_ids[passphrase])
|
||||
assert session_id == session_ids[passphrase]
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES[passphrase]
|
||||
|
||||
# creating one more session will exceed the limit
|
||||
_init_session(client)
|
||||
# new session asks for passphrase
|
||||
_get_xpub(client, passphrase="XX")
|
||||
|
||||
# restoring the sessions in reverse will evict the next-up session
|
||||
for passphrase in reversed(passphrases):
|
||||
_init_session(client, session_id=session_ids[passphrase])
|
||||
_get_xpub(client, passphrase="whatever") # passphrase is prompted
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@ -95,24 +229,7 @@ def test_session_enable_passphrase(client):
|
||||
# We clear the session id now, so the passphrase should be asked.
|
||||
new_session_id = _init_session(client)
|
||||
assert session_id != new_session_id
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@pytest.mark.setup_client(passphrase=True)
|
||||
def test_clear_session_passphrase(client):
|
||||
# at first attempt, we are prompted for passphrase
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
|
||||
# now the passphrase is cached
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||
|
||||
# Erase the cached passphrase
|
||||
response = client.call(messages.Initialize())
|
||||
assert isinstance(response, messages.Features)
|
||||
|
||||
# we have to enter passphrase again
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@ -126,12 +243,12 @@ def test_passphrase_on_device(client):
|
||||
assert isinstance(response, messages.PassphraseRequest)
|
||||
response = client.call_raw(messages.PassphraseAck(passphrase="A", on_device=False))
|
||||
assert isinstance(response, messages.PublicKey)
|
||||
assert response.xpub == XPUB_PASSPHRASE_A
|
||||
assert response.xpub == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# try to get xpub again, passphrase should be cached
|
||||
response = client.call_raw(XPUB_REQUEST)
|
||||
assert isinstance(response, messages.PublicKey)
|
||||
assert response.xpub == XPUB_PASSPHRASE_A
|
||||
assert response.xpub == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# make a new session
|
||||
_init_session(client)
|
||||
@ -144,12 +261,12 @@ def test_passphrase_on_device(client):
|
||||
client.debug.input("A")
|
||||
response = client.call_raw(messages.ButtonAck())
|
||||
assert isinstance(response, messages.PublicKey)
|
||||
assert response.xpub == XPUB_PASSPHRASE_A
|
||||
assert response.xpub == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# try to get xpub again, passphrase should be cached
|
||||
response = client.call_raw(XPUB_REQUEST)
|
||||
assert isinstance(response, messages.PublicKey)
|
||||
assert response.xpub == XPUB_PASSPHRASE_A
|
||||
assert response.xpub == XPUB_PASSPHRASES["A"]
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@ -181,10 +298,10 @@ def test_passphrase_always_on_device(client):
|
||||
_init_session(client)
|
||||
response = client.call_raw(XPUB_REQUEST)
|
||||
assert isinstance(response, messages.ButtonRequest)
|
||||
client.debug.input("A") # Input empty passphrase.
|
||||
client.debug.input("A") # Input non-empty passphrase.
|
||||
response = client.call_raw(messages.ButtonAck())
|
||||
assert isinstance(response, messages.PublicKey)
|
||||
assert response.xpub == XPUB_PASSPHRASE_A
|
||||
assert response.xpub == XPUB_PASSPHRASES["A"]
|
||||
|
||||
|
||||
@pytest.mark.skip_ui
|
||||
@ -275,10 +392,10 @@ def test_cardano_passphrase(client):
|
||||
|
||||
# GetPublicKey requires passphrase and since it is not cached,
|
||||
# Trezor will prompt for it.
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# The passphrase is now cached for non-Cardano coins.
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# Cardano will prompt for it again.
|
||||
assert _get_xpub_cardano(client, passphrase="B") == XPUB_CARDANO_PASSPHRASE_B
|
||||
@ -287,18 +404,18 @@ def test_cardano_passphrase(client):
|
||||
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
||||
|
||||
# And others behaviour did not change.
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# Initialize with the session id does not destroy the state
|
||||
_init_session(client, session_id=session_id)
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|
||||
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
||||
|
||||
# New session will destroy the state
|
||||
_init_session(client)
|
||||
|
||||
# GetPublicKey must ask for passphrase again
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASE_A
|
||||
assert _get_xpub(client, passphrase="A") == XPUB_PASSPHRASES["A"]
|
||||
|
||||
# Cardano must also ask for passphrase again
|
||||
assert _get_xpub_cardano(client, passphrase="B") == XPUB_CARDANO_PASSPHRASE_B
|
||||
|
@ -9,9 +9,9 @@
|
||||
"test_cancel.py::test_cancel_message_via_initialize[message1]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_applysettings.py-test_apply_settings": "b698654871541258f97d58ada0f010b2d77b74829791566746cad619d3740a94",
|
||||
"test_msg_applysettings.py-test_apply_settings_passphrase": "fb38537b921f8064f7ea6e1a584e70a8be74968a3be6726b7d36cf57de0d7865",
|
||||
"test_msg_backup_device.py::test_backup_bip39": "dfdbd0ae6774177d43f2f11d026c4d8679dd994b508c1d850d9cfee5dd1118ac",
|
||||
"test_msg_backup_device.py::test_backup_slip39_advanced": "244b31044a25e44847a6efb79ea4cb67c246a971bf828d46aa79d14151934a04",
|
||||
"test_msg_backup_device.py::test_backup_slip39_basic": "81a56c307342e46ad261d6a6c6da0a75224af8e72f40da7c630c1e03c0643e1c",
|
||||
"test_msg_backup_device.py::test_backup_bip39": "62ee50e8d0215e44e98fada920e3d3f8c5241821216ad6b1ec9626a72c85ad01",
|
||||
"test_msg_backup_device.py::test_backup_slip39_advanced": "32820cc8089e326fad55a8596dbad71dc63122214aab76037eb794ba719730e1",
|
||||
"test_msg_backup_device.py::test_backup_slip39_basic": "0c15a568b9c2e51620ac40112ba663c376ce6d74bd3df1454ce55a83c83663d4",
|
||||
"test_msg_backup_device.py::test_interrupt_backup_fails": "225b3da1acac6e9a65106fcc4a01de8a44de035aedb4dcc21c09f439199fdf40",
|
||||
"test_msg_backup_device.py::test_no_backup_fails": "93039a9472cfc9058563bd56e4a3dbe2e41af64744a61f6ee3255a04bd3a9366",
|
||||
"test_msg_backup_device.py::test_no_backup_show_entropy_fails": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
||||
@ -43,15 +43,15 @@
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[1097911063-inputs2-outputs2-transacti": "e6813a7fd973f49b02ef28cee15deb48d0389d1cb2696194848e4d690281361f",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs0-outputs0-transactio": "bc88a23280c0234860ccbb5e96d5cc3a851e2f2f9928c400f6c0907c68172d39",
|
||||
"test_msg_cardano_sign_tx_slip39_basic.py::test_cardano_sign_tx[764824073-inputs1-outputs1-transactio": "6956bb359388186b4c127ae88f4d86527381caf8098f55b4fa05c343640c081f",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "9519b574e3f749810977ac55f851553f2051b51ef7799ffaa155410d89e1cd63",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "82c0d1acbf5ff344189761f808d3cf0e632726341231c20b2c0925ab5549b6af",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "1642d2d15920a3bb2c666b39beca9943ba39adb59289ebc40b97d7088a4d7abf",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "15289574ceb002b5161305b0595dcd20e437d1dd4e7561332e1aba4c1615e9ea",
|
||||
"test_msg_changepin_t2.py::test_change_failed": "370c59da62a84aaefa242562c36a6facac89c7f819e37d1ae8cbe2c44a2de256",
|
||||
"test_msg_changepin_t2.py::test_change_pin": "c42fca9bf8f3b4c330516d90231ae0cfa7419d83370be9cfcf6a81cca3f3b06c",
|
||||
"test_msg_changepin_t2.py::test_remove_pin": "d049eaa6cd11e88b7af193b080cf868b62271266ad6f2973bfd82944b523741d",
|
||||
"test_msg_changepin_t2.py::test_set_failed": "59beeec1a00817f664a5fd93234012588613aac93c45d53c27550fe5d0ef8380",
|
||||
"test_msg_changepin_t2.py::test_set_pin": "3aafe16a451f928c9bfff2a3ff7e3c23ce4948c9a044ebd04834df045670183f",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_pin_to_wipe_code": "140d6e384534174c1a52fa720b2f996dcdb361cc65d84797f9671ad4cecf02a7",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_remove_wipe_code": "f9c56d1ed0115710cefa5314aa4ad01c7411bb84ad8f8e98a7443b4671873226",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_mismatch": "2d2eacb3bab1e5309d0f8b2beb054fd7785d0003aa320f38cf436b3aedbe6fb8",
|
||||
"test_msg_change_wipe_code_t2.py::test_set_wipe_code_to_pin": "1cc2877a4e4094ef1a0a4179fb112bdc60486cdd267e1b72ce40883db97d1e47",
|
||||
"test_msg_changepin_t2.py::test_change_failed": "bf6e8b1755a81ae2084f9b04a6f610b41d754cba81022a13cf2b7eb15b55c9fc",
|
||||
"test_msg_changepin_t2.py::test_change_pin": "045f39cd2ad20558fbeee44247d92681865498b425df7d2f7baf9afc1e36085d",
|
||||
"test_msg_changepin_t2.py::test_remove_pin": "ae73fc8cbf11ff8b67543972170eb633b23b99c5c352d67b936a36b97b167651",
|
||||
"test_msg_changepin_t2.py::test_set_failed": "7462bd9aa640743efe71d74608b5e20232c723af3340c8c1296f7657e7688c40",
|
||||
"test_msg_changepin_t2.py::test_set_pin": "783ba04900ad3b78e30af3d3af1e5f807cdb9b09f40312aa2301e7d73043dffc",
|
||||
"test_msg_cipherkeyvalue.py-test_decrypt": "166d85b1bf11aeaeb5b93ef5d047b6f8910c28b8fce1d853e6912d89d7bfca2f",
|
||||
"test_msg_cipherkeyvalue.py-test_decrypt_badlen": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_cipherkeyvalue.py-test_encrypt": "3a37e4004c87bc6df6a8fa7c93b6fe3e3524986914709fda2f9c99ba0ff69775",
|
||||
@ -200,7 +200,7 @@
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_uninitialized": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
||||
"test_msg_recoverydevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_t2.py-test_nopin_nopassphrase": "9769cde3e3951a76364973ade753682e2acc67d1633a9f982f0604b5702aa895",
|
||||
"test_msg_recoverydevice_bip39_t2.py-test_pin_passphrase": "d746fda5234be75cded559817d0fdfb4397ac757e9847d4dfbef44ac031381d1",
|
||||
"test_msg_recoverydevice_bip39_t2.py-test_pin_passphrase": "e321239b538d4dbdb5677f911b943e8359882f024277487e84c44caf7a5dd325",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_extra_share_entered": "00a94e20b786346c45f987860b2465f299075d7c6de4971f42a4749e1cc8bfc0",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_group_threshold_reached": "3b075a276c4e0d53fbc51ce1f29594bbd474d25f47c0f6a32caac41ba0ba2138",
|
||||
@ -214,7 +214,7 @@
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_abort": "793dde7fb47e9c4ad36369be396da20332560f29083d7f9a2b0582173371c9ed",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_ask_word_number": "8e9d9fd75e17f6b44829ae2d7b0eb9e60b48577f975abc6d75116f8365241082",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_noabort": "d374a9b85c03a0cc1bbb59130e454406513fc35f4f43b968db4920414de1bb72",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "ee0edd912b913d31b8308b64ceed7fa3e58f5b63c8a4d4d85f3651d4b121a202",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "59de0866cceef43519b01ebec5f4575b21df13628d878479b21c9899234bb773",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_same_share": "e6a54429fdbedea9efca9cbed736aada07f95f3b20f895f9c1c5ec056a2be014",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "54581a91b55ab531b215cba61052fd77c505232c510f170080760605eb9b8c46",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39bdbf2463be0878": "f73bef254762d761db27df46bff62641a9d2ac0602c34fc4e465262bf26ed08f",
|
||||
@ -224,11 +224,11 @@
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_dryrun": "d84427489f691ecc222b62f83af3e97fa09097404dcba07772a43b5eb0c689e8",
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "55f2dd6b4958659f071c3f57e06286f872ac38af4828f446a0f4e91c657dfccc",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_failed_pin": "a52f3479f7d8e14c7f89af9b305a50f3bd244fee28f17ebc0abedba701c32811",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device": "13d739a387a80aa96f533bb6f3b0f2ff12c7ba84608d0797ac603208d9aed796",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device_pin": "ed64165674816dfb408ead89abb8d1a5743f50f63933aceba6701149522c3866",
|
||||
"test_msg_resetdevice_slip39_advanced.py-test_reset_device_slip39_advanced": "005622513c517610a4a1af529ef94760b4d0406b971f0b2f2557c93ce0dac8c9",
|
||||
"test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic": "ebc62d2c49136c6ce75c8e026e51157d96a6f2440881dca0cdd8bc357e2c9354",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_failed_pin": "f284b630fef6ae437c9726cd0ee0d1728a77f677f37cd5f88aabb21e596f589e",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device": "d49e27c986c6e6349f8e6988e2fe3b94f0e51d29df833c6d50fecd4ba3d3fff9",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device_pin": "31c0e45f2e84193e56c884e2f5994ab99831e8df7ff0c0d7fe182249ab9174b7",
|
||||
"test_msg_resetdevice_slip39_advanced.py-test_reset_device_slip39_advanced": "ad6355a0f6c86df8dc6ad19154347666a6acc8da4b2c9305a44b509175997540",
|
||||
"test_msg_resetdevice_slip39_basic.py-test_reset_device_slip39_basic": "021c0eafa85c430e415ab81b76e1d50bc32ce4c60b6f70f91bc181d2e1496f1f",
|
||||
"test_msg_ripple_get_address.py-test_ripple_get_address": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
||||
"test_msg_ripple_get_address.py-test_ripple_get_address_other": "2bb7d7bf48f1218530b4d7045d48480cad6411e110df537551b2f80b342007f2",
|
||||
"test_msg_ripple_sign_tx.py-test_ripple_sign_invalid_fee": "1c0ca08b857da6121f43cfb1632c7f7e1d189ef1fdb665db7ba2cdfa7a59ea7c",
|
||||
@ -380,12 +380,12 @@
|
||||
"test_passphrase_slip39_advanced.py::test_256bit_passphrase": "69b6b8b22c819e1282d7d2c14b31bf8d015c81ac05fe034540dbb11c8a20dbdb",
|
||||
"test_passphrase_slip39_basic.py::test_2of5_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
||||
"test_passphrase_slip39_basic.py::test_3of6_passphrase": "1e00b1a7840bc144b98b7bce26f74fc913a0abf9d1c500571d7803b6b2e0943c",
|
||||
"test_reset_backup.py::test_skip_backup_manual[0-backup_flow_bip39]": "a73fcd9af54d3b55cc7c21e68b1637a6e29829bfd57d47b0503e67fa22e4106a",
|
||||
"test_reset_backup.py::test_skip_backup_manual[1-backup_flow_slip39_basic]": "4b117541a58e7c209786a728ebc1406720cbd7d5a234f20a550b1ad3970b4e76",
|
||||
"test_reset_backup.py::test_skip_backup_manual[2-backup_flow_slip39_advanced]": "2d1d55f25b21c2be2e7aeaa17eeb3a4b9ee4d7763c4a65ae2fd59da2d9dbbcc0",
|
||||
"test_reset_backup.py::test_skip_backup_msg[0-backup_flow_bip39]": "f448153452b154c0be153e18340f22c853430267316229548ad8f4f47cf946f7",
|
||||
"test_reset_backup.py::test_skip_backup_msg[1-backup_flow_slip39_basic]": "9fbcd0ab293de21ab3a24e0c29315d152c1bc5da4902ec054cc8800c0d855aa6",
|
||||
"test_reset_backup.py::test_skip_backup_msg[2-backup_flow_slip39_advanced]": "0bafb944169d0ab608513deadba15411d1756ef2658253740c5d8792f4527e9d",
|
||||
"test_reset_backup.py::test_skip_backup_manual[0-backup_flow_bip39]": "cefdd5ad785ef4039f3c3b6e745b2773ab839ddefa2fb18ebeaeb8b004d74284",
|
||||
"test_reset_backup.py::test_skip_backup_manual[1-backup_flow_slip39_basic]": "73143b4811dc6a34b690071c9bfdf6e1604632a7a2a22af3ce077ee9fe33d508",
|
||||
"test_reset_backup.py::test_skip_backup_manual[2-backup_flow_slip39_advanced]": "5ec91a6a1a70f3f999b31e7a82087492796f9de88305907db0e12ec1f48d2d03",
|
||||
"test_reset_backup.py::test_skip_backup_msg[0-backup_flow_bip39]": "eb1cebc98b854fadddfbb7a75961667bbaf87ebc0418b30b46b24260b1c175ee",
|
||||
"test_reset_backup.py::test_skip_backup_msg[1-backup_flow_slip39_basic]": "a6599fd7c9075d12ed05d07b9505f74f37c3baf2827663423ad5bf509407356a",
|
||||
"test_reset_backup.py::test_skip_backup_msg[2-backup_flow_slip39_advanced]": "df213fe327a775563fd8c442ed1ed2dfe5053d56d50307059292d5a5fe091b4c",
|
||||
"test_sdcard.py::test_sd_format": "e0da54c8a26386bff4eb310e9c7ebaa56a02fed3870f976d1dd3b230750a28fe",
|
||||
"test_sdcard.py::test_sd_no_format": "f47e897caee95cf98c1b4506732825f853c4b8afcdc2713e38e3b4055973c9ac",
|
||||
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe",
|
||||
|
Loading…
Reference in New Issue
Block a user