From b7520964fefa375385d7aff7fdd9335bd80ead8c Mon Sep 17 00:00:00 2001 From: Anukul Pandey Date: Thu, 5 Oct 2023 20:35:54 +0530 Subject: [PATCH 1/2] feat: export private key --- .../src/Popup/Accounts/Account.tsx | 9 + .../extension-ui/src/Popup/PrivateKey.tsx | 227 ++++++++++++++++++ packages/extension-ui/src/Popup/index.tsx | 3 + .../public/locales/en/translation.json | 7 +- packages/extension/public/style/accounts.css | 9 + 5 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 packages/extension-ui/src/Popup/PrivateKey.tsx diff --git a/packages/extension-ui/src/Popup/Accounts/Account.tsx b/packages/extension-ui/src/Popup/Accounts/Account.tsx index a5104eb4..5d15c33f 100644 --- a/packages/extension-ui/src/Popup/Accounts/Account.tsx +++ b/packages/extension-ui/src/Popup/Accounts/Account.tsx @@ -99,6 +99,15 @@ function Account ({ address, className, genesisHash, hideBalance, isExternal, is {t('Export Account using QR')} )} + {!isExternal && ( + + {t('Export Private Key')} + + )} , ThemeProps { + className?: string; +} + +function PrivateKey ({ className, match: { params: { address } } }: Props): React.ReactElement { + const { t } = useTranslation(); + const onAction = useContext(ActionContext); + const [isBusy, setIsBusy] = useState(false); + const [pass, setPass] = useState(''); + const [error, setError] = useState(''); + const [privateKey, setPrivateKey] = useState(''); + + const _goHome = useCallback( + () => onAction('/'), + [onAction] + ); + + const onPassChange = useCallback( + (password: string) => { + setPass(password); + setError(''); + } + , []); + + const displayPrivateKey = useCallback( + (jsonData: string) => { + setPrivateKey(jsonData); + } + , []); + + const _onExportButtonClick = useCallback( + (): void => { + setIsBusy(true); + + exportAccount(address, pass) + .then(({ exportedJson }) => { + const decrypted = jsonDecrypt(exportedJson,pass); + const header = decrypted.subarray(0, PKCS8_HEADER.length); + + if (!u8aEq(header, PKCS8_HEADER)) { + // throw new Error('Invalid Pkcs8 header found in body'); + setError('Encountered some error'); + } + + let secretKey = decrypted.subarray(SEED_OFFSET, SEED_OFFSET + SEC_LENGTH); + let divOffset = SEED_OFFSET + SEC_LENGTH; + let divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); + + if (!u8aEq(divider, PKCS8_DIVIDER)) { + divOffset = SEED_OFFSET + SEED_LENGTH; + secretKey = decrypted.subarray(SEED_OFFSET, divOffset); + divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); + + if (!u8aEq(divider, PKCS8_DIVIDER)) { + throw new Error('Invalid Pkcs8 divider found in body'); + } + } + + const pubOffset = divOffset + PKCS8_DIVIDER.length; + const publicKey = decrypted.subarray(pubOffset, pubOffset + PUB_LENGTH); + const combinedPublicAndSecretKey = new Uint8Array([...publicKey,...secretKey]); + + const pk = u8aToHex(combinedPublicAndSecretKey); //NOTE - to create keypair from pk , we need to extract public and secret key like this + /* + const publicKey = u8aToHex(hexToU8a(pk).slice(0,32)); + const secretKey = hexToU8a(pk).slice(32,pk.length) + const pair = keyring.createFromPair({publicKey,secretKey}, {}, 'sr25519'); + */ + + displayPrivateKey(pk); + }) + .catch((error: Error) => { + console.error(error); + setError(error.message); + setIsBusy(false); + }); + }, + [address, pass, displayPrivateKey] + ); + + return ( + <> +
('Export account')}> +
+ +
+
+
+
+ {privateKey === '' && ( + + {t("You are exporting your account. Keep it safe and don't share it with anyone.")}
+ {t('Password must be at least ' + MIN_LENGTH + ' characters long.')} +
+ )} +
+ {privateKey === '' && ( + ('password for this account')} + onChange={onPassChange} + type='password' + />)} + {privateKey !== '' && ( + + + +
+
+
Private Key
+
notify.info({ + aliveFor: 2, + message: 'Copied Private Key to Clipboard.' + })}> + +
+
+
+ {privateKey} +
+
+
+ + )} + {error && ( + + {error} + + )} +
+
+
+ + {privateKey === '' && ( + + + + )} + + ); +} + +export default withRouter(styled(PrivateKey)` + margin-top: 15px; + .actionArea { + padding: 10px 24px; + } + + .movedWarning { + margin-top: 8px; + } +`); diff --git a/packages/extension-ui/src/Popup/index.tsx b/packages/extension-ui/src/Popup/index.tsx index def252f7..3e802a97 100644 --- a/packages/extension-ui/src/Popup/index.tsx +++ b/packages/extension-ui/src/Popup/index.tsx @@ -42,6 +42,7 @@ import PhishingDetected from './PhishingDetected'; import RestoreJson from './RestoreJson'; import Signing from './Signing'; import Welcome from './Welcome'; +import PrivateKey from './PrivateKey'; const startSettings = uiSettings.get(); @@ -195,8 +196,10 @@ export default function Popup (): React.ReactElement { {wrapWithErrorBoundary(, 'forget-address')} {wrapWithErrorBoundary(, 'export-address')} {wrapWithErrorBoundary(, 'export-qr-address')} + {wrapWithErrorBoundary(, 'export-privatekey')} {wrapWithErrorBoundary(, 'export-all-address')} {wrapWithErrorBoundary(, 'import-ledger')} + {wrapWithErrorBoundary(, 'export-privatekey')} {wrapWithErrorBoundary(, 'import-qr')} {wrapWithErrorBoundary(, 'import-seed')} {wrapWithErrorBoundary(, 'restore-json')} diff --git a/packages/extension/public/locales/en/translation.json b/packages/extension/public/locales/en/translation.json index c83e50a3..668d4b73 100644 --- a/packages/extension/public/locales/en/translation.json +++ b/packages/extension/public/locales/en/translation.json @@ -172,5 +172,10 @@ "Create account": "", "Or import .": "", "You currently don't have any accounts. Create your first account to get started. Or import if you already have an account.": "", - "Export Account using QR": "" + "Export Account using QR": "", + "Show private key": "", + "Export privatekey": "", + "I want to export private key of this account": "", + "Copy Private Key": "", + "Export Private Key": "" } diff --git a/packages/extension/public/style/accounts.css b/packages/extension/public/style/accounts.css index b86a3309..75528cc1 100644 --- a/packages/extension/public/style/accounts.css +++ b/packages/extension/public/style/accounts.css @@ -436,6 +436,15 @@ svg + .account-card__address { color: var(--primary); } +.pk-copy-btn{ + margin-left: 6px; +} + +.pk-copy-btn:hover{ + color: var(--primary); + cursor: pointer; +} + .account-card__meta:active svg, .account-card__meta:hover svg { From baf15b79d7dab1f67e5aac379028e1d45d0b0322 Mon Sep 17 00:00:00 2001 From: Anukul Pandey Date: Thu, 5 Oct 2023 20:42:04 +0530 Subject: [PATCH 2/2] chore: lint --- .../extension-ui/src/Popup/PrivateKey.tsx | 125 +++++++++--------- packages/extension-ui/src/Popup/index.tsx | 2 +- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/packages/extension-ui/src/Popup/PrivateKey.tsx b/packages/extension-ui/src/Popup/PrivateKey.tsx index 8e7c4c67..ecc0e055 100644 --- a/packages/extension-ui/src/Popup/PrivateKey.tsx +++ b/packages/extension-ui/src/Popup/PrivateKey.tsx @@ -7,17 +7,18 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { faCopy, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React, { useCallback, useContext, useState } from 'react'; +import CopyToClipboard from 'react-copy-to-clipboard'; import { RouteComponentProps, withRouter } from 'react-router'; import styled from 'styled-components'; +import { u8aEq, u8aToHex } from '@polkadot/util'; +import { jsonDecrypt } from '@polkadot/util-crypto'; + import { Button } from '../../../reef/extension-ui/uik/Button'; import { ActionContext, Address, ButtonArea, InputWithLabel, VerticalSpace, Warning } from '../components'; import useTranslation from '../hooks/useTranslation'; import { exportAccount } from '../messaging'; import { Header } from '../partials'; -import { jsonDecrypt } from '@polkadot/util-crypto'; -import { u8aEq, u8aToHex } from '@polkadot/util'; -import CopyToClipboard from 'react-copy-to-clipboard'; import notify from './../../../reef/extension-ui/notify'; const MIN_LENGTH = 6; @@ -65,34 +66,34 @@ function PrivateKey ({ className, match: { params: { address } } }: Props): Reac exportAccount(address, pass) .then(({ exportedJson }) => { - const decrypted = jsonDecrypt(exportedJson,pass); - const header = decrypted.subarray(0, PKCS8_HEADER.length); - - if (!u8aEq(header, PKCS8_HEADER)) { - // throw new Error('Invalid Pkcs8 header found in body'); - setError('Encountered some error'); - } - - let secretKey = decrypted.subarray(SEED_OFFSET, SEED_OFFSET + SEC_LENGTH); - let divOffset = SEED_OFFSET + SEC_LENGTH; - let divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); - - if (!u8aEq(divider, PKCS8_DIVIDER)) { - divOffset = SEED_OFFSET + SEED_LENGTH; - secretKey = decrypted.subarray(SEED_OFFSET, divOffset); - divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); - - if (!u8aEq(divider, PKCS8_DIVIDER)) { - throw new Error('Invalid Pkcs8 divider found in body'); - } - } - - const pubOffset = divOffset + PKCS8_DIVIDER.length; - const publicKey = decrypted.subarray(pubOffset, pubOffset + PUB_LENGTH); - const combinedPublicAndSecretKey = new Uint8Array([...publicKey,...secretKey]); - - const pk = u8aToHex(combinedPublicAndSecretKey); //NOTE - to create keypair from pk , we need to extract public and secret key like this - /* + const decrypted = jsonDecrypt(exportedJson, pass); + const header = decrypted.subarray(0, PKCS8_HEADER.length); + + if (!u8aEq(header, PKCS8_HEADER)) { + // throw new Error('Invalid Pkcs8 header found in body'); + setError('Encountered some error'); + } + + let secretKey = decrypted.subarray(SEED_OFFSET, SEED_OFFSET + SEC_LENGTH); + let divOffset = SEED_OFFSET + SEC_LENGTH; + let divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); + + if (!u8aEq(divider, PKCS8_DIVIDER)) { + divOffset = SEED_OFFSET + SEED_LENGTH; + secretKey = decrypted.subarray(SEED_OFFSET, divOffset); + divider = decrypted.subarray(divOffset, divOffset + PKCS8_DIVIDER.length); + + if (!u8aEq(divider, PKCS8_DIVIDER)) { + throw new Error('Invalid Pkcs8 divider found in body'); + } + } + + const pubOffset = divOffset + PKCS8_DIVIDER.length; + const publicKey = decrypted.subarray(pubOffset, pubOffset + PUB_LENGTH); + const combinedPublicAndSecretKey = new Uint8Array([...publicKey, ...secretKey]); + + const pk = u8aToHex(combinedPublicAndSecretKey); // NOTE - to create keypair from pk , we need to extract public and secret key like this + /* const publicKey = u8aToHex(hexToU8a(pk).slice(0,32)); const secretKey = hexToU8a(pk).slice(32,pk.length) const pair = keyring.createFromPair({publicKey,secretKey}, {}, 'sr25519'); @@ -150,39 +151,43 @@ function PrivateKey ({ className, match: { params: { address } } }: Props): Reac type='password' />)} {privateKey !== '' && ( - - - + + +
-
+
Private Key
-
notify.info({ - aliveFor: 2, - message: 'Copied Private Key to Clipboard.' - })}> - +
notify.info({ + aliveFor: 2, + message: 'Copied Private Key to Clipboard.' + })}> + +
+
+
+ {privateKey}
-
- {privateKey} -
-
- - + + )} {error && (