Skip to content

Commit

Permalink
Add more tests EIP721, improve decoding (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
bitlogik committed Aug 30, 2021
1 parent 37badce commit 41b3342
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 9 deletions.
190 changes: 189 additions & 1 deletion tests/test_EIP712.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from wallets.typed_data_hash import encode_data, type_hash, hash_struct, typed_sign_hash


def test_eip712():

def test_eip712_A():

# Official test vector from EIP712
# https://eips.ethereum.org/EIPS/eip-712#test-cases
Expand Down Expand Up @@ -64,3 +65,190 @@ def test_eip712():
res = typed_sign_hash(typed_data)
assert res.hex() == "be609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2"


def test_eip712_B():

# Test vectors from the npm package ethers-eip712
# https://github.com/0xsequence/ethers-eip712/blob/master/tests/typed-data.test.ts

types_tst = {
"Person": [
{"name": "name", "type": "string"},
{"name": "wallet", "type": "address"},
],
"Mail": [
{"name": "from", "type": "Person"},
{"name": "to", "type": "Person"},
{"name": "contents", "type": "string"},
{"name": "asset", "type": "Asset"},
],
"Asset": [{"name": "name", "type": "string"}],
}
phash = type_hash("Person", types_tst)
assert phash.hex() == "b9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500"
thash = type_hash("Mail", types_tst)
assert thash.hex() == "5848dd854dd9179bf93f24186c392747bac3b59ff85125874ed562b05c02d8a6"

# Test encoding 1
typed_data1 = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallet", "type": "address"},
],
},
"primaryType": "Person",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
},
"message": {"name": "Bob", "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},
}
dom_sep = hash_struct("EIP712Domain", typed_data1["types"], typed_data1["domain"])
assert dom_sep.hex() == "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
thash = typed_sign_hash(typed_data1)
assert thash.hex() == "0a94cf6625e5860fc4f330d75bcd0c3a4737957d2321d1a024540ab5320fe903"

# Test encoding 2
typed_data2 = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallet", "type": "address"},
{"name": "count", "type": "uint8"},
],
},
"primaryType": "Person",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
},
"message": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"count": 4,
},
}
dom_sep = hash_struct("EIP712Domain", typed_data2["types"], typed_data2["domain"])
assert dom_sep.hex() == "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
thash = typed_sign_hash(typed_data2)
assert thash.hex() == "2218fda59750be7bb9e5dfb2b49e4ec000dc2542862c5826f1fe980d6d727e95"

# Test encoding 3
typed_data3 = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallets", "type": "address[]"},
],
"Mail": [
{"name": "from", "type": "Person"},
{"name": "to", "type": "Person[]"},
{"name": "contents", "type": "string"},
],
"Group": [
{"name": "name", "type": "string"},
{"name": "members", "type": "Person[]"},
],
},
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
},
"primaryType": "Mail",
"message": {
"from": {
"name": "Cow",
"wallets": [
"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF",
],
},
"to": [
{
"name": "Bob",
"wallets": [
"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57",
"0xB0B0b0b0b0b0B000000000000000000000000000",
],
}
],
"contents": "Hello, Bob!",
},
}
phash3 = type_hash("Group", typed_data3["types"])
assert phash3.hex() == "1d953c52160a9018dbb518700e05da47418ac2b6044f0c044f84c15a80103107"
phash3 = type_hash("Person", typed_data3["types"])
assert phash3.hex() == "fabfe1ed996349fc6027709802be19d047da1aa5d6894ff5f6486d92db2e6860"
hash_person_1 = hash_struct("Person", typed_data3["types"], typed_data3["message"]["from"])
assert hash_person_1.hex() == "9b4846dd48b866f0ac54d61b9b21a9e746f921cefa4ee94c4c0a1c49c774f67f"
hash_person_2 = hash_struct("Person", typed_data3["types"], typed_data3["message"]["to"][0])
assert hash_person_2.hex() == "efa62530c7ae3a290f8a13a5fc20450bdb3a6af19d9d9d2542b5a94e631a9168"
phash3 = type_hash("Mail", typed_data3["types"])
assert phash3.hex() == "4bd8a9a2b93427bb184aca81e24beb30ffa3c747e2a33d4225ec08bf12e2e753"
hash_primary = hash_struct(
typed_data3["primaryType"], typed_data3["types"], typed_data3["message"]
)
assert hash_primary.hex() == "eb4221181ff3f1a83ea7313993ca9218496e424604ba9492bb4052c03d5c3df8"
dom_sep = hash_struct("EIP712Domain", typed_data3["types"], typed_data3["domain"])
assert dom_sep.hex() == "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
global_hash = typed_sign_hash(typed_data3)
assert global_hash.hex() == "a85c2e2b118698e88db68a8105b794a8cc7cec074e89ef991cb4f5f533819cc2"

