Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MerkleTree and merkletree support in TypedData #1363

Merged
merged 98 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 81 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
95fe8a8
Differentiate typed data files by revision
franciszekjob Jun 14, 2024
139f970
Add `HashMethod`
franciszekjob Jun 14, 2024
1317848
Add `Revision` enum; Rename `StarkNetDomain` to `Domain`
franciszekjob Jun 14, 2024
2cf0aaa
Add tests to revision 1
franciszekjob Jun 14, 2024
9d9fda3
Remove prints
franciszekjob Jun 14, 2024
f67cdb7
Add more rev 1 test cases to `test_type_hash`
franciszekjob Jun 14, 2024
5469dd3
Change values param to `List[int]` in `HashMethod.hash()`
franciszekjob Jun 16, 2024
1be0060
Add `DomainSchema`; Add `RevisionField` and `ChainIdField`
franciszekjob Jun 17, 2024
9781a21
Move `HashMethod` to separate file
franciszekjob Jun 17, 2024
a7e7f89
Refactor `Domain.to_dict()`
franciszekjob Jun 17, 2024
57c16ec
Update comment
franciszekjob Jun 17, 2024
c648f84
Add `TypedData._hash_method` field
franciszekjob Jun 17, 2024
74cf615
Remove `TypedData._hash_method` field
franciszekjob Jun 17, 2024
549e143
Update docs
franciszekjob Jun 17, 2024
9c67d18
Update docs
franciszekjob Jun 17, 2024
be1e1ec
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 17, 2024
23562a4
Update comment
franciszekjob Jun 17, 2024
1c4cde6
Reduce `Domain._verify_types()`
franciszekjob Jun 18, 2024
e158122
Remove unnecessary variable duplication
franciszekjob Jun 18, 2024
57ed19f
Remove unused functions
franciszekjob Jun 18, 2024
64a7a6a
Add merkle tree type data jsons
franciszekjob Jun 20, 2024
599f6bd
Add `HashMethod.hash_many()`
franciszekjob Jun 20, 2024
8b6e28d
Add `MerkleTree` dataclass; Add merkle tree tests
franciszekjob Jun 20, 2024
d7540eb
Add `Parameter.contains` to handle merkle parameter
franciszekjob Jun 20, 2024
9290b5e
Introduce merkle trees in `TypedData`; Add merkle tree cases in typed…
franciszekjob Jun 20, 2024
3d92324
Convert `TypedData._hash_method` to `@property`
franciszekjob Jun 20, 2024
0c651d4
Change `Domain.revision` type to `Revision`
franciszekjob Jun 20, 2024
961e489
Use equality comparison for `resolved_revision`
franciszekjob Jun 20, 2024
e98ed80
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 20, 2024
59a7b2e
Set `revision` in `Domain` typed dict to be `Optional`
franciszekjob Jun 20, 2024
80e9b87
Update import
franciszekjob Jun 20, 2024
9335160
Refactor `Domain.to_dixt()`
franciszekjob Jun 20, 2024
ff8431a
Resolve conflicts with upstream branch
franciszekjob Jun 20, 2024
f189b04
Format
franciszekjob Jun 20, 2024
8d31b56
Fix doc examples for `sign_message` and `verify_message`; Update func…
franciszekjob Jun 20, 2024
493f4fa
Remove unused import
franciszekjob Jun 20, 2024
186df9d
Remove `test_sign_message_rev_v0` and `test_verify_message_rev_v0`
franciszekjob Jun 20, 2024
ed0f402
Change `sign_message()` and `verify_message()` to accept `TypedData` …
franciszekjob Jun 20, 2024
73680cb
Update migration guide
franciszekjob Jun 20, 2024
1131277
Resolve conflicts
franciszekjob Jun 20, 2024
256b334
Update `test_sign_offchain_message()` to use `TypedData` class instance
franciszekjob Jun 20, 2024
6b52ea5
Update previous changes in `sign_message()` and `verify_message()`
franciszekjob Jun 20, 2024
0d3b592
Update `sign_message()` and `verify_message()` description comments
franciszekjob Jun 20, 2024
0c80872
Minor `DomainSchema.make_dataclass()` refactor
franciszekjob Jun 20, 2024
35ddc45
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 20, 2024
10d9a1c
Minor refactor of `HashMethod`
franciszekjob Jun 20, 2024
48d90a8
Merge branch 'development' of https://github.com/software-mansion/sta…
franciszekjob Jun 20, 2024
46b8a48
Resolve conflicts with upstream branch
franciszekjob Jun 21, 2024
29e540d
Restore retrieving `revision` using `data.get()`
franciszekjob Jun 21, 2024
d0602af
Resolve conflicts with upstream branch
franciszekjob Jun 21, 2024
5c9c931
Add pylint directives to disable specific warnings
franciszekjob Jun 21, 2024
deef39d
Format
franciszekjob Jun 21, 2024
5ff98ed
Restore `**kwargs` in `make_dataclass()` methods
franciszekjob Jun 21, 2024
c4c9379
Add missing type annotation in `test_invalid_types()`
franciszekjob Jun 21, 2024
ff1d408
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 21, 2024
39982e3
Update `RevisionField._deserialize()` return type to `Revision`
franciszekjob Jun 21, 2024
dfec5e9
Add custom error message on missing value in `Revision` enum
franciszekjob Jun 21, 2024
3ae8bd7
Add module in migration guide to enable hyperlinks
franciszekjob Jun 21, 2024
09ad981
Check error message in `test_invalid_types`
franciszekjob Jun 21, 2024
e5240c0
Add `Parameter.to_dict()` and `TypedData.to_dict()`
franciszekjob Jun 21, 2024
27c24a1
Move `Revision` to common schemas
franciszekjob Jun 21, 2024
e6a2f26
Format
franciszekjob Jun 21, 2024
f604dbb
Set `ChainIdField._deserialize()` return type to `str`; Change 'Domai…
franciszekjob Jun 21, 2024
5505baa
Remove unnecessary import
franciszekjob Jun 21, 2024
3e00175
Resolve conflicts with upstream branch
franciszekjob Jun 21, 2024
bd6fc8f
Restore previous import of `keccak256`
franciszekjob Jun 21, 2024
37b650b
Format
franciszekjob Jun 21, 2024
7e1fcd1
Add newline in typed data rev 1 example json
franciszekjob Jun 21, 2024
1383a00
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 21, 2024
1477a3d
Add newlines in typed data example jsons
franciszekjob Jun 21, 2024
f355eca
Update starknet_py/utils/merkle_tree.py
franciszekjob Jun 21, 2024
65f3af9
Merge branch 'franciszekjob/1353-2-merkletree' of https://github.com/…
franciszekjob Jun 21, 2024
30f8bc9
Use `str` instead of `int` in `MerkleTree`
franciszekjob Jun 21, 2024
59ed61d
Format
franciszekjob Jun 24, 2024
36a6cb4
Remove unnecessary `ChainIdField`
franciszekjob Jun 24, 2024
c057544
Change chainId from `int` to `str` in all examples
franciszekjob Jun 24, 2024
2da5a66
Refactor `TypedData.to_dict()`
franciszekjob Jun 24, 2024
2b5a44d
Resolve conflicts with upstream branch
franciszekjob Jun 24, 2024
4d6d05f
Format
franciszekjob Jun 24, 2024
04a8d14
Rename `branches` to `levels` in `MerkleTree`
franciszekjob Jun 24, 2024
d429b4e
Rename variables in `MerkleTree.build()`
franciszekjob Jun 24, 2024
65e79fc
Remove `Parameter.to_dict()`; Add `RevisionField._serialize()`
franciszekjob Jun 24, 2024
f64ff2e
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 24, 2024
3574b8c
Update starknet_py/net/schemas/common.py
franciszekjob Jun 24, 2024
0412e27
Update starknet_py/net/schemas/common.py
franciszekjob Jun 24, 2024
6d59203
Merge branch 'franciszekjob/1353-snip-12' of https://github.com/softw…
franciszekjob Jun 24, 2024
91ef12c
Remove revision check in `get_hex()`
franciszekjob Jun 24, 2024
98cd0db
Refactor `MerkleTree` implementation; Update merkle tree tests
franciszekjob Jun 25, 2024
eccf2c9
Format
franciszekjob Jun 25, 2024
643d387
Remove unused `domain_object_v1`
franciszekjob Jun 25, 2024
4552e81
Update example domain types in typed data tests
franciszekjob Jun 25, 2024
a59c8a2
Format
franciszekjob Jun 25, 2024
c490faf
Add leaves and root hash to returned merkle tree levels
franciszekjob Jun 25, 2024
faee6d4
Add check for merkle tree levels count
franciszekjob Jun 25, 2024
5c3b4f4
Refactor `TypedData._get_merkle_tree_leaves_type()`
franciszekjob Jun 26, 2024
f1dda04
Update starknet_py/utils/typed_data.py
franciszekjob Jun 26, 2024
68d6217
Update starknet_py/utils/typed_data.py
franciszekjob Jun 26, 2024
963dc34
Fix lint
franciszekjob Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/api/typed_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ Parameter
:exclude-members: __init__

