diff --git a/package.json b/package.json index b791eb79e..d6572e63c 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/verify/starknet.spec.ts b/src/verify/starknet.spec.ts index f637e2698..7f79f6142 100644 --- a/src/verify/starknet.spec.ts +++ b/src/verify/starknet.spec.ts @@ -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()', () => { @@ -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'); }); }); }); diff --git a/src/verify/starknet.ts b/src/verify/starknet.ts index 34d6f397a..dea5d4fc3 100644 --- a/src/verify/starknet.ts +++ b/src/verify/starknet.ts @@ -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'; @@ -11,11 +12,11 @@ const RPC_URLS: Record = { const ABI = [ { - name: 'argent::account::interface::IDeprecatedArgentAccount', + name: 'argent::common::account::IAccount', type: 'interface', items: [ { - name: 'isValidSignature', + name: 'is_valid_signature', type: 'function', inputs: [ { @@ -23,7 +24,7 @@ const ABI = [ type: 'core::felt252' }, { - name: 'signatures', + name: 'signature', type: 'core::array::Array::' } ], @@ -67,16 +68,28 @@ export default async function verify( network: NetworkType = 'SN_MAIN', options: ProviderOptions = {} ): Promise { - 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; + } } diff --git a/test/fixtures/starknet/message-alias-rsv.json b/test/fixtures/starknet/message-alias-rsv.json new file mode 100644 index 000000000..c2c65256f --- /dev/null +++ b/test/fixtures/starknet/message-alias-rsv.json @@ -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" + } +}