Skip to content

Commit ce61ff0

Browse files
wa0x6eChaituVR
andauthored
feat: support starknet multiple signers signature (#1182)
* feat: support starknet multiple signers signature tests: fix test tests: fix tests refactor: code improvement * feat: add hash for new starknet SNIP-12 domain format * doc: add doc * fix: update alias type to use felt * Bump version from 0.14.11 to 0.14.12 --------- Co-authored-by: Chaitanya <[email protected]>
1 parent f05795c commit ce61ff0

File tree

5 files changed

+92
-14
lines changed

5 files changed

+92
-14
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@snapshot-labs/snapshot.js",
3-
"version": "0.14.11",
3+
"version": "0.14.12",
44
"repository": "snapshot-labs/snapshot.js",
55
"license": "MIT",
66
"main": "dist/snapshot.cjs.js",

src/sign/hashedTypes.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"6f6cdd15a1e9e6c4ee544231c4fa7b6c7e5183bc2b37fa4bd1a695f458348ab7": "delete-space",
5858
"1aad7825b991457fca04aae48a2a49d815ada524e10316af0da5522ea48b3df6": "alias",
5959
"8ea9074bff3a30cb61f4f0f0f142c9335c6be828feba0571a45de3f1fd0319c0": "alias",
60+
"23310e197cbdcd80e506c0dbdd41831c9cf91d103a3ab663b278a1944a210920": "alias",
6061
"42f8858a21d4aa232721cb97074851e729829ea362b88bb21f3879899663b586": "follow",
6162
"2bb75450e28b06f259ea764cd669de6bde0ba70ce729b0ff05ab9df56e0ff21d": "unfollow",
6263
"2ffbebcbd22ef48fd2f4a1182ff1feda7795b57689bd6f0dd73c89e925e7fefb": "profile",

src/verify/starknet.spec.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { test, expect, describe } from 'vitest';
22
import starknetMessage from '../../test/fixtures/starknet/message-alias.json';
33
import starknetMessageRsv from '../../test/fixtures/starknet/message-alias-rsv.json';
4+
import starknetMessageMultisigner from '../../test/fixtures/starknet/message-alias-multisigner.json';
45
import verify, { getHash } from './starknet';
56
import { validateAndParseAddress } from 'starknet';
67
import { clone } from '../utils';
@@ -24,9 +25,10 @@ describe('verify/starknet', () => {
2425

2526
describe('verify()', () => {
2627
describe.each([
27-
['2', starknetMessage],
28-
['3', starknetMessageRsv]
29-
])('with a %s items signature', (title, message) => {
28+
['2 items', starknetMessage, false],
29+
['3 items', starknetMessageRsv, false],
30+
['multiple signers', starknetMessageMultisigner, true]
31+
])('with a %s signature', (title, message, multisign) => {
3032
test('should return true if the signature is valid', () => {
3133
expect(
3234
verify(message.address, message.sig, message.data, 'SN_MAIN')
@@ -44,11 +46,19 @@ describe('verify/starknet', () => {
4446
).resolves.toBe(true);
4547
});
4648

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-
});
49+
if (multisign) {
50+
test('should throw an error when verifying on a different network', () => {
51+
expect(
52+
verify(message.address, message.sig, message.data, 'SN_SEPOLIA')
53+
).rejects.toThrowError();
54+
});
55+
} else {
56+
test('should return true when verifying on a different network', () => {
57+
expect(
58+
verify(message.address, message.sig, message.data, 'SN_SEPOLIA')
59+
).resolves.toBe(true);
60+
});
61+
}
5262

5363
test('should throw an error if the signature is invalid', () => {
5464
expect(

src/verify/starknet.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ export function getHash(data: SignaturePayload, address: string): string {
6262
);
6363
}
6464

65+
/**
66+
* Processes a StarkNet signature array and returns the appropriate signature format
67+
* for contract verification.
68+
* Returns the r ands values for each signature in the array.
69+
*
70+
* Handles the following cases:
71+
* - 2-item array: Standard signature, returns as-is.
72+
* - 3-item array: Some wallets (e.g., Braavos) may return a 3-item array; returns the last two items.
73+
* - Multi-signer array: For multisig accounts, the array may contain multiple signatures;
74+
* this function extracts the relevant signature pairs.
75+
*
76+
* @param {string[]} sig - The signature array to process. Must have at least 2 items.
77+
* @returns {string[]} The processed signature array suitable for contract verification.
78+
* @throws {Error} If the signature array has fewer than 2 items.
79+
*/
80+
function getSignatureArray(sig: string[]): string[] {
81+
if (sig.length < 2) {
82+
throw new Error('Invalid signature format');
83+
}
84+
85+
if (sig.length <= 3) {
86+
return sig.slice(-2);
87+
}
88+
89+
const results: string[] = [];
90+
for (let i = 1; i < sig.length; i += 4) {
91+
results.push(sig[i + 2], sig[i + 3]);
92+
}
93+
94+
return results;
95+
}
96+
6597
export default async function verify(
6698
address: string,
6799
sig: string[],
@@ -76,13 +108,9 @@ export default async function verify(
76108
getProvider(network, options)
77109
);
78110

79-
if (sig.length < 2) {
80-
throw new Error('Invalid signature format');
81-
}
82-
83111
const result = await contractAccount.is_valid_signature(
84112
getHash(data, address),
85-
sig.slice(-2)
113+
getSignatureArray(sig)
86114
);
87115

88116
return BigNumber.from(result).eq(BigNumber.from('370462705988'));
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"address": "0x03c56e9437c23bbfce260b1e6ae45eee4cf808cfef4b5e767d1a2a5e152a71df",
3+
"sig": [
4+
"0x2",
5+
"0x0",
6+
"0xec31c589d1f36a04d2855cc464ce048bb925430653f146c3d5edc31282405d",
7+
"0x696d12a9efa31910453a7cc3d051318636b1a8f20c9f6c499f53203cc9d5a76",
8+
"0x7824808635967515f03ed2d204fe0fe9095d4029679e863cf48febee7a2f23",
9+
"0x0",
10+
"0x424fd8e0158c9d744ca6c22e7c48a1b46914a98e0e2fc6e78e69a7d5a7b84b4",
11+
"0x3bb91f654cf18927e17c040642dd8503b0e5783a2633bb1e6badbae20226f2f",
12+
"0x625d001a648a0891a93fbc5f4447f08b14cfb011e373801598d8f9d45175b31"
13+
],
14+
"data": {
15+
"domain": {
16+
"name": "sx-starknet",
17+
"version": "0.1.0",
18+
"chainId": "0x534e5f4d41494e"
19+
},
20+
"types": {
21+
"StarkNetDomain": [
22+
{ "name": "name", "type": "felt" },
23+
{ "name": "version", "type": "felt" },
24+
{ "name": "chainId", "type": "felt" }
25+
],
26+
"SetAlias": [
27+
{ "name": "from", "type": "felt" },
28+
{ "name": "alias", "type": "felt" },
29+
{ "name": "timestamp", "type": "felt" }
30+
]
31+
},
32+
"message": {
33+
"alias": "0xFeDaBaEDB4DCa1536AD3A791C3666FAc0BE2B9DB",
34+
"from": "0x03c56e9437c23bbfce260b1e6ae45eee4cf808cfef4b5e767d1a2a5e152a71df",
35+
"timestamp": 1757857885
36+
},
37+
"primaryType": "SetAlias"
38+
}
39+
}

0 commit comments

Comments
 (0)