451 lines
16 KiB
Python
451 lines
16 KiB
Python
from web3 import Web3
|
|
|
|
from ledger_app_clients.ethereum.client import EthAppClient, StatusWord
|
|
import ledger_app_clients.ethereum.response_parser as ResponseParser
|
|
from ledger_app_clients.ethereum.utils import recover_transaction
|
|
|
|
from ragger.error import ExceptionRAPDU
|
|
from ragger.backend import BackendInterface
|
|
from ragger.firmware import Firmware
|
|
from ragger.navigator import Navigator, NavInsID, NavIns
|
|
|
|
from constants import ROOT_SNAPSHOT_PATH
|
|
|
|
|
|
# Values used across all tests
|
|
CHAIN_ID = 1
|
|
ADDR = bytes.fromhex("0011223344556677889900112233445566778899")
|
|
ADDR2 = bytes.fromhex("5a321744667052affa8386ed49e00ef223cbffc3")
|
|
ADDR3 = bytes.fromhex("dac17f958d2ee523a2206206994597c13d831ec7")
|
|
BIP32_PATH = "m/44'/60'/0'/0/0"
|
|
BIP32_PATH2 = "m/44'/60'/1'/0/0"
|
|
NONCE = 21
|
|
NONCE2 = 68
|
|
GAS_PRICE = 13
|
|
GAS_LIMIT = 21000
|
|
AMOUNT = 1.22
|
|
|
|
|
|
def common(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
tx_params: dict,
|
|
test_name: str = "",
|
|
path: str = BIP32_PATH):
|
|
app_client = EthAppClient(backend)
|
|
|
|
with app_client.get_public_addr(bip32_path=path, display=False):
|
|
pass
|
|
_, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
|
|
|
|
with app_client.sign(path, tx_params):
|
|
if firmware.device.startswith("nano"):
|
|
next_action = NavInsID.RIGHT_CLICK
|
|
confirm_action = NavInsID.BOTH_CLICK
|
|
end_text = "Accept"
|
|
else:
|
|
next_action = NavInsID.USE_CASE_REVIEW_TAP
|
|
confirm_action = NavInsID.USE_CASE_REVIEW_CONFIRM
|
|
end_text = "Sign"
|
|
|
|
if test_name:
|
|
navigator.navigate_until_text_and_compare(next_action,
|
|
[confirm_action],
|
|
end_text,
|
|
ROOT_SNAPSHOT_PATH,
|
|
test_name)
|
|
else:
|
|
navigator.navigate_until_text(next_action, [confirm_action], end_text)
|
|
|
|
# verify signature
|
|
vrs = ResponseParser.signature(app_client.response().data)
|
|
addr = recover_transaction(tx_params, vrs)
|
|
assert addr == DEVICE_ADDR
|
|
|
|
|
|
def common_reject(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
tx_params: dict,
|
|
test_name: str,
|
|
path: str = BIP32_PATH):
|
|
app_client = EthAppClient(backend)
|
|
|
|
try:
|
|
with app_client.sign(path, tx_params):
|
|
if firmware.device.startswith("nano"):
|
|
next_action = NavInsID.RIGHT_CLICK
|
|
confirm_action = NavInsID.BOTH_CLICK
|
|
navigator.navigate_until_text_and_compare(next_action,
|
|
[confirm_action],
|
|
"Reject",
|
|
ROOT_SNAPSHOT_PATH,
|
|
test_name)
|
|
else:
|
|
instructions = [NavInsID.USE_CASE_REVIEW_TAP] * 2
|
|
instructions += [NavInsID.USE_CASE_CHOICE_REJECT,
|
|
NavInsID.USE_CASE_CHOICE_CONFIRM,
|
|
NavInsID.USE_CASE_STATUS_DISMISS]
|
|
navigator.navigate_and_compare(ROOT_SNAPSHOT_PATH,
|
|
test_name,
|
|
instructions)
|
|
|
|
except ExceptionRAPDU as e:
|
|
assert e.status == StatusWord.CONDITION_NOT_SATISFIED
|
|
else:
|
|
assert False # An exception should have been raised
|
|
|
|
|
|
def common_fail(backend: BackendInterface,
|
|
tx_params: dict,
|
|
expected: StatusWord,
|
|
path: str = BIP32_PATH):
|
|
app_client = EthAppClient(backend)
|
|
|
|
try:
|
|
with app_client.sign(path, tx_params):
|
|
pass
|
|
|
|
except ExceptionRAPDU as e:
|
|
assert e.status == expected
|
|
else:
|
|
assert False # An exception should have been raised
|
|
|
|
|
|
def test_legacy(firmware: Firmware, backend: BackendInterface, navigator: Navigator):
|
|
tx_params: dict = {
|
|
"nonce": NONCE,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR,
|
|
"value": Web3.to_wei(AMOUNT, "ether"),
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params)
|
|
|
|
|
|
# Legacy Zemu Send: Transfer Ether on Ethereum app
|
|
def test_legacy_send(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, "gwei"),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 31415926913374232,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, BIP32_PATH2)
|
|
|
|
|
|
# Legacy Zemu Send: Transfer amount >= 2^87 Eth on Ethereum app should fail
|
|
def test_legacy_send_error(backend: BackendInterface):
|
|
tx_params: dict = {
|
|
"nonce": 38,
|
|
"gasPrice": 56775612312210000000001234554332,
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR3,
|
|
"value": 12345678912345678912345678000000000000000000,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common_fail(backend, tx_params, StatusWord.EXCEPTION_OVERFLOW, path=BIP32_PATH2)
|
|
|
|
|
|
# Legacy Zemu Send BSC: Transfer bsc
|
|
def test_legacy_send_bsc(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": 1,
|
|
"gasPrice": Web3.to_wei(5, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 31415926913374232,
|
|
"chainId": 56
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, BIP32_PATH2)
|
|
|
|
|
|
# Legacy Zemu ChainId: Transfer on network 112233445566 on Ethereum
|
|
def test_legacy_chainid1(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 31415926913374232,
|
|
"chainId": 112233445566
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, BIP32_PATH2)
|
|
|
|
|
|
# Legacy Zemu ChainId: Transfer on palm network on Ethereum
|
|
def test_legacy_chainid2(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 31415926913374232,
|
|
"chainId": 11297108109
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, BIP32_PATH2)
|
|
|
|
|
|
# Legacy Zemu contract data warning: Try to blind sign with setting disabled
|
|
def test_legacy_contract(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
|
|
buffer = bytes.fromhex("058000002c8000003c800000010000000000000000f849208506fc23ac008303dc3194f650c3d88d12db855b8bf7d11be6c55a4e07dcc980a4a1712d6800000000000000000000000000000000000000000000000000000000000acbc7018080")
|
|
app_client = EthAppClient(backend)
|
|
|
|
try:
|
|
with app_client.send_raw(0xe0, 0x04, 0x00, 0x00, buffer):
|
|
if firmware.device.startswith("nano"):
|
|
# No screens recorded on Stax
|
|
moves = [
|
|
NavInsID.RIGHT_CLICK,
|
|
NavInsID.BOTH_CLICK
|
|
]
|
|
navigator.navigate_and_compare(ROOT_SNAPSHOT_PATH,
|
|
test_name,
|
|
moves)
|
|
|
|
except ExceptionRAPDU as e:
|
|
assert e.status == StatusWord.INVALID_DATA
|
|
|
|
|
|
def test_1559(firmware: Firmware, backend: BackendInterface, navigator: Navigator):
|
|
tx_params: dict = {
|
|
"nonce": NONCE,
|
|
"maxFeePerGas": Web3.to_wei(145, "gwei"),
|
|
"maxPriorityFeePerGas": Web3.to_wei(1.5, "gwei"),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR,
|
|
"value": Web3.to_wei(AMOUNT, "ether"),
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params)
|
|
|
|
|
|
def test_sign_simple(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_limit_nonce(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": 2**64-1,
|
|
"gasPrice": 10,
|
|
"gas": 50000,
|
|
"to": ADDR2,
|
|
"value": 0x08762,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_nonce_display(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
# Activate nonce display
|
|
if firmware.device.startswith("nano"):
|
|
initial_instructions = [
|
|
NavInsID.LEFT_CLICK, # Application is ready
|
|
NavInsID.LEFT_CLICK, # Quit
|
|
NavInsID.BOTH_CLICK, # Blind signing
|
|
NavInsID.RIGHT_CLICK, # Debug data
|
|
NavInsID.RIGHT_CLICK, # Nonce display
|
|
NavInsID.BOTH_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
]
|
|
if firmware.device != "nanos":
|
|
initial_instructions += [NavInsID.RIGHT_CLICK] * 2
|
|
initial_instructions += [NavInsID.BOTH_CLICK] # Back
|
|
else:
|
|
initial_instructions = [
|
|
NavInsID.USE_CASE_HOME_SETTINGS, # Settings
|
|
NavInsID.USE_CASE_SETTINGS_NEXT, # Next page
|
|
NavIns(NavInsID.TOUCH, (340, 440)), # Nonce
|
|
NavInsID.USE_CASE_SETTINGS_MULTI_PAGE_EXIT,
|
|
]
|
|
|
|
# Navigate to settings menu to avoid 1st screen with random serial no
|
|
navigator.navigate(initial_instructions,
|
|
screen_change_before_first_instruction=False)
|
|
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_blind_simple(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
# Activate nonce display
|
|
if firmware.device.startswith("nano"):
|
|
initial_instructions = [
|
|
NavInsID.LEFT_CLICK, # Application is ready
|
|
NavInsID.LEFT_CLICK, # Quit
|
|
NavInsID.BOTH_CLICK, # Blind signing
|
|
NavInsID.BOTH_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
]
|
|
if firmware.device != "nanos":
|
|
initial_instructions += [NavInsID.RIGHT_CLICK] * 2
|
|
initial_instructions += [NavInsID.BOTH_CLICK] # Back
|
|
else:
|
|
initial_instructions = [
|
|
NavInsID.USE_CASE_HOME_SETTINGS, # Settings
|
|
NavInsID.USE_CASE_SETTINGS_NEXT, # Next page
|
|
NavIns(NavInsID.TOUCH, (340, 120)), # Blind signing
|
|
NavInsID.USE_CASE_SETTINGS_MULTI_PAGE_EXIT,
|
|
]
|
|
|
|
# Navigate to settings menu to avoid 1st screen with random serial no
|
|
navigator.navigate(initial_instructions,
|
|
screen_change_before_first_instruction=False)
|
|
|
|
data = "ok"
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID,
|
|
"data": data.encode('utf-8').hex()
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_blind_and_nonce_display(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
# Activate nonce display
|
|
if firmware.device.startswith("nano"):
|
|
initial_instructions = [
|
|
NavInsID.LEFT_CLICK, # Application is ready
|
|
NavInsID.LEFT_CLICK, # Quit
|
|
NavInsID.BOTH_CLICK, # Blind signing
|
|
NavInsID.BOTH_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
NavInsID.RIGHT_CLICK, # Nonce display
|
|
NavInsID.BOTH_CLICK,
|
|
NavInsID.RIGHT_CLICK,
|
|
]
|
|
if firmware.device != "nanos":
|
|
initial_instructions += [NavInsID.RIGHT_CLICK] * 2
|
|
initial_instructions += [NavInsID.BOTH_CLICK] # Back
|
|
else:
|
|
initial_instructions = [
|
|
NavInsID.USE_CASE_HOME_SETTINGS, # Settings
|
|
NavInsID.USE_CASE_SETTINGS_NEXT, # Next page
|
|
NavIns(NavInsID.TOUCH, (340, 120)), # Blind signing
|
|
NavIns(NavInsID.TOUCH, (340, 440)), # Nonce
|
|
NavInsID.USE_CASE_SETTINGS_MULTI_PAGE_EXIT,
|
|
]
|
|
|
|
# Navigate to settings menu to avoid 1st screen with random serial no
|
|
navigator.navigate(initial_instructions,
|
|
screen_change_before_first_instruction=False)
|
|
|
|
data = "That's a little message :)"
|
|
tx_params: dict = {
|
|
"nonce": 1844674,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID,
|
|
"data": data.encode('utf-8').hex()
|
|
}
|
|
common(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_reject(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str):
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
common_reject(firmware, backend, navigator, tx_params, test_name, "m/44'/60'/1'/0/0")
|
|
|
|
|
|
def test_sign_error_transaction_type(backend: BackendInterface):
|
|
tx_params: dict = {
|
|
"type": 0,
|
|
"nonce": 0,
|
|
"gasPrice": 10,
|
|
"gas": 50000,
|
|
"to": ADDR2,
|
|
"value": 0x19,
|
|
"chainId": CHAIN_ID
|
|
}
|
|
|
|
app_client = EthAppClient(backend)
|
|
try:
|
|
with app_client.sign(BIP32_PATH2, tx_params):
|
|
pass
|
|
|
|
except TypeError:
|
|
pass
|
|
else:
|
|
assert False # An exception should have been raised
|
|
|
|
|
|
def test_sign_blind_error_disabled(backend: BackendInterface):
|
|
data = "ok"
|
|
tx_params: dict = {
|
|
"nonce": NONCE2,
|
|
"gasPrice": Web3.to_wei(GAS_PRICE, 'gwei'),
|
|
"gas": GAS_LIMIT,
|
|
"to": ADDR2,
|
|
"value": 0x6f9c9e7bf61818,
|
|
"chainId": CHAIN_ID,
|
|
"data": data.encode('utf-8').hex()
|
|
}
|
|
|
|
common_fail(backend, tx_params, StatusWord.INVALID_DATA, BIP32_PATH2)
|