--------------
StarkNetDomain
Domain
--------------

.. autoclass:: starknet_py.net.models.typed_data.StarkNetDomainDict
.. autoclass:: starknet_py.utils.typed_data.Domain
:members:
:undoc-members:
:exclude-members: __init__
:member-order: bysource

18 changes: 18 additions & 0 deletions docs/migration_guide.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Migration guide
===============

******************************
0.23.0 Migration guide
******************************

0.23.0 Targeted versions
------------------------

- Starknet - `0.13.1.1 <https://docs.starknet.io/documentation/starknet_versions/version_notes/#version0.13.1.1>`_
- RPC - `0.7.1 <https://github.com/starkware-libs/starknet-specs/releases/tag/v0.7.1>`_

0.23.0 Breaking changes
-----------------------
.. currentmodule:: starknet_py.utils.typed_data

1. :class:`StarkNetDomain` has been renamed to :class:`Domain`

2. :class:`TypedData` field ``domain`` has been changed from ``dict`` to :class:`Domain`

******************************
0.22.0 Migration guide
******************************
Expand Down
29 changes: 29 additions & 0 deletions starknet_py/hash/hash_method.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from enum import Enum
from typing import List

from poseidon_py.poseidon_hash import poseidon_hash, poseidon_hash_many

from starknet_py.hash.utils import compute_hash_on_elements, pedersen_hash


