Ragger tests now have EIP721 filtering

This commit is contained in:
Alexandre Paillier
2022-08-04 18:25:02 +02:00
parent 0d38bea388
commit e8f58bdce7
4 changed files with 116 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os
import json import json
import sys import sys
import re import re
@@ -9,6 +10,7 @@ from ecdsa import SigningKey
from ecdsa.util import sigencode_der from ecdsa.util import sigencode_der
import pdb import pdb
from ethereum_client import EthereumClient, EIP712FieldType from ethereum_client import EthereumClient, EIP712FieldType
import base64
# global variables # global variables
app_client: EthereumClient = None app_client: EthereumClient = None
@@ -198,7 +200,7 @@ def send_struct_impl_field(value, field):
data = encoding_functions[field["enum"]](value, field["typesize"]) data = encoding_functions[field["enum"]](value, field["typesize"])
if False:#args.filtering: if filtering_paths:
path = ".".join(current_path) path = ".".join(current_path)
if path in filtering_paths.keys(): if path in filtering_paths.keys():
send_filtering_field_name(filtering_paths[path]) send_filtering_field_name(filtering_paths[path])
@@ -257,10 +259,10 @@ def send_sign():
#send_apdu(INS_SIGN, 0x00, P2_VERS_NEW, path_len + bip32path) #send_apdu(INS_SIGN, 0x00, P2_VERS_NEW, path_len + bip32path)
print("send_apdu(INS_SIGN, 0x00, P2_VERS_NEW, path_len + bip32path)") print("send_apdu(INS_SIGN, 0x00, P2_VERS_NEW, path_len + bip32path)")
def send_filtering_activate(): #def send_filtering_activate():
#send_apdu(INS_FILTERING, P1_ACTIVATE, 0x00, bytearray()) # #send_apdu(INS_FILTERING, P1_ACTIVATE, 0x00, bytearray())
print("send_apdu(INS_FILTERING, P1_ACTIVATE, 0x00, bytearray())") # print("send_apdu(INS_FILTERING, P1_ACTIVATE, 0x00, bytearray())")
#
def send_filtering_info(p1, display_name, sig): def send_filtering_info(p1, display_name, sig):
payload = bytearray() payload = bytearray()
payload.append(len(display_name)) payload.append(len(display_name))
@@ -272,7 +274,7 @@ def send_filtering_info(p1, display_name, sig):
print("send_apdu(INS_FILTERING, p1, 0x00, payload)") print("send_apdu(INS_FILTERING, p1, 0x00, payload)")
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures # ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
def send_filtering_contract_name(display_name): def send_filtering_contract_name(display_name: str):
global sig_ctx global sig_ctx
msg = bytearray() msg = bytearray()
@@ -283,7 +285,7 @@ def send_filtering_contract_name(display_name):
msg.append(ord(char)) msg.append(ord(char))
sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der)
send_filtering_info(P1_CONTRACT_NAME, display_name, sig) app_client.eip712_filtering_send_contract_name(display_name, sig)
# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures # ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures
def send_filtering_field_name(display_name): def send_filtering_field_name(display_name):
@@ -300,11 +302,11 @@ def send_filtering_field_name(display_name):
for char in display_name: for char in display_name:
msg.append(ord(char)) msg.append(ord(char))
sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der)
send_filtering_info(P1_FIELD_NAME, display_name, sig) app_client.eip712_filtering_send_field_name(display_name, sig)
def read_filtering_file(domain, message): def read_filtering_file(domain, message, filtering_file_path):
data_json = None data_json = None
with open("%s-filter.json" % (args.JSON_FILE)) as data: with open(filtering_file_path) as data:
data_json = json.load(data) data_json = json.load(data)
return data_json return data_json
@@ -319,8 +321,11 @@ def prepare_filtering(filtr_data, message):
def init_signature_context(types, domain): def init_signature_context(types, domain):
global sig_ctx global sig_ctx
with open(args.keypath, "r") as priv_file: env_key = os.getenv("CAL_SIGNATURE_TEST_KEY")
sig_ctx["key"] = SigningKey.from_pem(priv_file.read(), hashlib.sha256) if env_key:
key = base64.b64decode(env_key).decode() # base 64 string -> decode bytes -> string
print(key)
sig_ctx["key"] = SigningKey.from_pem(key, hashlib.sha256)
caddr = domain["verifyingContract"] caddr = domain["verifyingContract"]
if caddr.startswith("0x"): if caddr.startswith("0x"):
caddr = caddr[2:] caddr = caddr[2:]
@@ -337,7 +342,7 @@ def init_signature_context(types, domain):
return True return True
return False return False
def process_file(aclient: EthereumClient, input_file_path: str, filtering = False) -> bool: def process_file(aclient: EthereumClient, input_file_path: str, filtering_file_path = None) -> bool:
global sig_ctx global sig_ctx
global app_client global app_client
@@ -350,10 +355,10 @@ def process_file(aclient: EthereumClient, input_file_path: str, filtering = Fals
domain = data_json["domain"] domain = data_json["domain"]
message = data_json["message"] message = data_json["message"]
if filtering: if filtering_file_path:
if not init_signature_context(types, domain): if not init_signature_context(types, domain):
return False return False
filtr = read_filtering_file(domain, message) filtr = read_filtering_file(domain, message, filtering_file_path)
# send types definition # send types definition
for key in types.keys(): for key in types.keys():
@@ -362,8 +367,8 @@ def process_file(aclient: EthereumClient, input_file_path: str, filtering = Fals
(f["type"], f["enum"], f["typesize"], f["array_lvls"]) = \ (f["type"], f["enum"], f["typesize"], f["array_lvls"]) = \
send_struct_def_field(f["type"], f["name"]) send_struct_def_field(f["type"], f["name"])
if filtering: if filtering_file_path:
send_filtering_activate() app_client.eip712_filtering_activate()
prepare_filtering(filtr, message) prepare_filtering(filtr, message)
# send domain implementation # send domain implementation
@@ -371,17 +376,15 @@ def process_file(aclient: EthereumClient, input_file_path: str, filtering = Fals
if not send_struct_impl(types, domain, domain_typename): if not send_struct_impl(types, domain, domain_typename):
return False return False
if filtering: if filtering_file_path:
if filtr and "name" in filtr: if filtr and "name" in filtr:
send_filtering_contract_name(filtr["name"]) send_filtering_contract_name(filtr["name"])
else: else:
send_filtering_contract_name(sig_ctx["domain"]["name"]) send_filtering_contract_name(domain["name"])
# send message implementation # send message implementation
app_client.eip712_send_struct_impl_root_struct(message_typename) app_client.eip712_send_struct_impl_root_struct(message_typename)
if not send_struct_impl(types, message, message_typename): if not send_struct_impl(types, message, message_typename):
return False return False
# sign
#send_sign()
return True return True

View File

@@ -0,0 +1,9 @@
{
"name": "Depthy Test",
"fields": {
"contents": "Message",
"from.name": "Sender",
"to.members.[].name": "Recipient",
"attach.list.[].name": "Attachment"
}
}

View File

@@ -4,6 +4,7 @@ from typing import Iterator, Dict, List
from ragger.backend import BackendInterface from ragger.backend import BackendInterface
from ragger.utils import RAPDU from ragger.utils import RAPDU
import signal import signal
import pdb
class InsType(IntEnum): class InsType(IntEnum):
EIP712_SEND_STRUCT_DEF = 0x1a, EIP712_SEND_STRUCT_DEF = 0x1a,
@@ -13,14 +14,17 @@ class InsType(IntEnum):
class P1Type(IntEnum): class P1Type(IntEnum):
COMPLETE_SEND = 0x00, COMPLETE_SEND = 0x00,
PARTIAL_SEND = 0x01 PARTIAL_SEND = 0x01,
FILTERING_ACTIVATE = 0x00,
FILTERING_CONTRACT_NAME = 0x0f,
FILTERING_FIELD_NAME = 0xff
class P2Type(IntEnum): class P2Type(IntEnum):
STRUCT_NAME = 0x00, STRUCT_NAME = 0x00,
STRUCT_FIELD = 0xff, STRUCT_FIELD = 0xff,
ARRAY = 0x0f, ARRAY = 0x0f,
LEGACY_IMPLEM = 0x00 LEGACY_IMPLEM = 0x00
NEW_IMPLEM = 0x01, NEW_IMPLEM = 0x01
class EIP712FieldType(IntEnum): class EIP712FieldType(IntEnum):
CUSTOM = 0, CUSTOM = 0,
@@ -63,14 +67,17 @@ class EthereumClientCmdBuilder:
header.append(len(cdata)) header.append(len(cdata))
return header + cdata return header + cdata
def eip712_send_struct_def_struct_name(self, name: str) -> bytes: def _string_to_bytes(self, string: str) -> bytes:
data = bytearray() data = bytearray()
for char in name: for char in string:
data.append(ord(char)) data.append(ord(char))
return data
def eip712_send_struct_def_struct_name(self, name: str) -> bytes:
return self._serialize(InsType.EIP712_SEND_STRUCT_DEF, return self._serialize(InsType.EIP712_SEND_STRUCT_DEF,
P1Type.COMPLETE_SEND, P1Type.COMPLETE_SEND,
P2Type.STRUCT_NAME, P2Type.STRUCT_NAME,
data) self._string_to_bytes(name))
def eip712_send_struct_def_struct_field(self, def eip712_send_struct_def_struct_field(self,
field_type: EIP712FieldType, field_type: EIP712FieldType,
@@ -86,8 +93,7 @@ class EthereumClientCmdBuilder:
data.append(typedesc) data.append(typedesc)
if field_type == EIP712FieldType.CUSTOM: if field_type == EIP712FieldType.CUSTOM:
data.append(len(type_name)) data.append(len(type_name))
for char in type_name: data += self._string_to_bytes(type_name)
data.append(ord(char))
if type_size != None: if type_size != None:
data.append(type_size) data.append(type_size)
if len(array_levels) > 0: if len(array_levels) > 0:
@@ -97,21 +103,17 @@ class EthereumClientCmdBuilder:
if level != None: if level != None:
data.append(level) data.append(level)
data.append(len(key_name)) data.append(len(key_name))
for char in key_name: data += self._string_to_bytes(key_name)
data.append(ord(char))
return self._serialize(InsType.EIP712_SEND_STRUCT_DEF, return self._serialize(InsType.EIP712_SEND_STRUCT_DEF,
P1Type.COMPLETE_SEND, P1Type.COMPLETE_SEND,
P2Type.STRUCT_FIELD, P2Type.STRUCT_FIELD,
data) data)
def eip712_send_struct_impl_root_struct(self, name: str) -> bytes: def eip712_send_struct_impl_root_struct(self, name: str) -> bytes:
data = bytearray()
for char in name:
data.append(ord(char))
return self._serialize(InsType.EIP712_SEND_STRUCT_IMPL, return self._serialize(InsType.EIP712_SEND_STRUCT_IMPL,
P1Type.COMPLETE_SEND, P1Type.COMPLETE_SEND,
P2Type.STRUCT_NAME, P2Type.STRUCT_NAME,
data) self._string_to_bytes(name))
def eip712_send_struct_impl_array(self, size: int) -> bytes: def eip712_send_struct_impl_array(self, size: int) -> bytes:
data = bytearray() data = bytearray()
@@ -163,6 +165,32 @@ class EthereumClientCmdBuilder:
P2Type.LEGACY_IMPLEM, P2Type.LEGACY_IMPLEM,
data) data)
def eip712_filtering_activate(self):
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.FILTERING_ACTIVATE,
0x00,
bytearray())
def _eip712_filtering_send_name(self, name: str, sig: bytes) -> bytes:
data = bytearray()
data.append(len(name))
data += self._string_to_bytes(name)
data.append(len(sig))
data += sig
return data
def eip712_filtering_send_contract_name(self, name: str, sig: bytes) -> bytes:
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.FILTERING_CONTRACT_NAME,
0x00,
self._eip712_filtering_send_name(name, sig))
def eip712_filtering_send_field_name(self, name: str, sig: bytes) -> bytes:
return self._serialize(InsType.EIP712_SEND_FILTERING,
P1Type.FILTERING_FIELD_NAME,
0x00,
self._eip712_filtering_send_name(name, sig))
class EthereumResponseParser: class EthereumResponseParser:
def sign(self, data: bytes): def sign(self, data: bytes):
@@ -195,6 +223,7 @@ class EthereumClient:
) )
} }
_click_delay = 1/4 _click_delay = 1/4
_eip712_filtering = False
def __init__(self, client: BackendInterface, debug: bool = False): def __init__(self, client: BackendInterface, debug: bool = False):
self._client = client self._client = client
@@ -262,7 +291,8 @@ class EthereumClient:
def eip712_sign_new(self, bip32): def eip712_sign_new(self, bip32):
with self._send(self._cmd_builder.eip712_sign_new(bip32)): with self._send(self._cmd_builder.eip712_sign_new(bip32)):
if not self._settings[SettingType.VERBOSE_EIP712].value: # need to skip the message hash if not self._settings[SettingType.VERBOSE_EIP712].value and \
not self._eip712_filtering: # need to skip the message hash
self._client.right_click() self._client.right_click()
self._client.right_click() self._client.right_click()
self._client.both_click() # approve signature self._client.both_click() # approve signature
@@ -306,3 +336,21 @@ class EthereumClient:
self._settings[enum].value = new_values[enum] self._settings[enum].value = new_values[enum]
self._client.right_click() self._client.right_click()
self._client.both_click() self._client.both_click()
def eip712_filtering_activate(self):
with self._send(self._cmd_builder.eip712_filtering_activate()):
pass
self._eip712_filtering = True
assert self._recv().status == 0x9000
def eip712_filtering_send_contract_name(self, name: str, sig: bytes):
#pdb.set_trace()
with self._send(self._cmd_builder.eip712_filtering_send_contract_name(name, sig)):
self._enable_click_until_response()
self._disable_click_until_response()
assert self._recv().status == 0x9000
def eip712_filtering_send_field_name(self, name: str, sig: bytes):
with self._send(self._cmd_builder.eip712_filtering_send_field_name(name, sig)):
pass
assert self._recv().status == 0x9000

