diff --git a/tests/ragger/eip712/InputData.py b/tests/ragger/eip712/InputData.py new file mode 100644 index 0000000..6971293 --- /dev/null +++ b/tests/ragger/eip712/InputData.py @@ -0,0 +1,387 @@ +#!/usr/bin/env python3 + +import json +import sys +import re +from enum import IntEnum, auto +import hashlib +from ecdsa import SigningKey +from ecdsa.util import sigencode_der +import pdb +from ethereum_client import EthereumClient, EIP712FieldType + +# global variables +app_client: EthereumClient = None +parser = None +trans = None +filtering_paths = None +current_path = list() +sig_ctx = {} + + +class ArrayType(IntEnum): + dynamic = 0 + fixed_size = auto() + + + +# From a string typename, extract the type and all the array depth +# Input = "uint8[2][][4]" | "bool" +# Output = ('uint8', [2, None, 4]) | ('bool', []) +def get_array_levels(typename): + array_lvls = list() + regex = re.compile("(.*)\[([0-9]*)\]$") + + while True: + result = regex.search(typename) + if not result: + break + typename = result.group(1) + + level_size = result.group(2) + if len(level_size) == 0: + level_size = None + else: + level_size = int(level_size) + array_lvls.insert(0, level_size) + return (typename, array_lvls) + + +# From a string typename, extract the type and its size +# Input = "uint64" | "string" +# Output = ('uint', 64) | ('string', None) +def get_typesize(typename): + regex = re.compile("^(\w+?)(\d*)$") + result = regex.search(typename) + typename = result.group(1) + typesize = result.group(2) + if len(typesize) == 0: + typesize = None + else: + typesize = int(typesize) + return (typename, typesize) + + + +def parse_int(typesize): + return (EIP712FieldType.INT, int(typesize / 8)) + +def parse_uint(typesize): + return (EIP712FieldType.UINT, int(typesize / 8)) + +def parse_address(typesize): + return (EIP712FieldType.ADDRESS, None) + +def parse_bool(typesize): + return (EIP712FieldType.BOOL, None) + +def parse_string(typesize): + return (EIP712FieldType.STRING, None) + +def parse_bytes(typesize): + if typesize != None: + return (EIP712FieldType.FIX_BYTES, typesize) + return (EIP712FieldType.DYN_BYTES, None) + +# set functions for each type +parsing_type_functions = {}; +parsing_type_functions["int"] = parse_int +parsing_type_functions["uint"] = parse_uint +parsing_type_functions["address"] = parse_address +parsing_type_functions["bool"] = parse_bool +parsing_type_functions["string"] = parse_string +parsing_type_functions["bytes"] = parse_bytes + + + +def send_struct_def_field(typename, keyname): + type_enum = None + + (typename, array_lvls) = get_array_levels(typename) + (typename, typesize) = get_typesize(typename) + + if typename in parsing_type_functions.keys(): + (type_enum, typesize) = parsing_type_functions[typename](typesize) + else: + type_enum = EIP712FieldType.CUSTOM + typesize = None + + app_client.eip712_send_struct_def_struct_field(type_enum, + typename, + typesize, + array_lvls, + keyname) + return (typename, type_enum, typesize, array_lvls) + + + +def encode_integer(value, typesize): + data = bytearray() + + # Some are already represented as integers in the JSON, but most as strings + if isinstance(value, str): + base = 10 + if value.startswith("0x"): + base = 16 + value = int(value, base) + + if value == 0: + data.append(0) + else: + if value < 0: # negative number, send it as unsigned + mask = 0 + for i in range(typesize): # make a mask as big as the typesize + mask = (mask << 8) | 0xff + value &= mask + while value > 0: + data.append(value & 0xff) + value >>= 8 + data.reverse() + return data + +def encode_int(value, typesize): + return encode_integer(value, typesize) + +def encode_uint(value, typesize): + return encode_integer(value, typesize) + +def encode_hex_string(value, size): + data = bytearray() + value = value[2:] # skip 0x + byte_idx = 0 + while byte_idx < size: + data.append(int(value[(byte_idx * 2):(byte_idx * 2 + 2)], 16)) + byte_idx += 1 + return data + +def encode_address(value, typesize): + return encode_hex_string(value, 20) + +def encode_bool(value, typesize): + return encode_integer(value, typesize) + +def encode_string(value, typesize): + data = bytearray() + for char in value: + data.append(ord(char)) + return data + +def encode_byte(value, typesize): + return bytearray() + +def encode_bytes_fix(value, typesize): + return encode_hex_string(value, typesize) + +def encode_bytes_dyn(value, typesize): + # length of the value string + # - the length of 0x (2) + # / by the length of one byte in a hex string (2) + return encode_hex_string(value, int((len(value) - 2) / 2)) + +# set functions for each type +encoding_functions = {} +encoding_functions[EIP712FieldType.INT] = encode_int +encoding_functions[EIP712FieldType.UINT] = encode_uint +encoding_functions[EIP712FieldType.ADDRESS] = encode_address +encoding_functions[EIP712FieldType.BOOL] = encode_bool +encoding_functions[EIP712FieldType.STRING] = encode_string +encoding_functions[EIP712FieldType.FIX_BYTES] = encode_bytes_fix +encoding_functions[EIP712FieldType.DYN_BYTES] = encode_bytes_dyn + + + +def send_struct_impl_field(value, field): + # Something wrong happened if this triggers + if isinstance(value, list) or (field["enum"] == EIP712FieldType.CUSTOM): + breakpoint() + + data = encoding_functions[field["enum"]](value, field["typesize"]) + + + if False:#args.filtering: + path = ".".join(current_path) + if path in filtering_paths.keys(): + send_filtering_field_name(filtering_paths[path]) + + app_client.eip712_send_struct_impl_struct_field(data) + + + +def evaluate_field(structs, data, field, lvls_left, new_level = True): + array_lvls = field["array_lvls"] + + if new_level: + current_path.append(field["name"]) + if len(array_lvls) > 0 and lvls_left > 0: + app_client.eip712_send_struct_impl_array(len(data)) + idx = 0 + for subdata in data: + current_path.append("[]") + if not evaluate_field(structs, subdata, field, lvls_left - 1, False): + return False + current_path.pop() + idx += 1 + if array_lvls[lvls_left - 1] != None: + if array_lvls[lvls_left - 1] != idx: + print("Mismatch in array size! Got %d, expected %d\n" % + (idx, array_lvls[lvls_left - 1]), + file=sys.stderr) + return False + else: + if field["enum"] == EIP712FieldType.CUSTOM: + if not send_struct_impl(structs, data, field["type"]): + return False + else: + send_struct_impl_field(data, field) + if new_level: + current_path.pop() + return True + + + +def send_struct_impl(structs, data, structname): + # Check if it is a struct we don't known + if structname not in structs.keys(): + return False + + struct = structs[structname] + for f in struct: + if not evaluate_field(structs, data[f["name"]], f, len(f["array_lvls"])): + return False + return True + +def send_sign(): + bip32path = bytearray.fromhex("8000002c8000003c800000000000000000000000") + path_len = bytearray() + path_len.append(int(len(bip32path) / 4)) + #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(): + #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): + payload = bytearray() + payload.append(len(display_name)) + for char in display_name: + payload.append(ord(char)) + payload.append(len(sig)) + payload += sig + #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 +def send_filtering_contract_name(display_name): + global sig_ctx + + msg = bytearray() + msg += sig_ctx["chainid"] + msg += sig_ctx["caddr"] + msg += sig_ctx["schema_hash"] + for char in display_name: + msg.append(ord(char)) + + sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) + send_filtering_info(P1_CONTRACT_NAME, display_name, sig) + +# ledgerjs doesn't actually sign anything, and instead uses already pre-computed signatures +def send_filtering_field_name(display_name): + global sig_ctx + + path_str = ".".join(current_path) + + msg = bytearray() + msg += sig_ctx["chainid"] + msg += sig_ctx["caddr"] + msg += sig_ctx["schema_hash"] + for char in path_str: + msg.append(ord(char)) + for char in display_name: + msg.append(ord(char)) + sig = sig_ctx["key"].sign_deterministic(msg, sigencode=sigencode_der) + send_filtering_info(P1_FIELD_NAME, display_name, sig) + +def read_filtering_file(domain, message): + data_json = None + with open("%s-filter.json" % (args.JSON_FILE)) as data: + data_json = json.load(data) + return data_json + +def prepare_filtering(filtr_data, message): + global filtering_paths + + if "fields" in filtr_data: + filtering_paths = filtr_data["fields"] + else: + filtering_paths = {} + +def init_signature_context(types, domain): + global sig_ctx + + with open(args.keypath, "r") as priv_file: + sig_ctx["key"] = SigningKey.from_pem(priv_file.read(), hashlib.sha256) + caddr = domain["verifyingContract"] + if caddr.startswith("0x"): + caddr = caddr[2:] + sig_ctx["caddr"] = bytearray.fromhex(caddr) + chainid = domain["chainId"] + sig_ctx["chainid"] = bytearray() + for i in range(8): + sig_ctx["chainid"].append(chainid & (0xff << (i * 8))) + sig_ctx["chainid"].reverse() + schema_str = json.dumps(types).replace(" ","") + schema_hash = hashlib.sha224(schema_str.encode()) + sig_ctx["schema_hash"] = bytearray.fromhex(schema_hash.hexdigest()) + + return True + return False + +def process_file(aclient: EthereumClient, input_file_path: str, filtering = False) -> bool: + global sig_ctx + global app_client + + app_client = aclient + with open(input_file_path, "r") as data: + data_json = json.load(data) + domain_typename = "EIP712Domain" + message_typename = data_json["primaryType"] + types = data_json["types"] + domain = data_json["domain"] + message = data_json["message"] + + if filtering: + if not init_signature_context(types, domain): + return False + filtr = read_filtering_file(domain, message) + + # send types definition + for key in types.keys(): + app_client.eip712_send_struct_def_struct_name(key) + for f in types[key]: + (f["type"], f["enum"], f["typesize"], f["array_lvls"]) = \ + send_struct_def_field(f["type"], f["name"]) + + if filtering: + send_filtering_activate() + prepare_filtering(filtr, message) + + # send domain implementation + app_client.eip712_send_struct_impl_root_struct(domain_typename) + if not send_struct_impl(types, domain, domain_typename): + return False + + if filtering: + if filtr and "name" in filtr: + send_filtering_contract_name(filtr["name"]) + else: + send_filtering_contract_name(sig_ctx["domain"]["name"]) + + # send message implementation + app_client.eip712_send_struct_impl_root_struct(message_typename) + if not send_struct_impl(types, message, message_typename): + return False + + # sign + send_sign() + return True diff --git a/tests/ragger/eip712/__init__.py b/tests/ragger/eip712/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/ragger/eip712/input_files/00-simple_mail-test.json b/tests/ragger/eip712/input_files/00-simple_mail-test.json new file mode 100644 index 0000000..ef5ce4e --- /dev/null +++ b/tests/ragger/eip712/input_files/00-simple_mail-test.json @@ -0,0 +1,44 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallets", "type": "address[]" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/01-addresses_array_mail-test.json b/tests/ragger/eip712/input_files/01-addresses_array_mail-test.json new file mode 100644 index 0000000..e17e626 --- /dev/null +++ b/tests/ragger/eip712/input_files/01-addresses_array_mail-test.json @@ -0,0 +1,31 @@ +{ + "domain": { + "chainId": 1337, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "to": [ + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xb1a22cc48f6784f629a994917cd6474923630c48" + ], + "id": 7 + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Mail": [ + { "name": "from", "type": "address" }, + { "name": "to", "type": "address[]" }, + { "name": "contents", "type": "string" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/02-recipients_array_mail-test.json b/tests/ragger/eip712/input_files/02-recipients_array_mail-test.json new file mode 100644 index 0000000..51e8fef --- /dev/null +++ b/tests/ragger/eip712/input_files/02-recipients_array_mail-test.json @@ -0,0 +1,55 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Alice", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + }, + { + "name": "Bob", + "wallets": [ + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57" + ] + } + ] + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Group": [ + { "name": "name", "type": "string" }, + { "name": "members", "type": "Person[]" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person[]" }, + { "name": "contents", "type": "string" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallets", "type": "address[]" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/03-long_string-test.json b/tests/ragger/eip712/input_files/03-long_string-test.json new file mode 100644 index 0000000..03ee734 --- /dev/null +++ b/tests/ragger/eip712/input_files/03-long_string-test.json @@ -0,0 +1,50 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob! 012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012312345678012345678012345678012345678012344444444444444444444123456780123456780123456780123456780123456780123456780123456789999999999999999999999999999990123456789", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ] + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Group": [ + { "name": "name", "type": "string" }, + { "name": "members", "type": "Person[]" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person[]" }, + { "name": "contents", "type": "string" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallets", "type": "address[]" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/04-long_bytes-test.json b/tests/ragger/eip712/input_files/04-long_bytes-test.json new file mode 100644 index 0000000..c7dc888 --- /dev/null +++ b/tests/ragger/eip712/input_files/04-long_bytes-test.json @@ -0,0 +1,50 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "0x1123456789123456789123453678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678912345678991234567892345678991234567892345678991234567892345678991234567892345678991234567892345678912345678923456789", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + } + ] + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Group": [ + { "name": "name", "type": "string" }, + { "name": "members", "type": "Person[]" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person[]" }, + { "name": "contents", "type": "bytes" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallets", "type": "address[]" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/05-signed_ints-test.json b/tests/ragger/eip712/input_files/05-signed_ints-test.json new file mode 100644 index 0000000..8a94649 --- /dev/null +++ b/tests/ragger/eip712/input_files/05-signed_ints-test.json @@ -0,0 +1,45 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "neg256" : "-256", + "pos256" : "256", + "neg128" : "-128", + "pos128" : "128", + "neg64" : "-64", + "pos64" : "64", + "neg32" : "-32", + "pos32" : "32", + "neg16" : "-16", + "pos16" : "16", + "neg8" : "-8", + "pos8" : "8" + }, + "primaryType": "Test", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Test": [ + { "name": "neg256", "type": "int256" }, + { "name": "pos256", "type": "int256" }, + { "name": "neg128", "type": "int128" }, + { "name": "pos128", "type": "int128" }, + { "name": "neg64", "type": "int64" }, + { "name": "pos64", "type": "int64" }, + { "name": "neg32", "type": "int32" }, + { "name": "pos32", "type": "int32" }, + { "name": "neg16", "type": "int16" }, + { "name": "pos16", "type": "int16" }, + { "name": "neg8", "type": "int8" }, + { "name": "pos8", "type": "int8" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/06-boolean-test.json b/tests/ragger/eip712/input_files/06-boolean-test.json new file mode 100644 index 0000000..5effe8d --- /dev/null +++ b/tests/ragger/eip712/input_files/06-boolean-test.json @@ -0,0 +1,25 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "Bueno" : true, + "NoBueno": false + }, + "primaryType": "Test", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Test": [ + { "name": "Bueno", "type": "bool" }, + { "name": "NoBueno", "type": "bool" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/07-fixed_bytes-test.json b/tests/ragger/eip712/input_files/07-fixed_bytes-test.json new file mode 100644 index 0000000..cb00886 --- /dev/null +++ b/tests/ragger/eip712/input_files/07-fixed_bytes-test.json @@ -0,0 +1,23 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "val" : "0x973bb640" + }, + "primaryType": "Test", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Test": [ + { "name": "val", "type": "bytes4" } + ] + } +} diff --git a/tests/ragger/eip712/input_files/08-opensea-test.json b/tests/ragger/eip712/input_files/08-opensea-test.json new file mode 100644 index 0000000..35bd77a --- /dev/null +++ b/tests/ragger/eip712/input_files/08-opensea-test.json @@ -0,0 +1,153 @@ +{ + "domain" : { + "chainId" : 1, + "name" : "Wyvern Exchange Contract", + "verifyingContract" : "0x7f268357a8c2552623316e2562d90e642bb538e5", + "version" : "2.3" + }, + "message" : { + "basePrice" : "2000000000000000000", + "calldata" : "0x96809f90000000000000000000000000112f0732e59e7600768dfc35ba744b89f2356cd80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000495f947276749ce646f68ac8c248420045cb7b5ebdf2657ffc1fadfd73cf0a8cde95d50b62d3df8c0000000000000700000000320000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000", + "exchange" : "0x7f268357a8c2552623316e2562d90e642bb538e5", + "expirationTime" : "1646089435", + "extra" : "0", + "feeMethod" : 1, + "feeRecipient" : "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073", + "howToCall" : 1, + "listingTime" : "1645484541", + "maker" : "0x112f0732e59e7600768dfc35ba744b89f2356cd8", + "makerProtocolFee" : "0", + "makerRelayerFee" : "1250", + "nonce" : 0, + "paymentToken" : "0x0000000000000000000000000000000000000000", + "replacementPattern" : "0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "saleKind" : 0, + "salt" : "21014297276898013168171430966355369260039074692095359200549020767078729356431", + "side" : 1, + "staticExtradata" : "0x", + "staticTarget" : "0x0000000000000000000000000000000000000000", + "taker" : "0x0000000000000000000000000000000000000000", + "takerProtocolFee" : "0", + "takerRelayerFee" : "0", + "target" : "0xbaf2127b49fc93cbca6269fade0f7f31df4c88a7" + }, + "primaryType" : "Order", + "types" : { + "EIP712Domain" : [ + { + "name" : "name", + "type" : "string" + }, + { + "name" : "version", + "type" : "string" + }, + { + "name" : "chainId", + "type" : "uint256" + }, + { + "name" : "verifyingContract", + "type" : "address" + } + ], + "Order" : [ + { + "name" : "exchange", + "type" : "address" + }, + { + "name" : "maker", + "type" : "address" + }, + { + "name" : "taker", + "type" : "address" + }, + { + "name" : "makerRelayerFee", + "type" : "uint256" + }, + { + "name" : "takerRelayerFee", + "type" : "uint256" + }, + { + "name" : "makerProtocolFee", + "type" : "uint256" + }, + { + "name" : "takerProtocolFee", + "type" : "uint256" + }, + { + "name" : "feeRecipient", + "type" : "address" + }, + { + "name" : "feeMethod", + "type" : "uint8" + }, + { + "name" : "side", + "type" : "uint8" + }, + { + "name" : "saleKind", + "type" : "uint8" + }, + { + "name" : "target", + "type" : "address" + }, + { + "name" : "howToCall", + "type" : "uint8" + }, + { + "name" : "calldata", + "type" : "bytes" + }, + { + "name" : "replacementPattern", + "type" : "bytes" + }, + { + "name" : "staticTarget", + "type" : "address" + }, + { + "name" : "staticExtradata", + "type" : "bytes" + }, + { + "name" : "paymentToken", + "type" : "address" + }, + { + "name" : "basePrice", + "type" : "uint256" + }, + { + "name" : "extra", + "type" : "uint256" + }, + { + "name" : "listingTime", + "type" : "uint256" + }, + { + "name" : "expirationTime", + "type" : "uint256" + }, + { + "name" : "salt", + "type" : "uint256" + }, + { + "name" : "nonce", + "type" : "uint256" + } + ] + } +} diff --git a/tests/ragger/eip712/input_files/09-rarible-test.json b/tests/ragger/eip712/input_files/09-rarible-test.json new file mode 100644 index 0000000..f5284a6 --- /dev/null +++ b/tests/ragger/eip712/input_files/09-rarible-test.json @@ -0,0 +1,110 @@ +{ + "domain" : { + "chainId" : 1, + "name" : "Exchange", + "verifyingContract" : "0x9757f2d2b135150bbeb65308d4a91804107cd8d6", + "version" : "2" + }, + "message" : { + "data" : "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000001cf0df2a5a20cd61d68d4489eebbf85b8d39e18a00000000000000000000000000000000000000000000000000000000000000fa", + "dataType" : "0x23d235ef", + "end" : 0, + "makeAsset" : { + "assetType" : { + "assetClass" : "0x973bb640", + "data" : "0x000000000000000000000000495f947276749ce646f68ac8c248420045cb7b5ebdf2657ffc1fadfd73cf0a8cde95d50b62d3df8c000000000000070000000032" + }, + "value" : "1" + }, + "maker" : "0x112f0732e59e7600768dfc35ba744b89f2356cd8", + "salt" : "0xdbf0f98bc1746711579dcce549a4cc4e866fb71bf2e185bfefbb7d32f325972e", + "start" : 0, + "takeAsset" : { + "assetType" : { + "assetClass" : "0xaaaebeba", + "data" : "0x" + }, + "value" : "2000000000000000000" + }, + "taker" : "0x0000000000000000000000000000000000000000" + }, + "primaryType" : "Order", + "types" : { + "Asset" : [ + { + "name" : "assetType", + "type" : "AssetType" + }, + { + "name" : "value", + "type" : "uint256" + } + ], + "AssetType" : [ + { + "name" : "assetClass", + "type" : "bytes4" + }, + { + "name" : "data", + "type" : "bytes" + } + ], + "EIP712Domain" : [ + { + "name" : "name", + "type" : "string" + }, + { + "name" : "version", + "type" : "string" + }, + { + "name" : "chainId", + "type" : "uint256" + }, + { + "name" : "verifyingContract", + "type" : "address" + } + ], + "Order" : [ + { + "name" : "maker", + "type" : "address" + }, + { + "name" : "makeAsset", + "type" : "Asset" + }, + { + "name" : "taker", + "type" : "address" + }, + { + "name" : "takeAsset", + "type" : "Asset" + }, + { + "name" : "salt", + "type" : "uint256" + }, + { + "name" : "start", + "type" : "uint256" + }, + { + "name" : "end", + "type" : "uint256" + }, + { + "name" : "dataType", + "type" : "bytes4" + }, + { + "name" : "data", + "type" : "bytes" + } + ] + } +} diff --git a/tests/ragger/eip712/input_files/10-multidimensional_arrays-test.json b/tests/ragger/eip712/input_files/10-multidimensional_arrays-test.json new file mode 100644 index 0000000..92d7963 --- /dev/null +++ b/tests/ragger/eip712/input_files/10-multidimensional_arrays-test.json @@ -0,0 +1,92 @@ +{ + "types": { + "EIP712Domain": [ + { + "type": "string", + "name": "name" + } + ], + "LDPSigningRequest": [ + { + "type": "string[3][]", + "name": "document" + }, + { + "type": "string[][4]", + "name": "proof" + }, + { + "type": "uint8[][][][]", + "name": "depthy" + } + ] + }, + "primaryType": "LDPSigningRequest", + "domain": { + "name": "Eip712Method2021" + }, + "message": { + "depthy": [ + [ + [ + [ + "1", + "2" + ], + [ + "3" + ] + ] + ] + ], + "document": [ + [ + "", + "", + "\"did:ethr:0xf7398bacf610bb4e3b567811279fcb3c41919f89\"" + ], + [ + "", + "", + "" + ], + [ + "", + "", + "\"2021-03-04T21:08:22.615Z\"^^" + ], + [ + "", + "", + "" + ], + [ + "", + "", + "" + ] + ], + "proof": [ + [ + "_:c14n0", + "", + "\"2021-03-04T21:08:22.616Z\"^^" + ], + [ + "_:c14n0", + "", + "" + ], + [ + "_:c14n0", + "", + "" + ], + [ + "_:c14n0", + "", + "" + ] + ] + } +} diff --git a/tests/ragger/eip712/input_files/11-complex_structs-test.json b/tests/ragger/eip712/input_files/11-complex_structs-test.json new file mode 100644 index 0000000..3433564 --- /dev/null +++ b/tests/ragger/eip712/input_files/11-complex_structs-test.json @@ -0,0 +1,78 @@ +{ + "domain": { + "chainId": 5, + "name": "Ether Mail", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + "version": "1" + }, + "message": { + "contents": "Hello, Bob!", + "from": { + "name": "Cow", + "wallets": [ + "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" + ] + }, + "to": { + "name": "test list", + "members": [ + { + "name": "Bob", + "wallets": [ + "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "0xB0B0b0b0b0b0B000000000000000000000000000" + ] + }, + { + "name": "Alice", + "wallets": [ + "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57" + ] + } + ] + }, + "attach": { + "list": [ + { + "name": "first", + "size": "100" + }, + { + "name": "second", + "size": "3400" + } + ] + } + }, + "primaryType": "Mail", + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Attachment": [ + { "name": "name", "type": "string" }, + { "name": "size", "type": "uint16" } + ], + "Attachments": [ + { "name": "list", "type": "Attachment[]" } + ], + "MailingList": [ + { "name": "name", "type": "string" }, + { "name": "members", "type": "Person[]" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "MailingList" }, + { "name": "contents", "type": "string" }, + { "name": "attach", "type": "Attachments" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallets", "type": "address[]" } + ] + } +} diff --git a/tests/ragger/ethereum_client.py b/tests/ragger/ethereum_client.py index 5f1aea9..db0710b 100644 --- a/tests/ragger/ethereum_client.py +++ b/tests/ragger/ethereum_client.py @@ -20,14 +20,14 @@ class P2Type(IntEnum): LEGACY_IMPLEM = 0x00 NEW_IMPLEM = 0x01, -class EIP712FieldType: +class EIP712FieldType(IntEnum): CUSTOM = 0, - INT = auto(), - UINT = auto(), - ADDRESS = auto(), - BOOL = auto(), - STRING = auto(), - FIXED_BYTES = auto(), + INT = auto() + UINT = auto() + ADDRESS = auto() + BOOL = auto() + STRING = auto() + FIX_BYTES = auto() DYN_BYTES = auto() @@ -66,14 +66,14 @@ class EthereumClientCmdBuilder: data = bytearray() typedesc = 0 typedesc |= (len(array_levels) > 0) << 7 - typedesc |= (type_size > 0) << 6 + typedesc |= (type_size != None) << 6 typedesc |= field_type data.append(typedesc) if field_type == EIP712FieldType.CUSTOM: data.append(len(type_name)) for char in type_name: data.append(ord(char)) - if type_size > 0: + if type_size != None: data.append(type_size) if len(array_levels) > 0: data.append(len(array_levels)) @@ -107,12 +107,17 @@ class EthereumClientCmdBuilder: data) def eip712_send_struct_impl_struct_field(self, data: bytearray) -> Iterator[bytes]: - while len(data > 0): + # Add a 16-bit integer with the data's byte length (network byte order) + data_w_length = bytearray() + data_w_length.append((len(data) & 0xff00) >> 8) + data_w_length.append(len(data) & 0x00ff) + data_w_length += data + while len(data_w_length) > 0: yield self._serialize(InsType.EIP712_SEND_STRUCT_IMPL, P1Type.COMPLETE_SEND, P2Type.STRUCT_FIELD, - data[:0xff]) - data = data[0xff:] + data_w_length[:0xff]) + data_w_length = data_w_length[0xff:] def _format_bip32(self, bip32, data = bytearray()) -> bytearray: data.append(len(bip32)) @@ -195,17 +200,14 @@ class EthereumClient: return self._recv() def eip712_send_struct_impl_array(self, size: int): - send._send(self._cmd_builder.eip712_send_struct_impl_array(size)) + self._send(self._cmd_builder.eip712_send_struct_impl_array(size)) return self._recv() def eip712_send_struct_impl_struct_field(self, raw_value: bytes): ret = None - for apdu in self._cmd_builder.eip712_send_struct_impl_struct_field( - InsType.EIP712_SEND_STRUCT_IMPL, - P1Type.COMPLETE_SEND, - P2Type.STRUCT_FIELD, - data[:0xff]): + for apdu in self._cmd_builder.eip712_send_struct_impl_struct_field(raw_value): self._send(apdu) + # TODO: Do clicks ret = self._recv() return ret diff --git a/tests/ragger/test_eip712.py b/tests/ragger/test_eip712.py index c3b4de1..0b06f55 100644 --- a/tests/ragger/test_eip712.py +++ b/tests/ragger/test_eip712.py @@ -1,6 +1,7 @@ import os import fnmatch from ethereum_client import EthereumClient +from eip712 import InputData def test_eip712_legacy(app_client: EthereumClient): bip32 = [ @@ -20,3 +21,15 @@ def test_eip712_legacy(app_client: EthereumClient): assert v == bytes.fromhex("1c") assert r == bytes.fromhex("ea66f747173762715751c889fea8722acac3fc35db2c226d37a2e58815398f64") assert s == bytes.fromhex("52d8ba9153de9255da220ffd36762c0b027701a3b5110f0a765f94b16a9dfb55") + + +def test_eip712_new(app_client: EthereumClient): + if app_client._client.firmware.device == "nanos": # not supported + return + + # Loop through JSON files + for file in os.scandir("./eip712/input_files"): + if fnmatch.fnmatch(file, "*-test.json"): + print(file.path) + InputData.process_file(app_client, file.path, False) + assert 1 == 1