def test_recovery_device(client: Client): assert client.features.pin_protection is False assert client.features.passphrase_protection is False client.use_mnemonic(MNEMONIC12) with client: client.set_expected_responses( [messages.ButtonRequest] + [messages.WordRequest] * 24 + [messages.Success, messages.Features] ) device.recover( client, 12, False, False, "label", "en-US", client.mnemonic_callback ) with pytest.raises(TrezorFailure): # This must fail, because device is already initialized # Using direct call because `device.recover` has its own check client.call( messages.RecoveryDevice( word_count=12, passphrase_protection=False, pin_protection=False, label="label", language="en-US", ) )
def test_softlock_instability(client: Client): def load_device(): debuglink.load_device( client, mnemonic=MNEMONIC12, pin="1234", passphrase_protection=False, label="test", ) # start from a clean slate: resp = client.debug.reseed(0) if isinstance(resp, messages.Failure) and not isinstance( client.transport, udp.UdpTransport): pytest.xfail("reseed only supported on emulator") device.wipe(client) entropy_after_wipe = misc.get_entropy(client, 16) # configure and wipe the device load_device() client.debug.reseed(0) device.wipe(client) assert misc.get_entropy(client, 16) == entropy_after_wipe load_device() # the device has PIN -> lock it client.call(messages.LockDevice()) client.debug.reseed(0) # wipe_device should succeed with no need to unlock device.wipe(client) # the device is now trying to run the lockscreen, which attempts to unlock. # If the device actually called config.unlock(), it would use additional randomness. # That is undesirable. Assert that the returned entropy is still the same. assert misc.get_entropy(client, 16) == entropy_after_wipe
def test_reset_device(client: Client): assert client.features.pin_protection is False assert client.features.passphrase_protection is False os_urandom = mock.Mock(return_value=EXTERNAL_ENTROPY) with mock.patch("os.urandom", os_urandom), client: client.set_expected_responses( [messages.ButtonRequest] + [messages.EntropyRequest] + [messages.ButtonRequest] * 24 + [messages.Success, messages.Features] ) device.reset(client, False, 128, True, False, "label", "en-US") with pytest.raises(TrezorFailure): # This must fail, because device is already initialized # Using direct call because `device.reset` has its own check client.call( messages.ResetDevice( display_random=False, strength=128, passphrase_protection=True, pin_protection=False, label="label", language="en-US", ) )
def test_cancel_message_via_cancel(client: Client, message): def input_flow(): yield client.cancel() with client, pytest.raises(Cancelled): client.set_expected_responses([m.ButtonRequest(), m.Failure()]) client.set_input_flow(input_flow) client.call(message)
def test_bad_parameters(client: Client, field_name, field_value): msg = messages.RecoveryDevice( dry_run=True, word_count=12, enforce_wordlist=True, type=messages.RecoveryDeviceType.ScrambledWords, ) setattr(msg, field_name, field_value) with pytest.raises(exceptions.TrezorFailure, match="Forbidden field set in dry-run"): client.call(msg)
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"]
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"]
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_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
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
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"]
def test_ping(client: Client): ping = client.call(messages.Ping(message="ahoj!")) assert ping == messages.Success(message="ahoj!")
def test_features(client: Client): f0 = client.features # client erases session_id from its features f0.session_id = client.session_id f1 = client.call(messages.Initialize(session_id=f0.session_id)) assert f0 == f1
def test_sign_tx(client: Client): # NOTE: FAKE input tx commitment_data = b"\x0fwww.example.com" + (1).to_bytes(ROUND_ID_LEN, "big") with client: client.use_pin_sequence([PIN]) btc.authorize_coinjoin( client, coordinator="www.example.com", max_rounds=2, max_coordinator_fee_rate=50_000_000, # 0.5 % max_fee_per_kvbyte=3500, n=parse_path("m/84h/1h/0h"), coin_name="Testnet", script_type=messages.InputScriptType.SPENDWITNESS, ) client.call(messages.LockDevice()) with client: client.set_expected_responses( [messages.PreauthorizedRequest, messages.OwnershipProof] ) btc.get_ownership_proof( client, "Testnet", parse_path("m/84h/1h/0h/1/0"), script_type=messages.InputScriptType.SPENDWITNESS, user_confirmation=True, commitment_data=commitment_data, preauthorized=True, ) with client: client.set_expected_responses( [messages.PreauthorizedRequest, messages.OwnershipProof] ) btc.get_ownership_proof( client, "Testnet", parse_path("m/84h/1h/0h/1/5"), script_type=messages.InputScriptType.SPENDWITNESS, user_confirmation=True, commitment_data=commitment_data, preauthorized=True, ) inputs = [ messages.TxInputType( # seed "alcohol woman abuse must during monitor noble actual mixed trade anger aisle" # 84'/1'/0'/0/0 # tb1qnspxpr2xj9s2jt6qlhuvdnxw6q55jvygcf89r2 amount=100_000, prev_hash=TXHASH_e5b7e2, prev_index=0, script_type=messages.InputScriptType.EXTERNAL, script_pubkey=bytes.fromhex("00149c02608d469160a92f40fdf8c6ccced029493088"), ownership_proof=bytearray.fromhex( "534c001901016b2055d8190244b2ed2d46513c40658a574d3bc2deb6969c0535bb818b44d2c40002483045022100a6c7d59b453efa7b4abc9bc724a94c5655ae986d5924dc29d28bcc2b859cbace022047d2bc4422a47f7b044bd6cdfbf63fe1a0ecbf11393f4c0bf8565f867a5ced16012103505f0d82bbdd251511591b34f36ad5eea37d3220c2b81a1189084431ddb3aa3d" ), commitment_data=commitment_data, ), messages.TxInputType( address_n=parse_path("m/84h/1h/0h/1/0"), amount=7_289_000, prev_hash=FAKE_TXHASH_f982c0, prev_index=1, script_type=messages.InputScriptType.SPENDWITNESS, ), ]
def test_already_initialized(client: Client): with pytest.raises(RuntimeError): device.recover(client) with pytest.raises(exceptions.TrezorFailure, match="Already initialized"): client.call(messages.RecoveryDevice())