Skip to content

Commit 4bf4fda

Browse files
authored
feat(profile-metrics-service): add proof signing system (MetaMask#9016)
## Explanation <!-- Thanks for your contribution! Take a moment to answer these questions so that reviewers have the information they need to properly understand your changes: * What is the current state of things and why does it need to change? * What is the solution your changes offer and how does it work? * Are there any changes whose purpose might not obvious to those unfamiliar with the domain? * If your primary goal was to update one package but you found you had to update another one along the way, why did you do so? * If you had to upgrade a dependency, why did you do so? --> ## References Related to: https://consensyssoftware.atlassian.net/browse/MUL-1844 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Introduces cryptographic signing paths (keyring personal_sign and internal snap RPC) that must preserve correct message canonicalization and snap trust boundaries; hosts must delegate KeyringController and SnapController actions on the service messenger. > > **Overview** > Adds **`ProofOfOwnershipService`** to `@metamask/profile-metrics-controller` so wallets can produce server-verifiable ownership proofs bound to auth API nonces, for use on `AccountWithScopes.proof` when submitting profile metrics. > > **`ProofOfOwnershipService:sign({ account, nonce })`** picks the signer from the **first CAIP-2 scope**: `eip155` uses **`KeyringController:signPersonalMessage`** (EIP-191) on `metamask:proof-of-ownership:<nonce>:<canonical address>`; **Solana, Tron, and Bitcoin** use silent **`SnapController:handleRequest`** (`onClientRequest`, `signProofOfOwnership`) against `account.metadata.snap.id`. Unsupported or missing scopes throw **`ProofUnsupportedNamespaceError`**. > > Package surface: new snaps + `uuid` deps, auto-generated messenger action types, **`proofOfOwnershipServiceName`** export, and **`profileMetricsServiceName`** alias. Import paths for profile-metrics method-action types are tightened to explicit files (no barrel). Broad unit tests cover EVM/snap routing, canonical addresses, and error paths. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 26fc73d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e5fce4b commit 4bf4fda

9 files changed

Lines changed: 817 additions & 15 deletions

packages/profile-metrics-controller/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `ProofOfOwnershipService` for signing chain-native proofs of account ownership ([#9016](https://github.com/MetaMask/core/pull/9016))
13+
- Exposes `ProofOfOwnershipService:sign({ account, nonce })`, dispatching by the CAIP-2 namespace of the account's first scope.
14+
- EVM accounts are signed via `KeyringController:signPersonalMessage` (EIP-191); Solana, Tron, and Bitcoin accounts are signed via `SnapController:handleRequest` with the `onClientRequest` handler against the snap declared in `account.metadata.snap.id`, which keeps the request silent and client-internal.
15+
- The non-EVM snaps are expected to implement a `signProofOfOwnership` JSON-RPC method that validates the message prefix `metamask:proof-of-ownership:` before signing.
16+
- Add `profileMetricsServiceName` alias for the existing `serviceName` export, to disambiguate it from the new `proofOfOwnershipServiceName`. The original `serviceName` export is unchanged.
17+
1018
### Changed
1119

1220
- Bump `@metamask/transaction-controller` from `^67.0.0` to `^67.1.0` ([#9066](https://github.com/MetaMask/core/pull/9066))

packages/profile-metrics-controller/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,14 @@
6060
"@metamask/messenger": "^1.2.0",
6161
"@metamask/polling-controller": "^16.0.6",
6262
"@metamask/profile-sync-controller": "^28.1.1",
63+
"@metamask/snaps-controllers": "^19.0.0",
64+
"@metamask/snaps-sdk": "^11.0.0",
65+
"@metamask/snaps-utils": "^12.1.2",
6366
"@metamask/superstruct": "^3.1.0",
6467
"@metamask/transaction-controller": "^67.1.0",
6568
"@metamask/utils": "^11.9.0",
66-
"async-mutex": "^0.5.0"
69+
"async-mutex": "^0.5.0",
70+
"uuid": "^8.3.2"
6771
},
6872
"devDependencies": {
6973
"@metamask/auto-changelog": "^6.1.0",

packages/profile-metrics-controller/src/ProfileMetricsController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import { TransactionControllerTransactionSubmittedEvent } from '@metamask/transa
1919
import { Duration, inMilliseconds } from '@metamask/utils';
2020
import { Mutex } from 'async-mutex';
2121

22-
import type { ProfileMetricsServiceMethodActions } from '.';
23-
import type { ProfileMetricsControllerMethodActions } from '.';
22+
import type { ProfileMetricsControllerMethodActions } from './ProfileMetricsController-method-action-types';
2423
import type { AccountWithScopes } from './ProfileMetricsService';
24+
import type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';
2525

2626
/**
2727
* The name of the {@link ProfileMetricsController}, used to namespace the

packages/profile-metrics-controller/src/ProfileMetricsService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@metamask/superstruct';
1515
import type { IDisposable } from 'cockatiel';
1616

17-
import type { ProfileMetricsServiceMethodActions } from '.';
17+
import type { ProfileMetricsServiceMethodActions } from './ProfileMetricsService-method-action-types';
1818

1919
/**
2020
* The shape of an entry in the `POST /api/v2/nonce/batch` response body.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* This file is auto generated.
3+
* Do not edit manually.
4+
*/
5+
6+
import type { ProofOfOwnershipService } from './ProofOfOwnershipService';
7+
8+
/**
9+
* Sign a proof of ownership for the given account and server-issued nonce.
10+
*
11+
* The returned proof is shaped to drop directly into
12+
* `AccountWithScopes.proof` for `ProfileMetricsService:submitMetrics`.
13+
*
14+
* @param data - The account to prove ownership of and the nonce to bind
15+
* the proof to.
16+
* @returns The proof of ownership (nonce echo + signature).
17+
* @throws {ProofUnsupportedNamespaceError} if the account's first scope
18+
* carries a namespace this service does not know how to sign for, or if
19+
* the account has no scopes.
20+
* @throws if the underlying signer (keyring or snap) rejects, or if the
21+
* snap returns a malformed response.
22+
*/
23+
export type ProofOfOwnershipServiceSignAction = {
24+
type: `ProofOfOwnershipService:sign`;
25+
handler: ProofOfOwnershipService['sign'];
26+
};
27+
28+
/**
29+
* Union of all ProofOfOwnershipService action types.
30+
*/
31+
export type ProofOfOwnershipServiceMethodActions =
32+
ProofOfOwnershipServiceSignAction;

0 commit comments

Comments
 (0)