Skip to content

Commit 65b7766

Browse files
committed
feat: allow merkle trees as native type in eip712 like messages
1 parent 9ac48cc commit 65b7766

File tree

6 files changed

+288
-57
lines changed

6 files changed

+288
-57
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"primaryType": "Session",
3+
"types": {
4+
"Session": [
5+
{ "name": "key", "type": "felt" },
6+
{ "name": "expires", "type": "felt" },
7+
{ "name": "root", "type": "raw" }
8+
],
9+
"StarkNetDomain": [
10+
{ "name": "name", "type": "felt" },
11+
{ "name": "version", "type": "felt" },
12+
{ "name": "chain_id", "type": "felt" }
13+
]
14+
},
15+
"domain": {
16+
"name": "StarkNet Mail",
17+
"version": "1",
18+
"chain_id": 1
19+
},
20+
"message": {
21+
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
22+
"expires": "0x0000000000000000000000000000000000000000000000000000000000000000",
23+
"root": "0x0"
24+
}
25+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"primaryType": "Session",
3+
"types": {
4+
"Policy": [
5+
{ "name": "contractAddress", "type": "felt" },
6+
{ "name": "selector", "type": "selector" }
7+
],
8+
"Session": [
9+
{ "name": "key", "type": "felt" },
10+
{ "name": "expires", "type": "felt" },
11+
{ "name": "root", "type": "merkletree", "contains": "Policy" }
12+
],
13+
"StarkNetDomain": [
14+
{ "name": "name", "type": "felt" },
15+
{ "name": "version", "type": "felt" },
16+
{ "name": "chain_id", "type": "felt" }
17+
]
18+
},
19+
"domain": {
20+
"name": "StarkNet Mail",
21+
"version": "1",
22+
"chain_id": 1
23+
},
24+
"message": {
25+
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
26+
"expires": "0x0000000000000000000000000000000000000000000000000000000000000000",
27+
"root": [
28+
{
29+
"contractAddress": "0x1",
30+
"selector": "transfer"
31+
},
32+
{
33+
"contractAddress": "0x2",
34+
"selector": "transfer"
35+
},
36+
{
37+
"contractAddress": "0x3",
38+
"selector": "transfer"
39+
}
40+
]
41+
}
42+
}

__tests__/utils/typedData.test.ts

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,134 @@
11
import typedDataExample from '../../__mocks__/typedDataExample.json';
2+
import typedDataSessionExample from '../../__mocks__/typedDataSessionExample.json';
23
import typedDataStructArrayExample from '../../__mocks__/typedDataStructArrayExample.json';
34
import { number } from '../../src';
5+
import { getSelectorFromName } from '../../src/utils/hash';
6+
import { MerkleTree } from '../../src/utils/merkle';
47
import { BigNumberish } from '../../src/utils/number';
5-
import { encodeType, getMessageHash, getStructHash, getTypeHash } from '../../src/utils/typedData';
8+
import {
9+
StarkNetDomain,
10+
encodeType,
11+
encodeValue,
12+
getMessageHash,
13+
getStructHash,
14+
getTypeHash,
15+
} from '../../src/utils/typedData';
616

