Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/extension/src/libs/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
sendToTab,
newAccount,
lock,
getPrivateKey,
} from './internal';
import { handlePersistentEvents } from './external';
import SettingsState from '../settings-state';
Expand Down Expand Up @@ -167,6 +168,8 @@ class BackgroundHandler {
case InternalMethods.getNewAccount:
case InternalMethods.saveNewAccount:
return newAccount(this.#keyring, message);
case InternalMethods.getPrivateKey:
return getPrivateKey(this.#keyring, message);
default:
return Promise.resolve({
error: getCustomError(
Expand Down
28 changes: 28 additions & 0 deletions packages/extension/src/libs/background/internal/get-private-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { getCustomError } from '@/libs/error'
import KeyRingBase from '@/libs/keyring/keyring'
import { InternalOnMessageResponse } from '@/types/messenger'
import { EnkryptAccount, RPCRequestType } from '@enkryptcom/types'

const getPrivateKey = async (
keyring: KeyRingBase,
message: RPCRequestType,
): Promise<InternalOnMessageResponse> => {
if (!message.params || message.params.length < 2)
return {
error: getCustomError('background: invalid params for getting private key'),
}
const account = message.params[0] as EnkryptAccount
const password = message.params[1] as string
try {
const privKey = await keyring.getPrivateKey(account, password)
return {
result: JSON.stringify(privKey),
}
} catch (e: any) {
return {
error: getCustomError(e.message),
}
}
}

export default getPrivateKey
2 changes: 2 additions & 0 deletions packages/extension/src/libs/background/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import changeNetwork from './change-network';
import sendToTab from './send-to-tab';
import newAccount from './new-account';
import lock from './lock';
import getPrivateKey from './get-private-key';
export {
sign,
getEthereumPubKey,
Expand All @@ -15,4 +16,5 @@ export {
sendToTab,
newAccount,
lock,
getPrivateKey,
};
3 changes: 3 additions & 0 deletions packages/extension/src/libs/keyring/keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,8 @@ export class KeyRingBase {
deleteAccount(address: string): Promise<void> {
return this.#keyring.deleteAccount(address);
}
getPrivateKey(options: SignOptions, password: string): Promise<string> {
return this.#keyring.getPrivateKey(options, password);
}
}
export default KeyRingBase;
1 change: 1 addition & 0 deletions packages/extension/src/types/messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum InternalMethods {
getNewAccount = 'enkrypt_getNewAccount',
saveNewAccount = 'enkrypt_saveNewAccount',
changeNetwork = 'enkrypt_changeNetwork',
getPrivateKey = 'enkrypt_getPrivateKey',
}
export interface SendMessage {
[key: string]: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,28 @@
>
<delete-icon /><span class="delete">Delete</span>
</a>
<a
v-if="exportable"
class="accounts-item__edit-item"
@click.stop="$emit('action:export')"
>
<view-icon /><span class="export">Export</span>
</a>
</div>
</template>

<script setup lang="ts">
import EditIcon from '@action/icons/actions/edit.vue';
import DeleteIcon from '@action/icons/actions/delete.vue';
import ViewIcon from '@action/icons/actions/view.vue';
defineEmits<{
(e: 'action:rename'): void;
(e: 'action:delete'): void;
(e: 'action:export'): void;
}>();
defineProps({
deletable: Boolean,
exportable: Boolean,
});
</script>

Expand Down Expand Up @@ -77,6 +87,10 @@ defineProps({
&.delete {
color: @error;
}

&.export {
color: @grayPrimary;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
v-if="openEdit"
ref="dropdown"
:deletable="deletable"
:exportable="exportable"
v-bind="$attrs"
/>
</div>
Expand Down Expand Up @@ -78,6 +79,7 @@ defineProps({
active: Boolean,
showEdit: Boolean,
deletable: Boolean,
exportable: Boolean,
});

const toggleEdit = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<template>
<app-dialog v-model="model" width="344px" is-centered>
<div class="export-account-form">
<h3>Export private key</h3>

<span v-show="!isDone">
<h4>Enter Extension password</h4>

<base-input
type="password"
placeholder="Extension Password"
class="export-account-form__input"
:value="keyringPassword"
@update:value="updateKeyringPassword"
@keyup.enter="showAction"
/>

<p v-show="keyringError" class="export-account-form__error">
Invalid Keyring password
</p>

<base-button
class="export-account-form__button"
title="Show"
:click="showAction"
:disabled="isLoading"
/>
</span>

<div v-if="isDone" class="privkey">
<p class="warning">
⚠️ Keep your private key secure. Never share it with anyone.
</p>
<p class="title">Private Key:</p>
<span class="word">{{ privKey }}</span>
</div>
</div>
</app-dialog>
</template>

<script setup lang="ts">
import { PropType, ref, onMounted, computed } from 'vue';
import AppDialog from '@action/components/app-dialog/index.vue';
import BaseButton from '@action/components/base-button/index.vue';
import BaseInput from '@action/components/base-input/index.vue';
import { NodeType } from '@/types/provider';
import { EnkryptAccount } from '@enkryptcom/types';
import KeyRingBase from '@/libs/keyring/keyring';
import BackupState from '@/libs/backup-state';
import { sendToBackgroundFromAction } from '@/libs/messenger/extension';
import { InternalMethods } from '@/types/messenger';

const model = defineModel<boolean>();
const closeWindow = () => {
model.value = false;
};
const keyringError = ref(false);
const isDone = ref(false);
const isLoading = ref(false);
const keyringPassword = ref(__PREFILL_PASSWORD__!);
const privKey = ref('');
const keyringBase = new KeyRingBase();

const props = defineProps({
account: {
type: Object as PropType<EnkryptAccount>,
default: () => ({}),
},
});

const showAction = async () => {
try {
isLoading.value = true;
const res = await sendToBackgroundFromAction({
message: JSON.stringify({
method: InternalMethods.getPrivateKey,
params: [props.account, keyringPassword.value],
}),
});
if (res.error) {
throw res.error;
} else {
privKey.value = JSON.parse(res.result!);
}
isDone.value = true;
} catch (err) {
keyringError.value = true;
}
isLoading.value = false;
};

const updateKeyringPassword = (password: string) => {
keyringPassword.value = password;
};
</script>

<style lang="less" scoped>
@import '@action/styles/theme.less';
.export-account-form {
padding: 16px;

h3 {
font-style: normal;
font-weight: 700;
font-size: 24px;
line-height: 32px;
color: @primaryLabel;
margin: 0 0 16px 0;
}
&__button {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
width: 100%;
margin-top: 24px;
}
&__error {
width: 100%;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
text-align: center;
letter-spacing: 0.25px;
color: @error;
}

.privkey {
width: 100%;
margin-top: 28px;
.title {
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 24px;
color: @primaryLabel;
margin-bottom: 6px;
}

.word {
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 24px;
color: black;
background: @lightBg;
border: 1px solid rgba(95, 99, 104, 0.1);
box-sizing: border-box;
border-radius: 10px;
padding: 10px 16px;
margin: 0px;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: start;
text-wrap: auto;
user-select: all;
line-break: anywhere;
}
.warning {
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
color: @error;
margin: 0 0 12px 0;
}
}
}
</style>
20 changes: 20 additions & 0 deletions packages/extension/src/ui/action/views/accounts/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
:identicon-element="network.identicon"
:show-edit="true"
:deletable="account.walletType !== WalletType.mnemonic"
:exportable="true"
@action:rename="renameAccount(index)"
@action:delete="deleteAccount(index)"
@action:export="exportAccount(index)"
/>

<div v-if="displayInactive.length > 0" class="accounts__info">
Expand Down Expand Up @@ -100,6 +102,13 @@
:account="accountToDelete"
/>

<export-account-form
v-if="isExportAccount"
v-model="isExportAccount"
v-bind="$attrs"
:account="accountToExport"
/>

<import-account
v-bind="$attrs"
v-model="isImportAccount"
Expand All @@ -114,6 +123,7 @@ import AddAccount from '@action/icons/common/add-account.vue';
import AddAccountForm from './components/add-account-form.vue';
import RenameAccountForm from './components/rename-account-form.vue';
import DeleteAccountForm from './components/delete-account-form.vue';
import ExportAccountForm from './components/export-account-form.vue';
import AddHardwareAccount from '@action/icons/actions/add-hardware-account.vue';
import ImportAccountIcon from '@action/icons/actions/import-account-icon.vue';
import ImportAccount from '@action/views/import-account/index.vue';
Expand All @@ -134,6 +144,7 @@ const emit = defineEmits<{
}>();
const isAddAccount = ref(false);
const isRenameAccount = ref(false);
const isExportAccount = ref(false);
const isDeleteAccount = ref(false);
const isImportAccount = ref(false);
const hwWallet = new HWwallets();
Expand All @@ -154,6 +165,7 @@ const props = defineProps({
});
const accountToRename = ref<EnkryptAccount>();
const accountToDelete = ref<EnkryptAccount>();
const accountToExport = ref<EnkryptAccount>();

const close = () => {
props.toggle();
Expand Down Expand Up @@ -185,6 +197,14 @@ const renameAccount = (accountIdx: number) => {
}, 100);
};

const exportAccount = (accountIdx: number) => {
accountToExport.value = props.accountInfo.activeAccounts[accountIdx];
props.toggle();
setTimeout(() => {
isExportAccount.value = true;
}, 100);
};

const deleteAccount = (accountIdx: number) => {
accountToDelete.value = props.accountInfo.activeAccounts[accountIdx];
props.toggle();
Expand Down
24 changes: 24 additions & 0 deletions packages/keyring/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,30 @@ class KeyRing {
await this.#storage.set(configs.STORAGE_KEYS.KEY_INFO, existingKeys);
}

async getPrivateKey(
options: SignOptions,
keyringPassword: string,
): Promise<string> {
assert(
!Object.values(HWwalletType).includes(
options.walletType as unknown as HWwalletType,
),
Errors.KeyringErrors.CannotUseKeyring,
);
if (options.walletType === WalletType.privkey) {
const privkeys = await this.#getPrivateKeys(keyringPassword);
assert(privkeys[options.pathIndex.toString()], Errors.KeyringErrors.AddressDoesntExists);
return privkeys[options.pathIndex.toString()];
} else {
const mnemonic = await this.#getMnemonic(keyringPassword)
const keypair = await this.#signers[options.signerType].generate(
mnemonic,
pathParser(options.basePath, options.pathIndex, options.signerType),
);
return keypair.privateKey;
}
}

async #getPrivateKeys(
keyringPassword: string,
): Promise<Record<string, string>> {
Expand Down