From 2e17b16fa6af88d3ce5e0695f8d602f8984d1243 Mon Sep 17 00:00:00 2001 From: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:49:44 +0200 Subject: [PATCH] Add `MerkleTree` and merkletree support in `TypedData` (#1363) --- starknet_py/hash/hash_method.py | 13 +- starknet_py/net/models/typed_data.py | 6 + starknet_py/net/schemas/common.py | 2 +- ..._data_rev_0_struct_merkletree_example.json | 67 +++++++++ ...ed_data_rev_1_felt_merkletree_example.json | 29 ++++ starknet_py/utils/merkle_tree.py | 45 ++++++ starknet_py/utils/merkle_tree_test.py | 140 ++++++++++++++++++ starknet_py/utils/typed_data.py | 107 +++++++++++-- starknet_py/utils/typed_data_test.py | 44 ++++-- 9 files changed, 417 insertions(+), 36 deletions(-) create mode 100644 starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json create mode 100644 starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json create mode 100644 starknet_py/utils/merkle_tree.py create mode 100644 starknet_py/utils/merkle_tree_test.py diff --git a/starknet_py/hash/hash_method.py b/starknet_py/hash/hash_method.py index fe790214f..dddced0f6 100644 --- a/starknet_py/hash/hash_method.py +++ b/starknet_py/hash/hash_method.py @@ -1,9 +1,9 @@ from enum import Enum from typing import List -from poseidon_py.poseidon_hash import poseidon_hash_many +from poseidon_py.poseidon_hash import poseidon_hash, poseidon_hash_many -from starknet_py.hash.utils import compute_hash_on_elements +from starknet_py.hash.utils import compute_hash_on_elements, pedersen_hash class HashMethod(Enum): @@ -14,7 +14,14 @@ class HashMethod(Enum): PEDERSEN = "pedersen" POSEIDON = "poseidon" - def hash(self, values: List[int]): + def hash(self, left: int, right: int): + if self == HashMethod.PEDERSEN: + return pedersen_hash(left, right) + if self == HashMethod.POSEIDON: + return poseidon_hash(left, right) + raise ValueError(f"Unsupported hash method: {self}.") + + def hash_many(self, values: List[int]): if self == HashMethod.PEDERSEN: return compute_hash_on_elements(values) if self == HashMethod.POSEIDON: diff --git a/starknet_py/net/models/typed_data.py b/starknet_py/net/models/typed_data.py index 4229fa844..6a2c23aba 100644 --- a/starknet_py/net/models/typed_data.py +++ b/starknet_py/net/models/typed_data.py @@ -14,6 +14,7 @@ class ParameterDict(TypedDict): name: str type: str + contains: Optional[str] class DomainDict(TypedDict): @@ -36,3 +37,8 @@ class TypedDataDict(TypedDict): primaryType: str domain: DomainDict message: Dict[str, Any] + + +class TypeContext(TypedDict): + parent: str + key: str diff --git a/starknet_py/net/schemas/common.py b/starknet_py/net/schemas/common.py index fea39b4bf..b8ce23ae9 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -357,8 +357,8 @@ def _serialize(self, value: Any, attr: str, obj: Any, **kwargs): def _deserialize(self, value, attr, data, **kwargs) -> Revision: if isinstance(value, str): value = int(value) - revisions = [revision.value for revision in Revision] + revisions = [revision.value for revision in Revision] if value not in revisions: allowed_revisions_str = "".join(list(map(str, revisions))) raise ValidationError( diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json new file mode 100644 index 000000000..05ed4af7b --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json @@ -0,0 +1,67 @@ +{ + "primaryType": "Session", + "types": { + "Policy": [ + { + "name": "contractAddress", + "type": "felt" + }, + { + "name": "selector", + "type": "selector" + } + ], + "Session": [ + { + "name": "key", + "type": "felt" + }, + { + "name": "expires", + "type": "felt" + }, + { + "name": "root", + "type": "merkletree", + "contains": "Policy" + } + ], + "StarkNetDomain": [ + { + "name": "name", + "type": "felt" + }, + { + "name": "version", + "type": "felt" + }, + { + "name": "chainId", + "type": "felt" + } + ] + }, + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1" + }, + "message": { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expires": "0x0000000000000000000000000000000000000000000000000000000000000000", + "root": [ + { + "contractAddress": "0x1", + "selector": "transfer" + }, + { + "contractAddress": "0x2", + "selector": "transfer" + }, + { + "contractAddress": "0x3", + "selector": "transfer" + } + ] + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json new file mode 100644 index 000000000..891476c48 --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json @@ -0,0 +1,29 @@ +{ + "primaryType": "Example", + "types": { + "Example": [ + { "name": "value", "type": "felt" }, + { "name": "root", "type": "merkletree", "contains": "felt" } + ], + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ] + }, + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": "1" + }, + "message": { + "value": "0x2137", + "root": [ + "0x1", + "0x2", + "0x3" + ] + } +} diff --git a/starknet_py/utils/merkle_tree.py b/starknet_py/utils/merkle_tree.py new file mode 100644 index 000000000..fca65d9c7 --- /dev/null +++ b/starknet_py/utils/merkle_tree.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass, field +from typing import List, Tuple + +from starknet_py.hash.hash_method import HashMethod + + +@dataclass +class MerkleTree: + """ + Dataclass representing a MerkleTree object. + """ + + leaves: List[int] + hash_method: HashMethod + root_hash: int = field(init=False) + levels: List[List[int]] = field(init=False) + + def __post_init__(self): + self.root_hash, self.levels = self._build() + + def _build(self) -> Tuple[int, List[List[int]]]: + if not self.leaves: + raise ValueError("Cannot build Merkle tree from an empty list of leaves.") + + if len(self.leaves) == 1: + return self.leaves[0], [self.leaves] + + curr_level_nodes = self.leaves[:] + levels: List[List[int]] = [] + + while len(curr_level_nodes) > 1: + if len(curr_level_nodes) != len(self.leaves): + levels.append(curr_level_nodes[:]) + + new_nodes = [] + for i in range(0, len(curr_level_nodes), 2): + a, b = ( + curr_level_nodes[i], + curr_level_nodes[i + 1] if i + 1 < len(curr_level_nodes) else 0, + ) + new_nodes.append(self.hash_method.hash(*sorted([a, b]))) + + curr_level_nodes = new_nodes + levels = [self.leaves] + levels + [curr_level_nodes] + return curr_level_nodes[0], levels diff --git a/starknet_py/utils/merkle_tree_test.py b/starknet_py/utils/merkle_tree_test.py new file mode 100644 index 000000000..29b5899cd --- /dev/null +++ b/starknet_py/utils/merkle_tree_test.py @@ -0,0 +1,140 @@ +from typing import List + +import pytest +from poseidon_py.poseidon_hash import poseidon_hash + +from starknet_py.hash.hash_method import HashMethod +from starknet_py.hash.utils import pedersen_hash +from starknet_py.utils.merkle_tree import MerkleTree + + +@pytest.mark.parametrize( + "leaves, hash_method, expected_root_hash", + [ + ( + ["0x12", "0xa"], + HashMethod.PEDERSEN, + "0x586699e3ba6f118227e094ad423313a2d51871507dcbc23116f11cdd79d80f2", + ), + ( + ["0x12", "0xa"], + HashMethod.POSEIDON, + "0x6257f1f60f7c9fd49e2718c8ad19cd8dce6b1ba4b553b2123113f22b1e9c379", + ), + ( + [ + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + "0x3", + ], + HashMethod.PEDERSEN, + "0x551b4adb6c35d49c686a00b9192da9332b18c9b262507cad0ece37f3b6918d2", + ), + ( + [ + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + "0x3", + ], + HashMethod.POSEIDON, + "0xc118a3963c12777b0717d1dc89baa8b3ceed84dfd713a6bd1354676f03f021", + ), + ], +) +def test_calculate_hash( + leaves: List[str], hash_method: HashMethod, expected_root_hash: str +): + if hash_method == HashMethod.PEDERSEN: + apply_hash = pedersen_hash + elif hash_method == HashMethod.POSEIDON: + apply_hash = poseidon_hash + else: + raise ValueError(f"Unsupported hash method: {hash_method}.") + + a, b = int(leaves[0], 16), int(leaves[1], 16) + merkle_hash = hash_method.hash(*sorted([b, a])) + raw_hash = apply_hash(*sorted([b, a])) + + assert raw_hash == merkle_hash + assert int(expected_root_hash, 16) == merkle_hash + + +@pytest.mark.parametrize( + "hash_method", + [ + HashMethod.PEDERSEN, + HashMethod.POSEIDON, + ], +) +def test_build_from_0_elements(hash_method: HashMethod): + with pytest.raises( + ValueError, match="Cannot build Merkle tree from an empty list of leaves." + ): + MerkleTree([], hash_method) + + +@pytest.mark.parametrize( + "leaves, hash_method, expected_root_hash, expected_levels_count", + [ + (["0x1"], HashMethod.PEDERSEN, "0x1", 1), + (["0x1"], HashMethod.POSEIDON, "0x1", 1), + ( + ["0x1", "0x2"], + HashMethod.PEDERSEN, + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + 2, + ), + ( + ["0x1", "0x2"], + HashMethod.POSEIDON, + "0x5d44a3decb2b2e0cc71071f7b802f45dd792d064f0fc7316c46514f70f9891a", + 2, + ), + ( + ["0x1", "0x2", "0x3", "0x4"], + HashMethod.PEDERSEN, + "0x38118a340bbba28e678413cd3b07a9436a5e60fd6a7cbda7db958a6d501e274", + 3, + ), + ( + ["0x1", "0x2", "0x3", "0x4"], + HashMethod.POSEIDON, + "0xa4d02f1e82fc554b062b754d3a4995e0ed8fc7e5016a7ca2894a451a4bae64", + 3, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6"], + HashMethod.PEDERSEN, + "0x329d5b51e352537e8424bfd85b34d0f30b77d213e9b09e2976e6f6374ecb59", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6"], + HashMethod.POSEIDON, + "0x34d525f018d8d6b3e492b1c9cda9bbdc3bc7834b408a30a417186c698c34766", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7"], + HashMethod.PEDERSEN, + "0x7f748c75e5bdb7ae28013f076b8ab650c4e01d3530c6e5ab665f9f1accbe7d4", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7"], + HashMethod.POSEIDON, + "0x3308a3c50c25883753f82b21f14c644ec375b88ea5b0f83d1e6afe74d0ed790", + 4, + ), + ], +) +def test_build_from_elements( + leaves: List[str], + hash_method: HashMethod, + expected_root_hash: str, + expected_levels_count: int, +): + tree = MerkleTree([int(leaf, 16) for leaf in leaves], hash_method) + + assert tree.root_hash is not None + assert tree.levels is not None + assert tree.root_hash == int(expected_root_hash, 16) + assert len(tree.levels) == expected_levels_count diff --git a/starknet_py/utils/typed_data.py b/starknet_py/utils/typed_data.py index 56a0718e2..84e2e2835 100644 --- a/starknet_py/utils/typed_data.py +++ b/starknet_py/utils/typed_data.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from enum import Enum from typing import Dict, List, Optional, Union, cast from marshmallow import Schema, fields, post_load @@ -7,8 +8,10 @@ from starknet_py.hash.hash_method import HashMethod from starknet_py.hash.selector import get_selector_from_name from starknet_py.hash.utils import compute_hash_on_elements +from starknet_py.net.client_utils import _to_rpc_felt from starknet_py.net.models.typed_data import DomainDict, Revision, TypedDataDict from starknet_py.net.schemas.common import RevisionField +from starknet_py.utils.merkle_tree import MerkleTree @dataclass(frozen=True) @@ -19,6 +22,7 @@ class Parameter: name: str type: str + contains: Optional[str] = None @dataclass @@ -62,6 +66,16 @@ def to_dict(self) -> dict: return cast(Dict, DomainSchema().dump(obj=self)) +@dataclass(frozen=True) +class TypeContext: + """ + Dataclass representing a Context object + """ + + parent: str + key: str + + @dataclass(frozen=True) class TypedData: """ @@ -104,32 +118,53 @@ def to_dict(self) -> dict: def _is_struct(self, type_name: str) -> bool: return type_name in self.types - def _encode_value(self, type_name: str, value: Union[int, str, dict, list]) -> int: + def _encode_value( + self, + type_name: str, + value: Union[int, str, dict, list], + context: Optional[TypeContext] = None, + ) -> int: + if type_name in self.types and isinstance(value, dict): + return self.struct_hash(type_name, value) + if is_pointer(type_name) and isinstance(value, list): type_name = strip_pointer(type_name) + hashes = [self._encode_value(type_name, val) for val in value] + return compute_hash_on_elements(hashes) - if self._is_struct(type_name): - return compute_hash_on_elements( - [self.struct_hash(type_name, data) for data in value] - ) - return compute_hash_on_elements([int(get_hex(val), 16) for val in value]) + basic_type = BasicType(type_name) - if self._is_struct(type_name) and isinstance(value, dict): - return self.struct_hash(type_name, value) + if basic_type == BasicType.MERKLE_TREE and isinstance(value, list): + if context is None: + raise ValueError(f"Context is not provided for '{type_name}' type.") + return self._prepare_merkle_tree_root(value, context) - value = cast(Union[int, str], value) - return int(get_hex(value), 16) + if basic_type in (BasicType.FELT, BasicType.SHORT_STRING) and isinstance( + value, (int, str, Revision) + ): + return int(get_hex(value), 16) + + if basic_type == BasicType.SELECTOR and isinstance(value, str): + return prepare_selector(value) + + raise ValueError( + f"Error occurred while encoding value with type name {type_name}." + ) def _encode_data(self, type_name: str, data: dict) -> List[int]: values = [] for param in self.types[type_name]: - encoded_value = self._encode_value(param.type, data[param.name]) + encoded_value = self._encode_value( + param.type, + data[param.name], + TypeContext(parent=type_name, key=param.name), + ) values.append(encoded_value) return values def _verify_types(self): - reserved_type_names = ["felt", "felt*", "string", "selector"] + reserved_type_names = ["felt", "felt*", "string", "selector", "merkletree"] for type_name in reserved_type_names: if type_name in self.types: @@ -186,7 +221,7 @@ def struct_hash(self, type_name: str, data: dict) -> int: :param data: Data defining the struct. :return: Hash of the struct. """ - return self._hash_method.hash( + return self._hash_method.hash_many( [self.type_hash(type_name), *self._encode_data(type_name, data)] ) @@ -204,14 +239,39 @@ def message_hash(self, account_address: int) -> int: self.struct_hash(self.primary_type, self.message), ] - return self._hash_method.hash(message) + return self._hash_method.hash_many(message) + + def _prepare_merkle_tree_root(self, value: List, context: TypeContext) -> int: + merkle_tree_type = self._get_merkle_tree_leaves_type(context) + struct_hashes = [ + self._encode_value(merkle_tree_type, struct) for struct in value + ] + + return MerkleTree(struct_hashes, self._hash_method).root_hash + + def _get_merkle_tree_leaves_type(self, context: TypeContext) -> str: + parent, key = context.parent, context.key + + if parent not in self.types: + raise ValueError(f"Parent {parent} is not defined in types.") + + parent_type = self.types[parent] + + target_type = next((item for item in parent_type if item.name == key), None) + if target_type is None: + raise ValueError( + f"Key {key} is not defined in type {parent} or multiple definitions are present." + ) + + if target_type.contains is None: + raise ValueError("Missing 'contains' field in target type.") + + return target_type.contains def get_hex(value: Union[int, str]) -> str: if isinstance(value, int): return hex(value) - if isinstance(value, Revision): - return hex(value.value) if value[:2] == "0x": return value if value.isnumeric(): @@ -235,6 +295,20 @@ def escape(s: str, revision: Revision) -> str: return f'"{s}"' +def prepare_selector(name: str) -> int: + try: + return int(_to_rpc_felt(name), 16) + except ValueError: + return get_selector_from_name(name) + + +class BasicType(Enum): + FELT = "felt" + SELECTOR = "selector" + MERKLE_TREE = "merkletree" + SHORT_STRING = "shortstring" + + # pylint: disable=unused-argument # pylint: disable=no-self-use @@ -242,6 +316,7 @@ def escape(s: str, revision: Revision) -> str: class ParameterSchema(Schema): name = fields.String(data_key="name", required=True) type = fields.String(data_key="type", required=True) + contains = fields.String(data_key="contains", required=False) @post_load def make_dataclass(self, data, **kwargs) -> Parameter: diff --git a/starknet_py/utils/typed_data_test.py b/starknet_py/utils/typed_data_test.py index 176653c9d..ffe0a6e3d 100644 --- a/starknet_py/utils/typed_data_test.py +++ b/starknet_py/utils/typed_data_test.py @@ -9,7 +9,7 @@ from starknet_py.net.models.typed_data import Revision from starknet_py.tests.e2e.fixtures.constants import TYPED_DATA_DIR -from starknet_py.utils.typed_data import Domain, TypedData, get_hex +from starknet_py.utils.typed_data import Domain, Parameter, TypedData, get_hex class CasesRev0(Enum): @@ -17,10 +17,12 @@ class CasesRev0(Enum): TD_STRING = "typed_data_rev_0_long_string_example.json" TD_FELT_ARR = "typed_data_rev_0_felt_array_example.json" TD_STRUCT_ARR = "typed_data_rev_0_struct_array_example.json" + TD_STRUCT_MERKLE_TREE = "typed_data_rev_0_struct_merkletree_example.json" class CasesRev1(Enum): TD = "typed_data_rev_1_example.json" + TD_FELT_MERKLE_TREE = "typed_data_rev_1_felt_merkletree_example.json" def load_typed_data(file_name: str) -> TypedData: @@ -47,6 +49,7 @@ def test_get_hex(value, result): "example, type_name, encoded_type", [ (CasesRev0.TD.value, "Mail", "Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)"), + (CasesRev0.TD_STRUCT_MERKLE_TREE.value, "Session", "Session(key:felt,expires:felt,root:merkletree)"), (CasesRev0.TD_FELT_ARR.value, "Mail", "Mail(from:Person,to:Person,felts_len:felt,felts:felt*)Person(name:felt,wallet:felt)"), (CasesRev0.TD_STRING.value, "Mail", @@ -55,6 +58,7 @@ def test_get_hex(value, result): "Mail(from:Person,to:Person,posts_len:felt,posts:Post*)Person(name:felt,wallet:felt)Post(title:felt,content:felt)"), (CasesRev1.TD.value, "Mail", """"Mail"("from":"Person","to":"Person","contents":"felt")"Person"("name":"felt","wallet":"felt")"""), + (CasesRev1.TD_FELT_MERKLE_TREE.value, "Example", """"Example"("value":"felt","root":"merkletree")""") ], ) def test_encode_type(example, type_name, encoded_type): @@ -75,9 +79,15 @@ def test_encode_type(example, type_name, encoded_type): (CasesRev0.TD_FELT_ARR.value, "Mail", "0x5b03497592c0d1fe2f3667b63099761714a895c7df96ec90a85d17bfc7a7a0"), (CasesRev0.TD_STRUCT_ARR.value, "Post", "0x1d71e69bf476486b43cdcfaf5a85c00bb2d954c042b281040e513080388356d"), (CasesRev0.TD_STRUCT_ARR.value, "Mail", "0x873b878e35e258fc99e3085d5aaad3a81a0c821f189c08b30def2cde55ff27"), + (CasesRev0.TD_STRUCT_MERKLE_TREE.value, "Session", + "0x1aa0e1c56b45cf06a54534fa1707c54e520b842feb21d03b7deddb6f1e340c"), + (CasesRev0.TD_STRUCT_MERKLE_TREE.value, "Policy", + "0x2f0026e78543f036f33e26a8f5891b88c58dc1e20cbbfaf0bb53274da6fa568"), (CasesRev1.TD.value, "StarknetDomain", "0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210"), (CasesRev1.TD.value, "Person", "0x30f7aa21b8d67cb04c30f962dd29b95ab320cb929c07d1605f5ace304dadf34"), (CasesRev1.TD.value, "Mail", "0x560430bf7a02939edd1a5c104e7b7a55bbab9f35928b1cf5c7c97de3a907bd"), + (CasesRev1.TD_FELT_MERKLE_TREE.value, "Example", + "0x160b9c0e8a7c561f9c5d9e3cc2990a1b4d26e94aa319e9eb53e163cd06c71be"), ], ) def test_type_hash(example, type_name, type_hash): @@ -98,8 +108,12 @@ def test_type_hash(example, type_name, type_hash): "0x26186b02dddb59bf12114f771971b818f48fad83c373534abebaaa39b63a7ce"), (CasesRev0.TD_STRUCT_ARR.value, "Mail", "message", "0x5650ec45a42c4776a182159b9d33118a46860a6e6639bb8166ff71f3c41eaef"), + (CasesRev0.TD_STRUCT_MERKLE_TREE.value, "Session", "message", + "0x73602062421caf6ad2e942253debfad4584bff58930981364dcd378021defe8"), (CasesRev1.TD.value, "StarknetDomain", "domain", "0x555f72e550b308e50c1a4f8611483a174026c982a9893a05c185eeb85399657"), + (CasesRev1.TD_FELT_MERKLE_TREE.value, "Example", "message", + "0x40ef40c56c0469799a916f0b7e3bc4f1bbf28bf659c53fb8c5ee4d8d1b4f5f0") ], ) def test_struct_hash(example, type_name, attr_name, struct_hash): @@ -122,8 +136,12 @@ def test_struct_hash(example, type_name, attr_name, struct_hash): "0x30ab43ef724b08c3b0a9bbe425e47c6173470be75d1d4c55fd5bf9309896bce"), (CasesRev0.TD_STRUCT_ARR.value, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x5914ed2764eca2e6a41eb037feefd3d2e33d9af6225a9e7fe31ac943ff712c"), + (CasesRev0.TD_STRUCT_MERKLE_TREE.value, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x5d28fa1b31f92e63022f7d85271606e52bed89c046c925f16b09e644dc99794"), (CasesRev1.TD.value, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x7f6e8c3d8965b5535f5cc68f837c04e3bbe568535b71aa6c621ddfb188932b8"), + (CasesRev1.TD_FELT_MERKLE_TREE.value, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x4f706783e0d7d0e61433d41343a248a213e9ab341d50ba978dfc055f26484c9") ], ) def test_message_hash(example, account_address, msg_hash): @@ -134,18 +152,18 @@ def test_message_hash(example, account_address, msg_hash): domain_type_v0 = { "StarkNetDomain": [ - {"name": "name", "type": "felt"}, - {"name": "version", "type": "felt"}, - {"name": "chainId", "type": "felt"}, + Parameter(name="name", type="felt"), + Parameter(name="version", type="felt"), + Parameter(name="chainId", type="felt"), ] } domain_type_v1 = { "StarknetDomain": [ - {"name": "name", "type": "shortstring"}, - {"name": "version", "type": "shortstring"}, - {"name": "chainId", "type": "shortstring"}, - {"name": "revision", "type": "shortstring"}, + Parameter(name="name", type="shortstring"), + Parameter(name="version", type="shortstring"), + Parameter(name="chainId", type="shortstring"), + Parameter(name="revision", type="shortstring"), ] } @@ -162,13 +180,6 @@ def test_message_hash(example, account_address, msg_hash): revision=Revision.V1, ) -domain_object_v1 = { - "name": "DomainV1", - "version": "1", - "chainId": "1234", - "revision": 1 -} - def _make_typed_data(included_type: str, revision: Revision): domain_type, domain = (domain_type_v0, domain_v0) if revision == Revision.V0 else ( @@ -191,7 +202,8 @@ def _make_typed_data(included_type: str, revision: Revision): "felt", "felt*", "string", - "selector" + "selector", + "merkletree" ], ) def test_invalid_types(included_type: str):