def test_apply_settings(self): with self.client: self.setup_mnemonic_pin_passphrase() self.client.set_expected_responses( [ proto.PinMatrixRequest(), proto.ButtonRequest(), proto.Success(), proto.Features(), ] ) # TrezorClient reinitializes device device.apply_settings(self.client, label="nazdar")
def _setup_mnemonic(self, mnemonic=None, pin="", passphrase=False, lock=True): if mnemonic is None: mnemonic = TrezorTest.mnemonic12 debuglink.load_device_by_mnemonic( self.client, mnemonic=mnemonic, pin=pin, passphrase_protection=passphrase, label="test", language="english", ) if conftest.TREZOR_VERSION == 1 and lock: # remove cached PIN (introduced via load_device) self.client.clear_session() if conftest.TREZOR_VERSION > 1 and passphrase: device.apply_settings(self.client, passphrase_source=PASSPHRASE_ON_HOST)
def test_autolock_not_retained(client): with client: client.use_pin_sequence([PIN4]) device.apply_settings(client, auto_lock_delay_ms=10_000) assert client.features.auto_lock_delay_ms == 10_000 device.wipe(client) assert client.features.auto_lock_delay_ms > 10_000 with client: client.use_pin_sequence([PIN4, PIN4]) device.reset(client, skip_backup=True, pin_protection=True) time.sleep(10.5) with client: # after sleeping for the pre-wipe autolock amount, Trezor must still be unlocked client.set_expected_responses([messages.Address]) get_test_address(client)
def test_invalid_path_prompt(client: Client): inp1 = messages.TxInputType( address_n=parse_path("m/44h/0h/0h/0/0"), amount=390_000, prev_hash=PREV_HASH, prev_index=0, ) # address is converted from 1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1 by changing the version out1 = messages.TxOutputType( address="LfWz9wLHmqU9HoDkMg9NqbRosrHvEixeVZ", amount=390_000 - 10_000, script_type=messages.OutputScriptType.PAYTOADDRESS, ) device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) btc.sign_tx(client, "Litecoin", [inp1], [out1], prev_txes=PREV_TXES)
def test_apply_auto_lock_delay(self): self.setup_mnemonic_pin_passphrase() with self.client: self.client.set_expected_responses(EXPECTED_RESPONSES_PIN) device.apply_settings(self.client, auto_lock_delay_ms=int(10e3)) # 10 secs time.sleep(0.1) # sleep less than auto-lock delay with self.client: # No PIN protection is required. self.client.set_expected_responses([proto.Success()]) self.client.ping(msg="", pin_protection=True) time.sleep(10.1) # sleep more than auto-lock delay with self.client: self.client.set_expected_responses( [proto.PinMatrixRequest(), proto.Success()]) self.client.ping(msg="", pin_protection=True)
def test_cache(client: Client): # disable safety checks to access non-standard paths device.apply_settings(client, safety_checks=SafetyCheckLevel.PromptTemporarily) start = time.time() for x in range(10): btc.get_address(client, "Bitcoin", [x, 2, 3, 4, 5, 6, 7, 8]) nocache_time = time.time() - start start = time.time() for x in range(10): btc.get_address(client, "Bitcoin", [1, 2, 3, 4, 5, 6, 7, x]) cache_time = time.time() - start print("NOCACHE TIME", nocache_time) print("CACHED TIME", cache_time) # Cached time expected to be at least 2x faster assert cache_time <= nocache_time / 2.0
def test_passphrase_reporting(client: Client, passphrase): """On TT, passphrase_protection is a private setting, so a locked device should report passphrase_protection=None. """ with client: client.use_pin_sequence([PIN4]) device.apply_settings(client, use_passphrase=passphrase) client.lock() # on a locked device, passphrase_protection should be None assert client.features.unlocked is False assert client.features.passphrase_protection is None # on an unlocked device, protection should be reported accurately _assert_protection(client, pin=True, passphrase=passphrase) # after re-locking, the setting should be hidden again client.lock() assert client.features.unlocked is False assert client.features.passphrase_protection is None
def test_load_device_2(self, client): debuglink.load_device_by_mnemonic( client, mnemonic=MNEMONIC12, pin="1234", passphrase_protection=True, label="test", ) if client.features.model == "T": device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.set_passphrase("passphrase") state = client.debug.state() assert state.mnemonic_secret == MNEMONIC12.encode() if client.features.model == "1": # we do not send PIN in DebugLinkState in Core assert state.pin == "1234" assert state.passphrase_protection is True address = btc.get_address(client, "Bitcoin", []) assert address == "15fiTDFwZd2kauHYYseifGi9daH2wniDHH"
def test_apply_settings_passphrase(self): self.setup_mnemonic_pin_nopassphrase() assert self.client.features.passphrase_protection is False with self.client: self.client.set_expected_responses([ proto.PinMatrixRequest(), proto.ButtonRequest(), proto.Success(), proto.Features(), ]) if self.client.features.major_version >= 2: self.client.expected_responses.pop(0) # skip PinMatrixRequest device.apply_settings(self.client, use_passphrase=True) assert self.client.features.passphrase_protection is True with self.client: self.client.set_expected_responses( [proto.ButtonRequest(), proto.Success(), proto.Features()]) device.apply_settings(self.client, use_passphrase=False) assert self.client.features.passphrase_protection is False with self.client: self.client.set_expected_responses( [proto.ButtonRequest(), proto.Success(), proto.Features()]) device.apply_settings(self.client, use_passphrase=True) assert self.client.features.passphrase_protection is True
def test_experimental_features(self, client): def experimental_call(): btc.authorize_coinjoin( client, coordinator="www.example.com", max_total_fee=10010, fee_per_anonymity=5000000, # 0.005 % n=parse_path("m/84'/1'/0'"), coin_name="Testnet", script_type=messages.InputScriptType.SPENDWITNESS, ) assert client.features.experimental_features is None # unlock with client: _set_expected_responses(client) device.apply_settings(client, label="new label") assert client.features.experimental_features with client: client.set_expected_responses([ messages.ButtonRequest, messages.ButtonRequest, messages.Success ]) experimental_call() with client: client.set_expected_responses( [messages.Success, messages.Features]) device.apply_settings(client, experimental_features=False) assert not client.features.experimental_features with pytest.raises(exceptions.TrezorFailure, match="DataError"), client: client.set_expected_responses([messages.Failure]) experimental_call()
def test_invalid_path_prompt(client): # tx: d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882 # input 0: 0.0039 BTC inp1 = messages.TxInputType( address_n=parse_path("44h/0h/0h/0/0"), amount=390000, prev_hash=TXHASH_d5f65e, prev_index=0, ) # address is converted from 1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1 by changing the version out1 = messages.TxOutputType( address="LfWz9wLHmqU9HoDkMg9NqbRosrHvEixeVZ", amount=390000 - 10000, script_type=messages.OutputScriptType.PAYTOADDRESS, ) device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) btc.sign_tx(client, "Litecoin", [inp1], [out1], prev_txes=TX_CACHE_MAINNET)
def test_cardano_sign_tx(client, parameters, result): inputs = [cardano.parse_input(i) for i in parameters["inputs"]] outputs = [cardano.parse_output(o) for o in parameters["outputs"]] certificates = [ cardano.parse_certificate(c) for c in parameters["certificates"] ] withdrawals = [ cardano.parse_withdrawal(w) for w in parameters["withdrawals"] ] auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) input_flow = parameters.get("input_flow", ()) if parameters.get("security_checks") == "prompt": device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) else: device.apply_settings(client, safety_checks=messages.SafetyCheckLevel.Strict) with client: client.set_input_flow(_to_device_actions(client, input_flow)) response = cardano.sign_tx( client=client, inputs=inputs, outputs=outputs, fee=parameters["fee"], ttl=parameters.get("ttl"), validity_interval_start=parameters.get("validity_interval_start"), certificates=certificates, withdrawals=withdrawals, protocol_magic=parameters["protocol_magic"], network_id=parameters["network_id"], auxiliary_data=auxiliary_data, ) assert response.tx_hash.hex() == result["tx_hash"] assert response.serialized_tx.hex() == result["serialized_tx"]
def test_safety_checks(self, client): def get_bad_address(): btc.get_address(client, "Bitcoin", parse_path("m/44'"), show_display=True) assert client.features.safety_checks == messages.SafetyCheckLevel.Strict with pytest.raises(exceptions.TrezorFailure, match="Forbidden key path"), client: client.set_expected_responses([messages.Failure]) get_bad_address() if client.features.model != "1": with client: client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptAlways) assert (client.features.safety_checks == messages.SafetyCheckLevel.PromptAlways) with client: client.set_expected_responses([ messages.ButtonRequest, messages.ButtonRequest, messages.Address ]) get_bad_address() with client: client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.Strict) assert client.features.safety_checks == messages.SafetyCheckLevel.Strict with pytest.raises(exceptions.TrezorFailure, match="Forbidden key path"), client: client.set_expected_responses([messages.Failure]) get_bad_address() with client: client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) assert (client.features.safety_checks == messages.SafetyCheckLevel.PromptTemporarily) with client: client.set_expected_responses([ messages.ButtonRequest, messages.ButtonRequest, messages.Address ]) get_bad_address()
def client(request): client = get_device() wipe_device(client) client.open() # fmt: off setup_params = dict( uninitialized=False, mnemonic=" ".join(["all"] * 12), pin=None, passphrase=False, ) # fmt: on marker = request.node.get_closest_marker("setup_client") if marker: setup_params.update(marker.kwargs) if not setup_params["uninitialized"]: if setup_params["pin"] is True: setup_params["pin"] = "1234" debuglink.load_device_by_mnemonic( client, mnemonic=setup_params["mnemonic"], pin=setup_params["pin"], passphrase_protection=setup_params["passphrase"], label="test", language="english", ) client.clear_session() if setup_params["passphrase"] and client.features.model != "1": apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) yield client client.close()
def test_unknown_path_tt(client): UNKNOWN_PATH = parse_path("m/44'/9'/0'/0/0") with pytest.raises(TrezorFailure, match="Forbidden key path"): # account number is too high btc.get_address(client, "Bitcoin", UNKNOWN_PATH) # disable safety checks device.apply_settings(client, safety_checks=SafetyCheckLevel.PromptTemporarily) with client: client.set_expected_responses([ messages.ButtonRequest( code=messages.ButtonRequestType.UnknownDerivationPath), messages.ButtonRequest(code=messages.ButtonRequestType.Address), messages.Address, ]) # try again with a warning btc.get_address(client, "Bitcoin", UNKNOWN_PATH, show_display=True) with client: # no warning is displayed when the call is silent client.set_expected_responses([messages.Address]) btc.get_address(client, "Bitcoin", UNKNOWN_PATH, show_display=False)
def test_apply_settings_passphrase(client: Client): with client: _set_expected_responses(client) device.apply_settings(client, use_passphrase=True) assert client.features.passphrase_protection is True with client: client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings(client, use_passphrase=False) assert client.features.passphrase_protection is False with client: client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings(client, use_passphrase=True) assert client.features.passphrase_protection is True
def test_apply_settings_passphrase(self): self.setup_mnemonic_pin_nopassphrase() assert self.client.features.passphrase_protection is False with self.client: self.client.set_expected_responses(EXPECTED_RESPONSES) device.apply_settings(self.client, use_passphrase=True) assert self.client.features.passphrase_protection is True with self.client: self.client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings(self.client, use_passphrase=False) assert self.client.features.passphrase_protection is False with self.client: self.client.set_expected_responses(EXPECTED_RESPONSES_NOPIN) device.apply_settings(self.client, use_passphrase=True) assert self.client.features.passphrase_protection is True
yield client.debug.input(new_pin) if __name__ == "__main__": wirelink = get_device() client = TrezorClientDebugLink(wirelink) client.open() i = 0 last_pin = None while True: # set private field device.apply_settings(client, use_passphrase=True) assert client.features.passphrase_protection is True device.apply_settings(client, use_passphrase=False) assert client.features.passphrase_protection is False # set public field label = "".join( random.choices(string.ascii_uppercase + string.digits, k=17)) device.apply_settings(client, label=label) assert client.features.label == label # change PIN new_pin = "".join( random.choices(string.digits, k=random.randint(6, 10))) client.set_input_flow(pin_input_flow(client, last_pin, new_pin)) device.change_pin(client)
def client(request): """Client fixture. Every test function that requires a client instance will get it from here. If we can't connect to a debuggable device, the test will fail. If 'skip_t2' is used and TT is connected, the test is skipped. Vice versa with T1 and 'skip_t1'. The client instance is wiped and preconfigured with "all all all..." mnemonic, no password and no pin. It is possible to customize this with the `setup_client` marker. To specify a custom mnemonic and/or custom pin and/or enable passphrase: @pytest.mark.setup_client(mnemonic=MY_MNEMONIC, pin="9999", passphrase=True) To receive a client instance that was not initialized: @pytest.mark.setup_client(uninitialized=True) """ try: client = get_device() except RuntimeError: pytest.fail("No debuggable Trezor is available") if request.node.get_closest_marker( "skip_t2") and client.features.model == "T": pytest.skip("Test excluded on Trezor T") if request.node.get_closest_marker( "skip_t1") and client.features.model == "1": pytest.skip("Test excluded on Trezor 1") if (request.node.get_closest_marker("sd_card") and not client.features.sd_card_present): raise RuntimeError("This test requires SD card.\n" "To skip all such tests, run:\n" " pytest -m 'not sd_card' <test path>") wipe_device(client) # fmt: off setup_params = dict( uninitialized=False, mnemonic=" ".join(["all"] * 12), pin=None, passphrase=False, ) # fmt: on marker = request.node.get_closest_marker("setup_client") if marker: setup_params.update(marker.kwargs) if not setup_params["uninitialized"]: if setup_params["pin"] is True: setup_params["pin"] = "1234" debuglink.load_device_by_mnemonic( client, mnemonic=setup_params["mnemonic"], pin=setup_params["pin"], passphrase_protection=setup_params["passphrase"], label="test", language="english", ) client.clear_session() if setup_params["passphrase"] and client.features.model != "1": apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.open() yield client client.close()
def test_load_device_utf(self, client): words_nfkd = u"Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" words_nfc = u"P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" words_nfkc = u"P\u0159\xed\u0161ern\u011b \u017elu\u0165ou\u010dk\xfd k\u016f\u0148 \xfap\u011bl \u010f\xe1belsk\xe9 \xf3dy z\xe1ke\u0159n\xfd u\u010de\u0148 b\u011b\u017e\xed pod\xe9l z\xf3ny \xfal\u016f" words_nfd = u"Pr\u030ci\u0301s\u030cerne\u030c z\u030clut\u030couc\u030cky\u0301 ku\u030an\u030c u\u0301pe\u030cl d\u030ca\u0301belske\u0301 o\u0301dy za\u0301ker\u030cny\u0301 uc\u030cen\u030c be\u030cz\u030ci\u0301 pode\u0301l zo\u0301ny u\u0301lu\u030a" passphrase_nfkd = ( u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko" ) passphrase_nfc = ( u"Neuv\u011b\u0159iteln\u011b bezpe\u010dn\xe9 hesl\xed\u010dko") passphrase_nfkc = ( u"Neuv\u011b\u0159iteln\u011b bezpe\u010dn\xe9 hesl\xed\u010dko") passphrase_nfd = ( u"Neuve\u030cr\u030citelne\u030c bezpec\u030cne\u0301 hesli\u0301c\u030cko" ) device.wipe(client) debuglink.load_device_by_mnemonic( client, mnemonic=words_nfkd, pin="", passphrase_protection=True, label="test", language="english", skip_checksum=True, ) if client.features.model == "T": device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.set_passphrase(passphrase_nfkd) address_nfkd = btc.get_address(client, "Bitcoin", []) device.wipe(client) debuglink.load_device_by_mnemonic( client, mnemonic=words_nfc, pin="", passphrase_protection=True, label="test", language="english", skip_checksum=True, ) if client.features.model == "T": device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.set_passphrase(passphrase_nfc) address_nfc = btc.get_address(client, "Bitcoin", []) device.wipe(client) debuglink.load_device_by_mnemonic( client, mnemonic=words_nfkc, pin="", passphrase_protection=True, label="test", language="english", skip_checksum=True, ) if client.features.model == "T": device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.set_passphrase(passphrase_nfkc) address_nfkc = btc.get_address(client, "Bitcoin", []) device.wipe(client) debuglink.load_device_by_mnemonic( client, mnemonic=words_nfd, pin="", passphrase_protection=True, label="test", language="english", skip_checksum=True, ) if client.features.model == "T": device.apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) client.set_passphrase(passphrase_nfd) address_nfd = btc.get_address(client, "Bitcoin", []) assert address_nfkd == address_nfc assert address_nfkd == address_nfkc assert address_nfkd == address_nfd
def call_sign_tx(client: Client, parameters, input_flow=None): client.init_device(new_session=True, derive_cardano=True) signing_mode = messages.CardanoTxSigningMode.__members__[ parameters["signing_mode"]] inputs = [cardano.parse_input(i) for i in parameters["inputs"]] outputs = [cardano.parse_output(o) for o in parameters["outputs"]] certificates = [ cardano.parse_certificate(c) for c in parameters["certificates"] ] withdrawals = [ cardano.parse_withdrawal(w) for w in parameters["withdrawals"] ] auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) mint = cardano.parse_mint(parameters["mint"]) script_data_hash = cardano.parse_script_data_hash( parameters["script_data_hash"]) collateral_inputs = [ cardano.parse_collateral_input(i) for i in parameters["collateral_inputs"] ] required_signers = [ cardano.parse_required_signer(s) for s in parameters["required_signers"] ] collateral_return = (cardano.parse_output(parameters["collateral_return"]) if parameters["collateral_return"] is not None else None) reference_inputs = [ cardano.parse_reference_input(i) for i in parameters["reference_inputs"] ] additional_witness_requests = [ cardano.parse_additional_witness_request(p) for p in parameters["additional_witness_requests"] ] if parameters.get("security_checks") == "prompt": device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) else: device.apply_settings(client, safety_checks=messages.SafetyCheckLevel.Strict) with client: if input_flow is not None: client.watch_layout() client.set_input_flow(input_flow(client)) return cardano.sign_tx( client=client, signing_mode=signing_mode, inputs=inputs, outputs=outputs, fee=parameters["fee"], ttl=parameters["ttl"], validity_interval_start=parameters["validity_interval_start"], certificates=certificates, withdrawals=withdrawals, protocol_magic=parameters["protocol_magic"], network_id=parameters["network_id"], auxiliary_data=auxiliary_data, mint=mint, script_data_hash=script_data_hash, collateral_inputs=collateral_inputs, required_signers=required_signers, collateral_return=collateral_return, total_collateral=parameters["total_collateral"], reference_inputs=reference_inputs, additional_witness_requests=additional_witness_requests, include_network_id=parameters["include_network_id"], )
def client(request): """Client fixture. Every test function that requires a client instance will get it from here. If we can't connect to a debuggable device, the test will fail. If 'skip_t2' is used and TT is connected, the test is skipped. Vice versa with T1 and 'skip_t1'. The client instance is wiped and preconfigured with "all all all..." mnemonic, no password and no pin. It is possible to customize this with the `setup_client` marker. To specify a custom mnemonic and/or custom pin and/or enable passphrase: @pytest.mark.setup_client(mnemonic=MY_MNEMONIC, pin="9999", passphrase=True) To receive a client instance that was not initialized: @pytest.mark.setup_client(uninitialized=True) """ try: client = get_device() except RuntimeError: pytest.fail("No debuggable Trezor is available") if request.node.get_closest_marker( "skip_t2") and client.features.model == "T": pytest.skip("Test excluded on Trezor T") if request.node.get_closest_marker( "skip_t1") and client.features.model == "1": pytest.skip("Test excluded on Trezor 1") if (request.node.get_closest_marker("sd_card") and not client.features.sd_card_present): raise RuntimeError("This test requires SD card.\n" "To skip all such tests, run:\n" " pytest -m 'not sd_card' <test path>") test_ui = request.config.getoption("ui") if test_ui not in ("", "record", "test"): raise ValueError("Invalid ui option.") run_ui_tests = not request.node.get_closest_marker("skip_ui") and test_ui client.open() if run_ui_tests: # we need to reseed before the wipe client.debug.reseed(0) wipe_device(client) setup_params = dict( uninitialized=False, mnemonic=" ".join(["all"] * 12), pin=None, passphrase=False, needs_backup=False, no_backup=False, ) marker = request.node.get_closest_marker("setup_client") if marker: setup_params.update(marker.kwargs) if not setup_params["uninitialized"]: if setup_params["pin"] is True: setup_params["pin"] = "1234" debuglink.load_device( client, mnemonic=setup_params["mnemonic"], pin=setup_params["pin"], passphrase_protection=setup_params["passphrase"], label="test", language="en-US", needs_backup=setup_params["needs_backup"], no_backup=setup_params["no_backup"], ) if setup_params["passphrase"] and client.features.model != "1": apply_settings(client, passphrase_source=PASSPHRASE_ON_HOST) if setup_params["pin"]: # ClearSession locks the device. We only do that if the PIN is set. client.clear_session() if run_ui_tests: with ui_tests.screen_recording(client, request): yield client else: yield client client.close()
def enable_passphrase(hw_session: HwSessionInfo, passphrase_enabled): try: device.apply_settings(hw_session.hw_client, use_passphrase=passphrase_enabled) except exceptions.Cancelled: pass
yield client.debug.input(new_pin) if __name__ == "__main__": wirelink = get_device() client = TrezorClientDebugLink(wirelink) client.open() i = 0 last_pin = None while True: # set private field device.apply_settings(client, use_passphrase=True) assert client.features.passphrase_protection is True device.apply_settings(client, use_passphrase=False) assert client.features.passphrase_protection is False # set public field label = "".join(random.choices(string.ascii_uppercase + string.digits, k=17)) device.apply_settings(client, label=label) assert client.features.label == label # change PIN new_pin = "".join(random.choices(string.digits, k=random.randint(6, 10))) client.set_input_flow(pin_input_flow(client, last_pin, new_pin)) device.change_pin(client) client.set_input_flow(None) last_pin = new_pin
yield client.debug.input(old_pin) # enter new pin yield client.debug.input(new_pin) # repeat new pin yield client.debug.input(new_pin) if __name__ == "__main__": wirelink = get_device() client = TrezorClientDebugLink(wirelink) client.open() i = 0 last_pin = None while True: # set private field device.apply_settings(client, auto_lock_delay_ms=(i % 10 + 10) * 1000) # set public field label = "".join(random.choices(string.ascii_uppercase + string.digits, k=17)) device.apply_settings(client, label=label) assert client.features.label == label print("iteration %d" % i) i = i + 1
def test_2of5_pin_passphrase(self): # 256 bits security, 2 of 5 mnemonics = [ "hobo romp academic axis august founder knife legal recover alien expect emphasis loan kitchen involve teacher capture rebuild trial numb spider forward ladle lying voter typical security quantity hawk legs idle leaves gasoline", "hobo romp academic agency ancestor industry argue sister scene midst graduate profile numb paid headset airport daisy flame express scene usual welcome quick silent downtown oral critical step remove says rhythm venture aunt", ] word_count = len(mnemonics[0].split(" ")) ret = self.client.call_raw( proto.RecoveryDevice(passphrase_protection=True, pin_protection=True, label="label")) # Confirm Recovery assert isinstance(ret, proto.ButtonRequest) self.client.debug.press_yes() ret = self.client.call_raw(proto.ButtonAck()) # Enter word count assert ret == proto.ButtonRequest( code=proto.ButtonRequestType.MnemonicWordCount) self.client.debug.input(str(word_count)) ret = self.client.call_raw(proto.ButtonAck()) # Confirm T9 keyboard assert isinstance(ret, proto.ButtonRequest) self.client.debug.press_yes() ret = self.client.call_raw(proto.ButtonAck()) # Enter shares for mnemonic in mnemonics: # Enter mnemonic words assert ret == proto.ButtonRequest( code=proto.ButtonRequestType.MnemonicInput) self.client.transport.write(proto.ButtonAck()) for word in mnemonic.split(" "): time.sleep(1) self.client.debug.input(word) ret = self.client.transport.read() if mnemonic != mnemonics[-1]: # Confirm status assert isinstance(ret, proto.ButtonRequest) self.client.debug.press_yes() ret = self.client.call_raw(proto.ButtonAck()) # Enter PIN for first time assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) self.client.debug.input("654") ret = self.client.call_raw(proto.ButtonAck()) # Enter PIN for second time assert ret == proto.ButtonRequest(code=proto.ButtonRequestType.Other) self.client.debug.input("654") ret = self.client.call_raw(proto.ButtonAck()) # Confirm success assert isinstance(ret, proto.ButtonRequest) self.client.debug.press_yes() ret = self.client.call_raw(proto.ButtonAck()) # Workflow succesfully ended assert ret == proto.Success(message="Device recovered") # Check mnemonic self.client.init_device() assert ( self.client.debug.read_mnemonic_secret().hex() == "b770e0da1363247652de97a39bdbf2463be087848d709ecbf28e84508e31202a") assert self.client.features.pin_protection is True assert self.client.features.passphrase_protection is True device.apply_settings(self.client, passphrase_source=PASSPHRASE_ON_HOST) # BIP32 Root Key for empty passphrase # provided by Andrew, address calculated using T1 # xprv9s21ZrQH143K2kP9RYJE5AFggTHLs8PbDaaTYtvh238THxDyXqyqQV6H1QpFr3aaQ7CFusFMYyGZ6VcK7aLADyCaCJrszovxtzVZmnRfca4 address = btc.get_address(self.client, "Bitcoin", []) assert address == "1BmqXKM8M1gWA4bgkbPeCtJruRnrY2qYKP"
def test_attack_path_segwit(client): # Scenario: The attacker falsely claims that the transaction uses Testnet paths to # avoid the path warning dialog, but in step6_sign_segwit_inputs() uses Bitcoin paths # to get a valid signature. device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) inp1 = messages.TxInputType( # The actual input that the attcker wants to get signed. address_n=parse_path("84'/0'/0'/0/0"), amount=9426, prev_hash=TXHASH_fa80a9, prev_index=0, script_type=messages.InputScriptType.SPENDWITNESS, ) inp2 = messages.TxInputType( # The actual input that the attcker wants to get signed. # We need this one to be from a different account, so that the match checker # allows the transaction to pass. address_n=parse_path("84'/0'/1'/0/1"), amount=7086, prev_hash=TXHASH_5dfd1b, prev_index=0, script_type=messages.InputScriptType.SPENDWITNESS, ) out1 = messages.TxOutputType( # Attacker's Mainnet address encoded as Testnet. address="tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu", script_type=messages.OutputScriptType.PAYTOADDRESS, amount=9426 + 7086 - 500, ) attack_count = 6 def attack_processor(msg): nonlocal attack_count # Make the inputs look like they are coming from Testnet paths until we reach the # signing phase. if attack_count > 0 and msg.tx.inputs and msg.tx.inputs[0] in (inp1, inp2): attack_count -= 1 msg.tx.inputs[0].address_n[1] = H_(1) return msg with client: client.set_filter(messages.TxAck, attack_processor) client.set_expected_responses([ # Step: process inputs request_input(0), # Attacker bypasses warning about non-standard path. request_input(1), # Attacker bypasses warning about non-standard path. # Step: approve outputs request_output(0), messages.ButtonRequest(code=B.ConfirmOutput), messages.ButtonRequest(code=B.SignTx), # Step: verify inputs request_input(0), request_meta(TXHASH_fa80a9), request_input(0, TXHASH_fa80a9), request_output(0, TXHASH_fa80a9), request_output(1, TXHASH_fa80a9), request_input(1), request_meta(TXHASH_5dfd1b), request_input(0, TXHASH_5dfd1b), request_output(0, TXHASH_5dfd1b), request_output(1, TXHASH_5dfd1b), # Step: serialize inputs request_input(0), request_input(1), # Step: serialize outputs request_output(0), # Step: sign segwit inputs request_input(0), # Trezor must warn about non-standard path before signing. messages.ButtonRequest(code=B.UnknownDerivationPath), request_input(1), # Trezor must warn about non-standard path before signing. messages.ButtonRequest(code=B.UnknownDerivationPath), request_finished(), ]) btc.sign_tx(client, "Testnet", [inp1, inp2], [out1], prev_txes=TX_CACHE_MAINNET)
def test_apply_homescreen(self, client): img = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x04\x88\x02\x00\x00\x00\x02\x91\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x90@\x00\x11@\x00\x00\x00\x00\x00\x00\x08\x00\x10\x92\x12\x04\x00\x00\x05\x12D\x00\x00\x00\x00\x00 \x00\x00\x08\x00Q\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x00\x00\x10\x02 \x01\x04J\x00)$\x00\x00\x00\x00\x80\x00\x00\x00\x00\x08\x10\xa1\x00\x00\x02\x81 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\tP\x00\x00\x00\x00\x00\x00 \x00\x00\xa0\x00\xa0R \x12\x84\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x08\x00\tP\x00\x00\x00\x00 \x00\x04 \x00\x80\x02\x00@\x02T\xc2 \x00\x00\x00\x00\x00\x00\x00\x10@\x00)\t@\n\xa0\x80\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x80@\x14\xa9H\x04\x00\x00\x88@\x00\x00\x00\x00\x00\x02\x02$\x00\x15B@\x00\nP\x00\x00\x00\x00\x00\x80\x00\x00\x91\x01UP\x00\x00 \x02\x00\x00\x00\x00\x00\x00\x02\x08@ Z\xa5 \x00\x00\x80\x00\x00\x00\x00\x00\x00\x08\xa1%\x14*\xa0\x00\x00\x02\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xaa\x91 \x00\x05E\x80\x00\x00\x00\x00\x00\x02*T\x05-D\x00\x00\x05 @\x00\x00\x00\x00\x00%@\x80\x11V\xa0\x88\x00\x05@\xb0\x00\x00\x00\x00\x00\x818$\x04\xabD \x00\x06\xa1T\x00\x00\x00\x00\x02\x03\xb8\x01R\xd5\x01\x00\x00\x05AP\x00\x00\x00\x00\x08\xadT\x00\x05j\xa4@\x00\x87ah\x00\x00\x00\x00\x02\x8d\xb8\x08\x00.\x01\x00\x00\x02\xa5\xa8\x10\x00\x00\x00*\xc1\xec \n\xaa\x88 \x02@\xf6\xd0\x02\x00\x00\x00\x0bB\xb6\x14@U"\x80\x00\x01{`\x00\x00\x00\x00M\xa3\xf8 \x15*\x00\x00\x00\x10n\xc0\x04\x00\x00\x02\x06\xc2\xa8)\x00\x96\x84\x80\x00\x00\x1b\x00\x00\x80@\x10\x87\xa7\xf0\x84\x10\xaa\x10\x00\x00D\x00\x00\x02 \x00\x8a\x06\xfa\xe0P\n-\x02@\x00\x12\x00\x00\x00\x00\x10@\x83\xdf\xa0\x00\x08\xaa@\x00\x00\x01H\x00\x05H\x04\x12\x01\xf7\x81P\x02T\t\x00\x00\x00 \x00\x00\x84\x10\x00\x00z\x00@)* \x00\x00\x01\n\xa0\x02 \x05\n\x00\x00\x05\x10\x84\xa8\x84\x80\x00\x00@\x14\x00\x92\x10\x80\x00\x04\x11@\tT\x00\x00\x00\x00\n@\x00\x08\x84@$\x00H\x00\x12Q\x02\x00\x00\x00\x00\x90\x02A\x12\xa8\n\xaa\x92\x10\x04\xa8\x10@\x00\x00\x04\x04\x00\x04I\x00\x04\x14H\x80"R\x01\x00\x00\x00!@\x00\x00$\xa0EB\x80\x08\x95hH\x00\x00\x00\x84\x10 \x05Z\x00\x00(\x00\x02\x00\xa1\x01\x00\x00\x04\x00@\x82\x00\xadH*\x92P\x00\xaaP\x00\x00\x00\x00\x11\x02\x01*\xad\x01\x00\x01\x01"\x11D\x08\x00\x00\x10\x80 \x00\x81W\x80J\x94\x04\x08\xa5 !\x00\x00\x00\x02\x00B*\xae\xa1\x00\x80\x10\x01\x08\xa4\x00\x00\x00\x00\x00\x84\x00\t[@"HA\x04E\x00\x84\x00\x00\x00\x10\x00\x01J\xd5\x82\x90\x02\x00!\x02\xa2\x00\x00\x00\x00\x00\x00\x00\x05~\xa0\x00 \x10\n)\x00\x11\x00\x00\x00\x00\x00\x00!U\x80\xa8\x88\x82\x80\x01\x00\x00\x00\x00\x00\x00H@\x11\xaa\xc0\x82\x00 *\n\x00\x00\x00\x00\x00\x00\x00\x00\n\xabb@ \x04\x00! \x84\x00\x00\x00\x00\x02@\xa5\x15A$\x04\x81(\n\x00\x00\x00\x00\x00\x00 \x01\x10\x02\xe0\x91\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00\x01 \xa9\tQH@\x91 P\x00\x00\x00\x00\x00\x00\x08\x00\x00\xa0T\xa5\x00@\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"\x00\x00\x00\x00\xa2\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00 T\xa0\t\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00@\x02\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x10\x00\x00\x10\x02\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00@\x04\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x08@\x10\x00\x00\x00\x00' with client: _set_expected_responses(client) device.apply_settings(client, homescreen=img)
client.debug.input(old_pin) # enter new pin yield client.debug.input(new_pin) # repeat new pin yield client.debug.input(new_pin) if __name__ == "__main__": wirelink = get_device() client = TrezorClientDebugLink(wirelink) client.open() i = 0 last_pin = None while True: # set private field device.apply_settings(client, auto_lock_delay_ms=(i % 10 + 10) * 1000) # set public field label = "".join( random.choices(string.ascii_uppercase + string.digits, k=17)) device.apply_settings(client, label=label) assert client.features.label == label print("iteration %d" % i) i = i + 1
def test_attack_path_segwit(client: Client): # Scenario: The attacker falsely claims that the transaction uses Testnet paths to # avoid the path warning dialog, but in step6_sign_segwit_inputs() uses Bitcoin paths # to get a valid signature. device.apply_settings( client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily) # Generate keys address_a = btc.get_address( client, "Testnet", parse_path("m/84h/0h/0h/0/0"), script_type=messages.InputScriptType.SPENDWITNESS, ) address_b = btc.get_address( client, "Testnet", parse_path("m/84h/0h/1h/0/1"), script_type=messages.InputScriptType.SPENDWITNESS, ) prev_hash, prev_tx = forge_prevtx([(address_a, 9_426), (address_b, 7_086)], network="testnet") inp1 = messages.TxInputType( # The actual input that the attacker wants to get signed. address_n=parse_path("m/84h/0h/0h/0/0"), amount=9_426, prev_hash=prev_hash, prev_index=0, script_type=messages.InputScriptType.SPENDWITNESS, ) inp2 = messages.TxInputType( # The actual input that the attacker wants to get signed. # We need this one to be from a different account, so that the match checker # allows the transaction to pass. address_n=parse_path("m/84h/0h/1h/0/1"), amount=7_086, prev_hash=prev_hash, prev_index=1, script_type=messages.InputScriptType.SPENDWITNESS, ) out1 = messages.TxOutputType( # Attacker's Mainnet address encoded as Testnet. address="tb1q694ccp5qcc0udmfwgp692u2s2hjpq5h407urtu", script_type=messages.OutputScriptType.PAYTOADDRESS, amount=9_426 + 7_086 - 500, ) attack_count = 6 def attack_processor(msg): nonlocal attack_count # Make the inputs look like they are coming from Testnet paths until we reach the # signing phase. if attack_count > 0 and msg.tx.inputs and msg.tx.inputs[0] in (inp1, inp2): attack_count -= 1 msg.tx.inputs[0].address_n[1] = H_(1) return msg with client: client.set_filter(messages.TxAck, attack_processor) with pytest.raises(TrezorFailure): btc.sign_tx(client, "Testnet", [inp1, inp2], [out1], prev_txes={prev_hash: prev_tx})
def test_apply_homescreen_toif_fail(client: Client, toif_data): with pytest.raises(exceptions.TrezorFailure), client: client.use_pin_sequence([PIN4]) device.apply_settings(client, homescreen=toif_data)
def client(request, _raw_client): """Client fixture. Every test function that requires a client instance will get it from here. If we can't connect to a debuggable device, the test will fail. If 'skip_t2' is used and TT is connected, the test is skipped. Vice versa with T1 and 'skip_t1'. The client instance is wiped and preconfigured with "all all all..." mnemonic, no password and no pin. It is possible to customize this with the `setup_client` marker. To specify a custom mnemonic and/or custom pin and/or enable passphrase: @pytest.mark.setup_client(mnemonic=MY_MNEMONIC, pin="9999", passphrase=True) To receive a client instance that was not initialized: @pytest.mark.setup_client(uninitialized=True) """ if request.node.get_closest_marker( "skip_t2") and _raw_client.features.model == "T": pytest.skip("Test excluded on Trezor T") if request.node.get_closest_marker( "skip_t1") and _raw_client.features.model == "1": pytest.skip("Test excluded on Trezor 1") sd_marker = request.node.get_closest_marker("sd_card") if sd_marker and not _raw_client.features.sd_card_present: raise RuntimeError("This test requires SD card.\n" "To skip all such tests, run:\n" " pytest -m 'not sd_card' <test path>") test_ui = request.config.getoption("ui") run_ui_tests = not request.node.get_closest_marker("skip_ui") and test_ui _raw_client.reset_debug_features() _raw_client.open() try: _raw_client.init_device() except Exception: request.session.shouldstop = "Failed to communicate with Trezor" pytest.fail("Failed to communicate with Trezor") if run_ui_tests: # we need to reseed before the wipe _raw_client.debug.reseed(0) if sd_marker: should_format = sd_marker.kwargs.get("formatted", True) _raw_client.debug.erase_sd_card(format=should_format) wipe_device(_raw_client) setup_params = dict( uninitialized=False, mnemonic=" ".join(["all"] * 12), pin=None, passphrase=False, needs_backup=False, no_backup=False, ) marker = request.node.get_closest_marker("setup_client") if marker: setup_params.update(marker.kwargs) use_passphrase = setup_params["passphrase"] is True or isinstance( setup_params["passphrase"], str) if not setup_params["uninitialized"]: debuglink.load_device( _raw_client, mnemonic=setup_params["mnemonic"], pin=setup_params["pin"], passphrase_protection=use_passphrase, label="test", language="en-US", needs_backup=setup_params["needs_backup"], no_backup=setup_params["no_backup"], ) if _raw_client.features.model == "T": apply_settings(_raw_client, experimental_features=True) if use_passphrase and isinstance(setup_params["passphrase"], str): _raw_client.use_passphrase(setup_params["passphrase"]) _raw_client.clear_session() if run_ui_tests: with ui_tests.screen_recording(_raw_client, request): yield _raw_client else: yield _raw_client _raw_client.close()
def test_label_too_long(client: Client): with pytest.raises(exceptions.TrezorFailure), client: client.set_expected_responses([messages.Failure]) device.apply_settings(client, label="A" * 33)