Updated Ragger app client for trusted name & sign APDUs
Also added two new optional arguments to pytest : --gold for screenshot generation --chainid to specify an alternative app chain ID
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
venv/
|
||||
__pycache__/
|
||||
snapshots-tmp/
|
||||
elfs/
|
||||
|
||||
@@ -2,14 +2,18 @@ from enum import IntEnum, auto
|
||||
from typing import Optional
|
||||
from ragger.backend import BackendInterface
|
||||
from ragger.utils import RAPDU
|
||||
from ragger.navigator import NavInsID, NavIns, NanoNavigator
|
||||
from app.command_builder import EthereumCmdBuilder
|
||||
from app.setting import SettingType, SettingImpl
|
||||
from app.eip712 import EIP712FieldType
|
||||
from app.response_parser import EthereumRespParser
|
||||
import signal
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT_SCREENSHOT_PATH = Path(__file__).parent.parent
|
||||
|
||||
class EthereumClient:
|
||||
_settings: dict[SettingType, SettingImpl] = {
|
||||
SettingType.BLIND_SIGNING: SettingImpl(
|
||||
@@ -21,6 +25,9 @@ class EthereumClient:
|
||||
SettingType.NONCE: SettingImpl(
|
||||
[ "nanos", "nanox", "nanosp" ]
|
||||
),
|
||||
SettingType.VERBOSE_ENS: SettingImpl(
|
||||
[ "nanox", "nanosp" ]
|
||||
),
|
||||
SettingType.VERBOSE_EIP712: SettingImpl(
|
||||
[ "nanox", "nanosp" ]
|
||||
)
|
||||
@@ -28,10 +35,12 @@ class EthereumClient:
|
||||
_click_delay = 1/4
|
||||
_eip712_filtering = False
|
||||
|
||||
def __init__(self, client: BackendInterface):
|
||||
def __init__(self, client: BackendInterface, chain_id: int, golden_run: bool):
|
||||
self._client = client
|
||||
self._chain_id = chain_id
|
||||
self._cmd_builder = EthereumCmdBuilder()
|
||||
self._resp_parser = EthereumRespParser()
|
||||
self._nav = NanoNavigator(client, client.firmware, golden_run)
|
||||
signal.signal(signal.SIGALRM, self._click_signal_timeout)
|
||||
for setting in self._settings.values():
|
||||
setting.value = False
|
||||
@@ -156,3 +165,48 @@ class EthereumClient:
|
||||
with self._send(self._cmd_builder.eip712_filtering_show_field(name, sig)):
|
||||
pass
|
||||
assert self._recv().status == 0x9000
|
||||
|
||||
def send_fund(self,
|
||||
bip32_path: list[Optional[int]],
|
||||
nonce: int,
|
||||
gas_price: int,
|
||||
gas_limit: int,
|
||||
to: bytes,
|
||||
amount: float,
|
||||
chain_id: int,
|
||||
screenshot_collection: str = None):
|
||||
for chunk in self._cmd_builder.send_fund(bip32_path,
|
||||
nonce,
|
||||
gas_price,
|
||||
gas_limit,
|
||||
to,
|
||||
amount,
|
||||
chain_id):
|
||||
with self._send(chunk):
|
||||
nav_ins = NavIns(NavInsID.RIGHT_CLICK)
|
||||
final_ins = [ NavIns(NavInsID.BOTH_CLICK) ]
|
||||
target_text = "and send"
|
||||
if screenshot_collection:
|
||||
self._nav.navigate_until_text_and_compare(nav_ins,
|
||||
final_ins,
|
||||
target_text,
|
||||
ROOT_SCREENSHOT_PATH,
|
||||
screenshot_collection)
|
||||
else:
|
||||
self._nav.navigate_until_text(nav_ins,
|
||||
final_ins,
|
||||
target_text)
|
||||
assert self._recv().status == 0x9000
|
||||
|
||||
def get_challenge(self) -> int:
|
||||
with self._send(self._cmd_builder.get_challenge()):
|
||||
pass
|
||||
resp = self._recv()
|
||||
assert resp.status == 0x9000
|
||||
return self._resp_parser.challenge(resp.data)
|
||||
|
||||
def provide_trusted_name(self, name: str, key_id: int, algo_id: int, sig: bytes):
|
||||
for chunk in self._cmd_builder.provide_trusted_name(name, key_id, algo_id, sig):
|
||||
with self._send(chunk):
|
||||
pass
|
||||
assert self._recv().status == 0x9000
|
||||
|
||||
@@ -5,14 +5,19 @@ import struct
|
||||
import rlp
|
||||
|
||||
class InsType(IntEnum):
|
||||
SIGN = 0x04
|
||||
EIP712_SEND_STRUCT_DEF = 0x1a
|
||||
EIP712_SEND_STRUCT_IMPL = 0x1c
|
||||
EIP712_SEND_FILTERING = 0x1e
|
||||
EIP712_SIGN = 0x0c
|
||||
GET_CHALLENGE = 0x20
|
||||
PROVIDE_TRUSTED_NAME = 0x22
|
||||
|
||||
class P1Type(IntEnum):
|
||||
COMPLETE_SEND = 0x00
|
||||
PARTIAL_SEND = 0x01
|
||||
SIGN_FIRST_CHUNK = 0x00
|
||||
SIGN_SUBSQT_CHUNK = 0x80
|
||||
|
||||
class P2Type(IntEnum):
|
||||
STRUCT_NAME = 0x00
|
||||
@@ -31,7 +36,7 @@ class EthereumCmdBuilder:
|
||||
ins: InsType,
|
||||
p1: int,
|
||||
p2: int,
|
||||
cdata: bytearray = bytearray()) -> bytes:
|
||||
cdata: bytearray = bytes()) -> bytes:
|
||||
|
||||
header = bytearray()
|
||||
header.append(self._CLA)
|
||||
@@ -171,3 +176,53 @@ class EthereumCmdBuilder:
|
||||
P1Type.COMPLETE_SEND,
|
||||
P2Type.FILTERING_FIELD_NAME,
|
||||
self._eip712_filtering_send_name(name, sig))
|
||||
|
||||
def sign(self, bip32_path: list[Optional[int]], data: list) -> Iterator[bytes]:
|
||||
payload = self._format_bip32(bip32_path, bytearray())
|
||||
payload += rlp.encode(data)
|
||||
p1 = P1Type.SIGN_FIRST_CHUNK
|
||||
while len(payload) > 0:
|
||||
yield self._serialize(InsType.SIGN,
|
||||
p1,
|
||||
0x00,
|
||||
payload[:0xff])
|
||||
payload = payload[0xff:]
|
||||
p1 = P1Type.SIGN_SUBSQT_CHUNK
|
||||
|
||||
def send_fund(self,
|
||||
bip32_path: list[Optional[int]],
|
||||
nonce: int,
|
||||
gas_price: int,
|
||||
gas_limit: int,
|
||||
to: bytes,
|
||||
amount: float,
|
||||
chain_id: int) -> Iterator[bytes]:
|
||||
data = list()
|
||||
data.append(nonce)
|
||||
data.append(gas_price)
|
||||
data.append(gas_limit)
|
||||
data.append(to)
|
||||
data.append(int(amount * 1000000000000000000))
|
||||
data.append(bytes())
|
||||
data.append(chain_id)
|
||||
data.append(bytes())
|
||||
data.append(bytes())
|
||||
return self.sign(bip32_path, data)
|
||||
|
||||
def get_challenge(self) -> bytes:
|
||||
return self._serialize(InsType.GET_CHALLENGE, 0x00, 0x00)
|
||||
|
||||
def provide_trusted_name(self, name: str, key_id: int, algo_id: int, sig: bytes) -> bytes:
|
||||
payload = bytearray()
|
||||
payload.append(len(name))
|
||||
payload += name.encode()
|
||||
payload.append(key_id)
|
||||
payload.append(algo_id)
|
||||
payload.append(len(sig))
|
||||
payload += sig
|
||||
while len(payload) > 0:
|
||||
yield self._serialize(InsType.PROVIDE_TRUSTED_NAME,
|
||||
0x00,
|
||||
0x00,
|
||||
payload[:0xff])
|
||||
payload = payload[0xff:]
|
||||
|
||||
@@ -12,3 +12,7 @@ class EthereumRespParser:
|
||||
data = data[32:]
|
||||
|
||||
return v, r, s
|
||||
|
||||
def challenge(self, data: bytes) -> int:
|
||||
assert len(data) == 4
|
||||
return int.from_bytes(data, "big")
|
||||
|
||||
@@ -5,6 +5,7 @@ class SettingType(IntEnum):
|
||||
BLIND_SIGNING = 0,
|
||||
DEBUG_DATA = auto()
|
||||
NONCE = auto()
|
||||
VERBOSE_ENS = auto()
|
||||
VERBOSE_EIP712 = auto()
|
||||
|
||||
class SettingImpl:
|
||||
|
||||
@@ -15,8 +15,9 @@ def pytest_addoption(parser):
|
||||
parser.addoption("--path", action="store", default="./elfs")
|
||||
parser.addoption("--model", action="store", required=True)
|
||||
parser.addoption("--display", action="store_true", default=False)
|
||||
parser.addoption("--chainid", type=int, action="store", default=1)
|
||||
parser.addoption("--gold", action="store_true", default=False)
|
||||
|
||||
# accessing the value of the "--backend" option as a fixture
|
||||
@pytest.fixture
|
||||
def arg_backend(pytestconfig) -> str:
|
||||
return pytestconfig.getoption("backend")
|
||||
@@ -33,6 +34,14 @@ def arg_model(pytestconfig) -> str:
|
||||
def arg_display(pytestconfig) -> bool:
|
||||
return pytestconfig.getoption("display")
|
||||
|
||||
@pytest.fixture
|
||||
def arg_chainid(pytestconfig) -> int:
|
||||
return pytestconfig.getoption("chainid")
|
||||
|
||||
@pytest.fixture
|
||||
def arg_gold(pytestconfig) -> bool:
|
||||
return pytestconfig.getoption("gold")
|
||||
|
||||
# Providing the firmware as a fixture
|
||||
@pytest.fixture
|
||||
def firmware(arg_model: str) -> Firmware:
|
||||
@@ -41,6 +50,7 @@ def firmware(arg_model: str) -> Firmware:
|
||||
return fw
|
||||
raise ValueError("Unknown device model \"%s\"" % (arg_model))
|
||||
|
||||
|
||||
def get_speculos_args(arg_path: str, display: bool, fw: Firmware):
|
||||
extra_args = list()
|
||||
|
||||
@@ -74,5 +84,7 @@ def backend_client(arg_backend: str, arg_path: str, arg_display: bool, firmware:
|
||||
|
||||
# This final fixture will return the properly configured app client, to be used in tests
|
||||
@pytest.fixture
|
||||
def app_client(backend_client: BackendInterface) -> EthereumClient:
|
||||
return EthereumClient(backend_client)
|
||||
def app_client(backend_client: BackendInterface,
|
||||
arg_chainid: int,
|
||||
arg_gold: bool) -> EthereumClient:
|
||||
return EthereumClient(backend_client, arg_chainid, arg_gold)
|
||||
|
||||
Reference in New Issue
Block a user