# Test encoding 4
typed_data4 = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Person": [
{"name": "name", "type": "string"},
{"name": "wallet", "type": "address"},
{"name": "count", "type": "bytes8"},
],
},
"primaryType": "Person",
"domain": {
"name": "Ether Mail",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
},
"message": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
"count": "0x1122334455667788",
},
}
dom_sep = hash_struct("EIP712Domain", typed_data4["types"], typed_data4["domain"])
assert dom_sep.hex() == "f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f"
global_hash = typed_sign_hash(typed_data4)
assert global_hash.hex() == "2a3e64893ed4ba30ea34dbff3b0aa08c7677876cfdf7112362eccf3111f58d1d"
36 changes: 28 additions & 8 deletions wallets/typed_data_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ def collect_sub_types(name, types_obj, list_types=None):
"""Collect all types of a struct."""
if not list_types:
list_types = []
if name.endswith("[]"):
name = name[:-2]
if name in list_types:
return list_types
if name not in types_obj.keys():
Expand Down Expand Up @@ -64,6 +66,9 @@ def encode_types(types_list, types_dict):
"""Provide the struct type string signature (encodeType)."""
types_enc = ""
for type_key in types_list:
print(type_key)
if type_key.endswith("[]"):
type_key = type_key[:-2]
types_enc += encode_atype(type_key, types_dict[type_key])
return types_enc

Expand All @@ -72,46 +77,61 @@ def type_hash(name, types_obj):
"""Compute typeHash (hash of the encodeType type string)."""
subtypes_list = collect_sub_types(name, types_obj)
subtypes_list.sort()
print("subtypes", subtypes_list)
types_list = [name, *subtypes_list]
print(encode_types(types_list, types_obj).encode("utf8"))
return sha3(encode_types(types_list, types_obj).encode("utf8"))


def encode_value(vtype, value):
def encode_value(vtype, value, go):
"""Encode a value in Python bytes."""
if vtype == "bool":
# check is bool
return uint256(1) if value else uint256(0)
if vtype == "address":
# check is hex 40 chars
return uint256(int(value[2:], 16))
if vtype in int_types or vtype in uint_types:
# check is int
if value > 0:
intval_bin = uint256(value)
else:
intval_bin = uint256(2 ** 256 + value)
return intval_bin
if vtype in bytes_types:
# check is 0x hex
out = bytes.fromhex(value[2:])
while len(out) < 32:
out += b"\0"
return out
if vtype == "bytes":
# check is 0x hex
out = bytes.fromhex(value[2:])
return sha3(out)
if vtype == "string":
# check is str
return sha3(value.encode("utf8"))
# array not implemented
raise NotImplementedError("Array encoding not implemented.")
if vtype.endswith("[]"):
# check is list
elements = [encode_value(vtype[:-2], val, go) for val in value]
return sha3(b"".join(elements))
# should be a struct
# test if value is dict
return hash_struct(vtype, go, value)


def encode_data(name, types_obj, data_obj):
"""encodeData : Encode all the data of the members values."""
out = b""
for member in types_obj[name]:
mtype = member["type"]
mvalue = data_obj[member["name"]]
if isinstance(mvalue, dict):
out += hash_struct(mtype, types_obj, mvalue)
else:
out += encode_value(mtype, mvalue)
if "name" in member:
mvalue = data_obj[member["name"]]
mtype = member["type"]
if isinstance(mvalue, dict):
out += hash_struct(mtype, types_obj, mvalue)
else:
out += encode_value(mtype, mvalue, types_obj)
return out


Expand Down

0 comments on commit 41b3342

Please sign in to comment.