717
describe('typedData', () => {
818
test('should get right type encoding', () => {
9-
const typeEncoding = encodeType(typedDataExample, 'Mail');
19+
const typeEncoding = encodeType(typedDataExample.types, 'Mail');
1020
expect(typeEncoding).toMatchInlineSnapshot(
1121
`"Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)"`
1222
);
13-
const typeEncodingStructArr = encodeType(typedDataStructArrayExample, 'Mail');
23+
const typeEncodingStructArr = encodeType(typedDataStructArrayExample.types, 'Mail');
1424
expect(typeEncodingStructArr).toMatchInlineSnapshot(
1525
`"Mail(from:Person,to:Person,posts_len:felt,posts:Post*)Person(name:felt,wallet:felt)Post(title:felt,content:felt)"`
1626
);
1727
});
1828

1929
test('should get right type hash', () => {
20-
const typeHashDomain = getTypeHash(typedDataExample, 'StarkNetDomain');
30+
const typeHashDomain = getTypeHash(typedDataExample.types, 'StarkNetDomain');
2131
expect(typeHashDomain).toMatchInlineSnapshot(
2232
`"0x1bfc207425a47a5dfa1a50a4f5241203f50624ca5fdf5e18755765416b8e288"`
2333
);
24-
const typeHashPerson = getTypeHash(typedDataExample, 'Person');
34+
const typeHashPerson = getTypeHash(typedDataExample.types, 'Person');
2535
expect(typeHashPerson).toMatchInlineSnapshot(
2636
`"0x2896dbe4b96a67110f454c01e5336edc5bbc3635537efd690f122f4809cc855"`
2737
);
28-
const typeHashMail = getTypeHash(typedDataExample, 'Mail');
38+
const typeHashMail = getTypeHash(typedDataExample.types, 'Mail');
2939
expect(typeHashMail).toMatchInlineSnapshot(
3040
`"0x13d89452df9512bf750f539ba3001b945576243288137ddb6c788457d4b2f79"`
3141
);
32-
const typeHashPost = getTypeHash(typedDataStructArrayExample, 'Post');
42+
const typeHashPost = getTypeHash(typedDataStructArrayExample.types, 'Post');
3343
expect(typeHashPost).toMatchInlineSnapshot(
3444
`"0x1d71e69bf476486b43cdcfaf5a85c00bb2d954c042b281040e513080388356d"`
3545
);
36-
const typeHashMailWithStructArray = getTypeHash(typedDataStructArrayExample, 'Mail');
46+
const typeHashMailWithStructArray = getTypeHash(typedDataStructArrayExample.types, 'Mail');
3747
expect(typeHashMailWithStructArray).toMatchInlineSnapshot(
3848
`"0x873b878e35e258fc99e3085d5aaad3a81a0c821f189c08b30def2cde55ff27"`
3949
);
50+
const selectorTypeHash = getTypeHash({}, 'selector');
51+
expect(selectorTypeHash).toMatchInlineSnapshot(
52+
`"0x1d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"`
53+
);
54+
});
55+
56+
test('should transform type selector', () => {
57+
const selector = 'transfer';
58+
const selectorHash = getSelectorFromName(selector);
59+
const rawSelectorValueHash = encodeValue({}, 'raw', selectorHash);
60+
const selectorValueHash = encodeValue({}, 'selector', selector);
61+
expect(selectorValueHash).toEqual(rawSelectorValueHash);
62+
expect(selectorValueHash).toMatchInlineSnapshot(`
63+
Array [
64+
"felt",
65+
"0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e",
66+
]
67+
`);
68+
});
69+
70+
test('should transform merkle tree', () => {
71+
const tree = new MerkleTree(['0x1', '0x2', '0x3']);
72+
const [, merkleTreeHash] = encodeValue({}, 'merkletree', tree.leaves);
73+
expect(merkleTreeHash).toBe(tree.root);
74+
expect(merkleTreeHash).toMatchInlineSnapshot(
75+
`"0x551b4adb6c35d49c686a00b9192da9332b18c9b262507cad0ece37f3b6918d2"`
76+
);
77+
});
78+
79+
test('should transform merkle tree with custom types', () => {
80+
const leaves = [
81+
{
82+
contractAddress: '0x1',
83+
selector: 'transfer',
84+
},
85+
{
86+
contractAddress: '0x2',
87+
selector: 'transfer',
88+
},
89+
{
90+
contractAddress: '0x3',
91+
selector: 'transfer',
92+
},
93+
];
94+
const hashedLeaves = leaves.map(
95+
(leaf) =>
96+
encodeValue(
97+
{
98+
Policy: [
99+
{ name: 'contractAddress', type: 'felt' },
100+
{ name: 'selector', type: 'selector' },
101+
],
102+
},
103+
'Policy',
104+
leaf
105+
)[1]
106+
);
107+
const tree = new MerkleTree(hashedLeaves);
108+
const [, merkleTreeHash] = encodeValue(
109+
{
110+
Parent: [{ name: 'root', type: 'merkletree', contains: 'Policy' }],
111+
Policy: [
112+
{ name: 'contractAddress', type: 'felt' },
113+
{ name: 'selector', type: 'selector' },
114+
],
115+
},
116+
'merkletree',
117+
leaves,
118+
{ key: 'root', parent: 'Parent' }
119+
);
120+
expect(merkleTreeHash).toBe(tree.root);
121+
expect(merkleTreeHash).toMatchInlineSnapshot(
122+
`"0x75c4f467f4527a5348f3e302007228a6b0057fc4c015f981ffb5b3ace475727"`
123+
);
40124
});
41125

42126
test('should get right hash for StarkNetDomain', () => {
43-
const hash = getStructHash(typedDataExample, 'StarkNetDomain', typedDataExample.domain as any);
127+
const hash = getStructHash(
128+
typedDataExample.types,
129+
'StarkNetDomain',
130+
typedDataExample.domain as StarkNetDomain
131+
);
44132
expect(hash).toMatchInlineSnapshot(
45133
`"0x54833b121883a3e3aebff48ec08a962f5742e5f7b973469c1f8f4f55d470b07"`
46134
);
@@ -122,4 +210,14 @@ describe('typedData', () => {
122210
`"0x70338fb11b8f70b68b261de8a322bcb004bd85e88ac47d9147982c7f5ac66fd"`
123211
);
124212
});
213+
214+
test('should transform session message correctly', () => {
215+
const hash = getMessageHash(
216+
typedDataSessionExample,
217+
'0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
218+
);
219+
expect(hash).toMatchInlineSnapshot(
220+
`"0x1ad0330a62a4a94eae5ea1a7ad96388179d2e4d33e6f909d17421d315110653"`
221+
);
222+
});
125223
});

src/utils/session.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { AccountInterface } from '../account';
22
import { Signature } from '../types';
3-
import { getSelectorFromName, pedersen } from './hash';
3+
import { pedersen } from './hash';
44
import { MerkleTree } from './merkle';
5-
import { isHex } from './number';
6-
import { StarkNetDomain } from './typedData';
5+
import { StarkNetDomain, prepareSelector } from './typedData';
76

87
interface Policy {
98
contractAddress: string;
@@ -27,13 +26,13 @@ export interface SignedSession extends PreparedSession {
2726
signature: Signature;
2827
}
2928

29+
function preparePolicy({ contractAddress, selector }: Policy): string {
30+
return pedersen([contractAddress, prepareSelector(selector)]);
31+
}
32+
3033
export function prepareSession(session: RequestSession): PreparedSession {
3134
const { policies, ...rest } = session;
32-
const { root } = new MerkleTree(
33-
policies.map(({ contractAddress, selector }) =>
34-
pedersen([contractAddress, isHex(selector) ? selector : getSelectorFromName(selector)])
35-
)
36-
);
35+
const { root } = new MerkleTree(policies.map(preparePolicy));
3736
return { ...rest, root };
3837
}
3938

@@ -46,10 +45,14 @@ export async function createSession(
4645
const signature = await account.signMessage({
4746
primaryType: 'Session',
4847
types: {
48+
Policy: [
49+
{ name: 'contractAddress', type: 'felt' },
50+
{ name: 'selector', type: 'selector' },
51+
],
4952
Session: [
5053
{ name: 'key', type: 'felt' },
5154
{ name: 'expires', type: 'felt' },
52-
{ name: 'root', type: 'felt' },
55+
{ name: 'root', type: 'merkletree', contains: 'Policy*' },
5356
],
5457
StarkNetDomain: [
5558
{ name: 'name', type: 'felt' },
@@ -61,7 +64,7 @@ export async function createSession(
6164
message: {
6265
key,
6366
expires,
64-
root,
67+
root: session.policies,
6568
},
6669
});
6770
return {

0 commit comments

Comments
 (0)