diff --git a/__tests__/account.test.ts b/__tests__/account.test.ts index 5d2c29a53..19e40c90b 100644 --- a/__tests__/account.test.ts +++ b/__tests__/account.test.ts @@ -4,6 +4,7 @@ import { Contract, DeclareDeployUDCResponse, Provider, + SignatureVerifResult, TransactionType, cairo, constants, @@ -397,7 +398,7 @@ describe('deploy and test Wallet', () => { expect(toBigInt(response.number as string).toString()).toStrictEqual('57'); }); - test('sign and verify offchain message fail', async () => { + test('sign and verify EIP712 message fail', async () => { const signature = await account.signMessage(typedDataExample); const [r, s] = stark.formatSignature(signature); @@ -408,12 +409,32 @@ describe('deploy and test Wallet', () => { if (!signature2) return; - expect(await account.verifyMessage(typedDataExample, signature2)).toBe(false); + const verifMessageResponse: SignatureVerifResult = await account.verifyMessage( + typedDataExample, + signature2 + ); + expect(verifMessageResponse.isVerificationProcessed).toBe(true); + expect(verifMessageResponse.isSignatureValid).toBe(false); + + const wrongAccount = new Account(provider, '0x037891', '0x026789', undefined, TEST_TX_VERSION); // non existing account + const verifMessageResponse2: SignatureVerifResult = await wrongAccount.verifyMessage( + typedDataExample, + signature2 + ); + expect(verifMessageResponse2.isVerificationProcessed).toBe(false); + expect(verifMessageResponse2.error?.message).toContain( + 'Signature verification request is rejected by the network.' + ); }); - test('sign and verify offchain message', async () => { + test('sign and verify message', async () => { const signature = await account.signMessage(typedDataExample); - expect(await account.verifyMessage(typedDataExample, signature)).toBe(true); + const verifMessageResponse: SignatureVerifResult = await account.verifyMessage( + typedDataExample, + signature + ); + expect(verifMessageResponse.isVerificationProcessed).toBe(true); + expect(verifMessageResponse.isSignatureValid).toBe(true); }); describe('Contract interaction with Account', () => { diff --git a/src/account/default.ts b/src/account/default.ts index 4284d297b..27aebc276 100644 --- a/src/account/default.ts +++ b/src/account/default.ts @@ -32,6 +32,7 @@ import { Nonce, ProviderOptions, Signature, + SignatureVerifResult, SimulateTransactionDetails, SimulateTransactionResponse, TransactionType, @@ -573,9 +574,12 @@ export class Account extends Provider implements AccountInterface { return getMessageHash(typedData, this.address); } - public async verifyMessageHash(hash: BigNumberish, signature: Signature): Promise { + public async verifyMessageHash( + hash: BigNumberish, + signature: Signature + ): Promise { try { - await this.callContract({ + const resp = await this.callContract({ contractAddress: this.address, entrypoint: 'isValidSignature', calldata: CallData.compile({ @@ -583,13 +587,46 @@ export class Account extends Provider implements AccountInterface { signature: formatSignature(signature), }), }); - return true; - } catch { - return false; + // console.log('verifySign=', resp); + if (BigInt(resp.result[0]) === 0n) { + // OpenZeppelin 0.8.0 invalid signature + return { + isVerificationProcessed: true, + isSignatureValid: false, + } as SignatureVerifResult; + } + // OpenZeppelin 0.8.0, ArgentX 0.3.0 & Braavos Cairo 0 valid signature + return { + isVerificationProcessed: true, + isSignatureValid: true, + } as SignatureVerifResult; + } catch (err) { + // console.log('verifySign error=', err); + if ((err as Error).message.includes('argent/invalid-signature')) { + // ArgentX 0.3.0 invalid signature + return { + isVerificationProcessed: true, + isSignatureValid: false, + } as SignatureVerifResult; + } + if ((err as Error).message.includes('is invalid, with respect to the public key')) { + // Braavos Cairo 0 invalid signature + return { + isVerificationProcessed: true, + isSignatureValid: false, + } as SignatureVerifResult; + } + return { + isVerificationProcessed: false, + error: new Error('Signature verification request is rejected by the network.'), + } as SignatureVerifResult; } } - public async verifyMessage(typedData: TypedData, signature: Signature): Promise { + public async verifyMessage( + typedData: TypedData, + signature: Signature + ): Promise { const hash = await this.hashMessage(typedData); return this.verifyMessageHash(hash, signature); } diff --git a/src/account/interface.ts b/src/account/interface.ts index 3db5f9e62..72a20c161 100644 --- a/src/account/interface.ts +++ b/src/account/interface.ts @@ -25,6 +25,7 @@ import { MultiDeployContractResponse, Nonce, Signature, + SignatureVerifResult, SimulateTransactionDetails, SimulateTransactionResponse, TypedData, @@ -356,7 +357,10 @@ export abstract class AccountInterface extends ProviderInterface { * @returns true if the signature is valid, false otherwise * @throws {Error} if the JSON object is not a valid JSON or the signature is not a valid signature */ - public abstract verifyMessage(typedData: TypedData, signature: Signature): Promise; + public abstract verifyMessage( + typedData: TypedData, + signature: Signature + ): Promise; /** * Verify a signature of a given hash @@ -367,7 +371,10 @@ export abstract class AccountInterface extends ProviderInterface { * @returns true if the signature is valid, false otherwise * @throws {Error} if the signature is not a valid signature */ - public abstract verifyMessageHash(hash: BigNumberish, signature: Signature): Promise; + public abstract verifyMessageHash( + hash: BigNumberish, + signature: Signature + ): Promise; /** * Gets the nonce of the account with respect to a specific block diff --git a/src/types/lib/index.ts b/src/types/lib/index.ts index 2b85b9a9a..67a77256f 100644 --- a/src/types/lib/index.ts +++ b/src/types/lib/index.ts @@ -7,6 +7,11 @@ import { CompiledContract, CompiledSierraCasm, ContractClass } from './contract' export type WeierstrassSignatureType = weierstrass.SignatureType; export type ArraySignatureType = string[]; export type Signature = ArraySignatureType | WeierstrassSignatureType; +export type SignatureVerifResult = { + isVerificationProcessed: boolean; + isSignatureValid?: boolean; + error?: Error; +}; export type BigNumberish = string | number | bigint; diff --git a/www/docs/guides/signature.md b/www/docs/guides/signature.md index bb83e9a47..8312025e4 100644 --- a/www/docs/guides/signature.md +++ b/www/docs/guides/signature.md @@ -174,17 +174,11 @@ const signature2 = await account.signMessage(typedDataValidate) as WeierstrassSi On the receiver side, you receive the JSON, the signature, and the account address. To verify the message: ```typescript -const compiledAccount = json.parse(fs.readFileSync("./compiledContracts/Account_0_5_1.json").toString("ascii")); -const contractAccount = new Contract(compiledAccount.abi, accountAddress, provider); - -const msgHash5 = typedData.getMessageHash(typedDataValidate, accountAddress); -// The call of isValidSignature will generate an error if not valid -let result5: boolean; -try { - await contractAccount.isValidSignature(msgHash5, [signature2.r, signature2.s]); - result5 = true; -} catch { - result5 = false; +const myAccount = new Account(provider, accountAddress, "0x0123"); // fake private key +const result = await myAccount.verifyMessage(typedMessage, signature); +if (result.isVerificationProcessed) { + console.log("Result (boolean) =", result.isSignatureValid); +} else { + console.log("verification failed :", result.error); } -console.log("Result5 (boolean) =", result5); ```