Skip to content

Commit

Permalink
Add signEIP7702Authorization to KeyringController and corresponding t…
Browse files Browse the repository at this point in the history
…ests
  • Loading branch information
jeffsmale90 committed Feb 12, 2025
1 parent 060c729 commit 47935e0
Show file tree
Hide file tree
Showing 29 changed files with 555 additions and 81 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@metamask/eth-block-tracker": "^11.0.3",
"@metamask/eth-json-rpc-provider": "^4.1.8",
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@ts-bridge/cli": "^0.6.1",
"@types/jest": "^27.4.1",
"@types/lodash": "^4.14.191",
Expand Down
2 changes: 1 addition & 1 deletion packages/accounts-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@metamask/keyring-internal-api": "^4.0.1",
"@metamask/snaps-sdk": "^6.17.1",
"@metamask/snaps-utils": "^8.10.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"deepmerge": "^4.2.2",
"ethereum-cryptography": "^2.1.2",
"immer": "^9.0.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/approval-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"nanoid": "^3.3.8"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/assets-controllers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@metamask/polling-controller": "^12.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/snaps-utils": "^8.10.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/bn.js": "^5.1.5",
"@types/uuid": "^8.3.0",
"async-mutex": "^0.5.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/base-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"immer": "^9.0.6"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/build-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/eslint": "^8.44.7"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/controller-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@ethereumjs/util": "^8.1.0",
"@metamask/eth-query": "^4.0.0",
"@metamask/ethjs-unit": "^0.3.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@spruceid/siwe-parser": "2.1.0",
"@types/bn.js": "^5.1.5",
"bignumber.js": "^9.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/ens-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@ethersproject/providers": "^5.7.0",
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"punycode": "^2.1.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/eth-json-rpc-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"uuid": "^8.3.2"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/gas-fee-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@metamask/eth-query": "^4.0.0",
"@metamask/ethjs-unit": "^0.3.0",
"@metamask/polling-controller": "^12.0.3",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/bn.js": "^5.1.5",
"@types/uuid": "^8.3.0",
"bn.js": "^5.2.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/json-rpc-middleware-stream/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"readable-stream": "^3.6.2"
},
"devDependencies": {
Expand Down
8 changes: 4 additions & 4 deletions packages/keyring-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@
"@keystonehq/metamask-airgapped-keyring": "^0.14.1",
"@metamask/base-controller": "^8.0.0",
"@metamask/browser-passworder": "^4.3.0",
"@metamask/eth-hd-keyring": "^7.0.4",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/eth-simple-keyring": "^6.0.5",
"@metamask/eth-hd-keyring": "^10.0.0",
"@metamask/eth-sig-util": "^8.2.0",
"@metamask/eth-simple-keyring": "^8.1.0",
"@metamask/keyring-api": "^17.0.0",
"@metamask/keyring-internal-api": "^4.0.1",
"@metamask/message-manager": "^12.0.1",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"async-mutex": "^0.5.0",
"ethereumjs-wallet": "^1.0.1",
"immer": "^9.0.6"
Expand Down
72 changes: 70 additions & 2 deletions packages/keyring-controller/src/KeyringController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
recoverTypedSignature,
SignTypedDataVersion,
encrypt,
recoverEIP7702Authorization,
} from '@metamask/eth-sig-util';
import SimpleKeyring from '@metamask/eth-simple-keyring/dist/simple-keyring';
import SimpleKeyring from '@metamask/eth-simple-keyring';
import type { EthKeyring } from '@metamask/keyring-internal-api';
import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english';
import type { KeyringClass } from '@metamask/utils';
Expand Down Expand Up @@ -104,7 +105,6 @@ describe('KeyringController', () => {

it('allows overwriting the built-in Simple keyring builder', async () => {
const mockSimpleKeyringBuilder =
// @ts-expect-error The simple keyring doesn't yet conform to the KeyringClass type
buildKeyringBuilderWithSpy(SimpleKeyring);
await withController(
{ keyringBuilders: [mockSimpleKeyringBuilder] },
Expand Down Expand Up @@ -1561,6 +1561,74 @@ describe('KeyringController', () => {
});
});

describe('signEip7702Authorization', () => {
const from = '0x5AC6D462f054690a373FABF8CC28e161003aEB19';
stubKeyringClassWithAccount(MockKeyring, from);
const chainId = 1;
const contractAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
const nonce = 1;

describe('when the keyring for the given address supports signEip7702Authorization', () => {
it('should sign EIP-7702 authorization message', async () => {
await withController(async ({ controller, initialState }) => {
const account = initialState.keyrings[0].accounts[0];
const signature = await controller.signEip7702Authorization({
from: account,
chainId,
contractAddress,
nonce,
});

const recovered = recoverEIP7702Authorization({
authorization: [chainId, contractAddress, nonce],
signature,
});

expect(recovered).toBe(account);
});
});

it('should not sign EIP-7702 authorization message if from account is not passed', async () => {
await withController(async ({ controller }) => {
await expect(
controller.signEip7702Authorization({
chainId,
contractAddress,
nonce,
from: '',
}),
).rejects.toThrow(
'KeyringController - No keyring found. Error info: There are keyrings, but none match the address',
);
});
});
});

describe('when the keyring for the given address does not support signEip7702Authorization', () => {
it('should throw error', async () => {
stubKeyringClassWithAccount(MockKeyring, from);

await withController(
{ keyringBuilders: [keyringBuilderFactory(MockKeyring)] },
async ({ controller }) => {
await controller.addNewKeyring(MockKeyring.type);

await expect(
controller.signEip7702Authorization({
from,
chainId,
contractAddress,
nonce,
}),
).rejects.toThrow(
KeyringControllerError.UnsupportedSignEip7702Authorization,
);
},
);
});
});
});

describe('signTypedMessage', () => {
describe('when the keyring for the given address supports signTypedMessage', () => {
it('should throw when given invalid version', async () => {
Expand Down
54 changes: 54 additions & 0 deletions packages/keyring-controller/src/KeyringController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { EthKeyring } from '@metamask/keyring-internal-api';
import type {
PersonalMessageParams,
TypedMessageParams,
Eip7702AuthorizationMessageParams,
} from '@metamask/message-manager';
import type {
Eip1024EncryptedData,
Expand Down Expand Up @@ -116,6 +117,11 @@ export type KeyringControllerSignMessageAction = {
handler: KeyringController['signMessage'];
};

export type KeyringControllerSignAuthorizationMessageAction = {
type: `${typeof name}:signEIP7702AuthorizationMessage`;
handler: KeyringController['signEip7702Authorization'];
};

export type KeyringControllerSignPersonalMessageAction = {
type: `${typeof name}:signPersonalMessage`;
handler: KeyringController['signPersonalMessage'];
Expand Down Expand Up @@ -204,6 +210,7 @@ export type KeyringControllerQRKeyringStateChangeEvent = {
export type KeyringControllerActions =
| KeyringControllerGetStateAction
| KeyringControllerSignMessageAction
| KeyringControllerSignAuthorizationMessageAction
| KeyringControllerSignPersonalMessageAction
| KeyringControllerSignTypedMessageAction
| KeyringControllerDecryptMessageAction
Expand Down Expand Up @@ -1129,6 +1136,48 @@ export class KeyringController extends BaseController<
return await keyring.signMessage(address, messageParams.data);
}

/**
* Signs EIP-7702 Authorization message by calling down into a specific keyring.
*
* @param messageParams - EIP7702AuthorizationMessageParams object to sign.
* @returns Promise resolving to an EIP-7702 Authorization signature.
* @throws Will throw UnsupportedSignEIP7702Authorization if the keyring does not support signing EIP-7702 Authorization messages.
*/
async signEip7702Authorization(
messageParams: Eip7702AuthorizationMessageParams,
): Promise<string> {
const from = ethNormalize(messageParams.from) as Hex;

const keyring = (await this.getKeyringForAccount(from)) as EthKeyring<Json>;

if (!keyring.signEip7702Authorization) {
throw new Error(
KeyringControllerError.UnsupportedSignEip7702Authorization,
);
}

const { chainId, nonce } = messageParams;
const contractAddress = ethNormalize(messageParams.contractAddress) as
| Hex
| undefined;

if (!contractAddress === undefined) {

Check failure on line 1164 in packages/keyring-controller/src/KeyringController.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (20.x)

Unexpected constant binary expression. Compares constantly with the right-hand side of the `===`
throw new Error('Contract address must be a 20-byte hex string');
}
if (typeof chainId !== 'number' || chainId < 0) {
throw new Error('Chain ID must be a non-negative number');
}
if (typeof nonce !== 'number' || nonce < 0) {
throw new Error('Nonce must be a non-negativenumber');
}

return await keyring.signEip7702Authorization(from, [
chainId,
contractAddress as Hex,
nonce,
]);
}

/**
* Signs personal message by calling down into a specific keyring.
*
Expand Down Expand Up @@ -1736,6 +1785,11 @@ export class KeyringController extends BaseController<
this.signMessage.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:signEIP7702AuthorizationMessage`,
this.signEip7702Authorization.bind(this),
);

this.messagingSystem.registerActionHandler(
`${name}:signPersonalMessage`,
this.signPersonalMessage.bind(this),
Expand Down
1 change: 1 addition & 0 deletions packages/keyring-controller/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum KeyringControllerError {
UnsupportedSignTransaction = 'KeyringController - The keyring for the current address does not support the method signTransaction.',
UnsupportedSignMessage = 'KeyringController - The keyring for the current address does not support the method signMessage.',
UnsupportedSignPersonalMessage = 'KeyringController - The keyring for the current address does not support the method signPersonalMessage.',
UnsupportedSignEip7702Authorization = 'KeyringController - The keyring for the current address does not support the method signEip7702Authorization.',
UnsupportedGetEncryptionPublicKey = 'KeyringController - The keyring for the current address does not support the method getEncryptionPublicKey.',
UnsupportedDecryptMessage = 'KeyringController - The keyring for the current address does not support the method decryptMessage.',
UnsupportedSignTypedMessage = 'KeyringController - The keyring for the current address does not support the method signTypedMessage.',
Expand Down
4 changes: 2 additions & 2 deletions packages/message-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/eth-sig-util": "^8.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/eth-sig-util": "^8.2.0",
"@metamask/utils": "^11.2.0",
"@types/uuid": "^8.3.0",
"jsonschema": "^1.4.1",
"uuid": "^8.3.2"
Expand Down
6 changes: 6 additions & 0 deletions packages/message-manager/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ export type PersonalMessageParams = {
export type TypedMessageParams = {
data: Record<string, unknown>[] | string | SignTypedDataMessageV3V4;
} & AbstractMessageParams;

export type Eip7702AuthorizationMessageParams = {
chainId: number;
contractAddress: string;
nonce: number;
} & AbstractMessageParams;
2 changes: 1 addition & 1 deletion packages/multichain-transactions-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@metamask/snaps-controllers": "^9.19.0",
"@metamask/snaps-sdk": "^6.17.1",
"@metamask/snaps-utils": "^8.10.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/uuid": "^8.3.0",
"immer": "^9.0.6",
"uuid": "^8.3.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/multichain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@metamask/eth-json-rpc-filters": "^9.0.0",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/safe-event-emitter": "^3.0.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@open-rpc/schema-utils-js": "^2.0.5",
"jsonschema": "^1.4.1",
"lodash": "^4.17.21"
Expand Down
2 changes: 1 addition & 1 deletion packages/name-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"async-mutex": "^0.5.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/network-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/swappable-obj-proxy": "^2.3.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"async-mutex": "^0.5.0",
"fast-deep-equal": "^3.1.3",
"immer": "^9.0.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/notification-services-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@contentful/rich-text-html-renderer": "^16.5.2",
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"bignumber.js": "^9.1.2",
"firebase": "^11.2.0",
"loglevel": "^1.8.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/permission-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@metamask/controller-utils": "^11.5.0",
"@metamask/json-rpc-engine": "^10.0.3",
"@metamask/rpc-errors": "^7.0.2",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/deep-freeze-strict": "^1.1.0",
"deep-freeze-strict": "^1.1.1",
"immer": "^9.0.6",
Expand Down
2 changes: 1 addition & 1 deletion packages/polling-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"dependencies": {
"@metamask/base-controller": "^8.0.0",
"@metamask/controller-utils": "^11.5.0",
"@metamask/utils": "^11.1.0",
"@metamask/utils": "^11.2.0",
"@types/uuid": "^8.3.0",
"fast-json-stable-stringify": "^2.1.0",
"uuid": "^8.3.2"
Expand Down
Loading

0 comments on commit 47935e0

Please sign in to comment.