@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
135 lines • 5.32 kB
JavaScript
import { PermissionType, SubjectType } from "@metamask/permission-controller";
import { rpcErrors } from "@metamask/rpc-errors";
import { bip32entropy, Bip32PathStruct, CurveStruct, SnapCaveatType } from "@metamask/snaps-utils";
import { boolean, object, optional, string } from "@metamask/superstruct";
import { assertStruct } from "@metamask/utils";
import { getMnemonic, getMnemonicSeed, getNodeFromMnemonic, getNodeFromSeed, getValueFromEntropySource } from "../utils.mjs";
const targetName = 'snap_getBip32PublicKey';
export const Bip32PublicKeyArgsStruct = bip32entropy(object({
path: Bip32PathStruct,
curve: CurveStruct,
compressed: optional(boolean()),
source: optional(string()),
}));
/**
* The specification builder for the `snap_getBip32PublicKey` permission.
* `snap_getBip32PublicKey` lets the Snap retrieve public keys for a particular
* BIP-32 node.
*
* @param options - The specification builder options.
* @param options.messenger - The messenger.
* @param options.methodHooks - The RPC method hooks needed by the method implementation.
* @returns The specification for the `snap_getBip32PublicKey` permission.
*/
const specificationBuilder = ({ methodHooks, messenger, }) => {
return {
permissionType: PermissionType.RestrictedMethod,
targetName,
allowedCaveats: [SnapCaveatType.PermittedDerivationPaths],
methodImplementation: getBip32PublicKeyImplementation({
methodHooks,
messenger,
}),
validator: ({ caveats }) => {
if (caveats?.length !== 1 ||
caveats[0].type !== SnapCaveatType.PermittedDerivationPaths) {
throw rpcErrors.invalidParams({
message: `Expected a single "${SnapCaveatType.PermittedDerivationPaths}" caveat.`,
});
}
},
subjectTypes: [SubjectType.Snap],
};
};
const methodHooks = {
getUnlockPromise: true,
getClientCryptography: true,
};
/**
* Gets the [BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
* public key for the derivation path specified by the `path` parameter. Note
* that this returns the public key, not the extended public key (`xpub`), or
* Ethereum address.
*
* @example
* ```json name="Manifest"
* {
* "initialPermissions": {
* "snap_getBip32PublicKey": [
* {
* "path": ["m", "44'", "3'", "0'", "0", "0"],
* "curve": "secp256k1"
* }
* ]
* }
* }
* ```
* ```ts name="Usage"
* // This example uses Dogecoin, which has a derivation path starting with
* // "m / 44' / 3'".
* const dogecoinPublicKey = await snap.request({
* method: 'snap_getBip32PublicKey',
* params: {
* // The path and curve must be specified in the initial permissions.
* path: ['m', "44'", "3'", "0'", "0", "0"],
* curve: 'secp256k1',
* compressed: false,
* },
* })
*
* // '0x...'
* console.log(dogecoinPublicKey)
* ```
*/
export const getBip32PublicKeyBuilder = Object.freeze({
targetName,
specificationBuilder,
methodHooks,
actionNames: ['KeyringController:withKeyringV2Unsafe'],
});
/**
* Builds the method implementation for `snap_getBip32PublicKey`.
*
* @param options - The options.
* @param options.messenger - The messenger.
* @param options.methodHooks - The RPC method hooks.
* @param options.methodHooks.getUnlockPromise - A function that resolves once the MetaMask extension is unlocked
* and prompts the user to unlock their MetaMask if it is locked.
* @param options.methodHooks.getClientCryptography - A function to retrieve the cryptographic
* functions to use for the client.
* @returns The method implementation which returns a public key.
* @throws If the params are invalid.
*/
export function getBip32PublicKeyImplementation({ methodHooks: { getUnlockPromise, getClientCryptography }, messenger, }) {
return async function getBip32PublicKey(args) {
await getUnlockPromise(true);
assertStruct(args.params, Bip32PublicKeyArgsStruct, 'Invalid BIP-32 public key params', rpcErrors.invalidParams);
const { params } = args;
// Using the seed is much faster, but we can only do it for these specific curves.
if (params.curve === 'secp256k1' || params.curve === 'ed25519') {
const seed = await getValueFromEntropySource(getMnemonicSeed.bind(null, messenger), params.source);
const node = await getNodeFromSeed({
curve: params.curve,
path: params.path,
seed,
cryptographicFunctions: getClientCryptography(),
});
if (params.compressed) {
return node.compressedPublicKey;
}
return node.publicKey;
}
const secretRecoveryPhrase = await getValueFromEntropySource(getMnemonic.bind(null, messenger), params.source);
const node = await getNodeFromMnemonic({
curve: params.curve,
path: params.path,
secretRecoveryPhrase,
cryptographicFunctions: getClientCryptography(),
});
if (params.compressed) {
return node.compressedPublicKey;
}
return node.publicKey;
};
}
//# sourceMappingURL=getBip32PublicKey.mjs.map