208 lines
7.4 KiB
Python
208 lines
7.4 KiB
Python
from pathlib import Path
|
|
import json
|
|
from typing import Optional
|
|
import pytest
|
|
from web3 import Web3
|
|
|
|
from ragger.backend import BackendInterface
|
|
from ragger.firmware import Firmware
|
|
from ragger.navigator import Navigator, NavInsID
|
|
from ragger.error import ExceptionRAPDU
|
|
|
|
from constants import ABIS_FOLDER
|
|
|
|
from client.client import EthAppClient, StatusWord
|
|
from client.settings import SettingID, settings_toggle
|
|
import client.response_parser as ResponseParser
|
|
from client.utils import recover_transaction
|
|
|
|
|
|
BIP32_PATH = "m/44'/60'/0'/0/0"
|
|
DEVICE_ADDR: Optional[bytes] = None
|
|
|
|
# TODO: do one test with nonce display
|
|
|
|
|
|
@pytest.fixture(name="sign", params=[True, False])
|
|
def sign_fixture(request) -> bool:
|
|
return request.param
|
|
|
|
|
|
def common_tx_params() -> dict:
|
|
with open(f"{ABIS_FOLDER}/erc20.json", encoding="utf-8") as file:
|
|
contract = Web3().eth.contract(
|
|
abi=json.load(file),
|
|
address=None
|
|
)
|
|
data = contract.encode_abi("approve", [
|
|
# Uniswap Protocol: Permit2
|
|
bytes.fromhex("000000000022d473030f116ddee9f6b43ac78ba3"),
|
|
Web3.to_wei("2", "ether")
|
|
])
|
|
return {
|
|
"nonce": 235,
|
|
"maxFeePerGas": Web3.to_wei(100, "gwei"),
|
|
"maxPriorityFeePerGas": Web3.to_wei(10, "gwei"),
|
|
"gas": 44001,
|
|
# Maker: Dai Stablecoin
|
|
"to": bytes.fromhex("6b175474e89094c44da98b954eedeac495271d0f"),
|
|
"data": data,
|
|
"chainId": 1
|
|
}
|
|
|
|
|
|
# Token approval, would require loading the "internal plugin" &
|
|
# providing the token metadata from the CAL
|
|
def test_blind_sign(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
default_screenshot_path: Path,
|
|
test_name: str,
|
|
sign: bool):
|
|
global DEVICE_ADDR
|
|
app_client = EthAppClient(backend)
|
|
|
|
if DEVICE_ADDR is None:
|
|
with app_client.get_public_addr(bip32_path=BIP32_PATH, display=False):
|
|
pass
|
|
_, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
|
|
|
|
tx_params = common_tx_params()
|
|
try:
|
|
with app_client.sign(BIP32_PATH, tx_params):
|
|
if sign:
|
|
test_name += "_signed"
|
|
else:
|
|
test_name += "_rejected"
|
|
|
|
moves = []
|
|
if firmware.is_nano:
|
|
if firmware == Firmware.NANOS:
|
|
moves += [NavInsID.RIGHT_CLICK] * 2
|
|
else:
|
|
moves += [NavInsID.RIGHT_CLICK] * 4
|
|
|
|
if not sign:
|
|
moves += [NavInsID.RIGHT_CLICK]
|
|
|
|
moves += [NavInsID.BOTH_CLICK]
|
|
|
|
if sign:
|
|
if firmware == Firmware.NANOS:
|
|
moves += [NavInsID.RIGHT_CLICK] * 10
|
|
else:
|
|
moves += [NavInsID.RIGHT_CLICK] * 6
|
|
moves += [NavInsID.BOTH_CLICK]
|
|
else:
|
|
if firmware == Firmware.STAX:
|
|
tap_number = 2
|
|
else:
|
|
tap_number = 3
|
|
if sign:
|
|
moves += [NavInsID.USE_CASE_CHOICE_REJECT]
|
|
moves += [NavInsID.USE_CASE_CHOICE_CONFIRM]
|
|
moves += [NavInsID.SWIPE_CENTER_TO_LEFT] * tap_number
|
|
moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
|
|
else:
|
|
moves += [NavInsID.USE_CASE_CHOICE_CONFIRM]
|
|
navigator.navigate_and_compare(default_screenshot_path,
|
|
test_name,
|
|
moves)
|
|
except ExceptionRAPDU as e:
|
|
assert e.status == StatusWord.INVALID_DATA
|
|
else:
|
|
assert sign is True
|
|
# verify signature
|
|
vrs = ResponseParser.signature(app_client.response().data)
|
|
addr = recover_transaction(tx_params, vrs)
|
|
assert addr == DEVICE_ADDR
|
|
|
|
|
|
def test_blind_sign_reject_in_risk_review(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
default_screenshot_path: Path,
|
|
test_name: str):
|
|
app_client = EthAppClient(backend)
|
|
|
|
if firmware.is_nano:
|
|
pytest.skip("Not supported on non-NBGL apps")
|
|
|
|
try:
|
|
with app_client.sign(BIP32_PATH, common_tx_params()):
|
|
moves = [NavInsID.USE_CASE_CHOICE_REJECT] * 2
|
|
navigator.navigate_and_compare(default_screenshot_path,
|
|
test_name,
|
|
moves)
|
|
except ExceptionRAPDU as e:
|
|
assert e.status == StatusWord.INVALID_DATA
|
|
else:
|
|
assert False # Should have thrown
|
|
|
|
|
|
# Token approval, would require loading the "internal plugin" &
|
|
# providing the token metadata from the CAL
|
|
def test_sign_parameter_selector(firmware: Firmware,
|
|
backend: BackendInterface,
|
|
navigator: Navigator,
|
|
test_name: str,
|
|
default_screenshot_path: Path):
|
|
global DEVICE_ADDR
|
|
app_client = EthAppClient(backend)
|
|
|
|
if DEVICE_ADDR is None:
|
|
with app_client.get_public_addr(bip32_path=BIP32_PATH, display=False):
|
|
pass
|
|
_, DEVICE_ADDR, _ = ResponseParser.pk_addr(app_client.response().data)
|
|
|
|
settings_toggle(firmware, navigator, [SettingID.DEBUG_DATA])
|
|
|
|
tx_params = common_tx_params()
|
|
data_len = len(bytes.fromhex(tx_params["data"][2:]))
|
|
# selector
|
|
flows = 1
|
|
data_len -= 4
|
|
# parameters
|
|
flows += data_len // 32
|
|
with app_client.sign(BIP32_PATH, tx_params):
|
|
moves = []
|
|
if firmware.is_nano:
|
|
if firmware == Firmware.NANOS:
|
|
moves += [NavInsID.RIGHT_CLICK] * 2 + [NavInsID.BOTH_CLICK]
|
|
# Parameters on Nano S are split on multiple pages, hardcoded because the two parameters don't use the
|
|
# same amount of pages because of non-monospace fonts
|
|
moves += [NavInsID.RIGHT_CLICK] * 4 + [NavInsID.BOTH_CLICK]
|
|
moves += [NavInsID.RIGHT_CLICK] * 3 + [NavInsID.BOTH_CLICK]
|
|
else:
|
|
moves += ([NavInsID.RIGHT_CLICK] * 2 + [NavInsID.BOTH_CLICK]) * flows
|
|
|
|
if firmware == Firmware.NANOS:
|
|
moves += [NavInsID.RIGHT_CLICK] * 2
|
|
else:
|
|
moves += [NavInsID.RIGHT_CLICK] * 4
|
|
moves += [NavInsID.BOTH_CLICK]
|
|
|
|
if firmware == Firmware.NANOS:
|
|
moves += [NavInsID.RIGHT_CLICK] * 9
|
|
else:
|
|
moves += [NavInsID.RIGHT_CLICK] * 5
|
|
moves += [NavInsID.BOTH_CLICK]
|
|
else:
|
|
if firmware == Firmware.STAX:
|
|
tap_number = 2
|
|
else:
|
|
tap_number = 3
|
|
moves += ([NavInsID.SWIPE_CENTER_TO_LEFT] * 2 + [NavInsID.USE_CASE_REVIEW_CONFIRM]) * flows
|
|
moves += [NavInsID.USE_CASE_CHOICE_REJECT]
|
|
moves += [NavInsID.USE_CASE_CHOICE_CONFIRM]
|
|
moves += [NavInsID.SWIPE_CENTER_TO_LEFT] * tap_number
|
|
moves += [NavInsID.USE_CASE_REVIEW_CONFIRM]
|
|
navigator.navigate_and_compare(default_screenshot_path,
|
|
test_name,
|
|
moves)
|
|
|
|
# verify signature
|
|
vrs = ResponseParser.signature(app_client.response().data)
|
|
addr = recover_transaction(tx_params, vrs)
|
|
assert addr == DEVICE_ADDR
|