Skip to content

Commit

Permalink
feat: allow merkle trees as native type in eip712 like messages
Browse files Browse the repository at this point in the history
  • Loading branch information
janek26 committed Aug 18, 2022
1 parent 9ac48cc commit 65b7766
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 57 deletions.
25 changes: 25 additions & 0 deletions __mocks__/typedDataRawSessionExample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"primaryType": "Session",
"types": {
"Session": [
{ "name": "key", "type": "felt" },
{ "name": "expires", "type": "felt" },
{ "name": "root", "type": "raw" }
],
"StarkNetDomain": [
{ "name": "name", "type": "felt" },
{ "name": "version", "type": "felt" },
{ "name": "chain_id", "type": "felt" }
]
},
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chain_id": 1
},
"message": {
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
"expires": "0x0000000000000000000000000000000000000000000000000000000000000000",
"root": "0x0"
}
}
42 changes: 42 additions & 0 deletions __mocks__/typedDataSessionExample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"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": "chain_id", "type": "felt" }
]
},
"domain": {
"name": "StarkNet Mail",
"version": "1",
"chain_id": 1
},
"message": {
"key": "0x0000000000000000000000000000000000000000000000000000000000000000",
"expires": "0x0000000000000000000000000000000000000000000000000000000000000000",
"root": [
{
"contractAddress": "0x1",
"selector": "transfer"
},
{
"contractAddress": "0x2",
"selector": "transfer"
},
{
"contractAddress": "0x3",
"selector": "transfer"
}
]
}
}
116 changes: 107 additions & 9 deletions __tests__/utils/typedData.test.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,134 @@
import typedDataExample from '../../__mocks__/typedDataExample.json';
import typedDataSessionExample from '../../__mocks__/typedDataSessionExample.json';
import typedDataStructArrayExample from '../../__mocks__/typedDataStructArrayExample.json';
import { number } from '../../src';
import { getSelectorFromName } from '../../src/utils/hash';
import { MerkleTree } from '../../src/utils/merkle';
import { BigNumberish } from '../../src/utils/number';
import { encodeType, getMessageHash, getStructHash, getTypeHash } from '../../src/utils/typedData';
import {
StarkNetDomain,
encodeType,
encodeValue,
getMessageHash,
getStructHash,
getTypeHash,
} from '../../src/utils/typedData';

