Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • hive/metamask-snap
1 result
Select Git revision
Show changes
Commits on Source (2)
{ {
"name": "@hiveio/metamask-snap", "name": "@hiveio/metamask-snap",
"version": "1.2.1", "version": "1.3.0",
"description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet", "description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet",
"main": "./dist/bundle.js", "main": "./dist/bundle.js",
"files": [ "files": [
......
{ {
"version": "1.2.1", "version": "1.3.0",
"description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet", "description": "Hive wallet extension allowing you to sign transactions using keys derived from your Metamask wallet",
"proposedName": "Hive Wallet", "proposedName": "Hive Wallet",
"repository": { "repository": {
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"url": "git+https://gitlab.syncad.com/hive/metamask-snap.git" "url": "git+https://gitlab.syncad.com/hive/metamask-snap.git"
}, },
"source": { "source": {
"shasum": "MXajRhEb2TjGR6uz1Q/BWyrqfzvNX0WPxcdKx+2ZlaQ=", "shasum": "Qn3MhblQGIyqmEaAKxChWSC9ALvWQW/iOxk8Xxp74Js=",
"location": { "location": {
"npm": { "npm": {
"filePath": "dist/bundle.js", "filePath": "dist/bundle.js",
......
import { createWaxFoundation, type IWaxBaseInterface } from "@hiveio/wax"; import { createWaxFoundation, DEFAULT_WAX_OPTIONS, type IWaxBaseInterface } from "@hiveio/wax";
let _wax: undefined | IWaxBaseInterface; const waxInstances: Record<string, IWaxBaseInterface> = {};
export const getWax = async (): Promise<IWaxBaseInterface> => { export const getWax = async (chainId: string = DEFAULT_WAX_OPTIONS.chainId): Promise<IWaxBaseInterface> => {
if (!_wax) if (!waxInstances[chainId])
_wax = await createWaxFoundation(); waxInstances[chainId] = await createWaxFoundation(chainId !== undefined ? { chainId } : undefined);
return _wax; return waxInstances[chainId];
}; };
...@@ -27,7 +27,7 @@ export const onRpcRequest = async ({ ...@@ -27,7 +27,7 @@ export const onRpcRequest = async ({
case 'hive_signTransaction': case 'hive_signTransaction':
return { return {
signatures: await signTransaction(origin, request.params.transaction, request.params.keys) signatures: await signTransaction(origin, request.params.transaction, request.params.keys, request.params.chainId)
}; };
case 'hive_decrypt': case 'hive_decrypt':
......
...@@ -20,6 +20,7 @@ export type SignTransactionRequest = { ...@@ -20,6 +20,7 @@ export type SignTransactionRequest = {
method: 'hive_signTransaction'; method: 'hive_signTransaction';
params: { params: {
transaction: string; transaction: string;
chainId?: string;
keys: KeyIndex[]; keys: KeyIndex[];
}; };
} }
......
import type { KeyIndex } from "../rpc"; import type { KeyIndex } from "../rpc";
import { getWax } from "../hive/wax"; import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils"; import { importPrivateKeyToWallet } from "../utils/key-management";
import { keyIndexToPath } from "../utils/key-management";
import { getTempWallet } from "../hive/beekeeper"; import { getTempWallet } from "../hive/beekeeper";
import { ConfirmBufferDecode } from "./dialogs/ConfirmBufferDecode"; import { ConfirmBufferDecode } from "./dialogs/ConfirmBufferDecode";
import { SLIP10Node } from "@metamask/key-tree";
import type { THexString } from "@hiveio/wax"; import type { THexString } from "@hiveio/wax";
export const decodeBuffer = async (origin: string, buffer: THexString, decodeKey: KeyIndex): Promise<string> => { export const decodeBuffer = async (origin: string, buffer: THexString, decodeKey: KeyIndex): Promise<string> => {
...@@ -18,22 +16,7 @@ export const decodeBuffer = async (origin: string, buffer: THexString, decodeKey ...@@ -18,22 +16,7 @@ export const decodeBuffer = async (origin: string, buffer: THexString, decodeKey
const wallet = await getTempWallet(); const wallet = await getTempWallet();
try { try {
const snapResponse = await snap.request({ await importPrivateKeyToWallet(wallet, decodeKey);
method: 'snap_getBip32Entropy',
params: {
curve: "secp256k1",
path: keyIndexToPath(decodeKey)
}
});
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); const response = wax.decrypt(wallet, buffer);
......
import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; import { Bold, Copyable, Text, Box } from "@metamask/snaps-sdk/jsx";
import type { KeyIndex } from "../../rpc"; import type { KeyIndex } from "../../rpc";
import { KeyTypeNotice } from "./components/KeyTypeNotice";
export const ConfirmBufferDecode = (origin: string, buffer: string, decodeKey: KeyIndex) => snap.request({ export const ConfirmBufferDecode = (origin: string, buffer: string, decodeKey: KeyIndex) => snap.request({
method: 'snap_dialog', method: 'snap_dialog',
...@@ -14,9 +15,7 @@ export const ConfirmBufferDecode = (origin: string, buffer: string, decodeKey: K ...@@ -14,9 +15,7 @@ export const ConfirmBufferDecode = (origin: string, buffer: string, decodeKey: K
<Text> <Text>
Confirm if you want to sign it using your: Confirm if you want to sign it using your:
</Text> </Text>
<Text> {KeyTypeNotice(decodeKey)}
- <Bold>{decodeKey.role}</Bold> key (account index: <Italic>#{ String(decodeKey.accountIndex ?? 0) }</Italic>)
</Text>
</Box> </Box>
) )
} }
......
import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; import { Bold, Copyable, Text, Box } from "@metamask/snaps-sdk/jsx";
import { KeyIndex } from "../../rpc"; import { KeyIndex } from "../../rpc";
import { KeyTypeNotice } from "./components/KeyTypeNotice";
export const ConfirmBufferSign = (origin: string, buffer: string, firstKey: KeyIndex, secondKey?: KeyIndex | string) => snap.request({ export const ConfirmBufferSign = (origin: string, buffer: string, firstKey: KeyIndex, secondKey?: KeyIndex | string) => snap.request({
method: 'snap_dialog', method: 'snap_dialog',
...@@ -14,14 +15,11 @@ export const ConfirmBufferSign = (origin: string, buffer: string, firstKey: KeyI ...@@ -14,14 +15,11 @@ export const ConfirmBufferSign = (origin: string, buffer: string, firstKey: KeyI
<Text> <Text>
Confirm if you want to sign it using your: Confirm if you want to sign it using your:
</Text> </Text>
<Text> {KeyTypeNotice(firstKey)}
- <Bold>{firstKey.role}</Bold> key (account index: <Italic>#{ String(firstKey.accountIndex ?? 0) }</Italic>)
</Text>
<Text>This message will be encrypted for: </Text> <Text>This message will be encrypted for: </Text>
{ secondKey ? (typeof secondKey === "string" ? { secondKey && typeof secondKey === "string" && <Copyable value={ secondKey }/> }
<Copyable value={ secondKey }/> : { secondKey && typeof secondKey === "object" && KeyTypeNotice(secondKey) }
<Text>- <Bold>{secondKey.role}</Bold> key (account index: <Italic>#{ String(secondKey.accountIndex ?? 0) }</Italic>)</Text> { !secondKey && <Text>- <Bold>yourself</Bold></Text> }
) : <Text>- <Bold>yourself</Bold></Text> }
</Box> </Box>
) )
} }
......
import { Bold, Copyable, Text, Box, Italic } from "@metamask/snaps-sdk/jsx"; import { Bold, Copyable, Text, Box, Banner, Italic } from "@metamask/snaps-sdk/jsx";
import { KeyIndex } from "../../rpc"; import { KeyIndex } from "../../rpc";
import { KeyTypeNotice } from "./components/KeyTypeNotice";
import { DEFAULT_WAX_OPTIONS } from "@hiveio/wax";
export const ConfirmTransactionSign = (origin: string, transaction: string, keys: KeyIndex[]) => snap.request({ export const ConfirmTransactionSign = (origin: string, transaction: string, keys: KeyIndex[], chainId?: string) => snap.request({
method: 'snap_dialog', method: 'snap_dialog',
params: { params: {
type: 'confirmation', type: 'confirmation',
content: ( content: (
<Box> <Box>
<Text> <Text>
<Bold>{ origin }</Bold> asked to sign a transaction: <Bold>{origin}</Bold> asked to sign a transaction:
</Text> </Text>
<Copyable value={ transaction } /> <Copyable value={transaction} />
{chainId && chainId !== DEFAULT_WAX_OPTIONS.chainId && <Banner title="Custom chain signing" severity="warning">
<Text>
<Bold>Warning:</Bold> You are signing this transaction for a custom chain. Make sure you trust this chain: <Italic>{chainId}</Italic>
</Text>
</Banner>}
<Text> <Text>
Confirm if you want to sign it using your: Confirm if you want to sign it using your:
</Text> </Text>
{ keys.map(key => (<Text> {KeyTypeNotice(...keys)}
- <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex ?? 0) }</Italic>)
</Text>))}
</Box> </Box>
) )
} }
......
import { Bold, Text, Italic } from "@metamask/snaps-sdk/jsx";
import type { KeyIndex } from "../../../rpc";
export const KeyTypeNotice = (...keys: KeyIndex[]) => keys.map(key => (
<Text>
- <Bold>{key.role}</Bold> key (account index: <Italic>#{ String(key.accountIndex ?? 0) }</Italic>)
</Text>
));
import type { KeyIndex } from "../rpc"; import type { KeyIndex } from "../rpc";
import { getWax } from "../hive/wax"; import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils"; import { importPrivateKeyToWallet } from "../utils/key-management";
import { keyIndexToPath } from "../utils/key-management";
import { getTempWallet } from "../hive/beekeeper"; import { getTempWallet } from "../hive/beekeeper";
import { ConfirmBufferSign } from "./dialogs/ConfirmBufferSign"; import { ConfirmBufferSign } from "./dialogs/ConfirmBufferSign";
import { SLIP10Node } from "@metamask/key-tree";
export const encodeBuffer = async (origin: string, buffer: string, firstKey: KeyIndex, secondKey?: KeyIndex | string): Promise<string> => { export const encodeBuffer = async (origin: string, buffer: string, firstKey: KeyIndex, secondKey?: KeyIndex | string): Promise<string> => {
const confirmDecode = await ConfirmBufferSign(origin, buffer, firstKey, secondKey); const confirmDecode = await ConfirmBufferSign(origin, buffer, firstKey, secondKey);
...@@ -17,38 +15,13 @@ export const encodeBuffer = async (origin: string, buffer: string, firstKey: Key ...@@ -17,38 +15,13 @@ export const encodeBuffer = async (origin: string, buffer: string, firstKey: Key
const wallet = await getTempWallet(); const wallet = await getTempWallet();
try { try {
const firstKeyBip32 = await snap.request({ const publicKeyFirstKey = await importPrivateKeyToWallet(wallet, firstKey);
method: 'snap_getBip32Entropy',
params: {
curve: "secp256k1",
path: keyIndexToPath(firstKey)
}
});
const nodeFirstKey = await SLIP10Node.fromJSON(firstKeyBip32);
if (!nodeFirstKey.privateKey)
throw new Error('No private key found');
const wifFirstKey = wax.convertRawPrivateKeyToWif(remove0x(nodeFirstKey.privateKey));
const publicKeyFirstKey = await wallet.importKey(wifFirstKey);
let publicKeySecondKey: string | undefined; let publicKeySecondKey: string | undefined;
if(typeof secondKey === "string") if(typeof secondKey === "string")
publicKeySecondKey = secondKey; publicKeySecondKey = secondKey;
else if (secondKey) { else if (secondKey)
const secondKeyBip32 = await snap.request({ publicKeySecondKey = await importPrivateKeyToWallet(wallet, secondKey);
method: 'snap_getBip32Entropy',
params: {
curve: "secp256k1",
path: keyIndexToPath(secondKey)
}
});
const nodeSecondKey = await SLIP10Node.fromJSON(secondKeyBip32);
if (!nodeSecondKey.privateKey)
throw new Error('No private key found');
const wifSecondKey = wax.convertRawPrivateKeyToWif(remove0x(nodeSecondKey.privateKey));
publicKeySecondKey = await wallet.importKey(wifSecondKey);
}
const response = wax.encrypt(wallet, buffer, publicKeyFirstKey, publicKeySecondKey); const response = wax.encrypt(wallet, buffer, publicKeyFirstKey, publicKeySecondKey);
......
import type { KeyIndex, PublicKeyData } from "../rpc"; import type { KeyIndex, PublicKeyData } from "../rpc";
import { getWax } from "../hive/wax"; import { getPublicKeyWifFromKeyIndex } from "../utils/key-management";
import { remove0x } from "@metamask/utils";
import { keyIndexToPath } from "../utils/key-management";
export const getPublicKeys = async (keys: KeyIndex[]): Promise<PublicKeyData[]> => { export const getPublicKeys = async (keys: KeyIndex[]): Promise<PublicKeyData[]> => {
const wax = await getWax();
const publicKeys: PublicKeyData[] = []; const publicKeys: PublicKeyData[] = [];
for(const key of keys) { for(const key of keys) {
const bip32 = await snap.request({ const publicKey = await getPublicKeyWifFromKeyIndex(key);
method: 'snap_getBip32PublicKey',
params: {
curve: "secp256k1",
path: keyIndexToPath(key),
compressed: true
}
});
const publicKey = wax.convertRawPublicKeyToWif(remove0x(bip32));
publicKeys.push({ publicKeys.push({
accountIndex: key.accountIndex ?? 0, accountIndex: key.accountIndex ?? 0,
......
import type { KeyIndex } from "../rpc"; import type { KeyIndex } from "../rpc";
import { getWax } from "../hive/wax"; import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils"; import { importPrivateKeyToWallet } from "../utils/key-management";
import { keyIndexToPath } from "../utils/key-management";
import { getTempWallet } from "../hive/beekeeper"; import { getTempWallet } from "../hive/beekeeper";
import { ConfirmTransactionSign } from "./dialogs/ConfirmTransactionSign"; import { ConfirmTransactionSign } from "./dialogs/ConfirmTransactionSign";
import type { THexString } from "@hiveio/wax"; import type { THexString } from "@hiveio/wax";
import { SLIP10Node } from "@metamask/key-tree";
export const signTransaction = async (origin: string, transaction: string, keys: KeyIndex[]): Promise<THexString[]> => { export const signTransaction = async (origin: string, transaction: string, keys: KeyIndex[], chainId?: string): Promise<THexString[]> => {
if (keys.length < 1) if (keys.length < 1)
throw new Error('No keys provided'); throw new Error('No keys provided');
const confirmSign = await ConfirmTransactionSign(origin, transaction, keys); const confirmSign = await ConfirmTransactionSign(origin, transaction, keys, chainId);
if(!confirmSign) if(!confirmSign)
throw new Error('User denied the transaction'); throw new Error('User denied the transaction');
// The order is important: First create wax, then transaction and if all success then create wallet // The order is important: First create wax, then transaction and if all success then create wallet
const wax = await getWax(); const wax = await getWax(chainId);
const tx = wax.createTransactionFromJson(transaction); const tx = wax.createTransactionFromJson(transaction);
const wallet = await getTempWallet(); const wallet = await getTempWallet();
...@@ -25,22 +23,7 @@ export const signTransaction = async (origin: string, transaction: string, keys: ...@@ -25,22 +23,7 @@ export const signTransaction = async (origin: string, transaction: string, keys:
const signatures: THexString[] = []; const signatures: THexString[] = [];
for(const key of keys) { for(const key of keys) {
const snapResponse = await snap.request({ const publicKey = await importPrivateKeyToWallet(wallet, key);
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);
const signature = tx.sign(wallet, publicKey); const signature = tx.sign(wallet, publicKey);
......
import type { TRole } from "@hiveio/wax"; import type { TPublicKey, TRole } from "@hiveio/wax";
import type { KeyIndex } from "../rpc"; import type { KeyIndex } from "../rpc";
import { getWax } from "../hive/wax";
import { remove0x } from "@metamask/utils";
import { SLIP10Node } from "@metamask/key-tree";
import type { IBeekeeperUnlockedWallet } from "@hiveio/beekeeper";
const KeyIndexToPathMap = { const KeyIndexToPathMap = {
owner: 0, owner: 0,
...@@ -15,3 +19,49 @@ export const keyIndexToPath = (keyIndex: KeyIndex): string[] => { ...@@ -15,3 +19,49 @@ export const keyIndexToPath = (keyIndex: KeyIndex): string[] => {
return ["m", "48'", "13'", `${keyIndexType}'`, `${keyIndex.accountIndex ?? 0}'`, "0'"]; return ["m", "48'", "13'", `${keyIndexType}'`, `${keyIndex.accountIndex ?? 0}'`, "0'"];
}; };
export const getPublicKeyWifFromKeyIndex = async (keyIndex: KeyIndex): Promise<TPublicKey> => {
const wax = await getWax();
const bip32 = await snap.request({
method: 'snap_getBip32PublicKey',
params: {
curve: "secp256k1",
path: keyIndexToPath(keyIndex),
compressed: true
}
});
const publicKey = wax.convertRawPublicKeyToWif(remove0x(bip32));
return publicKey;
};
export const getPrivateKeyWifFromKeyIndex = async (keyIndex: KeyIndex): Promise<string> => {
const wax = await getWax();
const snapResponse = await snap.request({
method: 'snap_getBip32Entropy',
params: {
curve: "secp256k1",
path: keyIndexToPath(keyIndex)
}
});
const node = await SLIP10Node.fromJSON(snapResponse);
if (!node.privateKey)
throw new Error('No private key found');
const wif = wax.convertRawPrivateKeyToWif(remove0x(node.privateKey));
return wif;
};
export const importPrivateKeyToWallet = async (wallet: IBeekeeperUnlockedWallet, keyIndex: KeyIndex): Promise<TPublicKey> => {
const privateKeyWif = await getPrivateKeyWifFromKeyIndex(keyIndex);
const publicKey = await wallet.importKey(privateKeyWif);
return publicKey;
};