Add Private key to V3 keystore functionality (#336)
* setup components, reducers, actions, and added routes * removed redux, using local state and ethereumjs-wallet * added validation and state reset * added visibility options and changed btn colors * updated isValidPrivKey and updated some components to stateless functional comp * componentize input and add placeholder message * removed cn from KeystoreDetails * adds isValidPrivate to buffer check and min pw length to 0 * remove packagelock to fix merge conflict * added utilities tab removed keystore tab * adds fixpkey in validators and uses it across two components * added checksum removal and btn css fixes * Fixed en.json formatting - also removed fixedPkey * Added unit tests for isValidPrivKey * add runtime checks and rename stripHexPrefix to strippedPrivateKey * switch back to stripHexPrefix * Add constant for n-factor * enforce 9 char minimum
This commit is contained in:
parent
4f48eee99e
commit
818ad9fef5
|
@ -11,6 +11,7 @@ import Swap from 'containers/Tabs/Swap';
|
||||||
import ViewWallet from 'containers/Tabs/ViewWallet';
|
import ViewWallet from 'containers/Tabs/ViewWallet';
|
||||||
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
|
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
|
||||||
import BroadcastTx from 'containers/Tabs/BroadcastTx';
|
import BroadcastTx from 'containers/Tabs/BroadcastTx';
|
||||||
|
import RestoreKeystore from 'containers/Tabs/RestoreKeystore';
|
||||||
|
|
||||||
// TODO: fix this
|
// TODO: fix this
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -33,12 +34,12 @@ export default class Root extends Component<Props, {}> {
|
||||||
<Route path="/send-transaction" component={SendTransaction} />
|
<Route path="/send-transaction" component={SendTransaction} />
|
||||||
<Route path="/contracts" component={Contracts} />
|
<Route path="/contracts" component={Contracts} />
|
||||||
<Route path="/ens" component={ENS} />
|
<Route path="/ens" component={ENS} />
|
||||||
|
<Route path="/utilities" component={RestoreKeystore} />
|
||||||
<Route
|
<Route
|
||||||
path="/sign-and-verify-message"
|
path="/sign-and-verify-message"
|
||||||
component={SignAndVerifyMessage}
|
component={SignAndVerifyMessage}
|
||||||
/>
|
/>
|
||||||
<Route path="/pushTx" component={BroadcastTx} />
|
<Route path="/pushTx" component={BroadcastTx} />
|
||||||
|
|
||||||
<LegacyRoutes />
|
<LegacyRoutes />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
|
@ -8,6 +8,7 @@ const tabs = [
|
||||||
name: 'NAV_GenerateWallet',
|
name: 'NAV_GenerateWallet',
|
||||||
to: '/'
|
to: '/'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'NAV_SendEther',
|
name: 'NAV_SendEther',
|
||||||
to: 'send-transaction'
|
to: 'send-transaction'
|
||||||
|
@ -36,6 +37,10 @@ const tabs = [
|
||||||
name: 'Broadcast Transaction',
|
name: 'Broadcast Transaction',
|
||||||
to: 'pushTx'
|
to: 'pushTx'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'NAV_Utilities',
|
||||||
|
to: 'utilities'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'NAV_Help',
|
name: 'NAV_Help',
|
||||||
to: 'https://myetherwallet.groovehq.com/help_center',
|
to: 'https://myetherwallet.groovehq.com/help_center',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { isValidEncryptedPrivKey, isValidPrivKey } from 'libs/validators';
|
import { isValidEncryptedPrivKey, isValidPrivKey } from 'libs/validators';
|
||||||
|
import { stripHexPrefix } from 'libs/values';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import translate, { translateRaw } from 'translations';
|
import translate, { translateRaw } from 'translations';
|
||||||
|
|
||||||
|
@ -8,13 +9,6 @@ export interface PrivateKeyValue {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixPkey(key) {
|
|
||||||
if (key.indexOf('0x') === 0) {
|
|
||||||
return key.slice(2);
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Validated {
|
interface Validated {
|
||||||
fixedPkey: string;
|
fixedPkey: string;
|
||||||
isValidPkey: boolean;
|
isValidPkey: boolean;
|
||||||
|
@ -23,7 +17,7 @@ interface Validated {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePkeyAndPass(pkey: string, pass: string): Validated {
|
function validatePkeyAndPass(pkey: string, pass: string): Validated {
|
||||||
const fixedPkey = fixPkey(pkey);
|
const fixedPkey = stripHexPrefix(pkey);
|
||||||
const validPkey = isValidPrivKey(fixedPkey);
|
const validPkey = isValidPrivKey(fixedPkey);
|
||||||
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
|
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
|
||||||
const isValidPkey = validPkey || validEncPkey;
|
const isValidPkey = validPkey || validEncPkey;
|
||||||
|
@ -58,15 +52,13 @@ export default class PrivateKeyDecrypt extends Component {
|
||||||
return (
|
return (
|
||||||
<section className="col-md-4 col-sm-6">
|
<section className="col-md-4 col-sm-6">
|
||||||
<div id="selectedTypeKey">
|
<div id="selectedTypeKey">
|
||||||
<h4>
|
<h4>{translate('ADD_Radio_3')}</h4>
|
||||||
{translate('ADD_Radio_3')}
|
|
||||||
</h4>
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<textarea
|
<textarea
|
||||||
id="aria-private-key"
|
id="aria-private-key"
|
||||||
className={`form-control ${isValidPkey
|
className={`form-control ${
|
||||||
? 'is-valid'
|
isValidPkey ? 'is-valid' : 'is-invalid'
|
||||||
: 'is-invalid'}`}
|
}`}
|
||||||
value={key}
|
value={key}
|
||||||
onChange={this.onPkeyChange}
|
onChange={this.onPkeyChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
|
@ -75,22 +67,21 @@ export default class PrivateKeyDecrypt extends Component {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isValidPkey &&
|
{isValidPkey &&
|
||||||
isPassRequired &&
|
isPassRequired && (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<p>
|
<p>{translate('ADD_Label_3')}</p>
|
||||||
{translate('ADD_Label_3')}
|
|
||||||
</p>
|
|
||||||
<input
|
<input
|
||||||
className={`form-control ${password.length > 0
|
className={`form-control ${
|
||||||
? 'is-valid'
|
password.length > 0 ? 'is-valid' : 'is-invalid'
|
||||||
: 'is-invalid'}`}
|
}`}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={this.onPasswordChange}
|
onChange={this.onPasswordChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
placeholder={translateRaw('x_Password')}
|
placeholder={translateRaw('x_Password')}
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { networkIdToName } from 'libs/values';
|
||||||
export const languages = require('./languages.json');
|
export const languages = require('./languages.json');
|
||||||
// Displays in the header
|
// Displays in the header
|
||||||
export const VERSION = '4.0.0 (Alpha 0.0.4)';
|
export const VERSION = '4.0.0 (Alpha 0.0.4)';
|
||||||
|
export const N_FACTOR = 1024;
|
||||||
|
|
||||||
// Displays at the top of the site, make message empty string to remove.
|
// Displays at the top of the site, make message empty string to remove.
|
||||||
// Type can be primary, warning, danger, success, or info.
|
// Type can be primary, warning, danger, success, or info.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import translate from 'translations';
|
||||||
import { makeBlob } from 'utils/blob';
|
import { makeBlob } from 'utils/blob';
|
||||||
import './DownloadWallet.scss';
|
import './DownloadWallet.scss';
|
||||||
import Template from './Template';
|
import Template from './Template';
|
||||||
|
import { N_FACTOR } from 'config/data';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
wallet: IFullWallet;
|
wallet: IFullWallet;
|
||||||
|
@ -47,7 +48,7 @@ export default class DownloadWallet extends Component<Props, State> {
|
||||||
role="button"
|
role="button"
|
||||||
className="DlWallet-download btn btn-primary btn-lg"
|
className="DlWallet-download btn btn-primary btn-lg"
|
||||||
aria-label="Download Keystore File (UTC / JSON · Recommended · Encrypted)"
|
aria-label="Download Keystore File (UTC / JSON · Recommended · Encrypted)"
|
||||||
aria-describedby="x_KeystoreDesc"
|
aria-describedby={translate('x_KeystoreDesc')}
|
||||||
download={filename}
|
download={filename}
|
||||||
href={this.getBlob()}
|
href={this.getBlob()}
|
||||||
onClick={this.handleDownloadKeystore}
|
onClick={this.handleDownloadKeystore}
|
||||||
|
@ -130,7 +131,7 @@ export default class DownloadWallet extends Component<Props, State> {
|
||||||
this.state.hasDownloadedWallet && this.props.continueToPaper();
|
this.state.hasDownloadedWallet && this.props.continueToPaper();
|
||||||
|
|
||||||
private setWallet(wallet: IFullWallet, password: string) {
|
private setWallet(wallet: IFullWallet, password: string) {
|
||||||
const keystore = wallet.toV3(password, { n: 1024 });
|
const keystore = wallet.toV3(password, { n: N_FACTOR });
|
||||||
keystore.address = toChecksumAddress(keystore.address);
|
keystore.address = toChecksumAddress(keystore.address);
|
||||||
this.setState({ keystore });
|
this.setState({ keystore });
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
@import 'common/sass/variables';
|
||||||
|
|
||||||
|
.KeystoreDetails {
|
||||||
|
&-title {
|
||||||
|
margin: $space auto $space * 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-password,
|
||||||
|
&-key {
|
||||||
|
max-width: 40rem;
|
||||||
|
margin: 0 auto $space;
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
margin-bottom: $space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-submit,
|
||||||
|
&-download {
|
||||||
|
max-width: 16rem;
|
||||||
|
margin: 0 auto $space * 3;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import Template from './Template';
|
||||||
|
import KeystoreInput from './KeystoreInput';
|
||||||
|
import { fromPrivateKey, IFullWallet, fromV3 } from 'ethereumjs-wallet';
|
||||||
|
import { makeBlob } from 'utils/blob';
|
||||||
|
import { isValidPrivKey } from 'libs/validators';
|
||||||
|
import { stripHexPrefix } from 'libs/values';
|
||||||
|
import translate from 'translations';
|
||||||
|
import './KeystoreDetails.scss';
|
||||||
|
import { N_FACTOR } from 'config/data';
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
secretKey: string;
|
||||||
|
password: string;
|
||||||
|
fileName: string;
|
||||||
|
isPasswordVisible: boolean;
|
||||||
|
isPrivateKeyVisible: boolean;
|
||||||
|
wallet: IFullWallet | null | undefined;
|
||||||
|
}
|
||||||
|
const initialState: State = {
|
||||||
|
secretKey: '',
|
||||||
|
password: '',
|
||||||
|
isPasswordVisible: false,
|
||||||
|
isPrivateKeyVisible: false,
|
||||||
|
fileName: '',
|
||||||
|
wallet: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const minLength = min => value => value && value.length >= min;
|
||||||
|
const minLength9 = minLength(9);
|
||||||
|
|
||||||
|
class KeystoreDetails extends Component<{}, State> {
|
||||||
|
public state = initialState;
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.resetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
secretKey,
|
||||||
|
isPasswordVisible,
|
||||||
|
isPrivateKeyVisible,
|
||||||
|
password,
|
||||||
|
wallet,
|
||||||
|
fileName
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const privateKey = stripHexPrefix(secretKey);
|
||||||
|
const privateKeyValid = isValidPrivKey(privateKey);
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<div className="KeystoreDetails">
|
||||||
|
<div>
|
||||||
|
<label className="KeystoreDetails-key">
|
||||||
|
<h4 className="KeystoreDetails-label">Private Key</h4>
|
||||||
|
<KeystoreInput
|
||||||
|
isValid={privateKeyValid}
|
||||||
|
isVisible={isPrivateKeyVisible}
|
||||||
|
name="secretKey"
|
||||||
|
value={secretKey}
|
||||||
|
handleInput={this.handleInput}
|
||||||
|
placeholder="Enter your saved private key here"
|
||||||
|
handleToggle={this.togglePrivateKey}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="KeystoreDetails-password">
|
||||||
|
<h4 className="KeystoreDetails-label">Password</h4>
|
||||||
|
<KeystoreInput
|
||||||
|
isValid={minLength9(password)}
|
||||||
|
isVisible={isPasswordVisible}
|
||||||
|
name="password"
|
||||||
|
value={password}
|
||||||
|
placeholder="Enter your encryption password here."
|
||||||
|
handleInput={this.handleInput}
|
||||||
|
handleToggle={this.togglePassword}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{!wallet ? (
|
||||||
|
<button
|
||||||
|
onClick={this.handleKeystoreGeneration}
|
||||||
|
className="KeystoreDetails-submit btn btn-primary btn-block"
|
||||||
|
disabled={!privateKeyValid || !minLength9(password)}
|
||||||
|
>
|
||||||
|
Generate Keystore
|
||||||
|
</button>
|
||||||
|
) : this.runtimeKeystoreCheck() ? (
|
||||||
|
<a
|
||||||
|
onClick={this.resetState}
|
||||||
|
href={this.getBlob()}
|
||||||
|
className="KeystoreDetails-download btn btn-success btn-block"
|
||||||
|
aria-label="Download Keystore File (UTC / JSON · Recommended · Encrypted)"
|
||||||
|
aria-describedby={translate('x_KeystoreDesc')}
|
||||||
|
download={fileName}
|
||||||
|
>
|
||||||
|
Download Keystore
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
Error generating a valid keystore that matches your private key. In
|
||||||
|
order to protect our users, if our runtime check fails, we prevent
|
||||||
|
you from downloading a potentially corrupted wallet.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Template title="Regenerate Keystore File" content={content} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private togglePrivateKey = () => {
|
||||||
|
this.setState({
|
||||||
|
isPrivateKeyVisible: !this.state.isPrivateKeyVisible
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private togglePassword = () => {
|
||||||
|
this.setState({
|
||||||
|
isPasswordVisible: !this.state.isPasswordVisible
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private resetState = () => {
|
||||||
|
this.setState(initialState);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleKeystoreGeneration = () => {
|
||||||
|
const { secretKey } = this.state;
|
||||||
|
const removeChecksumPkey = stripHexPrefix(secretKey);
|
||||||
|
const keyBuffer = Buffer.from(removeChecksumPkey, 'hex');
|
||||||
|
const wallet = fromPrivateKey(keyBuffer);
|
||||||
|
const fileName = wallet.getV3Filename();
|
||||||
|
this.setState({
|
||||||
|
wallet,
|
||||||
|
fileName
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleInput = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
const name = e.currentTarget.name;
|
||||||
|
const value = e.currentTarget.value;
|
||||||
|
if (name === 'secretKey') {
|
||||||
|
this.setState({
|
||||||
|
wallet: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ [name as any]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
private runtimeKeystoreCheck(): boolean {
|
||||||
|
const { wallet, password, secretKey } = this.state;
|
||||||
|
if (wallet) {
|
||||||
|
const keystore = wallet.toV3(password, { n: N_FACTOR });
|
||||||
|
const backToWallet = fromV3(keystore, password, true);
|
||||||
|
if (stripHexPrefix(backToWallet.getPrivateKeyString()) === secretKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getBlob() {
|
||||||
|
const { wallet, password } = this.state;
|
||||||
|
if (wallet) {
|
||||||
|
const keystore = wallet.toV3(password, { n: N_FACTOR });
|
||||||
|
return makeBlob('text/json;charset=UTF-8', keystore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KeystoreDetails;
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isValid: boolean;
|
||||||
|
isVisible: boolean;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
|
handleInput(e: React.FormEvent<HTMLInputElement>): void;
|
||||||
|
handleToggle(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeystoreInput: React.SFC<Props> = ({
|
||||||
|
isValid,
|
||||||
|
isVisible,
|
||||||
|
handleInput,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
handleToggle
|
||||||
|
}) => (
|
||||||
|
<div className="input-group">
|
||||||
|
<input
|
||||||
|
className={classnames(
|
||||||
|
'form-control',
|
||||||
|
isValid ? 'is-valid' : 'is-invalid'
|
||||||
|
)}
|
||||||
|
type={isVisible ? 'text' : 'password'}
|
||||||
|
name={name}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
onChange={handleInput}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
onClick={handleToggle}
|
||||||
|
role="button"
|
||||||
|
className="input-group-addon eye"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default KeystoreInput;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
content: React.ReactElement<any>;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RestoreKeystoreTemplate: React.SFC<Props> = ({ title, content }) => (
|
||||||
|
<div className="Tab-content">
|
||||||
|
<div className="Tab-content-pane text-center">
|
||||||
|
<h1>{title}</h1>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
export default RestoreKeystoreTemplate;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
import TabSection from 'containers/TabSection';
|
||||||
|
import KeystoreDetails from './components/KeystoreDetails';
|
||||||
|
|
||||||
|
const RestoreKeystore: React.SFC<{}> = () => (
|
||||||
|
<TabSection>
|
||||||
|
<KeystoreDetails />
|
||||||
|
</TabSection>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default RestoreKeystore;
|
|
@ -1,5 +1,6 @@
|
||||||
import { toChecksumAddress } from 'ethereumjs-util';
|
import { toChecksumAddress, isValidPrivate } from 'ethereumjs-util';
|
||||||
import { RawTransaction } from 'libs/transaction';
|
import { RawTransaction } from 'libs/transaction';
|
||||||
|
import { stripHexPrefix } from 'libs/values';
|
||||||
import WalletAddressValidator from 'wallet-address-validator';
|
import WalletAddressValidator from 'wallet-address-validator';
|
||||||
import { normalise } from './ens';
|
import { normalise } from './ens';
|
||||||
import { Validator } from 'jsonschema';
|
import { Validator } from 'jsonschema';
|
||||||
|
@ -88,9 +89,15 @@ function validateEtherAddress(address: string): boolean {
|
||||||
|
|
||||||
export function isValidPrivKey(privkey: string | Buffer): boolean {
|
export function isValidPrivKey(privkey: string | Buffer): boolean {
|
||||||
if (typeof privkey === 'string') {
|
if (typeof privkey === 'string') {
|
||||||
return privkey.length === 64;
|
const strippedKey = stripHexPrefix(privkey);
|
||||||
|
const initialCheck = strippedKey.length === 64;
|
||||||
|
if (initialCheck) {
|
||||||
|
const keyBuffer = Buffer.from(strippedKey, 'hex');
|
||||||
|
return isValidPrivate(keyBuffer);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
} else if (privkey instanceof Buffer) {
|
} else if (privkey instanceof Buffer) {
|
||||||
return privkey.length === 32;
|
return privkey.length === 32 && isValidPrivate(privkey);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
"NAV_Swap": "Swap ",
|
"NAV_Swap": "Swap ",
|
||||||
"NAV_ViewWallet": "View Wallet Info ",
|
"NAV_ViewWallet": "View Wallet Info ",
|
||||||
"NAV_YourWallets": "Your Wallets ",
|
"NAV_YourWallets": "Your Wallets ",
|
||||||
|
"NAV_Utilities": "Utilities",
|
||||||
"x_Access": "Access ",
|
"x_Access": "Access ",
|
||||||
"x_AddessDesc": "Your Address can also be known as you `Account #` or your `Public Key`. It is what you share with people so they can send you Ether or Tokens. Find the colorful address icon. Make sure it matches your paper wallet & whenever you enter your address somewhere.",
|
"x_AddessDesc": "Your Address can also be known as you `Account #` or your `Public Key`. It is what you share with people so they can send you Ether or Tokens. Find the colorful address icon. Make sure it matches your paper wallet & whenever you enter your address somewhere.",
|
||||||
"x_Address": "Your Address ",
|
"x_Address": "Your Address ",
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import {
|
import {
|
||||||
isValidBTCAddress,
|
isValidBTCAddress,
|
||||||
isValidETHAddress,
|
isValidETHAddress,
|
||||||
isValidPath
|
isValidPath,
|
||||||
|
isValidPrivKey
|
||||||
} from '../../common/libs/validators';
|
} from '../../common/libs/validators';
|
||||||
|
|
||||||
const VALID_BTC_ADDRESS = '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6';
|
const VALID_BTC_ADDRESS = '1MEWT2SGbqtz6mPCgFcnea8XmWV5Z4Wc6';
|
||||||
const VALID_ETH_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
|
const VALID_ETH_ADDRESS = '0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8';
|
||||||
|
const VALID_ETH_PRIVATE_KEY =
|
||||||
|
'3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5c782';
|
||||||
|
const INVALID_ETH_PRIVATE_KEY =
|
||||||
|
'3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5ZZZZ';
|
||||||
|
const VALID_ETH_PRIVATE_BUFFER = Buffer.from(VALID_ETH_PRIVATE_KEY, 'hex');
|
||||||
|
const VALID_ETH_PRIVATE_0X =
|
||||||
|
'0x3f4fd89ea4970cc77bfd2d07a95786575ea62e183857afe6301578e1a3c5c782';
|
||||||
|
|
||||||
describe('Validator', () => {
|
describe('Validator', () => {
|
||||||
it('should validate correct BTC address as true', () => {
|
it('should validate correct BTC address as true', () => {
|
||||||
|
@ -25,11 +33,22 @@ describe('Validator', () => {
|
||||||
isValidETHAddress('nonsense' + VALID_ETH_ADDRESS + 'nonsense')
|
isValidETHAddress('nonsense' + VALID_ETH_ADDRESS + 'nonsense')
|
||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate a correct DPath as true', () => {
|
it('should validate a correct DPath as true', () => {
|
||||||
expect(isValidPath("m/44'/60'/0'/0")).toBeTruthy();
|
expect(isValidPath("m/44'/60'/0'/0")).toBeTruthy();
|
||||||
});
|
});
|
||||||
it('should validate an incorrect DPath as false', () => {
|
it('should validate an incorrect DPath as false', () => {
|
||||||
expect(isValidPath('m/44/60/0/0')).toBeFalsy();
|
expect(isValidPath('m/44/60/0/0')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
it('should validate private key as true', () => {
|
||||||
|
expect(isValidPrivKey(VALID_ETH_PRIVATE_KEY)).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should validate invalid private key as false', () => {
|
||||||
|
expect(isValidPrivKey(INVALID_ETH_PRIVATE_KEY)).toBeFalsy();
|
||||||
|
});
|
||||||
|
it('should validate 0x private keys as true', () => {
|
||||||
|
expect(isValidPrivKey(VALID_ETH_PRIVATE_0X)).toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should validate private key buffer type as true', () => {
|
||||||
|
expect(isValidPrivKey(VALID_ETH_PRIVATE_BUFFER)).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import { Wei } from 'libs/units';
|
import { Wei } from 'libs/units';
|
||||||
import { changeNodeIntent } from 'actions/config';
|
import { changeNodeIntent } from 'actions/config';
|
||||||
import { INode } from 'libs/nodes/INode';
|
import { INode } from 'libs/nodes/INode';
|
||||||
import { initWeb3Node, Token } from 'config/data';
|
import { initWeb3Node, Token, N_FACTOR } from 'config/data';
|
||||||
import { apply, call, cps, fork, put, select } from 'redux-saga/effects';
|
import { apply, call, cps, fork, put, select } from 'redux-saga/effects';
|
||||||
import { getNetworkConfig, getNodeLib } from 'selectors/config';
|
import { getNetworkConfig, getNodeLib } from 'selectors/config';
|
||||||
import { getTokens, getWalletInst } from 'selectors/wallet';
|
import { getTokens, getWalletInst } from 'selectors/wallet';
|
||||||
|
@ -65,7 +65,7 @@ const utcKeystore = {
|
||||||
kdfparams: {
|
kdfparams: {
|
||||||
dklen: 32,
|
dklen: 32,
|
||||||
salt: '037a53e520f2d00fb70f02f39b31b77374de9e0e1d35fd7cbe9c8a8b21d6b0ab',
|
salt: '037a53e520f2d00fb70f02f39b31b77374de9e0e1d35fd7cbe9c8a8b21d6b0ab',
|
||||||
n: 1024,
|
n: N_FACTOR,
|
||||||
r: 8,
|
r: 8,
|
||||||
p: 1
|
p: 1
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue