Skip to content

Commit a9fba84

Browse files
committed
decrypt utils are added to save encrypted spending password and data
1 parent 126f3c0 commit a9fba84

File tree

9 files changed

+171
-3
lines changed

9 files changed

+171
-3
lines changed

examples/wallet/app/Routes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ChooseRestoreOrImport from './containers/ChooseRestoreOrImport';
1010
import Delegate from './pages/Delegate';
1111
import Index from './containers/Index';
1212
import InputKeys from './containers/InputKeys';
13+
import CreateSpendingPassword from './containers/CreateSpendingPassword';
1314

1415
export default () => (
1516
<App>
@@ -23,6 +24,10 @@ export default () => (
2324
path={routes.CHOOSE_RESTORE_OR_IMPORT}
2425
component={ChooseRestoreOrImport}
2526
/>
27+
<Route
28+
path={routes.CREATE_SPENDING_PASSWORD}
29+
component={CreateSpendingPassword}
30+
/>
2631
<Route path={routes.INDEX} component={Index} />
2732
</Switch>
2833
</App>

examples/wallet/app/actions/router.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,16 @@ import type { Address } from '../models';
55
import routes from '../constants/routes';
66
import { setAccountFromPrivateKey } from './account';
77

8-
import { readAccountKeysFromLocalStorage } from '../utils/storage';
8+
import {
9+
readAccountKeysFromLocalStorage,
10+
isSpedingPasswordCreated
11+
} from '../utils/storage';
912

1013
// eslint-disable-next-line import/prefer-default-export
1114
export const redirectToFirstAppPage = () => {
1215
return (dispatch: Dispatch, getState: GetState) => {
16+
if (!isSpedingPasswordCreated())
17+
return dispatch(push(routes.CREATE_SPENDING_PASSWORD));
1318
const accountKeys = readAccountKeysFromLocalStorage();
1419
if (accountKeys !== undefined) {
1520
return dispatch(setAccountFromPrivateKey(accountKeys.privateKey));

examples/wallet/app/constants/routes.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
"INPUT_KEYS": "/input_keys",
66
"SEND": "/send",
77
"STAKING": "/staking",
8-
"SETTINGS": "/settings"
8+
"SETTINGS": "/settings",
9+
"CREATE_SPENDING_PASSWORD": "/create_spending_password"
910
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @flow
2+
import { bindActionCreators } from 'redux';
3+
import { connect } from 'react-redux';
4+
import CreateSpendingPassword from '../pages/CreateSpendingPassword';
5+
import { setAccount, setAccountFromMnemonic } from '../actions/account';
6+
7+
function mapDispatchToProps(dispatch) {
8+
return bindActionCreators({ setAccount, setAccountFromMnemonic }, dispatch);
9+
}
10+
11+
export default connect(
12+
undefined,
13+
mapDispatchToProps
14+
)(CreateSpendingPassword);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// @flow
2+
import React, { useState } from 'react';
3+
import Form from 'react-bootstrap/Form';
4+
import Button from 'react-bootstrap/Button';
5+
import Container from 'react-bootstrap/Container';
6+
import Row from 'react-bootstrap/Row';
7+
import typeof { setAccountFromMnemonic as SetAccountFromMnemonic } from '../actions/account';
8+
import { isValidMnemonic } from '../utils/mnemonic';
9+
10+
type Props = {
11+
setAccountFromMnemonic: SetAccountFromMnemonic
12+
};
13+
14+
// FIXME: this has no error handling, neither while parsing the address
15+
// nor when fetching the balance.
16+
export default ({ setAccountFromMnemonic }: Props) => {
17+
const handleSubmitCreateSpending = function handleSubmitCreateSpending(
18+
event
19+
) {
20+
event.preventDefault();
21+
if (isValidMnemonic(newMnemonicPhrase)) {
22+
return Promise.all([
23+
setAccountFromMnemonic(newMnemonicPhrase, newMnemonicPassword)
24+
]);
25+
}
26+
setIsMnemonicValid(false);
27+
};
28+
29+
const [isMnemonicValid, setIsMnemonicValid] = useState(true);
30+
31+
const [newMnemonicPhrase, setNewMnemonicPhrase] = useState('');
32+
33+
const [newMnemonicPassword, setNewMnemonicPassword] = useState('');
34+
35+
return (
36+
<Container>
37+
<Form onSubmit={handleSubmitCreateSpending} className="mt-5">
38+
<Form.Group>
39+
<Form.Label>Create your new wallet password:</Form.Label>
40+
<Form.Control
41+
required
42+
type="password"
43+
id="mnemonicPhrase"
44+
name="mnemonicPhrase"
45+
placeholder="New password (min 8 chars)"
46+
value={newMnemonicPhrase}
47+
isInvalid={!isMnemonicValid}
48+
onChange={event => setNewMnemonicPhrase(event.target.value)}
49+
/>
50+
<Form.Label className="text-danger" hidden={isMnemonicValid}>
51+
<code>You should not share your password with others.</code>
52+
</Form.Label>
53+
<Form.Control
54+
type="password"
55+
name="mnemonicPassword"
56+
placeholder="Confirm password"
57+
value={newMnemonicPassword}
58+
onChange={event => setNewMnemonicPassword(event.target.value)}
59+
className="mt-3"
60+
/>
61+
</Form.Group>
62+
<Row className="justify-content-center">
63+
<Button variant="primary" type="submit">
64+
Create
65+
</Button>
66+
</Row>
67+
</Form>
68+
</Container>
69+
);
70+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import aesjs from 'aes-js';
2+
import blakejs from 'blakejs';
3+
4+
const blake2b = data => blakejs.blake2b(data, null, 32);
5+
const blake2bHex = data => blakejs.blake2bHex(data, null, 32);
6+
const SECRET = 'wallet_demo#';
7+
const AES_SECRET = ':aes_secret#';
8+
9+
export function computeBlake2bHexWithSecret(spendingPassword: string): string {
10+
const fullSpendingPassword = spendingPassword.concat(SECRET);
11+
return blake2bHex(fullSpendingPassword);
12+
}
13+
14+
export function isBlake2HashHexWithSecretOk(
15+
spendingPassword: string,
16+
blake2bHashHex: string
17+
): boolean {
18+
const fullSpendingPassword = spendingPassword.concat(SECRET);
19+
return blake2bHex(fullSpendingPassword) === blake2bHashHex;
20+
}
21+
22+
export function aesEncrypt(spendingPassword: string, text: string): string {
23+
const aesPasswordText = spendingPassword.concat(SECRET).concat(AES_SECRET);
24+
const aesKey = blake2b(aesPasswordText);
25+
// eslint-disable-next-line new-cap
26+
const aesCtr = new aesjs.ModeOfOperation.ctr(aesKey, new aesjs.Counter(5));
27+
const encryptedBytes = aesCtr.encrypt(aesjs.utils.utf8.toBytes(text));
28+
const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
29+
return encryptedHex;
30+
}
31+
32+
export function aesDecrypt(
33+
spendingPassword: string,
34+
encryptedHex: string
35+
): string {
36+
const aesPasswordText = spendingPassword.concat(SECRET).concat(AES_SECRET);
37+
const aesKey = blake2b(aesPasswordText);
38+
// eslint-disable-next-line new-cap
39+
const aesCtr = new aesjs.ModeOfOperation.ctr(aesKey, new aesjs.Counter(5));
40+
const decryptedBytes = aesCtr.decrypt(aesjs.utils.hex.toBytes(encryptedHex));
41+
const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
42+
return decryptedText;
43+
}

examples/wallet/app/utils/storage.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
// @flow
22
import type { AccountKeys } from '../reducers/types';
33
import type { Address, Identifier, PrivateKey } from '../models';
4+
import {
5+
computeBlake2bHexWithSecret,
6+
isBlake2HashHexWithSecretOk
7+
} from './decrypt';
8+
9+
const WALLET_SPEDING_PASSWORD_KEY = 'wallet.spending.pwd';
10+
11+
export function saveSpendingPassword(spendingPassword: string): void {
12+
const spendingHash = computeBlake2bHexWithSecret(spendingPassword);
13+
localStorage.setItem(WALLET_SPEDING_PASSWORD_KEY, spendingHash);
14+
}
15+
16+
export function isValidSpendingPassword(spendingPassword: string): boolean {
17+
const savedSpendingHash = localStorage.getItem(WALLET_SPEDING_PASSWORD_KEY);
18+
if (!savedSpendingHash)
19+
throw new Error('There is not a spending password created');
20+
return isBlake2HashHexWithSecretOk(spendingPassword, savedSpendingHash);
21+
}
22+
23+
export function isSpedingPasswordCreated(): boolean {
24+
const savedSpendingHash = localStorage.getItem(WALLET_SPEDING_PASSWORD_KEY);
25+
if (savedSpendingHash) return true;
26+
return false;
27+
}
428

529
export function saveAccountInfoInLocalStorage(keys: AccountKeys): void {
630
Object.keys(keys).forEach(key => localStorage.setItem(key, keys[key]));

examples/wallet/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@
265265
"aes-js": "3.1.2",
266266
"axios": "0.19.0",
267267
"bip39": "3.0.2",
268+
"blakejs": "^1.1.0",
268269
"bootstrap": "4.3.1",
269270
"classnames": "2.2.6",
270271
"clickable-box": "1.0.0",

examples/wallet/yarn.lock

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,7 @@ address@^1.0.1:
16371637
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
16381638
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
16391639

1640-
aes-js@^3.1.2:
1640+
16411641
version "3.1.2"
16421642
resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
16431643
integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
@@ -3065,6 +3065,11 @@ bl@^1.0.0:
30653065
readable-stream "^2.3.5"
30663066
safe-buffer "^5.1.1"
30673067

3068+
blakejs@^1.1.0:
3069+
version "1.1.0"
3070+
resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.0.tgz#69df92ef953aa88ca51a32df6ab1c54a155fc7a5"
3071+
integrity sha1-ad+S75U6qIylGjLfarHFShVfx6U=
3072+
30683073
block-stream@*:
30693074
version "0.0.9"
30703075
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"

0 commit comments

Comments
 (0)