class HashMethod(Enum):
"""
Enum representing hash method.
"""

PEDERSEN = "pedersen"
POSEIDON = "poseidon"

def hash(self, left: int, right: int):
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
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:
return poseidon_hash_many(values)
raise ValueError(f"Unsupported hash method: {self}.")
2 changes: 2 additions & 0 deletions starknet_py/net/account/base_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ def sign_message(self, typed_data: TypedDataDict) -> List[int]:
"""
Sign an TypedData TypedDict for off-chain usage with the Starknet private key and return the signature.
This adds a message prefix, so it can't be interchanged with transactions.
Both v0 and v1 domain revision versions are supported.

:param typed_data: TypedData TypedDict to be signed.
:return: The signature of the TypedData TypedDict.
Expand All @@ -327,6 +328,7 @@ def sign_message(self, typed_data: TypedDataDict) -> List[int]:
def verify_message(self, typed_data: TypedDataDict, signature: List[int]) -> bool:
"""
Verify a signature of a TypedData dict on Starknet.
Both v0 and v1 domain revision versions are supported.

:param typed_data: TypedData TypedDict to be verified.
:param signature: signature of the TypedData TypedDict.
Expand Down
19 changes: 14 additions & 5 deletions starknet_py/net/models/typed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
TypedDict structures for TypedData
"""

from typing import Any, Dict, List, TypedDict, Union
from typing import Any, Dict, List, Optional, TypedDict

from starknet_py.net.schemas.common import Revision


class ParameterDict(TypedDict):
Expand All @@ -12,16 +14,18 @@ class ParameterDict(TypedDict):

name: str
type: str
contains: Optional[str]


class StarkNetDomainDict(TypedDict):
class DomainDict(TypedDict):
"""
TypedDict representing a StarkNetDomain object
TypedDict representing a domain object (both StarkNetDomain, StarknetDomain).
"""

name: str
version: str
chainId: Union[str, int]
chainId: str
revision: Optional[Revision]


class TypedDataDict(TypedDict):
Expand All @@ -31,5 +35,10 @@ class TypedDataDict(TypedDict):

types: Dict[str, List[ParameterDict]]
primaryType: str
domain: StarkNetDomainDict
domain: DomainDict
message: Dict[str, Any]


class TypeContext(TypedDict):
parent: str
key: str
franciszekjob marked this conversation as resolved.
Show resolved Hide resolved
25 changes: 25 additions & 0 deletions starknet_py/net/schemas/common.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re
import sys
from enum import Enum
from typing import Any, Mapping, Optional, Union

from marshmallow import Schema, ValidationError, fields, post_load
Expand Down Expand Up @@ -336,3 +337,27 @@ class StorageEntrySchema(Schema):
def make_dataclass(self, data, **kwargs):
# pylint: disable=no-self-use
return StorageEntry(**data)


class Revision(Enum):
"""
Enum representing the revision of the specification to be used.
"""

V0 = 0
V1 = 1


class RevisionField(fields.Field):
def _deserialize(self, value, attr, data, **kwargs) -> Revision:
if isinstance(value, str):
value = int(value)

revisions = [revision.value for revision in Revision]
if value not in revisions:
allowed_revisions_str = "".join(list(map(str, revisions)))
raise ValidationError(
f"Invalid value provided for Revision: {value}. Allowed values are {allowed_revisions_str}."
)

return Revision(value)
4 changes: 2 additions & 2 deletions starknet_py/tests/e2e/docs/code_examples/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def test_sign_message(account):
],
},
primaryType="Example",
domain={"name": "StarkNet Example", "version": "1", "chainId": 1},
domain={"name": "StarkNet Example", "version": "1", "chainId": "1"},
message={"value": 1},
)
)
Expand All @@ -100,7 +100,7 @@ def test_verify_message(account):
],
},
primaryType="Example",
domain={"name": "StarkNet Example", "version": "1", "chainId": 1},
domain={"name": "StarkNet Example", "version": "1", "chainId": "1"},
message={"value": 1},
),
signature=[12, 34],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def test_sign_offchain_message(account):
],
},
"primaryType": "Mail",
"domain": {"name": "StarkNet Mail", "version": "1", "chainId": 1},
"domain": {"name": "StarkNet Mail", "version": "1", "chainId": "1"},
"message": {
"from": {
"name": "Cow",
Expand Down
9 changes: 5 additions & 4 deletions starknet_py/tests/e2e/fixtures/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ def pytest_addoption(parser):

@pytest.fixture(
params=[
"typed_data_example.json",
"typed_data_felt_array_example.json",
"typed_data_long_string_example.json",
"typed_data_struct_array_example.json",
"typed_data_rev_0_example.json",
"typed_data_rev_0_felt_array_example.json",
"typed_data_rev_0_long_string_example.json",
"typed_data_rev_0_struct_array_example.json",
"typed_data_rev_1_example.json",
],
)
def typed_data(request) -> TypedDataDict:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": 1
"chainId": "1"
},
"message": {
"from": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": 1
"chainId": "1"
},
"message": {
"from": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": 1
"chainId": "1"
},
"message": {
"from": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": 1
"chainId": "1"
},
"message": {
"from": {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"types": {
"StarknetDomain": [
{ "name": "name", "type": "shortstring" },
{ "name": "version", "type": "shortstring" },
{ "name": "chainId", "type": "shortstring" },
{ "name": "revision", "type": "shortstring" }
],
"Person": [
{ "name": "name", "type": "felt" },
{ "name": "wallet", "type": "felt" }
],
"Mail": [
{ "name": "from", "type": "Person" },
{ "name": "to", "type": "Person" },
{ "name": "contents", "type": "felt" }
]
},
"primaryType": "Mail",
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chainId": "1",
"revision": 1
},
"message": {
"from": {
"name": "Cow",
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
},
"to": {
"name": "Bob",
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
},
"contents": "Hello, Bob!"
}
}
Original file line number Diff line number Diff line change
@@ -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"
]
}
}
Loading
Loading