describe('typedData', () => {
test('should get right type encoding', () => {
const typeEncoding = encodeType(typedDataExample, 'Mail');
const typeEncoding = encodeType(typedDataExample.types, 'Mail');
expect(typeEncoding).toMatchInlineSnapshot(
`"Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)"`
);
const typeEncodingStructArr = encodeType(typedDataStructArrayExample, 'Mail');
const typeEncodingStructArr = encodeType(typedDataStructArrayExample.types, 'Mail');
expect(typeEncodingStructArr).toMatchInlineSnapshot(
`"Mail(from:Person,to:Person,posts_len:felt,posts:Post*)Person(name:felt,wallet:felt)Post(title:felt,content:felt)"`
);
});

test('should get right type hash', () => {
const typeHashDomain = getTypeHash(typedDataExample, 'StarkNetDomain');
const typeHashDomain = getTypeHash(typedDataExample.types, 'StarkNetDomain');
expect(typeHashDomain).toMatchInlineSnapshot(
`"0x1bfc207425a47a5dfa1a50a4f5241203f50624ca5fdf5e18755765416b8e288"`
);
const typeHashPerson = getTypeHash(typedDataExample, 'Person');
const typeHashPerson = getTypeHash(typedDataExample.types, 'Person');
expect(typeHashPerson).toMatchInlineSnapshot(
`"0x2896dbe4b96a67110f454c01e5336edc5bbc3635537efd690f122f4809cc855"`
);
const typeHashMail = getTypeHash(typedDataExample, 'Mail');
const typeHashMail = getTypeHash(typedDataExample.types, 'Mail');
expect(typeHashMail).toMatchInlineSnapshot(
`"0x13d89452df9512bf750f539ba3001b945576243288137ddb6c788457d4b2f79"`
);
const typeHashPost = getTypeHash(typedDataStructArrayExample, 'Post');
const typeHashPost = getTypeHash(typedDataStructArrayExample.types, 'Post');
expect(typeHashPost).toMatchInlineSnapshot(
`"0x1d71e69bf476486b43cdcfaf5a85c00bb2d954c042b281040e513080388356d"`
);
const typeHashMailWithStructArray = getTypeHash(typedDataStructArrayExample, 'Mail');
const typeHashMailWithStructArray = getTypeHash(typedDataStructArrayExample.types, 'Mail');
expect(typeHashMailWithStructArray).toMatchInlineSnapshot(
`"0x873b878e35e258fc99e3085d5aaad3a81a0c821f189c08b30def2cde55ff27"`
);
const selectorTypeHash = getTypeHash({}, 'selector');
expect(selectorTypeHash).toMatchInlineSnapshot(
`"0x1d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"`
);
});

test('should transform type selector', () => {
const selector = 'transfer';
const selectorHash = getSelectorFromName(selector);
const rawSelectorValueHash = encodeValue({}, 'raw', selectorHash);
const selectorValueHash = encodeValue({}, 'selector', selector);
expect(selectorValueHash).toEqual(rawSelectorValueHash);
expect(selectorValueHash).toMatchInlineSnapshot(`
Array [
"felt",
"0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e",
]
`);
});

test('should transform merkle tree', () => {
const tree = new MerkleTree(['0x1', '0x2', '0x3']);
const [, merkleTreeHash] = encodeValue({}, 'merkletree', tree.leaves);
expect(merkleTreeHash).toBe(tree.root);
expect(merkleTreeHash).toMatchInlineSnapshot(
`"0x551b4adb6c35d49c686a00b9192da9332b18c9b262507cad0ece37f3b6918d2"`
);
});

test('should transform merkle tree with custom types', () => {
const leaves = [
{
contractAddress: '0x1',
selector: 'transfer',
},
{
contractAddress: '0x2',
selector: 'transfer',
},
{
contractAddress: '0x3',
selector: 'transfer',
},
];
const hashedLeaves = leaves.map(
(leaf) =>
encodeValue(
{
Policy: [
{ name: 'contractAddress', type: 'felt' },
{ name: 'selector', type: 'selector' },
],
},
'Policy',
leaf
)[1]
);
const tree = new MerkleTree(hashedLeaves);
const [, merkleTreeHash] = encodeValue(
{
Parent: [{ name: 'root', type: 'merkletree', contains: 'Policy' }],
Policy: [
{ name: 'contractAddress', type: 'felt' },
{ name: 'selector', type: 'selector' },
],
},
'merkletree',
leaves,
{ key: 'root', parent: 'Parent' }
);
expect(merkleTreeHash).toBe(tree.root);
expect(merkleTreeHash).toMatchInlineSnapshot(
`"0x75c4f467f4527a5348f3e302007228a6b0057fc4c015f981ffb5b3ace475727"`
);
});

test('should get right hash for StarkNetDomain', () => {
const hash = getStructHash(typedDataExample, 'StarkNetDomain', typedDataExample.domain as any);
const hash = getStructHash(
typedDataExample.types,
'StarkNetDomain',
typedDataExample.domain as StarkNetDomain
);
expect(hash).toMatchInlineSnapshot(
`"0x54833b121883a3e3aebff48ec08a962f5742e5f7b973469c1f8f4f55d470b07"`
);
Expand Down Expand Up @@ -122,4 +210,14 @@ describe('typedData', () => {
`"0x70338fb11b8f70b68b261de8a322bcb004bd85e88ac47d9147982c7f5ac66fd"`
);
});

test('should transform session message correctly', () => {
const hash = getMessageHash(
typedDataSessionExample,
'0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826'
);
expect(hash).toMatchInlineSnapshot(
`"0x1ad0330a62a4a94eae5ea1a7ad96388179d2e4d33e6f909d17421d315110653"`
);
});
});
23 changes: 13 additions & 10 deletions src/utils/session.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { AccountInterface } from '../account';
import { Signature } from '../types';
import { getSelectorFromName, pedersen } from './hash';
import { pedersen } from './hash';
import { MerkleTree } from './merkle';
import { isHex } from './number';
import { StarkNetDomain } from './typedData';
import { StarkNetDomain, prepareSelector } from './typedData';

interface Policy {
contractAddress: string;
Expand All @@ -27,13 +26,13 @@ export interface SignedSession extends PreparedSession {
signature: Signature;
}

function preparePolicy({ contractAddress, selector }: Policy): string {
return pedersen([contractAddress, prepareSelector(selector)]);
}

export function prepareSession(session: RequestSession): PreparedSession {
const { policies, ...rest } = session;
const { root } = new MerkleTree(
policies.map(({ contractAddress, selector }) =>
pedersen([contractAddress, isHex(selector) ? selector : getSelectorFromName(selector)])
)
);
const { root } = new MerkleTree(policies.map(preparePolicy));
return { ...rest, root };
}

Expand All @@ -46,10 +45,14 @@ export async function createSession(
const signature = await account.signMessage({
primaryType: 'Session',
types: {
Policy: [
{ name: 'contractAddress', type: 'felt' },
{ name: 'selector', type: 'selector' },
],
Session: [
{ name: 'key', type: 'felt' },
{ name: 'expires', type: 'felt' },
{ name: 'root', type: 'felt' },
{ name: 'root', type: 'merkletree', contains: 'Policy*' },
],
StarkNetDomain: [
{ name: 'name', type: 'felt' },
Expand All @@ -61,7 +64,7 @@ export async function createSession(
message: {
key,
expires,
root,
root: session.policies,
},
});
return {
Expand Down
Loading

0 comments on commit 65b7766

Please sign in to comment.