diff --git a/package.json b/package.json index 34bb595f7997deb632ec984b62af44e5662ec765..debf67491bf100a4e7811149e554842f8b61bee4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hiveio/metamask-snap", - "version": "1.0.1", + "version": "1.1.0", "description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet", "main": "./dist/bundle.js", "files": [ diff --git a/snap.manifest.json b/snap.manifest.json index 386329211fdd1705c36d0b28b21290f3a74e47f3..fb7ac4ab0af842c46ba388dcaca4f2933b3e4afe 100644 --- a/snap.manifest.json +++ b/snap.manifest.json @@ -1,5 +1,5 @@ { - "version": "1.0.1", + "version": "1.1.0", "description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet", "proposedName": "Hive Wallet", "repository": { @@ -7,7 +7,7 @@ "url": "git+https://gitlab.syncad.com/hive/metamask-snap.git" }, "source": { - "shasum": "FzpJXdDNOVkP9aESauH9YuVQCd0PdhJDR8AXmBblk2E=", + "shasum": "kHytM2WI19j6IpN1uBFbJpGp7okw+DdHd88p3KBaw30=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/src/index.ts b/src/index.ts index 5b7910ed18828901519e323e579ad89e0e247330..7480fb1422a8b439e3b69a9c7d858c93f9a6a088 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import type { RpcRequest, RpcResponse } from './rpc'; +import { decodeBuffer } from './snap/decodeBuffer'; +import { encodeBuffer } from './snap/encodeBuffer'; import { getPublicKeys } from './snap/getPublicKeys'; import { signTransaction } from './snap/signTransaction'; @@ -26,7 +28,17 @@ export const onRpcRequest = async ({ case 'hive_signTransaction': return { signatures: await signTransaction(origin, request.params.transaction, request.params.keys) - } + }; + + case 'hive_decrypt': + return { + buffer: await decodeBuffer(origin, request.params.buffer, request.params.firstKey, request.params.secondKey) + }; + + case 'hive_encrypt': + return { + buffer: await encodeBuffer(origin, request.params.buffer, request.params.firstKey, request.params.secondKey) + }; default: throw new Error('Method not found.'); diff --git a/src/rpc.ts b/src/rpc.ts index 14e76236a8733a11cea577857eb4fec16999ed41..1de485cf2c5bb7d305da750f8eb3c49fee39782a 100644 --- a/src/rpc.ts +++ b/src/rpc.ts @@ -24,6 +24,28 @@ export type SignTransactionRequest = { }; } +export type SignBufferRequest = { + method: 'hive_encrypt'; + params: { + buffer: string; + firstKey: KeyIndex; + secondKey?: KeyIndex; + }; +} + +export type DecodeBufferRequest = { + method: 'hive_decrypt'; + params: { + buffer: string; + firstKey: KeyIndex; + secondKey?: KeyIndex; + }; +} + +export type BufferResponse = { + buffer: string; +} + export type GetPublicKeyResponse = { publicKeys: PublicKeyData[]; } @@ -32,5 +54,5 @@ export type SignTransactionResponse = { signatures: THexString[]; } -export type RpcRequest = GetPublicKeyRequest | SignTransactionRequest; -export type RpcResponse = GetPublicKeyResponse | SignTransactionResponse; +export type RpcRequest = GetPublicKeyRequest | SignTransactionRequest | SignBufferRequest | DecodeBufferRequest; +export type RpcResponse = GetPublicKeyResponse | SignTransactionResponse | BufferResponse; diff --git a/src/snap/decodeBuffer.ts b/src/snap/decodeBuffer.ts new file mode 100644 index 0000000000000000000000000000000000000000..b10df84ff0234314e6e228923dc8b8414ba55a09 --- /dev/null +++ b/src/snap/decodeBuffer.ts @@ -0,0 +1,48 @@ +import type { KeyIndex } from "../rpc"; +import { getWax } from "../hive/wax"; +import { remove0x } from "@metamask/utils"; +import { keyIndexToPath } from "../utils/key-management"; +import { getTempWallet } from "../hive/beekeeper"; +import { ConfirmBufferDecode } from "./dialogs/ConfirmBufferDecode"; +import { SLIP10Node } from "@metamask/key-tree"; +import type { THexString } from "@hiveio/wax"; + +export const decodeBuffer = async (origin: string, buffer: THexString, firstKey: KeyIndex, secondKey?: KeyIndex): Promise<string> => { + const keys = secondKey ? [ firstKey, secondKey ] : [ firstKey ]; + + const confirmDecode = await ConfirmBufferDecode(origin, buffer, keys); + + if(!confirmDecode) + throw new Error('User denied the buffer decode'); + + // The order is important: First create wax, then create wallet + const wax = await getWax(); + const wallet = await getTempWallet(); + + try { + for(const key of keys) { + const snapResponse = await snap.request({ + method: 'snap_getBip32Entropy', + params: { + curve: "secp256k1", + path: keyIndexToPath(key) + } + }); + + const node = await SLIP10Node.fromJSON(snapResponse); + + if (!node.privateKey) + throw new Error('No private key found'); + + const wif = wax.convertRawPrivateKeyToWif(remove0x(node.privateKey)); + + await wallet.importKey(wif); + } + + const response = wax.decrypt(wallet, buffer); + + return response; + } finally { + wallet.close(); + } +}; diff --git a/src/snap/dialogs/ConfirmBufferDecode.tsx b/src/snap/dialogs/ConfirmBufferDecode.tsx new file mode 100644 index 0000000000000000000000000000000000000000..47650fc5342117e291c9a5f07ac4ef22df07992e --- /dev/null +++ b/src/snap/dialogs/ConfirmBufferDecode.tsx @@ -0,0 +1,23 @@ +import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; +import type { KeyIndex } from "../../rpc"; + +export const ConfirmBufferDecode = (origin: string, buffer: string, keys: KeyIndex[]) => snap.request({ + method: 'snap_dialog', + params: { + type: 'confirmation', + content: ( + <Box> + <Text> + <Bold>{ origin }</Bold> asked to decode a buffer: + </Text> + <Copyable value={ buffer } /> + <Text> + Confirm if you want to sign it using your: + </Text> + { keys.map(key => (<Text> + - <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex ?? 0) }</Italic>) + </Text>))} + </Box> + ) + } +}); diff --git a/src/snap/dialogs/ConfirmBufferSign.tsx b/src/snap/dialogs/ConfirmBufferSign.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ab9328ea12aac80bf20bfcda9fa6d7a969308cba --- /dev/null +++ b/src/snap/dialogs/ConfirmBufferSign.tsx @@ -0,0 +1,23 @@ +import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; +import { KeyIndex } from "../../rpc"; + +export const ConfirmBufferSign = (origin: string, buffer: string, keys: KeyIndex[]) => snap.request({ + method: 'snap_dialog', + params: { + type: 'confirmation', + content: ( + <Box> + <Text> + <Bold>{ origin }</Bold> asked to sign a buffer: + </Text> + <Copyable value={ buffer } /> + <Text> + Confirm if you want to sign it using your: + </Text> + { keys.map(key => (<Text> + - <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex ?? 0) }</Italic>) + </Text>))} + </Box> + ) + } +}); diff --git a/src/snap/dialogs/ConfirmSign.tsx b/src/snap/dialogs/ConfirmTransactionSign.tsx similarity index 78% rename from src/snap/dialogs/ConfirmSign.tsx rename to src/snap/dialogs/ConfirmTransactionSign.tsx index b21e3765691fe4eac566312b15fdffc5cdc5b651..334916c9b8af040bf5edee13782d9c6706930876 100644 --- a/src/snap/dialogs/ConfirmSign.tsx +++ b/src/snap/dialogs/ConfirmTransactionSign.tsx @@ -1,7 +1,7 @@ import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; import { KeyIndex } from "../../rpc"; -export const ConfirmSign = (origin: string, transaction: string, keys: KeyIndex[]) => snap.request({ +export const ConfirmTransactionSign = (origin: string, transaction: string, keys: KeyIndex[]) => snap.request({ method: 'snap_dialog', params: { type: 'confirmation', @@ -15,7 +15,7 @@ export const ConfirmSign = (origin: string, transaction: string, keys: KeyIndex[ Confirm if you want to sign it using your: </Text> { keys.map(key => (<Text> - - <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex || 0) }</Italic>) + - <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex ?? 0) }</Italic>) </Text>))} </Box> ) diff --git a/src/snap/encodeBuffer.ts b/src/snap/encodeBuffer.ts new file mode 100644 index 0000000000000000000000000000000000000000..ad2f67d22f534bf877e9e0b28e9e9b5ce0e96d5d --- /dev/null +++ b/src/snap/encodeBuffer.ts @@ -0,0 +1,52 @@ +import type { KeyIndex } from "../rpc"; +import { getWax } from "../hive/wax"; +import { remove0x } from "@metamask/utils"; +import { keyIndexToPath } from "../utils/key-management"; +import { getTempWallet } from "../hive/beekeeper"; +import { SLIP10Node } from "@metamask/key-tree"; +import type { TPublicKey } from "@hiveio/wax"; +import { ConfirmBufferSign } from "./dialogs/ConfirmBufferSign"; + +export const encodeBuffer = async (origin: string, buffer: string, firstKey: KeyIndex, secondKey?: KeyIndex): Promise<string> => { + const keys = secondKey ? [ firstKey, secondKey ] : [ firstKey ]; + + const confirmDecode = await ConfirmBufferSign(origin, buffer, keys); + + if(!confirmDecode) + throw new Error('User denied the buffer decode'); + + // The order is important: First create wax, then create wallet + const wax = await getWax(); + const wallet = await getTempWallet(); + + try { + const publicKeys: TPublicKey[] = []; + + for(const key of keys) { + const snapResponse = await snap.request({ + method: 'snap_getBip32Entropy', + params: { + curve: "secp256k1", + path: keyIndexToPath(key) + } + }); + + const node = await SLIP10Node.fromJSON(snapResponse); + + if (!node.privateKey) + throw new Error('No private key found'); + + const wif = wax.convertRawPrivateKeyToWif(remove0x(node.privateKey)); + + const publicKey = await wallet.importKey(wif); + + publicKeys.push(publicKey); + } + + const response = wax.encrypt(wallet, buffer, ...(publicKeys as [TPublicKey])); + + return response; + } finally { + wallet.close(); + } +}; diff --git a/src/snap/signTransaction.ts b/src/snap/signTransaction.ts index 52f9406a4af656f81b0cc1fa315cd3bb4d82491e..8e9e28148e94b92acf180a774c55b825f6aa19c3 100644 --- a/src/snap/signTransaction.ts +++ b/src/snap/signTransaction.ts @@ -3,7 +3,7 @@ import { getWax } from "../hive/wax"; import { remove0x } from "@metamask/utils"; import { keyIndexToPath } from "../utils/key-management"; import { getTempWallet } from "../hive/beekeeper"; -import { ConfirmSign } from "./dialogs/ConfirmSign"; +import { ConfirmTransactionSign } from "./dialogs/ConfirmTransactionSign"; import type { THexString } from "@hiveio/wax"; import { SLIP10Node } from "@metamask/key-tree"; @@ -11,7 +11,7 @@ export const signTransaction = async (origin: string, transaction: string, keys: if (keys.length < 1) throw new Error('No keys provided'); - const confirmSign = await ConfirmSign(origin, transaction, keys); + const confirmSign = await ConfirmTransactionSign(origin, transaction, keys); if(!confirmSign) throw new Error('User denied the transaction');