Skip to content

Commit

Permalink
feat: add support for more starknet signature format (#1040)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
wa0x6e and ChaituVR authored Jul 25, 2024
1 parent 383b2ba commit 04b881d
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 37 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@snapshot-labs/snapshot.js",
"version": "0.12.1",
"version": "0.12.2",
"repository": "snapshot-labs/snapshot.js",
"license": "MIT",
"main": "dist/snapshot.cjs.js",
Expand All @@ -11,6 +11,7 @@
"@ensdomains/eth-ens-namehash": "^2.0.15",
"@ethersproject/abi": "^5.6.4",
"@ethersproject/address": "^5.6.1",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.6.1",
"@ethersproject/contracts": "^5.6.2",
"@ethersproject/hash": "^5.7.0",
Expand Down
80 changes: 57 additions & 23 deletions src/verify/starknet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { test, expect, describe } from 'vitest';
import starknetMessage from '../../test/fixtures/starknet/message-alias.json';
import starknetMessageRsv from '../../test/fixtures/starknet/message-alias-rsv.json';
import verify, { getHash } from './starknet';
import { validateAndParseAddress } from 'starknet';
import { clone } from '../utils';

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

describe('verify()', () => {
test('should return true if the signature is valid', () => {
expect(
verify(
starknetMessage.address,
starknetMessage.sig,
starknetMessage.data,
'SN_SEPOLIA'
)
).resolves.toBe(true);
describe.each([
['2', starknetMessage],
['3', starknetMessageRsv]
])('with a %s items signature', (title, message) => {
test('should return true if the signature is valid', () => {
expect(
verify(message.address, message.sig, message.data, 'SN_MAIN')
).resolves.toBe(true);
});

test('should return true if the signature is valid with a padded address', () => {
expect(
verify(
validateAndParseAddress(message.address),
message.sig,
message.data,
'SN_MAIN'
)
).resolves.toBe(true);
});

test('should return true when verifying on a different network', () => {
expect(
verify(message.address, message.sig, message.data, 'SN_SEPOLIA')
).resolves.toBe(true);
});

test('should throw an error if the signature is invalid', () => {
expect(
verify(
'0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1',
message.sig,
message.data
)
).rejects.toThrow();
});
});

test('should return true if the signature is valid with a padded address', () => {
test('should throw an error when the contract is not deployed', () => {
expect(
verify(
validateAndParseAddress(starknetMessage.address),
'0x07f71118e351c02f6EC7099C8CDf93AED66CEd8406E94631cC91637f7D7F203A',
starknetMessage.sig,
starknetMessage.data,
'SN_SEPOLIA'
'SN_MAIN'
)
).resolves.toBe(true);
).rejects.toThrowError('Contract not deployed');
});

test('should return true when verifying on a different network', () => {
test('should return false when the signature is not valid', () => {
expect(
verify(
starknetMessage.address,
starknetMessage.sig,
['1', '2'],
starknetMessage.data,
'SN_MAIN'
)
).resolves.toBe(true);
).resolves.toBe(false);
});

test('should throw an error if the signature is invalid', () => {
test('should return false when the signature is not valid', () => {
const data = clone(starknetMessage.data);
data.message.timestamp = 1234;

expect(
verify(
'0x7667469b8e93faa642573078b6bf8c790d3a6184b2a1bb39c5c923a732862e1',
starknetMessage.sig,
starknetMessage.data
)
).rejects.toThrow();
verify(starknetMessage.address, starknetMessage.sig, data, 'SN_MAIN')
).resolves.toBe(false);
});

test('should throw an error on wrong signature length', () => {
expect(
verify(starknetMessage.address, ['1'], starknetMessage.data, 'SN_MAIN')
).rejects.toThrowError('Invalid signature format');
});
});
});
39 changes: 26 additions & 13 deletions src/verify/starknet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Contract, RpcProvider, typedData } from 'starknet';
import { BigNumber } from '@ethersproject/bignumber';
import type { SignaturePayload } from '.';
import type { ProviderOptions } from '../utils/provider';

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

const ABI = [
{
name: 'argent::account::interface::IDeprecatedArgentAccount',
name: 'argent::common::account::IAccount',
type: 'interface',
items: [
{
name: 'isValidSignature',
name: 'is_valid_signature',
type: 'function',
inputs: [
{
name: 'hash',
type: 'core::felt252'
},
{
name: 'signatures',
name: 'signature',
type: 'core::array::Array::<core::felt252>'
}
],
Expand Down Expand Up @@ -67,16 +68,28 @@ export default async function verify(
network: NetworkType = 'SN_MAIN',
options: ProviderOptions = {}
): Promise<boolean> {
const contractAccount = new Contract(
ABI,
address,
getProvider(network, options)
);
try {
const contractAccount = new Contract(
ABI,
address,
getProvider(network, options)
);

if (sig.length < 2) {
throw new Error('Invalid signature format');
}

await contractAccount.isValidSignature(getHash(data, address), [
sig[0],
sig[1]
]);
const result = await contractAccount.is_valid_signature(
getHash(data, address),
sig.slice(-2)
);

return true;
return BigNumber.from(result).eq(BigNumber.from('370462705988'));
} catch (e: any) {
if (e.message.includes('Contract not found')) {
throw new Error('Contract not deployed');
}

throw e;
}
}
56 changes: 56 additions & 0 deletions test/fixtures/starknet/message-alias-rsv.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"address": "0x008cf64fc19a22c94c4d751ebd39ff897dbe2c8646b2900d646558e293b0a2e5",
"sig": [
"0x1",
"0xa0f72077d0b928be15ddfc21c481af27a9c79d7b67e049bb97a9919cb9218e",
"0x895bcbd404d7374850219753d98a397c962985ffa3f36d5bd06aab41a01709"
],
"data": {
"domain": {
"name": "sx-starknet",
"version": "0.1.0",
"chainId": "0x534e5f4d41494e",
"verifyingContract": ""
},
"types": {
"StarkNetDomain": [
{
"name": "name",
"type": "felt252"
},
{
"name": "version",
"type": "felt252"
},
{
"name": "chainId",
"type": "felt252"
},
{
"name": "verifyingContract",
"type": "ContractAddress"
}
],
"SetAlias": [
{
"name": "from",
"type": "ContractAddress"
},
{
"name": "alias",
"type": "string"
},
{
"name": "timestamp",
"type": "felt"
}
]
},
"message": {
"from": "0x008cf64fc19a22c94c4d751ebd39ff897dbe2c8646b2900d646558e293b0a2e5",
"timestamp": 1721836843,
"alias": "0x6ceddb030f3ef6dBD04B8b3691CaB101ECe226f6"
},
"primaryType": "SetAlias"
}
}

0 comments on commit 04b881d

Please sign in to comment.