Skip to content

Persist profile storage keys to e2ee backend #5630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

mirceanis
Copy link

@mirceanis mirceanis commented Apr 11, 2025

Don't merge until @metamask/[email protected]+ is available in the clients.

see MetaMask/metamask-extension#31943
and MetaMask/metamask-mobile#14653

Explanation

The backup and sync UserStorageController internally uses some storageKeys for end to end encryption (and collision avoidance). For now, these storage keys are derived using data specific to the SRP and profile ID but this blocks any progress on pairing multiple SRPs.

This PR introduces a new way to manage these profile storage keys, by leveraging the new deterministic encryption capabilities in the @metamask/message-signing-snap.
The profile storage keys, once derived, are encrypted for the public key provided by the snap and stored in the user-storage backend under a deterministic, SRP specific, key.

The user storage component will attempt to download and decrypt the profile storage key and only derive it if it cannot find it.

This is not a complete replacement of the way storage keys are managed, as we need to remain compatible with previous deployments, but at least it starts storing the encrypted keys in the right box.

This work is a prerequisite to proper pairing of profiles in the milti-SRP universe, where we need to allow different SRPs to supplement each-other's profile-data so that the user maintains a coherent experience regardless of how or where they import their different SRPs.

Dependencies

  • @metamask/eth-sig-util
    I introduced this "new" dependency to perform the encryption.
    I figured it was safe to use it since this is already a core dependency in several other controllers and is also used in the clients.
    Ideally, the ERC1024 functionality that is imported from that library would be isolated in a smaller library and reused in the message-signing-snap as well. (the snap uses a different set of smaller libraries to implement the same functionality).
    This new dependency may affect the bundle size of the @metamask/profile-sync-controller package, but it should continue to be tree-shakeable.

  • @metamask/[email protected]
    While not a direct dependency of this library, the controllers do rely on this preinstalled snap to perform signing and decryption. The decryption functionality was implemented in v1.1.0 of the snap, so that version needs to exist in the clients before a new version of the @metamask/profile-sync-controller is released and used.

References

Related to IDENTITY-7
BLOCKED by: MetaMask/metamask-extension#31943
BLOCKED by: MetaMask/metamask-mobile#14653

Changelog

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@mirceanis mirceanis added the team-identity Identity Team changes. https://github.com/orgs/MetaMask/teams/identity label Apr 11, 2025
@mirceanis mirceanis force-pushed the persist-profile-storage-keys-to-e2ee-backend branch 2 times, most recently from f328904 to d492e67 Compare April 14, 2025 11:58
Copy link
Contributor

@mathieuartu mathieuartu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review in progress :) We should add a new entry to the changelog for this PR also.

github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Apr 15, 2025
## **Description**

This is a version bump for the preinstalled
@metamask/message-signing-snap to v1.1.1

This latest version has a number of improvements including support for
multiple entropy sources (following SIP-30) and encryption, as well as
results differentiated by origin.
This snap is used by the AuthenticationController /
UserStorageController and the latest version is necessary to make
progress in the controllers.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31943?quickstart=1)

## **Related issues**

relates to:
[IDENTITY-7](https://consensyssoftware.atlassian.net/browse/IDENTITY-7)
required by: MetaMask/core#5630
relates to: MetaMask/metamask-mobile#14653

## **Manual testing steps**

No user-facing changes

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[IDENTITY-7]:
https://consensyssoftware.atlassian.net/browse/IDENTITY-7?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Signed-off-by: Mircea Nistor <[email protected]>
github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Apr 15, 2025
## **Description**

This is a version bump for the preinstalled
@metamask/message-signing-snap to v1.1.1

This latest version has a number of improvements including support for
multiple entropy sources (following SIP-30) and encryption, as well as
results differentiated by origin.
This snap is used by the AuthenticationController /
UserStorageController and the latest version is necessary to make
progress in the controllers.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31943?quickstart=1)

## **Related issues**

