Skip to content

Commit 04b881d

Browse files
wa0x6eChaituVR
andauthored
feat: add support for more starknet signature format (#1040)
* chore: add tests * fix: add support for validating signatures with `v` element * chore: update test fixtures * fix: app support for [rsv] signature * fix: throw meaningful error when starknet contract is not deployed * fix: validate result before returning * fix: reject with a meaningful error on invalid signature format * fix: default to return false * Revert "fix: default to return false" This reverts commit 0211641. * fix: improve error message * chore: add more tests * fix: fix function not available on lower node version * v0.12.2 --------- Co-authored-by: Chaitanya <[email protected]>
1 parent 383b2ba commit 04b881d

File tree

4 files changed

+141
-37
lines changed

4 files changed

+141
-37
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@snapshot-labs/snapshot.js",
3-
"version": "0.12.1",
3+
"version": "0.12.2",
44
"repository": "snapshot-labs/snapshot.js",
55
"license": "MIT",
66
"main": "dist/snapshot.cjs.js",
@@ -11,6 +11,7 @@
1111
"@ensdomains/eth-ens-namehash": "^2.0.15",
1212
"@ethersproject/abi": "^5.6.4",
1313
"@ethersproject/address": "^5.6.1",
14+
"@ethersproject/bignumber": "^5.7.0",
1415
"@ethersproject/bytes": "^5.6.1",
1516
"@ethersproject/contracts": "^5.6.2",
1617
"@ethersproject/hash": "^5.7.0",

src/verify/starknet.spec.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { test, expect, describe } from 'vitest';
22
import starknetMessage from '../../test/fixtures/starknet/message-alias.json';
3+
import starknetMessageRsv from '../../test/fixtures/starknet/message-alias-rsv.json';
34
import verify, { getHash } from './starknet';
45
import { validateAndParseAddress } from 'starknet';
6+
import { clone } from '../utils';
57

68
describe('verify/starknet', () => {
79
describe('getHash()', () => {
@@ -21,47 +23,79 @@ describe('verify/starknet', () => {
2123
});
2224

2325
describe('verify()', () => {
24-
test('should return true if the signature is valid', () => {
25-
expect(
26-
verify(
27-
starknetMessage.address,
28-
starknetMessage.sig,
29-
starknetMessage.data,
30-
'SN_SEPOLIA'
31-
)
32-
).resolves.toBe(true);
26+
describe.each([
27+
['2', starknetMessage],
28+
['3', starknetMessageRsv]
29+
])('with a %s items signature', (title, message) => {
30+
test('should return true if the signature is valid', () => {
31+
expect(
32+
verify(message.address, message.sig, message.data, 'SN_MAIN')
33+
).resolves.toBe(true);
34+
});
35+
36+
test('should return true if the signature is valid with a padded address', () => {
37+
expect(
38+
verify(
39+
validateAndParseAddress(message.address),
40+
message.sig,
41+
message.data,
42+
'SN_MAIN'
43+
)
44+
).resolves.toBe(true);
45+
});
46+
47+
test('should return true when verifying on a different network', () => {
48+
expect(
49+
verify(message.address, message.sig, message.data, 'SN_SEPOLIA')
50+
).resolves.toBe(true);
51+
});
52+
53+
test('should throw an error if the signature is invalid', () => {
54+
expect(
55+
verify(
56+
'0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1',
57+
message.sig,
58+
message.data
59+
)
60+
).rejects.toThrow();
61+
});
3362
});
3463

35-
test('should return true if the signature is valid with a padded address', () => {
64+
test('should throw an error when the contract is not deployed', () => {
3665
expect(
3766
verify(
38-
validateAndParseAddress(starknetMessage.address),
67+
'0x07f71118e351c02f6EC7099C8CDf93AED66CEd8406E94631cC91637f7D7F203A',
3968
starknetMessage.sig,
4069
starknetMessage.data,
41-
'SN_SEPOLIA'
70+
'SN_MAIN'
4271
)
43-
).resolves.toBe(true);
72+
).rejects.toThrowError('Contract not deployed');
4473
});
4574

46-
test('should return true when verifying on a different network', () => {
75+
test('should return false when the signature is not valid', () => {
4776
expect(
4877
verify(
4978
starknetMessage.address,
50-
starknetMessage.sig,
79+
['1', '2'],
5180
starknetMessage.data,
5281
'SN_MAIN'
5382
)
54-
).resolves.toBe(true);
83+
).resolves.toBe(false);
5584
});
5685

57-
test('should throw an error if the signature is invalid', () => {
86+
test('should return false when the signature is not valid', () => {
87+
const data = clone(starknetMessage.data);
88+
data.message.timestamp = 1234;
89+
5890
expect(
59-
verify(
60-
'0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1',
61-
starknetMessage.sig,
62-
starknetMessage.data
63-
)
64-
).rejects.toThrow();
91+
verify(starknetMessage.address, starknetMessage.sig, data, 'SN_MAIN')
92+
).resolves.toBe(false);
93+
});
94+
95+
test('should throw an error on wrong signature length', () => {
96+
expect(
97+
verify(starknetMessage.address, ['1'], starknetMessage.data, 'SN_MAIN')
98+
).rejects.toThrowError('Invalid signature format');
6599
});
66100
});
67101
});

src/verify/starknet.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Contract, RpcProvider, typedData } from 'starknet';
2+
import { BigNumber } from '@ethersproject/bignumber';
23
import type { SignaturePayload } from '.';
34
import type { ProviderOptions } from '../utils/provider';
45

@@ -11,19 +12,19 @@ const RPC_URLS: Record<NetworkType, string> = {
1112

1213
const ABI = [
1314
{
14-
name: 'argent::account::interface::IDeprecatedArgentAccount',
15+
name: 'argent::common::account::IAccount',
1516
type: 'interface',
1617
items: [
1718
{
18-
name: 'isValidSignature',
19+
name: 'is_valid_signature',
1920
type: 'function',
2021
inputs: [
2122
{
2223
name: 'hash',
2324
type: 'core::felt252'
2425
},
2526
{
26-
name: 'signatures',
27+
name: 'signature',
2728
type: 'core::array::Array::<core::felt252>'
2829
}
2930
],
@@ -67,16 +68,28 @@ export default async function verify(
6768
network: NetworkType = 'SN_MAIN',
6869
options: ProviderOptions = {}
6970
): Promise<boolean> {
70-
const contractAccount = new Contract(
71-
ABI,
72-
address,
73-
getProvider(network, options)
74-
);
71+
try {
72+
const contractAccount = new Contract(
73+
ABI,
74+
address,
75+
getProvider(network, options)
76+
);
77+
78+
if (sig.length < 2) {
79+
throw new Error('Invalid signature format');
80+
}
7581

76-
await contractAccount.isValidSignature(getHash(data, address), [
77-
sig[0],
78-
sig[1]
79-
]);
82+
const result = await contractAccount.is_valid_signature(
83+
getHash(data, address),
84+
sig.slice(-2)
85+
);
8086

81-
return true;
87+
return BigNumber.from(result).eq(BigNumber.from('370462705988'));
88+
} catch (e: any) {
89+
if (e.message.includes('Contract not found')) {
90+
throw new Error('Contract not deployed');
91+
}
92+
93+
throw e;
94+
}
8295
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"address": "0x008cf64fc19a22c94c4d751ebd39ff897dbe2c8646b2900d646558e293b0a2e5",
3+
"sig": [
4+
"0x1",
5+
"0xa0f72077d0b928be15ddfc21c481af27a9c79d7b67e049bb97a9919cb9218e",
6+
"0x895bcbd404d7374850219753d98a397c962985ffa3f36d5bd06aab41a01709"
7+
],
8+
"data": {
9+
"domain": {
10+
"name": "sx-starknet",
11+
"version": "0.1.0",
12+
"chainId": "0x534e5f4d41494e",
13+
"verifyingContract": ""
14+
},
15+
"types": {
16+
"StarkNetDomain": [
17+
{
18+
"name": "name",
19+
"type": "felt252"
20+
},
21+
{
22+
"name": "version",
23+
"type": "felt252"
24+
},
25+
{
26+
"name": "chainId",
27+
"type": "felt252"
28+
},
29+
{
30+
"name": "verifyingContract",
31+
"type": "ContractAddress"
32+
}
33+
],
34+
"SetAlias": [
35+
{
36+
"name": "from",
37+
"type": "ContractAddress"
38+
},
39+
{
40+
"name": "alias",
41+
"type": "string"
42+
},
43+
{
44+
"name": "timestamp",
45+
"type": "felt"
46+
}
47+
]
48+
},
49+
"message": {
50+
"from": "0x008cf64fc19a22c94c4d751ebd39ff897dbe2c8646b2900d646558e293b0a2e5",
51+
"timestamp": 1721836843,
52+
"alias": "0x6ceddb030f3ef6dBD04B8b3691CaB101ECe226f6"
53+
},
54+
"primaryType": "SetAlias"
55+
}
56+
}

0 commit comments

Comments
 (0)