mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-16 04:29:08 +00:00
7f1a5ac4c1
WIP - refactor and extend font generation for non-ascii characters WIP - add czech characters mapping between UTF8 value and index WIP - regenerate font files with czech characters WIP - shorten czech button text, it was causing SHUTDOWN for some reason WIP - support UTF8 characters in fonts.c WIP - account for translation in tests WIP - small fixes WIP - fix last test WIP - support UTF8 also in Rust font operations WIP - add a script to find non-translated english strings in micropython code WIP - add a validator script for checking missing micropython translations WIP - translate remaining altcoins and other apps in core (fido, sdcard, TT layouts, ...) WIP - generate czech glyphs for TT fonts WIP - modify gen_font.py to account for negative bearing czech characters WIP - extend translation validation scripts, move them into core/tools WIP - translate TT layouts in Rust WIP - fix tests WIP - fix inverse coloring of nonprintable glyph WIP - add build and test pipelines for Czech language WIP - merge both JSON files together WIP - run new isort WIP - unify all the translation in Rust, expose to micropython TEMP - leave en_merged.json file, so it is accessible by translators with old link WIP - fixes WIP - add french characters and translation via Google Translator WIP - skip rustfmt in mako-created files WIP - revert all the font height changes causing false-positive UI diff WIP - fixes after rebase WIP - fix broken translations WIP - revert some wording changes causing UI diff WIP - improve validation and translate scripts, translate missing strings WIP - sort all keys alphabetically WIP - remove any usage of translation in bootloader WIP - add newline at the end of JSON file WIP - fix bitcoin-only strings check WIP - fix python support check WIP - add some missing translations WIP - fix SD card device test WIP - fix pystyle WIP - fix rust unittests WIP - fix click tests WIP - flag errors in french translations WIP - add script transferring translations data into a byte blob WIP - regenerate fr.rs WIP - store and read language translations from flash WIP - storing language name in storage WIP - sending language_data in apply_settings protobuf message WIP - separate protobuf message for translations, fixes WIP - set up translations area for TT as well WIP - get rid of TREZOR_LANG env variable during build WIP - make the firmware buildable for TT WIP - add basic device tests WIP - set language for tests WIP - counting with language when writing fixtures WIP - add todos WIP - fix CI WIP - unify translations, make titles CAPITAL WIP - translate missing english WIP - skip translations messages for T1 WIP - not changing tests names for english WIP - fix flake8 WIP - no test language setting for T1 WIP - clippy lint about complex data type WIP - fix some english UI diff for TR WIP - fix cstyle WIP - minimize the usage of #[cfg(feature = "micropython")] outside translations module WIP - minimize TT's UI diff WIP - fix ruststyle WIP - fix TR build WIP - advanced Shamir text change WIP - storing the language name as the first item in the translation data WIP - modify and extend tests after storing language name WIP - modify checklist sentence WIP - add TEST_LANG into Makefile for all the emu tests WIP - default arguments WIP - reimplement default arguments remove unneeded pub from get_info function WIP - Rust handling of object attributes lookups from upy - thanks Matejcik! WIP - generate mock interface for attribute-based translations lookups WIP - change function calls to object attributes WIP - symbolic link for unix/translations.c WIP - fix and improve the reading of translations - thanks Matejcik! WIP - add support for multiple languages in removing missing tests WIP - fix multiple-accounts warning in tests WIP - fix encoding of newlines in translations WIP - fix czech tutorial text WIP - fix czech click tests WIP - do not translate wire error messages WIP - add language options to click tests as well WIP - setup czech device tests in CI WIP - setup czech click tests in CI WIP - record czech device tests for TR WIP - record czech click tests for TR WIP - record czech device tests for TT WIP - record czech click tests for TT WIP - pystyle WIP - cstyle WIP - fix Rust micropython import dependency WIP - fix czech recordings WIP - support french translations in tests WIP - shorten some french words to fix the tests WIP - fix micropython cfg compilation WIP - record french click tests for TR WIP - record french device tests for TR WIP - record french device tests for TT WIP - record french click tests for TT WIP - fix french translations - shorten them WIP - translate missing french words WIP - fix click tests WIP - add french tests into CI WIP - pystyle WIP - allow for czech/french tests in update script WIP - update czech fixtures WIP - update french fixtures WIP - ruststyle WIP - disallow MPU to run it on hardware WIP - cstyle WIP - change translations delimiter from * to \x00 WIP - change translations protobufs WIP - remove language handling from storage WIP - add header into JSON files WIP - count with header in translations blob WIP - yml style fixes WIP - fix proto gen WIP - verify version and data hash WIP - fix loading test translations feat(core): allow access to translations area in firmware [no changelog] WIP - fixes after rebase WIP - increase the TT's translations area to 3 sectors WIP - dynamically read the maximum translations size WIP - record non-english tests from CI WIP - loading font data from translations blob WIP - bump translations version WIP - include czech and french glyph data WIP - whitelist another negative-bearing glyph WIP - remove czech/french glyphs from common font files WIP - fix language tests WIP - specific fonts for specific models WIP - revert the non-ascii font hardcoding WIP - include missing BIG font into nonprintable logic WIP - minor Rust code improvements WIP - include newlines at the end of json files WIP - move glyph Rust function to librust_fonts.h WIP - add all fonts into translations file WIP - move fonts into its own dir WIP - reflect separate dir for fonts WIP - not putting translations trezorhal into bootloader WIP - write and read multiple fonts into translations data WIP - silence pyright issue/notissue WIP - delete no more used translations/*.py imports WIP - fix bootloader builds by introducing translations feature and TRANSLATIONS flag WIP - fix TT's bootloader Rust build WIP - fix tests in non-english languages WIP - not search for UTF-8 when there are no translations data WIP - add colons to strings where missing WIP - fix language loading in tests WIP - fix signmessage input flow to work in all languages WIP - create offset table for translation strings WIP - code improvements WIP - record foreign language fixtures + sync with main in english WIP - do alignment check before reading u16 data WIP - allocate blob in RAM for translations data WIP - add TODO for blob generation WIP - record non-english device tests WIP - use bytes.align_to instead of messing with pointers WIP - fixtures WIP - remove unused import WIP - add order.py WIP - add order.json WIP - take order.json into account in creating general.rs WIP - take order.json into account in generating the blob WIP - style WIP - sort the language files WIP - remove unused file WIP - code improvements WIP - add TODO for homescreen notification WIP - translate plural forms WIP - translate time intervals WIP - sign translations with dev keys, validate signatures, improve robustness WIP - improve tests for translations WIP - add `trezorctl utils sign-translations` for production signing of the blob WIP - pyright fix WIP - changing TR progress loader offset - it was colliding with title WIP - show indeterminate loader when loading translations data WIP - record new and updated language tests WIP - show the change language title/prompt in the target language WIP - sort keys WIP - add crowdin-cli into shell.nix WIP - add crowdin sync script
516 lines
21 KiB
Python
516 lines
21 KiB
Python
# This file is part of the Trezor project.
|
|
#
|
|
# Copyright (C) 2012-2019 SatoshiLabs and contributors
|
|
#
|
|
# This library is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License version 3
|
|
# as published by the Free Software Foundation.
|
|
#
|
|
# This library is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# 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 device, exceptions, messages
|
|
from trezorlib.debuglink import TrezorClientDebugLink as Client
|
|
from trezorlib.exceptions import TrezorFailure
|
|
from trezorlib.messages import FailureType, SafetyCheckLevel
|
|
from trezorlib.tools import parse_path
|
|
|
|
from .. import translations as TR
|
|
|
|
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_A = "d37eba66d6183547b11b4d0c3e08e761da9f07c3ef32183f8b79360b2b66850e47e8eb3865251784c3c471a854ee40dfc067f7f3afe47d093388ea45239606fd"
|
|
XPUB_CARDANO_PASSPHRASE_B = "d80e770f6dfc3edb58eaab68aa091b2c27b08a47583471e93437ac5f8baa61880c7af4938a941c084c19731e6e57a5710e6ad1196263291aea297ce0eec0f177"
|
|
|
|
ADDRESS_N = parse_path("m/44h/0h/0h")
|
|
XPUB_REQUEST = messages.GetPublicKey(address_n=ADDRESS_N, coin_name="Bitcoin")
|
|
|
|
SESSIONS_STORED = 10
|
|
|
|
|
|
def _init_session(client: Client, session_id=None, derive_cardano=False):
|
|
"""Call Initialize, check and return the session ID."""
|
|
response = client.call(
|
|
messages.Initialize(session_id=session_id, derive_cardano=derive_cardano)
|
|
)
|
|
assert isinstance(response, messages.Features)
|
|
assert len(response.session_id) == 32
|
|
return response.session_id
|
|
|
|
|
|
def _get_xpub(client: Client, passphrase=None):
|
|
"""Get XPUB and check that the appropriate passphrase flow has happened."""
|
|
if passphrase is not None:
|
|
expected_responses = [
|
|
messages.PassphraseRequest,
|
|
messages.ButtonRequest,
|
|
messages.ButtonRequest,
|
|
messages.PublicKey,
|
|
]
|
|
else:
|
|
expected_responses = [messages.PublicKey]
|
|
|
|
with client:
|
|
client.use_passphrase(passphrase or "")
|
|
client.set_expected_responses(expected_responses)
|
|
result = client.call(XPUB_REQUEST)
|
|
return result.xpub
|
|
|
|
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_session_with_passphrase(client: Client):
|
|
# Let's start the communication by calling Initialize.
|
|
session_id = _init_session(client)
|
|
|
|
# GetPublicKey requires passphrase and since it is not cached,
|
|
# Trezor will prompt for it.
|
|
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_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_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_PASSPHRASES["A"]
|
|
|
|
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_multiple_sessions(client: 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.setup_client(passphrase=True)
|
|
def test_multiple_passphrases(client: 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.slow
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_max_sessions_with_passphrases(client: 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
|
|
|
|
|
|
def test_session_enable_passphrase(client: Client):
|
|
# Let's start the communication by calling Initialize.
|
|
session_id = _init_session(client)
|
|
|
|
# Trezor will not prompt for passphrase because it is turned off.
|
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_NONE
|
|
|
|
# Turn on passphrase.
|
|
# Emit the call explicitly to avoid ClearSession done by the library function
|
|
response = client.call(messages.ApplySettings(use_passphrase=True))
|
|
assert isinstance(response, messages.Success)
|
|
|
|
# The session id is unchanged, therefore we do not prompt for the passphrase.
|
|
new_session_id = _init_session(client, session_id=session_id)
|
|
assert session_id == new_session_id
|
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASE_NONE
|
|
|
|
# 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_PASSPHRASES["A"]
|
|
|
|
|
|
@pytest.mark.skip_t1
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_passphrase_on_device(client: Client):
|
|
_init_session(client)
|
|
|
|
# try to get xpub with passphrase on host:
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
# using `client.call` to auto-skip subsequent ButtonRequests for "show passphrase"
|
|
response = client.call(messages.PassphraseAck(passphrase="A", on_device=False))
|
|
|
|
assert isinstance(response, messages.PublicKey)
|
|
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_PASSPHRASES["A"]
|
|
|
|
# make a new session
|
|
_init_session(client)
|
|
|
|
# try to get xpub with passphrase on device:
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call_raw(messages.PassphraseAck(on_device=True))
|
|
# no "show passphrase" here
|
|
assert isinstance(response, messages.ButtonRequest)
|
|
client.debug.input("A")
|
|
response = client.call_raw(messages.ButtonAck())
|
|
assert isinstance(response, messages.PublicKey)
|
|
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_PASSPHRASES["A"]
|
|
|
|
|
|
@pytest.mark.skip_t1
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_passphrase_always_on_device(client: Client):
|
|
# Let's start the communication by calling Initialize.
|
|
session_id = _init_session(client)
|
|
|
|
# Force passphrase entry on Trezor.
|
|
response = client.call(messages.ApplySettings(passphrase_always_on_device=True))
|
|
assert isinstance(response, messages.Success)
|
|
|
|
# Since we enabled the always_on_device setting, Trezor will send ButtonRequests and ask for it on the device.
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.ButtonRequest)
|
|
client.debug.input("") # Input empty passphrase.
|
|
response = client.call_raw(messages.ButtonAck())
|
|
assert isinstance(response, messages.PublicKey)
|
|
assert response.xpub == XPUB_PASSPHRASE_NONE
|
|
|
|
# Passphrase will not be prompted. The session id stays the same and the passphrase is cached.
|
|
_init_session(client, session_id=session_id)
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PublicKey)
|
|
assert response.xpub == XPUB_PASSPHRASE_NONE
|
|
|
|
# In case we want to add a new passphrase we need to send session_id = None.
|
|
_init_session(client)
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.ButtonRequest)
|
|
client.debug.input("A") # Input non-empty passphrase.
|
|
response = client.call_raw(messages.ButtonAck())
|
|
assert isinstance(response, messages.PublicKey)
|
|
assert response.xpub == XPUB_PASSPHRASES["A"]
|
|
|
|
|
|
@pytest.mark.skip_t2
|
|
@pytest.mark.skip_tr
|
|
@pytest.mark.setup_client(passphrase="")
|
|
def test_passphrase_on_device_not_possible_on_t1(client: Client):
|
|
# This setting makes no sense on T1.
|
|
response = client.call_raw(messages.ApplySettings(passphrase_always_on_device=True))
|
|
assert isinstance(response, messages.Failure)
|
|
assert response.code == FailureType.DataError
|
|
|
|
# T1 should not accept on_device request
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call_raw(messages.PassphraseAck(on_device=True))
|
|
assert isinstance(response, messages.Failure)
|
|
assert response.code == FailureType.DataError
|
|
|
|
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_passphrase_ack_mismatch(client: Client):
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call_raw(messages.PassphraseAck(passphrase="A", on_device=True))
|
|
assert isinstance(response, messages.Failure)
|
|
assert response.code == FailureType.DataError
|
|
|
|
|
|
@pytest.mark.setup_client(passphrase="")
|
|
def test_passphrase_missing(client: Client):
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call_raw(messages.PassphraseAck(passphrase=None))
|
|
assert isinstance(response, messages.Failure)
|
|
assert response.code == FailureType.DataError
|
|
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call_raw(messages.PassphraseAck(passphrase=None, on_device=False))
|
|
assert isinstance(response, messages.Failure)
|
|
assert response.code == FailureType.DataError
|
|
|
|
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_passphrase_length(client: Client):
|
|
def call(passphrase: str, expected_result: bool):
|
|
_init_session(client)
|
|
response = client.call_raw(XPUB_REQUEST)
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
try:
|
|
response = client.call(messages.PassphraseAck(passphrase=passphrase))
|
|
assert expected_result is True, "Call should have failed"
|
|
assert isinstance(response, messages.PublicKey)
|
|
except exceptions.TrezorFailure as e:
|
|
assert expected_result is False, "Call should have succeeded"
|
|
assert e.code == FailureType.DataError
|
|
|
|
# 50 is ok
|
|
call(passphrase="A" * 50, expected_result=True)
|
|
# 51 is not
|
|
call(passphrase="A" * 51, expected_result=False)
|
|
# "š" has two bytes - 48x A and "š" should be fine (50 bytes)
|
|
call(passphrase="A" * 48 + "š", expected_result=True)
|
|
# "š" has two bytes - 49x A and "š" should not (51 bytes)
|
|
call(passphrase="A" * 49 + "š", expected_result=False)
|
|
|
|
|
|
@pytest.mark.skip_t1
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_hide_passphrase_from_host(client: Client):
|
|
# Without safety checks, turning it on fails
|
|
with pytest.raises(TrezorFailure, match="Safety checks are strict"), client:
|
|
device.apply_settings(client, hide_passphrase_from_host=True)
|
|
|
|
device.apply_settings(client, safety_checks=SafetyCheckLevel.PromptTemporarily)
|
|
|
|
# Turning it on
|
|
device.apply_settings(client, hide_passphrase_from_host=True)
|
|
|
|
passphrase = "abc"
|
|
|
|
with client:
|
|
|
|
def input_flow():
|
|
yield
|
|
TR.assert_in(
|
|
client.debug.wait_layout().text_content(),
|
|
"passphrase.access_hidden_wallet",
|
|
)
|
|
if client.debug.model == "T":
|
|
client.debug.press_yes()
|
|
elif client.debug.model == "Safe 3":
|
|
client.debug.press_right()
|
|
client.debug.press_right()
|
|
client.debug.press_yes()
|
|
|
|
client.watch_layout()
|
|
client.set_input_flow(input_flow)
|
|
client.set_expected_responses(
|
|
[
|
|
messages.PassphraseRequest,
|
|
messages.ButtonRequest,
|
|
messages.PublicKey,
|
|
]
|
|
)
|
|
client.use_passphrase(passphrase)
|
|
result = client.call(XPUB_REQUEST)
|
|
assert isinstance(result, messages.PublicKey)
|
|
xpub_hidden_passphrase = result.xpub
|
|
|
|
# Turning it off
|
|
device.apply_settings(client, hide_passphrase_from_host=False)
|
|
|
|
# Starting new session, otherwise the passphrase would be cached
|
|
_init_session(client)
|
|
|
|
with client:
|
|
|
|
def input_flow():
|
|
yield
|
|
TR.assert_in(
|
|
client.debug.wait_layout().text_content(),
|
|
"passphrase.next_screen_will_show_passphrase",
|
|
)
|
|
client.debug.press_yes()
|
|
|
|
yield
|
|
TR.assert_equals(
|
|
client.debug.wait_layout().title(),
|
|
"passphrase.title_confirm",
|
|
)
|
|
assert passphrase in client.debug.wait_layout().text_content()
|
|
client.debug.press_yes()
|
|
|
|
client.watch_layout()
|
|
client.set_input_flow(input_flow)
|
|
client.set_expected_responses(
|
|
[
|
|
messages.PassphraseRequest,
|
|
messages.ButtonRequest,
|
|
messages.ButtonRequest,
|
|
messages.PublicKey,
|
|
]
|
|
)
|
|
client.use_passphrase(passphrase)
|
|
result = client.call(XPUB_REQUEST)
|
|
assert isinstance(result, messages.PublicKey)
|
|
xpub_shown_passphrase = result.xpub
|
|
|
|
assert xpub_hidden_passphrase == xpub_shown_passphrase
|
|
|
|
|
|
def _get_xpub_cardano(client: Client, passphrase):
|
|
msg = messages.CardanoGetPublicKey(
|
|
address_n=parse_path("m/44h/1815h/0h/0/0"),
|
|
derivation_type=messages.CardanoDerivationType.ICARUS,
|
|
)
|
|
response = client.call_raw(msg)
|
|
if passphrase is not None:
|
|
assert isinstance(response, messages.PassphraseRequest)
|
|
response = client.call(messages.PassphraseAck(passphrase=passphrase))
|
|
assert isinstance(response, messages.CardanoPublicKey)
|
|
return response.xpub
|
|
|
|
|
|
@pytest.mark.skip_t1
|
|
@pytest.mark.altcoin
|
|
@pytest.mark.setup_client(passphrase=True)
|
|
def test_cardano_passphrase(client: Client):
|
|
# Cardano has a separate derivation method that needs to access the plaintext
|
|
# of the passphrase.
|
|
# Historically, Cardano calls would ask for passphrase again. Now, they should not.
|
|
|
|
session_id = _init_session(client, derive_cardano=True)
|
|
|
|
# GetPublicKey requires passphrase and since it is not cached,
|
|
# Trezor will prompt for it.
|
|
assert _get_xpub(client, passphrase="B") == XPUB_PASSPHRASES["B"]
|
|
|
|
# The passphrase is now cached for non-Cardano coins.
|
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["B"]
|
|
|
|
# The passphrase should be cached for Cardano as well
|
|
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
|
|
|
# Initialize with the session id does not destroy the state
|
|
_init_session(client, session_id=session_id, derive_cardano=True)
|
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["B"]
|
|
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_B
|
|
|
|
# New session will destroy the state
|
|
_init_session(client, derive_cardano=True)
|
|
|
|
# Cardano must ask for passphrase again
|
|
assert _get_xpub_cardano(client, passphrase="A") == XPUB_CARDANO_PASSPHRASE_A
|
|
|
|
# Passphrase is now cached for Cardano
|
|
assert _get_xpub_cardano(client, passphrase=None) == XPUB_CARDANO_PASSPHRASE_A
|
|
|
|
# Passphrase is cached for non-Cardano coins too
|
|
assert _get_xpub(client, passphrase=None) == XPUB_PASSPHRASES["A"]
|