From 3e9fdac1d9124f1d923b5f645e16b0fb6fd2c7a7 Mon Sep 17 00:00:00 2001 From: Maarten Zuidhoorn Date: Wed, 26 Feb 2025 12:18:23 +0100 Subject: [PATCH 1/4] Add SIP-30 `source` to all examples and test-snaps --- packages/examples/packages/bip32/README.md | 2 +- .../examples/packages/bip32/snap.config.ts | 2 +- .../packages/bip32/snap.manifest.json | 2 +- .../src/{index.test.ts => index.test.tsx} | 103 +++++++++++++----- .../bip32/src/{index.ts => index.tsx} | 22 ++-- packages/examples/packages/bip32/src/types.ts | 12 ++ packages/examples/packages/bip44/README.md | 2 +- .../examples/packages/bip44/snap.config.ts | 2 +- .../packages/bip44/snap.manifest.json | 2 +- .../src/{index.test.ts => index.test.tsx} | 87 +++++++++++---- .../bip44/src/{index.ts => index.tsx} | 22 ++-- packages/examples/packages/bip44/src/types.ts | 6 + packages/examples/packages/bip44/src/utils.ts | 3 + .../src/features/snaps/bip32/BIP32.tsx | 12 +- .../snaps/bip32/components/PublicKey.tsx | 8 +- .../snaps/bip32/components/SignMessage.tsx | 7 +- .../src/features/snaps/bip44/BIP44.tsx | 4 + .../features/snaps/get-entropy/GetEntropy.tsx | 8 +- ...EntropySources.tsx => EntropySelector.tsx} | 36 +++--- .../snaps/get-entropy/components/index.ts | 2 +- .../features/snaps/get-entropy/hooks/index.ts | 2 +- .../get-entropy/hooks/useEntropySelector.tsx | 36 ++++++ .../get-entropy/hooks/useEntropySources.ts | 23 ---- 23 files changed, 280 insertions(+), 125 deletions(-) rename packages/examples/packages/bip32/src/{index.test.ts => index.test.tsx} (66%) rename packages/examples/packages/bip32/src/{index.ts => index.tsx} (87%) rename packages/examples/packages/bip44/src/{index.test.ts => index.test.tsx} (62%) rename packages/examples/packages/bip44/src/{index.ts => index.tsx} (84%) rename packages/test-snaps/src/features/snaps/get-entropy/components/{EntropySources.tsx => EntropySelector.tsx} (61%) create mode 100644 packages/test-snaps/src/features/snaps/get-entropy/hooks/useEntropySelector.tsx delete mode 100644 packages/test-snaps/src/features/snaps/get-entropy/hooks/useEntropySources.ts diff --git a/packages/examples/packages/bip32/README.md b/packages/examples/packages/bip32/README.md index 170604ab9f..c841341d79 100644 --- a/packages/examples/packages/bip32/README.md +++ b/packages/examples/packages/bip32/README.md @@ -56,4 +56,4 @@ JSON-RPC methods: (ECDSA for `secp256k1` and EdDSA for `ed25519`). For more information, you can refer to -[the end-to-end tests](./src/index.test.ts). +[the end-to-end tests](./src/index.test.tsx). diff --git a/packages/examples/packages/bip32/snap.config.ts b/packages/examples/packages/bip32/snap.config.ts index 910d5efc4a..a667f5b7d9 100644 --- a/packages/examples/packages/bip32/snap.config.ts +++ b/packages/examples/packages/bip32/snap.config.ts @@ -2,7 +2,7 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - input: resolve(__dirname, 'src/index.ts'), + input: resolve(__dirname, 'src/index.tsx'), server: { port: 8001, }, diff --git a/packages/examples/packages/bip32/snap.manifest.json b/packages/examples/packages/bip32/snap.manifest.json index 62b7cabfee..bc4fd13876 100644 --- a/packages/examples/packages/bip32/snap.manifest.json +++ b/packages/examples/packages/bip32/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "dzOD3mPn8PgVXC3LT3OtKqs09WIcvuYo/mIZGfhsTPw=", + "shasum": "8dCMTEaTarO8HLw6wttq8EWYcBkG1F/d+gv/OG6sgts=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/bip32/src/index.test.ts b/packages/examples/packages/bip32/src/index.test.tsx similarity index 66% rename from packages/examples/packages/bip32/src/index.test.ts rename to packages/examples/packages/bip32/src/index.test.tsx index 7aca481873..eb89783acd 100644 --- a/packages/examples/packages/bip32/src/index.test.ts +++ b/packages/examples/packages/bip32/src/index.test.tsx @@ -1,6 +1,6 @@ import { expect } from '@jest/globals'; import { assertIsConfirmationDialog, installSnap } from '@metamask/snaps-jest'; -import { copyable, heading, panel, text } from '@metamask/snaps-sdk'; +import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx'; describe('onRpcRequest', () => { it('throws an error if the requested method does not exist', async () => { @@ -38,6 +38,23 @@ describe('onRpcRequest', () => { ); }); + it('returns an secp256k1 public key for a given BIP-32 path with a different source', async () => { + const { request } = await installSnap(); + + const response = await request({ + method: 'getPublicKey', + params: { + path: ['m', "44'", "0'"], + curve: 'secp256k1', + source: 'alternative', + }, + }); + + expect(response).toRespondWith( + '0x04f72f0e3684b0d7295f391616f12a469070bfcd175c55366239047495a2c1c410b4d820fb4147de213a2d25fb19f9451354ad5949fc881a2d219529703416de73', + ); + }); + it('returns a compressed secp256k1 public key for a given BIP-32 path', async () => { const { request } = await installSnap(); @@ -110,15 +127,14 @@ describe('onRpcRequest', () => { const ui = await response.getInterface(); assertIsConfirmationDialog(ui); expect(ui).toRender( - panel([ - heading('Signature request'), - text( - `Do you want to secp256k1 sign "Hello, world!" with the following public key?`, - ), - copyable( - '0x0423a6a6f8800b2d0710595969f40148a28953c9eebc0c0da78a89be3b3935f59c0069dfe1cace1a083e9c962c9f2ef932e9346cd907e647d993d787c4e59d03d1', - ), - ]), + + Signature request + + Do you want to {'secp256k1'} sign "{'Hello, world!'}" with the + following public key? + + + , ); await ui.ok(); @@ -128,6 +144,39 @@ describe('onRpcRequest', () => { ); }); + it('signs a message for the given BIP-32 path using secp256k1 with a different source', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'signMessage', + params: { + path: ['m', "44'", "0'"], + curve: 'secp256k1', + message: 'Hello, world!', + source: 'alternative', + }, + }); + + const ui = await response.getInterface(); + assertIsConfirmationDialog(ui); + expect(ui).toRender( + + Signature request + + Do you want to {'secp256k1'} sign "{'Hello, world!'}" with the + following public key? + + + , + ); + + await ui.ok(); + + expect(await response).toRespondWith( + '0x3044022049a3e74ed526df8b2a8e16e95a181d909255c90f6f63eb8efc16625af917b07d022014f2b203b0749058cbfc3ad0456c7b2bdf1ab809fd5913c6ee272cfc56f30ef2', + ); + }); + it('signs a message for the given BIP-32 path using ed25519', async () => { const { request } = await installSnap(); @@ -143,15 +192,14 @@ describe('onRpcRequest', () => { const ui = await response.getInterface(); assertIsConfirmationDialog(ui); expect(ui).toRender( - panel([ - heading('Signature request'), - text( - `Do you want to ed25519 sign "Hello, world!" with the following public key?`, - ), - copyable( - '0x000b96ba23cae9597de51e0187d7ef1b2d1a782dc2d5ceac770a327de3844dd533', - ), - ]), + + Signature request + + Do you want to {'ed25519'} sign "{'Hello, world!'}" with the + following public key? + + + , ); await ui.ok(); @@ -176,15 +224,14 @@ describe('onRpcRequest', () => { const ui = await response.getInterface(); assertIsConfirmationDialog(ui); expect(ui).toRender( - panel([ - heading('Signature request'), - text( - `Do you want to ed25519Bip32 sign "Hello, world!" with the following public key?`, - ), - copyable( - '0x2c3ac523b470dead7981df46c93d894ed4381e94c23aa1ec3806a320ff8ceb42', - ), - ]), + + Signature request + + Do you want to {'ed25519Bip32'} sign "{'Hello, world!'}" with the + following public key? + + + , ); await ui.ok(); diff --git a/packages/examples/packages/bip32/src/index.ts b/packages/examples/packages/bip32/src/index.tsx similarity index 87% rename from packages/examples/packages/bip32/src/index.ts rename to packages/examples/packages/bip32/src/index.tsx index f9a196c70b..520bb6e074 100644 --- a/packages/examples/packages/bip32/src/index.ts +++ b/packages/examples/packages/bip32/src/index.tsx @@ -1,14 +1,11 @@ import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, - panel, - text, - heading, - copyable, InvalidParamsError, UserRejectedRequestError, MethodNotFoundError, } from '@metamask/snaps-sdk'; +import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx'; import { add0x, assert, @@ -62,13 +59,16 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { method: 'snap_dialog', params: { type: DialogType.Confirmation, - content: panel([ - heading('Signature request'), - text( - `Do you want to ${curve} sign "${message}" with the following public key?`, - ), - copyable(add0x(node.publicKey)), - ]), + content: ( + + Signature request + + Do you want to {curve} sign "{message}" with the following + public key? + + + + ), }, }); diff --git a/packages/examples/packages/bip32/src/types.ts b/packages/examples/packages/bip32/src/types.ts index 38a3b07df4..0d7c957eda 100644 --- a/packages/examples/packages/bip32/src/types.ts +++ b/packages/examples/packages/bip32/src/types.ts @@ -20,6 +20,12 @@ export type GetBip32PublicKeyParams = { */ compressed?: boolean | undefined; + /** + * The entropy source to use for the signature. If not provided, the primary + * entropy source will be used. + */ + source?: string | undefined; + /** * Miscellaneous parameters, which are passed to `snap_getBip32PublicKey`. */ @@ -47,4 +53,10 @@ export type SignMessageParams = { * The curve used to derive the account. */ curve: 'secp256k1' | 'ed25519' | 'ed25519Bip32'; + + /** + * The entropy source to use for the signature. If not provided, the primary + * entropy source will be used. + */ + source?: string | undefined; }; diff --git a/packages/examples/packages/bip44/README.md b/packages/examples/packages/bip44/README.md index 56c13c1c3e..4de7507a0c 100644 --- a/packages/examples/packages/bip44/README.md +++ b/packages/examples/packages/bip44/README.md @@ -44,4 +44,4 @@ JSON-RPC methods: elliptic curve to sign the message. For more information, you can refer to -[the end-to-end tests](./src/index.test.ts). +[the end-to-end tests](./src/index.test.tsx). diff --git a/packages/examples/packages/bip44/snap.config.ts b/packages/examples/packages/bip44/snap.config.ts index 15b2a229ba..804529ef52 100644 --- a/packages/examples/packages/bip44/snap.config.ts +++ b/packages/examples/packages/bip44/snap.config.ts @@ -2,7 +2,7 @@ import type { SnapConfig } from '@metamask/snaps-cli'; import { resolve } from 'path'; const config: SnapConfig = { - input: resolve(__dirname, 'src/index.ts'), + input: resolve(__dirname, 'src/index.tsx'), server: { port: 8002, }, diff --git a/packages/examples/packages/bip44/snap.manifest.json b/packages/examples/packages/bip44/snap.manifest.json index 7f013f7763..e879085724 100644 --- a/packages/examples/packages/bip44/snap.manifest.json +++ b/packages/examples/packages/bip44/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snaps.git" }, "source": { - "shasum": "t0+OXVvAOnJOEgxjLB9Gz9CtAhNJr0nj9WC7nEeEZUs=", + "shasum": "cCHLwOkIhbtImq9IkpDZhuNmE/sLspy4WWTMRLiCiDw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/examples/packages/bip44/src/index.test.ts b/packages/examples/packages/bip44/src/index.test.tsx similarity index 62% rename from packages/examples/packages/bip44/src/index.test.ts rename to packages/examples/packages/bip44/src/index.test.tsx index 92f8794857..f201400497 100644 --- a/packages/examples/packages/bip44/src/index.test.ts +++ b/packages/examples/packages/bip44/src/index.test.tsx @@ -1,6 +1,6 @@ import { expect } from '@jest/globals'; import { assertIsConfirmationDialog, installSnap } from '@metamask/snaps-jest'; -import { copyable, heading, panel, text } from '@metamask/snaps-sdk'; +import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx'; describe('onRpcRequest', () => { it('throws an error if the requested method does not exist', async () => { @@ -38,6 +38,23 @@ describe('onRpcRequest', () => { ); }); + it('returns a BIP-44 public key for a given coin type and address index with a different source', async () => { + const { request } = await installSnap(); + + const response = await request({ + method: 'getPublicKey', + params: { + coinType: 3, + addressIndex: 5, + source: 'alternative', + }, + }); + + expect(response).toRespondWith( + '0xadf494f336c373de010491dfb442a9dda4b6f640fbb81f0139deed7edb236817eaf8621722b01553134c4c91fbe89c45', + ); + }); + it('returns a BIP-44 public key for the default coin type and address index if no parameters are provided', async () => { const { request } = await installSnap(); @@ -86,15 +103,14 @@ describe('onRpcRequest', () => { assertIsConfirmationDialog(ui); expect(ui).toRender( - panel([ - heading('Signature request'), - text( - `Do you want to BLS sign "Hello, world!" with the following public key?`, - ), - copyable( - '0x96e2b36a8af526928326683f6d8ddb82fbfcd1ba1cd3f0382a4f092a19fcb46b87e836dd34075514c9b1a3b8f7bdc4f0', - ), - ]), + + Signature request + + Do you want to BLS sign "{'Hello, world!'}" with the following + public key? + + + , ); await ui.ok(); @@ -104,6 +120,40 @@ describe('onRpcRequest', () => { ); }); + it('signs a message for the given coin type and address index with a different source', async () => { + const { request } = await installSnap(); + + const response = request({ + method: 'signMessage', + params: { + coinType: 3, + addressIndex: 5, + message: 'Hello, world!', + source: 'alternative', + }, + }); + + const ui = await response.getInterface(); + assertIsConfirmationDialog(ui); + + expect(ui).toRender( + + Signature request + + Do you want to BLS sign "{'Hello, world!'}" with the following + public key? + + + , + ); + + await ui.ok(); + + expect(await response).toRespondWith( + '0xb01b6dd84f1aae227b25f8679d739508112c304819276843d057806e436cc0ccba16966e7b5b1bef1d55699531f31ed40908ce2a8d349e0bb5a8a5d840fb928b3fad499c6f117afdc740ac205d76c1ece8d4134822f88156243e926ddac99ab0', + ); + }); + it('signs a message using the default coin type and address index', async () => { const { request } = await installSnap(); @@ -118,15 +168,14 @@ describe('onRpcRequest', () => { assertIsConfirmationDialog(ui); expect(ui).toRender( - panel([ - heading('Signature request'), - text( - `Do you want to BLS sign "Hello, world!" with the following public key?`, - ), - copyable( - '0xa9ad546540fca1662bdf3de110a456f2d825271e6d960cc5028224d4dc37c0e7fdd806b22fe94d9325548933e9c1ee68', - ), - ]), + + Signature request + + Do you want to BLS sign "{'Hello, world!'}" with the following + public key? + + + , ); await ui.ok(); diff --git a/packages/examples/packages/bip44/src/index.ts b/packages/examples/packages/bip44/src/index.tsx similarity index 84% rename from packages/examples/packages/bip44/src/index.ts rename to packages/examples/packages/bip44/src/index.tsx index 4025ea5155..7c80debcb0 100644 --- a/packages/examples/packages/bip44/src/index.ts +++ b/packages/examples/packages/bip44/src/index.tsx @@ -1,13 +1,10 @@ import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { DialogType, - panel, - text, - heading, - copyable, MethodNotFoundError, UserRejectedRequestError, } from '@metamask/snaps-sdk'; +import { Box, Copyable, Heading, Text } from '@metamask/snaps-sdk/jsx'; import { bytesToHex, stringToBytes } from '@metamask/utils'; import { getPublicKey, sign } from '@noble/bls12-381'; @@ -48,13 +45,16 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => { method: 'snap_dialog', params: { type: DialogType.Confirmation, - content: panel([ - heading('Signature request'), - text( - `Do you want to BLS sign "${message}" with the following public key?`, - ), - copyable(publicKey), - ]), + content: ( + + Signature request + + Do you want to BLS sign "{message}" with the following public + key? + + + + ), }, }); diff --git a/packages/examples/packages/bip44/src/types.ts b/packages/examples/packages/bip44/src/types.ts index 8f76d8044c..342f6ff35d 100644 --- a/packages/examples/packages/bip44/src/types.ts +++ b/packages/examples/packages/bip44/src/types.ts @@ -14,6 +14,12 @@ export type GetAccountParams = { * defaults to the first address (`address_index` = 0). */ addressIndex?: number; + + /** + * The entropy source to use for the signature. If not provided, the primary + * entropy source will be used. + */ + source?: string | undefined; }; /** diff --git a/packages/examples/packages/bip44/src/utils.ts b/packages/examples/packages/bip44/src/utils.ts index 622073abf4..1f5ea3c464 100644 --- a/packages/examples/packages/bip44/src/utils.ts +++ b/packages/examples/packages/bip44/src/utils.ts @@ -13,12 +13,14 @@ import type { GetAccountParams } from './types'; * specified, it defaults to the Bitcoin coin type (1). * @param params.addressIndex - The address index to get the account for. If * this is not specified, it defaults to the first address (`address_index` = 0). + * @param params.source - The entropy source to use for the entropy derivation. * @returns A private key, as a hexadecimal string, without the leading `0x`. * @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_getbip44entropy */ export const getPrivateKey = async ({ coinType = 1, addressIndex = 0, + source, }: GetAccountParams = {}) => { // `snap_getBip44Entropy` returns a `JsonBIP44CoinTypeNode` object, which can // be used with the `deriveBIP44AddressKey` function from `@metamask/key-tree` @@ -27,6 +29,7 @@ export const getPrivateKey = async ({ method: 'snap_getBip44Entropy', params: { coinType, + source, }, }); diff --git a/packages/test-snaps/src/features/snaps/bip32/BIP32.tsx b/packages/test-snaps/src/features/snaps/bip32/BIP32.tsx index 8ee6490ae5..b92ac2b134 100644 --- a/packages/test-snaps/src/features/snaps/bip32/BIP32.tsx +++ b/packages/test-snaps/src/features/snaps/bip32/BIP32.tsx @@ -3,8 +3,11 @@ import type { FunctionComponent } from 'react'; import { PublicKey, SignMessage } from './components'; import { BIP_32_PORT, BIP_32_SNAP_ID, BIP_32_VERSION } from './constants'; import { Snap } from '../../../components'; +import { useEntropySelector } from '../get-entropy/hooks'; export const BIP32: FunctionComponent = () => { + const { selector, source } = useEntropySelector(); + return ( { version={BIP_32_VERSION} testId="bip32" > - - - - + {selector} + + + + ); }; diff --git a/packages/test-snaps/src/features/snaps/bip32/components/PublicKey.tsx b/packages/test-snaps/src/features/snaps/bip32/components/PublicKey.tsx index 342955a165..b9c18993a3 100644 --- a/packages/test-snaps/src/features/snaps/bip32/components/PublicKey.tsx +++ b/packages/test-snaps/src/features/snaps/bip32/components/PublicKey.tsx @@ -8,7 +8,11 @@ import { Result } from '../../../../components'; import { getSnapId } from '../../../../utils'; import { BIP_32_PORT, BIP_32_SNAP_ID } from '../constants'; -export const PublicKey: FunctionComponent = () => { +export type PublicKeyProps = { + source?: string | undefined; +}; + +export const PublicKey: FunctionComponent = ({ source }) => { const [invokeSnap, { isLoading, data }] = useInvokeMutation(); const handleClick = (method: string, params: JsonRpcParams) => () => { @@ -28,6 +32,7 @@ export const PublicKey: FunctionComponent = () => { path: ['m', "44'", "0'"], curve: 'secp256k1', compressed: false, + ...(source !== undefined && { source }), })} disabled={isLoading} > @@ -51,6 +56,7 @@ export const PublicKey: FunctionComponent = () => { onClick={handleClick('getPublicKey', { path: ['m', "44'", "1'"], curve: 'secp256k1', + ...(source !== undefined && { source }), })} disabled={isLoading} > diff --git a/packages/test-snaps/src/features/snaps/bip32/components/SignMessage.tsx b/packages/test-snaps/src/features/snaps/bip32/components/SignMessage.tsx index 60a9665c9d..6ee4cd3b82 100644 --- a/packages/test-snaps/src/features/snaps/bip32/components/SignMessage.tsx +++ b/packages/test-snaps/src/features/snaps/bip32/components/SignMessage.tsx @@ -10,9 +10,13 @@ import { BIP_32_PORT, BIP_32_SNAP_ID } from '../constants'; export type SignMessageProps = { curve: 'secp256k1' | 'ed25519' | 'ed25519Bip32'; + source?: string | undefined; }; -export const SignMessage: FunctionComponent = ({ curve }) => { +export const SignMessage: FunctionComponent = ({ + curve, + source, +}) => { const [message, setMessage] = useState(''); const [invokeSnap, { isLoading, data, error }] = useInvokeMutation(); @@ -30,6 +34,7 @@ export const SignMessage: FunctionComponent = ({ curve }) => { message, curve, path: ['m', "44'", "0'"], + ...(source !== undefined && { source }), }, }).catch(logError); }; diff --git a/packages/test-snaps/src/features/snaps/bip44/BIP44.tsx b/packages/test-snaps/src/features/snaps/bip44/BIP44.tsx index 4aa047269a..2c4e9aee66 100644 --- a/packages/test-snaps/src/features/snaps/bip44/BIP44.tsx +++ b/packages/test-snaps/src/features/snaps/bip44/BIP44.tsx @@ -7,9 +7,11 @@ import { BIP_44_PORT, BIP_44_SNAP_ID, BIP_44_VERSION } from './constants'; import { useInvokeMutation } from '../../../api'; import { Result, Snap } from '../../../components'; import { getSnapId } from '../../../utils'; +import { useEntropySelector } from '../get-entropy/hooks'; export const BIP44: FunctionComponent = () => { const [invokeSnap, { isLoading, data, error }] = useInvokeMutation(); + const { selector, source } = useEntropySelector(); const handleClick = (method: string, coinType: number) => () => { invokeSnap({ @@ -17,6 +19,7 @@ export const BIP44: FunctionComponent = () => { method, params: { coinType, + ...(source !== undefined && { source }), }, }).catch(logError); }; @@ -29,6 +32,7 @@ export const BIP44: FunctionComponent = () => { version={BIP_44_VERSION} testId="bip44" > + {selector}