relates to:
[IDENTITY-7](https://consensyssoftware.atlassian.net/browse/IDENTITY-7)
required by: MetaMask/core#5630
relates to: MetaMask/metamask-mobile#14653

## **Manual testing steps**

No user-facing changes

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[IDENTITY-7]:
https://consensyssoftware.atlassian.net/browse/IDENTITY-7?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Signed-off-by: Mircea Nistor <[email protected]>
Copy link
Contributor

@mathieuartu mathieuartu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks amazing, I know it's WIP so I've added a few comments and will wait for de-draft to approve

@@ -144,7 +148,7 @@ export function createEntryPath<T extends boolean>(
options: {
validateAgainstSchema: T;
} = { validateAgainstSchema: true as T },
): string {
): `${string}/${string}` {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hehe

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

easier inference downstream

@@ -426,11 +513,11 @@ export class UserStorage {
try {
const headers = await this.#getAuthorizationHeader();
const storageKey = await this.getStorageKey();
const encryptedPath = createEntryPath(path, storageKey, {
const entryPath = createEntryPath(path, storageKey, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice renaming this here also

Comment on lines +167 to +171
profileStorageKeyPath = createEntryPath(
`${USER_STORAGE_FEATURE_NAMES.keys}.${PROFILE_STORAGE_KEY}`,
srpKey,
{ validateAgainstSchema: false },
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this could be moved L165 as a const assignment, and we could remove the if (profileStorageKeyPath) L193

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh well, we could save some compute time & memory by doing this here only if (srpKey). Felt it was a tad complicated when reading, but it makes sense in this regard. Feel free to dismiss the above

Copy link
Author

@mirceanis mirceanis Apr 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it like this for the case where the deterministic encryption layer is missing.
Then this all short circuits to the behavior we had before

@@ -11,6 +11,7 @@ import type {
StateMetadata,
} from '@metamask/base-controller';
import { BaseController } from '@metamask/base-controller';
import { encrypt } from '@metamask/eth-sig-util';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: WDYT we maybe alias encrypt so it's not confusing where it's coming from when reading L389 (@metamask/eth-sig-util or shared/encryption/encryption.ts).

I wouldn't know what to use though 😛

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point!
I'll rename it

Comment on lines +661 to +692
async #snapGetEncryptionPublicKey(): Promise<string> {
if (this.#_snapEncryptionKeyCache) {
return this.#_snapEncryptionKeyCache;
}

if (!this.#isUnlocked) {
throw new Error(
'#snapGetEncryptionPublicKey - unable to call snap, wallet is locked',
);
}

const result = (await this.messagingSystem.call(
'SnapController:handleRequest',
createSnapEncryptionPublicKeyRequest(),
)) as string;

this.#_snapEncryptionKeyCache = result;

return result;
}

#_snapDecryptMessageCache: Record<string, string> = {};

/**
* Calls the snap to attempt to decrypt the message.
*
* @param ciphertext - the encrypted text to decrypt.
* @returns The decrypted message, if decryption was possible.
*/
async #snapDecryptMessage(ciphertext: string): Promise<string> {
if (this.#_snapDecryptMessageCache[ciphertext]) {
return this.#_snapDecryptMessageCache[ciphertext];
}

if (!this.#isUnlocked) {
throw new Error(
'#snapDecryptMessage - unable to call snap, wallet is locked',
);
}

const result = (await this.messagingSystem.call(
'SnapController:handleRequest',
createSnapDecryptMessageRequest(ciphertext),
)) as string;

if (result) {
this.#_snapDecryptMessageCache[ciphertext] = result;
}

return result;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I feel there's a lot of overlap between those two methods and #snapSignMessage, should we try to refactor and have one snap interaction method that handles designated caches and params?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it could provide some marginal readability benefits, but at the cost of increasing the complexity of this PR.
For now, we have 4 methods using this pattern, all in one file, all rather small, so I don't think it's a very big deal.
Let's do this in a separate issue/PR, WDYT?

@@ -1,5 +1,6 @@
import type { NotNamespacedBy } from '@metamask/base-controller';
import { Messenger } from '@metamask/base-controller';
import { decrypt, type EthEncryptedData } from '@metamask/eth-sig-util';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Same comment as for UserStorageController.ts encrypt, should we alias this so its clear its coming from a library?

@mirceanis mirceanis force-pushed the persist-profile-storage-keys-to-e2ee-backend branch from d492e67 to a6f7ce3 Compare April 15, 2025 11:59
github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Apr 15, 2025
## **Description**

This is a version bump for the preinstalled
@metamask/message-signing-snap to v1.1.1

This latest version has a number of improvements including support for
multiple entropy sources (following SIP-30) and encryption, as well as
results differentiated by origin.
This snap is used by the AuthenticationController /
UserStorageController and the latest version is necessary to make
progress in the controllers.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31943?quickstart=1)

## **Related issues**

relates to:
[IDENTITY-7](https://consensyssoftware.atlassian.net/browse/IDENTITY-7)
required by: MetaMask/core#5630
relates to: MetaMask/metamask-mobile#14653

## **Manual testing steps**

No user-facing changes

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[IDENTITY-7]:
https://consensyssoftware.atlassian.net/browse/IDENTITY-7?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Signed-off-by: Mircea Nistor <[email protected]>
github-merge-queue bot pushed a commit to MetaMask/metamask-extension that referenced this pull request Apr 15, 2025
## **Description**

This is a version bump for the preinstalled
@metamask/message-signing-snap to v1.1.1

This latest version has a number of improvements including support for
multiple entropy sources (following SIP-30) and encryption, as well as
results differentiated by origin.
This snap is used by the AuthenticationController /
UserStorageController and the latest version is necessary to make
progress in the controllers.

[![Open in GitHub
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/31943?quickstart=1)

## **Related issues**

relates to:
[IDENTITY-7](https://consensyssoftware.atlassian.net/browse/IDENTITY-7)
required by: MetaMask/core#5630
relates to: MetaMask/metamask-mobile#14653

## **Manual testing steps**

No user-facing changes

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[IDENTITY-7]:
https://consensyssoftware.atlassian.net/browse/IDENTITY-7?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

Signed-off-by: Mircea Nistor <[email protected]>
github-merge-queue bot pushed a commit to MetaMask/metamask-mobile that referenced this pull request Apr 16, 2025
## **Description**

This is a version bump for the preinstalled
@metamask/message-signing-snap to v1.1.1

This latest version has a number of improvements including support for
multiple entropy sources (following SIP-30) and encryption, as well as
results differentiated by origin.
This snap is used by the AuthenticationController /
UserStorageController and the latest version is necessary to make
progress in the controllers.

## **Related issues**

relates to:
[IDENTITY-7](https://consensyssoftware.atlassian.net/browse/IDENTITY-7)
required by: MetaMask/core#5630
relates to: MetaMask/metamask-extension#31943

## **Manual testing steps**

There are no user-facing changes to test

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<!-- [screenshots/recordings] -->

## **Pre-merge author checklist**

- [X] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.


[IDENTITY-7]:
https://consensyssoftware.atlassian.net/browse/IDENTITY-7?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-changelog team-identity Identity Team changes. https://github.com/orgs/MetaMask/teams/identity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants