diff --git a/.changeset/cold-apples-happen.md b/.changeset/cold-apples-happen.md new file mode 100644 index 0000000000000..5876642a1b0e7 --- /dev/null +++ b/.changeset/cold-apples-happen.md @@ -0,0 +1,5 @@ +--- +'@mysten/sui': minor +--- + +Add a new `address` options on methods that verify signatures that ensures the signature is valid for the provided address diff --git a/.changeset/small-toys-begin.md b/.changeset/small-toys-begin.md new file mode 100644 index 0000000000000..2531c8891881e --- /dev/null +++ b/.changeset/small-toys-begin.md @@ -0,0 +1,5 @@ +--- +'@mysten/sui': minor +--- + +Add a new `publicKey.verifyAddress` method on PublicKey instances diff --git a/crates/sui-core/src/unit_tests/data/entry_point_types/Move.lock b/crates/sui-core/src/unit_tests/data/entry_point_types/Move.lock index a3fcd9c1ec33f..fd31dc5d5a85b 100644 --- a/crates/sui-core/src/unit_tests/data/entry_point_types/Move.lock +++ b/crates/sui-core/src/unit_tests/data/entry_point_types/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.lock b/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.lock index 2e9051778ac64..323d9bd164395 100644 --- a/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.lock +++ b/crates/sui-core/src/unit_tests/data/entry_point_vector/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/sdk/docs/pages/typescript/cryptography/keypairs.mdx b/sdk/docs/pages/typescript/cryptography/keypairs.mdx index ba7c173b8f0c5..800126d344a51 100644 --- a/sdk/docs/pages/typescript/cryptography/keypairs.mdx +++ b/sdk/docs/pages/typescript/cryptography/keypairs.mdx @@ -83,11 +83,29 @@ const { signature } = await keypair.signPersonalMessage(message); const publicKey = await verifyPersonalMessageSignature(message, signature); -if (publicKey.toSuiAddress() !== keypair.getPublicKey().toSuiAddress()) { +if (publicKey.verifyAddress(keypair.getPublicKey().toSuiAddress())) { throw new Error('Signature was valid, but was signed by a different key pair'); } ``` +## Verifying that a signature is valid for a specific address + +`verifyPersonalMessageSignature` and `verifyTransactionSignature` accept an optional `address`, and +will throw an error if the signature is not valid for the provided address. + +```typescript +import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; +import { verifyPersonalMessageSignature } from '@mysten/sui/verify'; + +const keypair = new Ed25519Keypair(); +const message = new TextEncoder().encode('hello world'); +const { signature } = await keypair.signPersonalMessage(message); + +await verifyPersonalMessageSignature(message, signature, { + address: keypair.getPublicKey().toSuiAddress(), +}); +``` + ## Verifying transaction signatures Verifying transaction signatures is similar to personal message signature verification, except you @@ -108,14 +126,10 @@ const bytes = await tx.build({ client }); const keypair = new Ed25519Keypair(); const { signature } = await keypair.signTransaction(bytes); -// if you have a public key, you can verify it -// const isValid = await publicKey.verifyTransaction(bytes, signature); -// or get the public key from the transaction -const publicKey = await verifyTransactionSignature(bytes, signature); - -if (publicKey.toSuiAddress() !== keypair.getPublicKey().toSuiAddress()) { - throw new Error('Signature was valid, but was signed by a different keyPair'); -} +await verifyTransactionSignature(bytes, signature, { + // optionally verify that the signature is valid for a specific address + address: keypair.getPublicKey().toSuiAddress(), +}); ``` ## Verifying zkLogin signatures @@ -137,6 +151,34 @@ const publicKey = await verifyPersonalMessageSignature(message, zkSignature, { }); ``` +For some zklogin accounts, there are 2 valid addresses for a given set of inputs. This means you may +run into issues if you try to compare the address returned by `publicKey.toSuiAddress()` directly +with an expected address. + +Instead, you can either pass in the expected address during verification, or use the +`publicKey.verifyAddress(address)` method: + +```typescript +import { SuiGraphQLClient } from '@mysten/sui/graphql'; +import { verifyPersonalMessageSignature } from '@mysten/sui/verify'; + +const publicKey = await verifyPersonalMessageSignature(message, zkSignature, { + client: new SuiGraphQLClient({ + url: 'https://sui-testnet.mystenlabs.com/graphql', + }), + // Pass in the expected address, and the verification method will throw an error if the signature is not valid for the provided address + address: '0x...expectedAddress', +}); +// or + +if (!publicKey.verifyAddress('0x...expectedAddress')) { + throw new Error('Signature was valid, but was signed by a different key pair'); +} +``` + +Both of these methods will check the signature against both the standard and +[legacy versions of the zklogin address](https://sdk.mystenlabs.com/typescript/zklogin#legacy-addresses). + ## Deriving a key pair from a mnemonic The Sui TypeScript SDK supports deriving a key pair from a mnemonic phrase. This can be useful when diff --git a/sdk/typescript/src/cryptography/publickey.ts b/sdk/typescript/src/cryptography/publickey.ts index c4ada671b3aa5..182673602f39e 100644 --- a/sdk/typescript/src/cryptography/publickey.ts +++ b/sdk/typescript/src/cryptography/publickey.ts @@ -93,6 +93,13 @@ export abstract class PublicKey { return this.verifyWithIntent(transaction, signature, 'TransactionData'); } + /** + * Verifies that the public key is associated with the provided address + */ + verifyAddress(address: string): boolean { + return this.toSuiAddress() === address; + } + /** * Returns the bytes representation of the public key * prefixed with the signature scheme flag diff --git a/sdk/typescript/src/verify/verify.ts b/sdk/typescript/src/verify/verify.ts index 97216bc689c64..f515b70d3340c 100644 --- a/sdk/typescript/src/verify/verify.ts +++ b/sdk/typescript/src/verify/verify.ts @@ -14,20 +14,30 @@ import { Secp256r1PublicKey } from '../keypairs/secp256r1/publickey.js'; import { MultiSigPublicKey } from '../multisig/publickey.js'; import { ZkLoginPublicIdentifier } from '../zklogin/publickey.js'; -export async function verifySignature(bytes: Uint8Array, signature: string): Promise { +export async function verifySignature( + bytes: Uint8Array, + signature: string, + options?: { + address?: string; + }, +): Promise { const parsedSignature = parseSignature(signature); if (!(await parsedSignature.publicKey.verify(bytes, parsedSignature.serializedSignature))) { throw new Error(`Signature is not valid for the provided data`); } + if (options?.address && !parsedSignature.publicKey.verifyAddress(options.address)) { + throw new Error(`Signature is not valid for the provided address`); + } + return parsedSignature.publicKey; } export async function verifyPersonalMessageSignature( message: Uint8Array, signature: string, - options: { client?: SuiGraphQLClient } = {}, + options: { client?: SuiGraphQLClient; address?: string } = {}, ): Promise { const parsedSignature = parseSignature(signature, options); @@ -40,13 +50,17 @@ export async function verifyPersonalMessageSignature( throw new Error(`Signature is not valid for the provided message`); } + if (options?.address && !parsedSignature.publicKey.verifyAddress(options.address)) { + throw new Error(`Signature is not valid for the provided address`); + } + return parsedSignature.publicKey; } export async function verifyTransactionSignature( transaction: Uint8Array, signature: string, - options: { client?: SuiGraphQLClient } = {}, + options: { client?: SuiGraphQLClient; address?: string } = {}, ): Promise { const parsedSignature = parseSignature(signature, options); @@ -59,6 +73,10 @@ export async function verifyTransactionSignature( throw new Error(`Signature is not valid for the provided Transaction`); } + if (options?.address && !parsedSignature.publicKey.verifyAddress(options.address)) { + throw new Error(`Signature is not valid for the provided address`); + } + return parsedSignature.publicKey; } diff --git a/sdk/typescript/src/zklogin/publickey.ts b/sdk/typescript/src/zklogin/publickey.ts index da3035428f683..cec31b8a114af 100644 --- a/sdk/typescript/src/zklogin/publickey.ts +++ b/sdk/typescript/src/zklogin/publickey.ts @@ -55,18 +55,22 @@ export class ZkLoginPublicIdentifier extends PublicKey { override toSuiAddress(): string { if (this.#legacyAddress) { - const legacyBytes = normalizeZkLoginPublicKeyBytes(this.#data, true); - const addressBytes = new Uint8Array(legacyBytes.length + 1); - addressBytes[0] = this.flag(); - addressBytes.set(legacyBytes, 1); - return normalizeSuiAddress( - bytesToHex(blake2b(addressBytes, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2), - ); + return this.#toLegacyAddress(); } return super.toSuiAddress(); } + #toLegacyAddress() { + const legacyBytes = normalizeZkLoginPublicKeyBytes(this.#data, true); + const addressBytes = new Uint8Array(legacyBytes.length + 1); + addressBytes[0] = this.flag(); + addressBytes.set(legacyBytes, 1); + return normalizeSuiAddress( + bytesToHex(blake2b(addressBytes, { dkLen: 32 })).slice(0, SUI_ADDRESS_LENGTH * 2), + ); + } + /** * Return the byte array representation of the zkLogin public identifier */ @@ -118,6 +122,13 @@ export class ZkLoginPublicIdentifier extends PublicKey { client: this.#client, }); } + + /** + * Verifies that the public key is associated with the provided address + */ + override verifyAddress(address: string): boolean { + return address === super.toSuiAddress() || address === this.#toLegacyAddress(); + } } // Derive the public identifier for zklogin based on address seed and iss. diff --git a/sdk/typescript/test/e2e/data/coin_metadata/Move.lock b/sdk/typescript/test/e2e/data/coin_metadata/Move.lock index 345facce46655..157a1ba147253 100644 --- a/sdk/typescript/test/e2e/data/coin_metadata/Move.lock +++ b/sdk/typescript/test/e2e/data/coin_metadata/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock b/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock index de346af015fae..52a726cbb7829 100644 --- a/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock +++ b/sdk/typescript/test/e2e/data/dynamic_fields/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/id_entry_args/Move.lock b/sdk/typescript/test/e2e/data/id_entry_args/Move.lock index 48498a46386b7..405a8be49d810 100644 --- a/sdk/typescript/test/e2e/data/id_entry_args/Move.lock +++ b/sdk/typescript/test/e2e/data/id_entry_args/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/sdk/typescript/test/e2e/data/serializer/Move.lock b/sdk/typescript/test/e2e/data/serializer/Move.lock index b423d605853fd..1dc551de819de 100644 --- a/sdk/typescript/test/e2e/data/serializer/Move.lock +++ b/sdk/typescript/test/e2e/data/serializer/Move.lock @@ -22,6 +22,6 @@ dependencies = [ ] [move.toolchain-version] -compiler-version = "1.38.0" +compiler-version = "1.40.0" edition = "2024.beta" flavor = "sui" diff --git a/sdk/typescript/test/e2e/verify-signatures.test.ts b/sdk/typescript/test/e2e/verify-signatures.test.ts new file mode 100644 index 0000000000000..cefd4e2df5a6b --- /dev/null +++ b/sdk/typescript/test/e2e/verify-signatures.test.ts @@ -0,0 +1,203 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { fromBase64 } from '@mysten/bcs'; +import { describe, expect, it } from 'vitest'; + +import { SuiGraphQLClient } from '../../src/graphql'; +import { Ed25519Keypair } from '../../src/keypairs/ed25519'; +import { Secp256k1Keypair } from '../../src/keypairs/secp256k1'; +import { Secp256r1Keypair } from '../../src/keypairs/secp256r1'; +import { MultiSigPublicKey } from '../../src/multisig/publickey'; +import { verifyPersonalMessageSignature } from '../../src/verify'; + +describe('Verify Signatures', () => { + // describe('transaction signatures', () => {}); + describe('personal message signatures', () => { + describe('single signatures', () => { + describe('Ed25519', () => { + const keypair = new Ed25519Keypair(); + const address = keypair.getPublicKey().toSuiAddress(); + const message = new TextEncoder().encode('hello world'); + + it('verifies valid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const publicKey = await verifyPersonalMessageSignature(message, signature); + expect(publicKey.toSuiAddress()).toBe(address); + }); + + it('verifies signatures against provided address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + await expect( + verifyPersonalMessageSignature(message, signature, { address }), + ).resolves.toBeDefined(); + }); + + it('fails for invalid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const invalidMessage = new TextEncoder().encode('wrong message'); + await expect(verifyPersonalMessageSignature(invalidMessage, signature)).rejects.toThrow(); + }); + + it('fails for wrong address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const wrongAddress = new Ed25519Keypair().getPublicKey().toSuiAddress(); + await expect( + verifyPersonalMessageSignature(message, signature, { address: wrongAddress }), + ).rejects.toThrow(); + }); + }); + + describe('Secp256k1', () => { + const keypair = new Secp256k1Keypair(); + const address = keypair.getPublicKey().toSuiAddress(); + const message = new TextEncoder().encode('hello world'); + + it('verifies valid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const publicKey = await verifyPersonalMessageSignature(message, signature); + expect(publicKey.toSuiAddress()).toBe(address); + }); + + it('verifies signatures against provided address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + await expect( + verifyPersonalMessageSignature(message, signature, { address }), + ).resolves.toBeDefined(); + }); + + it('fails for invalid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const invalidMessage = new TextEncoder().encode('wrong message'); + await expect(verifyPersonalMessageSignature(invalidMessage, signature)).rejects.toThrow(); + }); + + it('fails for wrong address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const wrongAddress = new Secp256k1Keypair().getPublicKey().toSuiAddress(); + await expect( + verifyPersonalMessageSignature(message, signature, { address: wrongAddress }), + ).rejects.toThrow(); + }); + }); + + describe('Secp256r1', () => { + const keypair = new Secp256r1Keypair(); + const address = keypair.getPublicKey().toSuiAddress(); + const message = new TextEncoder().encode('hello world'); + + it('verifies valid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const publicKey = await verifyPersonalMessageSignature(message, signature); + expect(publicKey.toSuiAddress()).toBe(address); + }); + + it('verifies signatures against provided address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + await expect( + verifyPersonalMessageSignature(message, signature, { address }), + ).resolves.toBeDefined(); + }); + + it('fails for invalid signatures', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const invalidMessage = new TextEncoder().encode('wrong message'); + await expect(verifyPersonalMessageSignature(invalidMessage, signature)).rejects.toThrow(); + }); + + it('fails for wrong address', async () => { + const { signature } = await keypair.signPersonalMessage(message); + const wrongAddress = new Secp256r1Keypair().getPublicKey().toSuiAddress(); + await expect( + verifyPersonalMessageSignature(message, signature, { address: wrongAddress }), + ).rejects.toThrow(); + }); + }); + }); + + describe('multisig signatures', () => { + const k1 = new Ed25519Keypair(); + const k2 = new Secp256k1Keypair(); + const k3 = new Secp256r1Keypair(); + const pk1 = k1.getPublicKey(); + const pk2 = k2.getPublicKey(); + const pk3 = k3.getPublicKey(); + + it('verifies valid multisig signatures', async () => { + const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({ + threshold: 3, + publicKeys: [ + { publicKey: pk1, weight: 1 }, + { publicKey: pk2, weight: 2 }, + { publicKey: pk3, weight: 3 }, + ], + }); + + const message = new TextEncoder().encode('hello world'); + const sig1 = await k1.signPersonalMessage(message); + const sig2 = await k2.signPersonalMessage(message); + + const multisig = multiSigPublicKey.combinePartialSignatures([ + sig1.signature, + sig2.signature, + ]); + + const publicKey = await verifyPersonalMessageSignature(message, multisig); + expect(publicKey.toSuiAddress()).toBe(multiSigPublicKey.toSuiAddress()); + }); + + it('fails for invalid multisig signatures', async () => { + const multiSigPublicKey = MultiSigPublicKey.fromPublicKeys({ + threshold: 3, + publicKeys: [ + { publicKey: pk1, weight: 1 }, + { publicKey: pk2, weight: 2 }, + { publicKey: pk3, weight: 3 }, + ], + }); + + const message = new TextEncoder().encode('hello world'); + const wrongMessage = new TextEncoder().encode('wrong message'); + const sig1 = await k1.signPersonalMessage(message); + const sig2 = await k2.signPersonalMessage(message); + + const multisig = multiSigPublicKey.combinePartialSignatures([ + sig1.signature, + sig2.signature, + ]); + + await expect(verifyPersonalMessageSignature(wrongMessage, multisig)).rejects.toThrow(); + }); + }); + + describe('zkLogin signatures', () => { + const client = new SuiGraphQLClient({ url: 'http://127.0.0.1:9125' }); + // this test assumes the localnet epoch is smaller than 3. it will fail if localnet has ran for too long and passed epoch 3. + // test case generated from `sui keytool zk-login-insecure-sign-personal-message --data "hello" --max-epoch 3` + const bytes = fromBase64('aGVsbG8='); // the base64 encoding of "hello" + const testSignature = + 'BQNMNTk1OTM0OTYxMDU0NTQyODY4Mjc1NDY0OTc0NTI4NzkyMTIyMjQ2NjIzMTU1ODY4ODUxNDk4Mzc0Mzc5OTkyNjc4NTMzODAzOTM0OE0xMzg3MDY1NTY1MjI1NjI2MjYzNzgxMjUyNzc0ODg3ODQ2MTg0Njc4MzgwMjY1Njg5NTE3MjAyNjgwNDE0NzQzOTcwNTM1NDgzMDIxNwExAwJNMTEyNzgwMzY0NTU1NjAzNTQwMTY1OTI5NDIxMTg3Mjk2OTQwNzQyMTI4NTUzNjcwODUxNTA2MDY2NTU1OTM1OTYwMzYzMzc1NjIxNjVNMTIxMDg3MjQ3MjQyNjQ3MjUzMTMzMTUwNTY3NjIxNjkxNTgyMzQ2NDIyODQwOTkwNzgwNzM3OTUwMTk4NTE3OTE3MjI1MTkyNTk3NTYCTTE4MDM5NjgwODY4NTY4NzQwNDY4NzU1NTg2NDE4NjI1MTQzMDMyMzM4MzQxODk3MjM2NDE5NzEwOTYxNTcyMTA4MDU4MTc4NjY4MjgxTTExMjE2MjQxMTEzNjYzMjg2OTcxNTQ1MjAwMjI1OTIxMzYzNDkyMjMxMTYwMzgwMDQ3MjQ4NjczNTQyODg0MjQ4Nzc4NzMwNjEwNTM5AgExATADTTIxNTUyNjMyMDI1OTM2NDUyNDQzMTY5MDAzMDUwNDQyMTE1MDE0NzIzMDg5NDkyOTU5MTU5MjM2NTI1OTc1Nzk3OTQ5NzM4NjU0NjI2TTE1MTUxNjExMzkzMTY2NzczMzU1MDQ1ODExODA0Nzg2NTczNDQ4MTc1NzMzODQ2MTQwMjUxNTY2NDg0NjkyMjYxMTUwNDExOTQxODU2ATEod2lhWE56SWpvaWFIUjBjSE02THk5dllYVjBhQzV6ZFdrdWFXOGlMQwI+ZXlKcmFXUWlPaUp6ZFdrdGEyVjVMV2xrSWl3aWRIbHdJam9pU2xkVUlpd2lZV3huSWpvaVVsTXlOVFlpZlFNMjA0MzUzNjY2MDAwMzYzNzU3NDU5MjU5NjM0NTY4NjEzMDc5MjUyMDk0NzAyMTkzMzQwMTg1NjQxNTgxNDg1NDQwMzYxOTYyODQ2NDIeAAAAAAAAAGEA+XrHUDMkMaPswTIFsqIgx3yX6j7IvU1T/1yzw4kjKwjgLL0ZtPQZjc2djX7Q9pFoBdObkSZ6JZ4epiOf05q4BrnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA'; + + it('verifies valid signatures', async () => { + const publicKey = await verifyPersonalMessageSignature(bytes, testSignature, { + client, + address: '0xc0f0d0e2a2ca8b8d0e4055ec48210ec77d055db353402cda01d7085ba61d3d5c', + }); + expect(publicKey).toBeDefined(); + + expect(publicKey.toSuiAddress()).toBe( + '0xc0f0d0e2a2ca8b8d0e4055ec48210ec77d055db353402cda01d7085ba61d3d5c', + ); + }); + + it('fails for invalid signatures', async () => { + const bytes = fromBase64('aGVsbG8='); + const invalidSignature = + 'BQNMNTk1OTM0OTYxMDU0NTQyODY4Mjc1NDY0OTc0NTI4NzkyMTIyMjQ2NjIzMTU1ODY4ODUxNDk4Mzc0Mzc5OTkyNjc4NTMzODAzOTM0OE0xMzg3MDY1NTY1MjI1NjI2MjYzNzgxMjUyNzc0ODg3ODQ2MTg0Njc4MzgwMjY1Njg5NTE3MjAyNjgwNDE0NzQzOTcwNTM1NDgzMDIxNwExAwJNMTEyNzgwMzY0NTU1NjAzNTQwMTY1OTI5NDIxMTg3Mjk2OTQwNzQyMTI4NTUzNjcwODUxNTA2MDY2NTU1OTM1OTYwMzYzMzc1NjIxNjVNMTIxMDg3MjQ3MjQyNjQ3MjUzMTMzMTUwNTY3NjIxNjkxNTgyMzQ2NDIyODQwOTkwNzgwNzM3OTUwMTk4NTE3OTE3MjI1MTkyNTk3NTYCTTE4MDM5NjgwODY4NTY4NzQwNDY4NzU1NTg2NDE4NjI1MTQzMDMyMzM4MzQxODk3MjM2NDE5NzEwOTYxNTcyMTA4MDU4MTc4NjY4MjgxTTExMjE2MjQxMTEzNjYzMjg2OTcxNTQ1MjAwMjI1OTIxMzYzNDkyMjMxMTYwMzgwMDQ3MjQ4NjczNTQyODg0MjQ4Nzc4NzMwNjEwNTM5AgExATADTTIxNTUyNjMyMDI1OTM2NDUyNDQzMTY5MDAzMDUwNDQyMTE1MDE0NzIzMDg5NDkyOTU5MTU5MjM2NTI1OTc1Nzk3OTQ5NzM4NjU0NjI2TTE1MTUxNjExMzkzMTY2NzczMzU1MDQ1ODExODA0Nzg2NTczNDQ4MTc1NzMzODQ2MTQwMjUxNTY2NDg0NjkyMjYxMTUwNDExOTQxODU2ATEod2lhWE56SWpvaWFIUjBjSE02THk5dllYVjBhQzV6ZFdrdWFXOGlMQwI+ZXlKcmFXUWlPaUp6ZFdrdGEyVjVMV2xrSWl3aWRIbHdJam9pU2xkVUlpd2lZV3huSWpvaVVsTXlOVFlpZlFNMjA0MzUzNjY2MDAwMzYzNzU3NDU5MjU5NjM0NTY4NjEzMDc5MjUyMDk0NzAyMTkzMzQwMTg1NjQxNTgxNDg1NDQwMzYxOTYyODQ2NDIeAAAAAAAAAGEA+XrHUDMkMaPswTIFsqIgx3yX6j7IvU1T/1yzw4kjKwjgLL0ZtPQZjc2djX7Q9pFoBdObkSZ6JZ4epiOf05q4BrnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIa'; + + await expect( + verifyPersonalMessageSignature(bytes, invalidSignature, { client }), + ).rejects.toThrow(); + }); + }); + }); +});