Skip to content

Commit a40d7ed

Browse files
committed
Add source parameter to snap_get*Entropy methods
1 parent c017ce8 commit a40d7ed

15 files changed

+276
-34
lines changed

packages/snaps-rpc-methods/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = deepmerge(baseConfig, {
1010
],
1111
coverageThreshold: {
1212
global: {
13-
branches: 95.11,
13+
branches: 95.17,
1414
functions: 98.61,
1515
lines: 98.81,
1616
statements: 98.49,

packages/snaps-rpc-methods/src/restricted/caveats/permittedCoinTypes.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,36 @@ describe('validateBIP44Params', () => {
6767
'Invalid "coinType" parameter. Coin type must be a non-negative integer.',
6868
);
6969
});
70+
71+
it.each([
72+
{},
73+
[],
74+
true,
75+
false,
76+
null,
77+
-1,
78+
1.1,
79+
Infinity,
80+
-Infinity,
81+
NaN,
82+
0x80000000,
83+
])('throws an error if the source is invalid', (value) => {
84+
expect(() => {
85+
validateBIP44Params({ coinType: 1, source: value });
86+
}).toThrow(
87+
'Invalid "source" parameter. Source must be a string if provided.',
88+
);
89+
});
90+
91+
it.each([
92+
{ coinType: 1 },
93+
{ coinType: 1, source: 'source-id' },
94+
{ coinType: 1, source: undefined },
95+
])('does not throw if the parameters are valid', (value) => {
96+
expect(() => {
97+
validateBIP44Params(value);
98+
}).not.toThrow();
99+
});
70100
});
71101

72102
describe('validateBIP44Caveat', () => {

packages/snaps-rpc-methods/src/restricted/caveats/permittedCoinTypes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ export function validateBIP44Params(
5757
});
5858
}
5959

