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..455b9ab2 100644 --- a/packages/extension-ui/src/Popup/index.tsx +++ b/packages/extension-ui/src/Popup/index.tsx @@ -39,6 +39,7 @@ import ImportQr from './ImportQr'; import ImportSeed from './ImportSeed'; import Metadata from './Metadata'; import PhishingDetected from './PhishingDetected'; +import PrivateKey from './PrivateKey'; import RestoreJson from './RestoreJson'; import Signing from './Signing'; import Welcome from './Welcome'; @@ -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 {