View File

@@ -31,6 +31,10 @@ def input_file(request) -> str:
def verbose(request) -> bool: def verbose(request) -> bool:
return request.param return request.param
@pytest.fixture(params=[False, True])
def filtering(request) -> bool:
return request.param
def test_eip712_legacy(app_client: EthereumClient): def test_eip712_legacy(app_client: EthereumClient):
v, r, s = app_client.eip712_sign_legacy( v, r, s = app_client.eip712_sign_legacy(
@@ -49,6 +53,10 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool,
if app_client._client.firmware.device != "nanos": if app_client._client.firmware.device != "nanos":
test_path = "%s/%s" % (input_file.parent, "-".join(input_file.stem.split("-")[:-1])) test_path = "%s/%s" % (input_file.parent, "-".join(input_file.stem.split("-")[:-1]))
conf_file = "%s.ini" % (test_path) conf_file = "%s.ini" % (test_path)
filter_file = None
if filtering:
filter_file = "%s-filter.json" % (test_path)
config = ConfigParser() config = ConfigParser()
config.read(conf_file) config.read(conf_file)
@@ -59,16 +67,19 @@ def test_eip712_new(app_client: EthereumClient, input_file: Path, verbose: bool,
assert "r" in config["signature"] assert "r" in config["signature"]
assert "s" in config["signature"] assert "s" in config["signature"]
if verbose: if not filtering or Path(filter_file).is_file():
app_client.settings_set({ if verbose:
SettingType.VERBOSE_EIP712: True app_client.settings_set({
}) SettingType.VERBOSE_EIP712: True
})
InputData.process_file(app_client, input_file, False) assert InputData.process_file(app_client, input_file, filter_file) == True
v, r, s = app_client.eip712_sign_new(bip32) v, r, s = app_client.eip712_sign_new(bip32)
assert v == bytes.fromhex(config["signature"]["v"]) assert v == bytes.fromhex(config["signature"]["v"])
assert r == bytes.fromhex(config["signature"]["r"]) assert r == bytes.fromhex(config["signature"]["r"])
assert s == bytes.fromhex(config["signature"]["s"]) assert s == bytes.fromhex(config["signature"]["s"])
else:
print("No filter file found, skipping...")
else: else:
print("Not supported by LNS") print("Not supported by LNS, skipping...")