60+
if (
61+
hasProperty(value, 'source') &&
62+
typeof value.source !== 'undefined' &&
63+
typeof value.source !== 'string'
64+
) {
65+
throw rpcErrors.invalidParams({
66+
message:
67+
'Invalid "source" parameter. Source must be a string if provided.',
68+
});
69+
}
70+
6071
if (FORBIDDEN_COIN_TYPES.includes(value.coinType)) {
6172
throw rpcErrors.invalidParams({
6273
message: `Coin type ${value.coinType} is forbidden.`,

packages/snaps-rpc-methods/src/restricted/getBip32Entropy.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { PermissionType, SubjectType } from '@metamask/permission-controller';
22
import { SnapCaveatType } from '@metamask/snaps-utils';
3-
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';
3+
import {
4+
MOCK_SNAP_ID,
5+
TEST_SECRET_RECOVERY_PHRASE_BYTES,
6+
} from '@metamask/snaps-utils/test-utils';
47

58
import {
69
getBip32EntropyBuilder,
@@ -193,6 +196,45 @@ describe('getBip32EntropyImplementation', () => {
193196
`);
194197
});
195198

199+
it('calls `getMnemonic` with a different entropy source', async () => {
200+
const getMnemonic = jest
201+
.fn()
202+
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE_BYTES);
203+
204+
const getUnlockPromise = jest.fn();
205+
const getClientCryptography = jest.fn().mockReturnValue({});
206+
207+
expect(
208+
await getBip32EntropyImplementation({
209+
getUnlockPromise,
210+
getMnemonic,
211+
getClientCryptography,
212+
})({
213+
method: 'snap_getBip32Entropy',
214+
context: { origin: MOCK_SNAP_ID },
215+
params: {
216+
path: ['m', "44'", "1'"],
217+
curve: 'secp256k1',
218+
source: 'source-id',
219+
},
220+
}),
221+
).toMatchInlineSnapshot(`
222+
{
223+
"chainCode": "0x50ccfa58a885b48b5eed09486b3948e8454f34856fb81da5d7b8519d7997abd1",
224+
"curve": "secp256k1",
225+
"depth": 2,
226+
"index": 2147483649,
227+
"masterFingerprint": 1404659567,
228+
"network": "mainnet",
229+
"parentFingerprint": 1829122711,
230+
"privateKey": "0xc73cedb996e7294f032766853a8b7ba11ab4ce9755fc052f2f7b9000044c99af",
231+
"publicKey": "0x048e129862c1de5ca86468add43b001d32fd34b8113de716ecd63fa355b7f1165f0e76f5dc6095100f9fdaa76ddf28aa3f21406ac5fda7c71ffbedb45634fe2ceb",
232+
}
233+
`);
234+
235+
expect(getMnemonic).toHaveBeenCalledWith('source-id');
236+
});
237+
196238
it('uses custom client cryptography functions', async () => {
197239
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
198240
const getMnemonic = jest

packages/snaps-rpc-methods/src/restricted/getBip32Entropy.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ const targetName = 'snap_getBip32Entropy';
2222

2323
export type GetBip32EntropyMethodHooks = {
2424
/**
25-
* @returns The mnemonic of the user's primary keyring.
25+
* Get the mnemonic of the provided source. If no source is provided, the
26+
* mnemonic of the primary keyring will be returned.
27+
*
28+
* @param source - The optional ID of the source to get the mnemonic of.
29+
* @returns The mnemonic of the provided source, or the default source if no
30+
* source is provided.
2631
*/
27-
getMnemonic: () => Promise<Uint8Array>;
32+
getMnemonic: (source?: string | undefined) => Promise<Uint8Array>;
2833

2934
/**
3035
* Waits for the extension to be unlocked.
@@ -128,7 +133,7 @@ export function getBip32EntropyImplementation({
128133
const node = await getNode({
129134
curve: params.curve,
130135
path: params.path,
131-
secretRecoveryPhrase: await getMnemonic(),
136+
secretRecoveryPhrase: await getMnemonic(params.source),
132137
cryptographicFunctions: getClientCryptography(),
133138
});
134139

packages/snaps-rpc-methods/src/restricted/getBip32PublicKey.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { PermissionType, SubjectType } from '@metamask/permission-controller';
22
import { SnapCaveatType } from '@metamask/snaps-utils';
3-
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';
3+
import {
4+
MOCK_SNAP_ID,
5+
TEST_SECRET_RECOVERY_PHRASE_BYTES,
6+
} from '@metamask/snaps-utils/test-utils';
47

58
import {
69
getBip32PublicKeyBuilder,
@@ -155,6 +158,35 @@ describe('getBip32PublicKeyImplementation', () => {
155158
);
156159
});
157160

161+
it('calls `getMnemonic` with a different entropy source', async () => {
162+
const getMnemonic = jest
163+
.fn()
164+
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE_BYTES);
165+
166+
const getUnlockPromise = jest.fn();
167+
const getClientCryptography = jest.fn().mockReturnValue({});
168+
169+
expect(
170+
await getBip32PublicKeyImplementation({
171+
getUnlockPromise,
172+
getMnemonic,
173+
getClientCryptography,
174+
})({
175+
method: 'snap_getBip32PublicKey',
176+
context: { origin: MOCK_SNAP_ID },
177+
params: {
178+
path: ['m', "44'", "1'", '1', '2', '3'],
179+
curve: 'secp256k1',
180+
source: 'source-id',
181+
},
182+
}),
183+
).toMatchInlineSnapshot(
184+
`"0x042de17487a660993177ce2a85bb73b6cd9ad436184d57bdf5a93f5db430bea914f7c31d378fe68f4723b297a04e49ef55fbf490605c4a3f9ca947a4af4f06526a"`,
185+
);
186+
187+
expect(getMnemonic).toHaveBeenCalledWith('source-id');
188+
});
189+
158190
it('uses custom client cryptography functions', async () => {
159191
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
160192
const getMnemonic = jest

packages/snaps-rpc-methods/src/restricted/getBip32PublicKey.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
CurveStruct,
1818
SnapCaveatType,
1919
} from '@metamask/snaps-utils';
20-
import { boolean, object, optional } from '@metamask/superstruct';
20+
import { boolean, object, optional, string } from '@metamask/superstruct';
2121
import type { NonEmptyArray } from '@metamask/utils';
2222
import { assertStruct } from '@metamask/utils';
2323

@@ -28,9 +28,14 @@ const targetName = 'snap_getBip32PublicKey';
2828

2929
export type GetBip32PublicKeyMethodHooks = {
3030
/**
31-
* @returns The mnemonic of the user's primary keyring.
31+
* Get the mnemonic of the provided source. If no source is provided, the
32+
* mnemonic of the primary keyring will be returned.
33+
*
34+
* @param source - The optional ID of the source to get the mnemonic of.
35+
* @returns The mnemonic of the provided source, or the default source if no
36+
* source is provided.
3237
*/
33-
getMnemonic: () => Promise<Uint8Array>;
38+
getMnemonic: (source?: string | undefined) => Promise<Uint8Array>;
3439

3540
/**
3641
* Waits for the extension to be unlocked.
@@ -66,6 +71,7 @@ export const Bip32PublicKeyArgsStruct = bip32entropy(
6671
path: Bip32PathStruct,
6772
curve: CurveStruct,
6873
compressed: optional(boolean()),
74+
source: optional(string()),
6975
}),
7076
);
7177

@@ -147,7 +153,7 @@ export function getBip32PublicKeyImplementation({
147153
const node = await getNode({
148154
curve: params.curve,
149155
path: params.path,
150-
secretRecoveryPhrase: await getMnemonic(),
156+
secretRecoveryPhrase: await getMnemonic(params.source),
151157
cryptographicFunctions: getClientCryptography(),
152158
});
153159

packages/snaps-rpc-methods/src/restricted/getBip44Entropy.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { SubjectType, PermissionType } from '@metamask/permission-controller';
22
import { SnapCaveatType } from '@metamask/snaps-utils';
3-
import { TEST_SECRET_RECOVERY_PHRASE_BYTES } from '@metamask/snaps-utils/test-utils';
3+
import {
4+
MOCK_SNAP_ID,
5+
TEST_SECRET_RECOVERY_PHRASE_BYTES,
6+
} from '@metamask/snaps-utils/test-utils';
47

58
import {
69
getBip44EntropyBuilder,
@@ -90,6 +93,42 @@ describe('getBip44EntropyImplementation', () => {
9093
`);
9194
});
9295

96+
it('calls `getMnemonic` with a different entropy source', async () => {
97+
const getMnemonic = jest
98+
.fn()
99+
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE_BYTES);
100+
101+
const getUnlockPromise = jest.fn();
102+
const getClientCryptography = jest.fn().mockReturnValue({});
103+
104+
expect(
105+
await getBip44EntropyImplementation({
106+
getUnlockPromise,
107+
getMnemonic,
108+
getClientCryptography,
109+
})({
110+
method: 'snap_getBip44Entropy',
111+
context: { origin: MOCK_SNAP_ID },
112+
params: { coinType: 1, source: 'source-id' },
113+
}),
114+
).toMatchInlineSnapshot(`
115+
{
116+
"chainCode": "0x50ccfa58a885b48b5eed09486b3948e8454f34856fb81da5d7b8519d7997abd1",
117+
"coin_type": 1,
118+
"depth": 2,
119+
"index": 2147483649,
120+
"masterFingerprint": 1404659567,
121+
"network": "mainnet",
122+
"parentFingerprint": 1829122711,
123+
"path": "m / bip32:44' / bip32:1'",
124+
"privateKey": "0xc73cedb996e7294f032766853a8b7ba11ab4ce9755fc052f2f7b9000044c99af",
125+
"publicKey": "0x048e129862c1de5ca86468add43b001d32fd34b8113de716ecd63fa355b7f1165f0e76f5dc6095100f9fdaa76ddf28aa3f21406ac5fda7c71ffbedb45634fe2ceb",
126+
}
127+
`);
128+
129+
expect(getMnemonic).toHaveBeenCalledWith('source-id');
130+
});
131+
93132
it('uses custom client cryptography functions', async () => {
94133
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
95134
const getMnemonic = jest

packages/snaps-rpc-methods/src/restricted/getBip44Entropy.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,14 @@ const targetName = 'snap_getBip44Entropy';
2121

2222
export type GetBip44EntropyMethodHooks = {
2323
/**
24-
* @returns The mnemonic of the user's primary keyring.
24+
* Get the mnemonic of the provided source. If no source is provided, the
25+
* mnemonic of the primary keyring will be returned.
26+
*
27+
* @param source - The optional ID of the source to get the mnemonic of.
28+
* @returns The mnemonic of the provided source, or the default source if no
29+
* source is provided.
2530
*/
26-
getMnemonic: () => Promise<Uint8Array>;
31+
getMnemonic: (source?: string | undefined) => Promise<Uint8Array>;
2732

2833
/**
2934
* Waits for the extension to be unlocked.
@@ -128,7 +133,11 @@ export function getBip44EntropyImplementation({
128133
const params = args.params as GetBip44EntropyParams;
129134

130135
const node = await BIP44CoinTypeNode.fromDerivationPath(
131-
[await getMnemonic(), `bip32:44'`, `bip32:${params.coinType}'`],
136+
[
137+
await getMnemonic(params.source),
138+
`bip32:44'`,
139+
`bip32:${params.coinType}'`,
140+
],
132141
'mainnet',
133142
getClientCryptography(),
134143
);

packages/snaps-rpc-methods/src/restricted/getEntropy.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,43 @@ describe('getEntropyImplementation', () => {
7373
);
7474
});
7575

76+
it('calls `getMnemonic` with a different entropy source', async () => {
77+
const getMnemonic = jest
78+
.fn()
79+
.mockImplementation(() => TEST_SECRET_RECOVERY_PHRASE_BYTES);
80+
81+
const getUnlockPromise = jest.fn();
82+
const getClientCryptography = jest.fn().mockReturnValue({});
83+
84+
const methodHooks = {
85+
getMnemonic,
86+
getUnlockPromise,
87+
getClientCryptography,
88+
};
89+
90+
const implementation = getEntropyBuilder.specificationBuilder({
91+
methodHooks,
92+
}).methodImplementation;
93+
94+
const result = await implementation({
95+
method: 'snap_getEntropy',
96+
params: {
97+
version: 1,
98+
salt: 'foo',
99+
source: 'source-id',
100+
},
101+
context: {
102+
origin: MOCK_SNAP_ID,
103+
},
104+
});
105+
106+
expect(result).toBe(
107+
'0x6d8e92de419401c7da3cedd5f60ce5635b26059c2a4a8003877fec83653a4921',
108+
);
109+
110+
expect(getMnemonic).toHaveBeenCalledWith('source-id');
111+
});
112+
76113
it('uses custom client cryptography functions', async () => {
77114
const getUnlockPromise = jest.fn().mockResolvedValue(undefined);
78115
const getMnemonic = jest

0 commit comments

Comments
 (0)