EthereumJS-Wallet (Part 2) (#310)

* Progress commit -- ethereumjs-wallet typings

* Add hdkey module + better wallet typing

* Add provider-engine typings

* Add jsdoc descriptions for hdkey constructor methods

* Fix missing return type

* Fix another missing return

* Make provider engine options optional

* Add priv/pubkey members to wallet instance

* Turn into SFC + Use ethereumjs-lib

* Use proper interface naming for V3Wallet

* Switch to ethereumjs-wallet

* Switch to ethereumjs-wallet and refactor using NewTabLink

* Use proper interface naming for V3Wallet

* Use proper interface naming for PublicKeyOnlyWallet

* Fix broken test, re-add scryptsy to make this PR pass

* Fix definition module for thirdparty wallets

* Decrease n-factor to 1024, checksum address of keystore

* Update typedef for react-dom from 15 to 16

* Lock react-dom, set typescript to 2.5.2
This commit is contained in:
HenryNguyen5 2017-11-07 13:42:53 -05:00 committed by Daniel Ternyak
parent cb130a9bb6
commit 3bea632a9a
12 changed files with 3085 additions and 3554 deletions

View File

@ -1,4 +1,4 @@
import { PrivKeyWallet } from 'libs/wallet';
import { generate } from 'ethereumjs-wallet';
import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
@ -8,7 +8,7 @@ export function generateNewWallet(
): interfaces.GenerateNewWalletAction {
return {
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET,
wallet: PrivKeyWallet.generate(),
wallet: generate(),
password
};
}

View File

@ -1,10 +1,10 @@
import { PrivKeyWallet } from 'libs/wallet';
import { IFullWallet } from 'ethereumjs-wallet';
import { TypeKeys } from './constants';
/*** Generate Wallet File ***/
export interface GenerateNewWalletAction {
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET;
wallet: PrivKeyWallet;
wallet: IFullWallet;
password: string;
}

View File

@ -1,71 +1,53 @@
import { PaperWallet } from 'components';
import PrivKeyWallet from 'libs/wallet/privkey';
import React, { Component } from 'react';
import { IFullWallet } from 'ethereumjs-wallet';
import React from 'react';
import translate from 'translations';
import printElement from 'utils/printElement';
interface Props {
wallet: PrivKeyWallet;
}
const print = (address: string, privateKey: string) => () =>
address &&
privateKey &&
printElement(<PaperWallet address={address} privateKey={privateKey} />, {
popupFeatures: {
scrollbars: 'no'
},
styles: `
* {
box-sizing: border-box;
}
interface State {
address: string | null;
privateKey: string | null;
}
body {
font-family: Lato, sans-serif;
font-size: 1rem;
line-height: 1.4;
margin: 0;
}
`
});
const initialState = {
address: null,
privateKey: null
const PrintableWallet: React.SFC<{ wallet: IFullWallet }> = ({ wallet }) => {
const address = wallet.getAddressString();
const privateKey = wallet.getPrivateKeyString();
if (!address || !privateKey) {
return null;
}
return (
<div>
<PaperWallet address={address} privateKey={privateKey} />
<a
role="button"
aria-label={translate('x_Print')}
aria-describedby="x_PrintDesc"
className={'btn btn-lg btn-primary'}
onClick={print(address, privateKey)}
style={{ marginTop: 10 }}
>
{translate('x_Print')}
</a>
</div>
);
};
export default class PrintableWallet extends Component<Props, {}> {
public state: State = initialState;
public async componentDidMount() {
const address = await this.props.wallet.getAddress();
const privateKey = this.props.wallet.getPrivateKey();
this.setState({ address, privateKey });
}
public print = () => {
const { address, privateKey } = this.state;
if (address && privateKey) {
printElement(<PaperWallet address={address} privateKey={privateKey} />, {
popupFeatures: {
scrollbars: 'no'
},
styles: `
* {
box-sizing: border-box;
}
body {
font-family: Lato, sans-serif;
font-size: 1rem;
line-height: 1.4;
margin: 0;
}
`
});
}
};
public render() {
const { address, privateKey } = this.state;
return address && privateKey ? (
<div>
<PaperWallet address={address} privateKey={privateKey} />
<a
role="button"
aria-label={translate('x_Print')}
aria-describedby="x_PrintDesc"
className={'btn btn-lg btn-primary'}
onClick={this.print}
style={{ marginTop: 10 }}
>
{translate('x_Print')}
</a>
</div>
) : null;
}
}
export default PrintableWallet;

View File

@ -29,14 +29,15 @@ interface AAttributes {
type?: string;
}
interface NewTabLinkProps extends AAttributes {
interface NewTabLinkProps extends AAttributes {
content?: React.ReactElement<any> | string;
children?: React.ReactElement<any> | string;
}
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) =>
const NewTabLink = ({ content, children, ...rest }: NewTabLinkProps) => (
<a target="_blank" rel="noopener" {...rest}>
{content || children} {/* Keep content for short-hand text insertion */}
</a>;
</a>
);
export default NewTabLink;

View File

@ -7,33 +7,39 @@ import chromeIcon from 'assets/images/browsers/chrome.svg';
import operaIcon from 'assets/images/browsers/opera.svg';
import './CryptoWarning.scss';
const BROWSERS = [{
name: "Firefox",
href: "https://www.mozilla.org/en-US/firefox/new/",
icon: firefoxIcon,
}, {
name: "Chrome",
href: "https://www.google.com/chrome/browser/desktop/index.html",
icon: chromeIcon,
}, {
name: "Opera",
href: "http://www.opera.com/",
icon: operaIcon,
}];
const BROWSERS = [
{
name: 'Firefox',
href: 'https://www.mozilla.org/en-US/firefox/new/',
icon: firefoxIcon
},
{
name: 'Chrome',
href: 'https://www.google.com/chrome/browser/desktop/index.html',
icon: chromeIcon
},
{
name: 'Opera',
href: 'http://www.opera.com/',
icon: operaIcon
}
];
const CryptoWarning: React.SFC<{}> = () =>
const CryptoWarning: React.SFC<{}> = () => (
<div className="Tab-content-pane">
<div className="CryptoWarning">
<h2 className="CryptoWarning-title">
Your Browser Cannot Generate a Wallet
</h2>
<p className="CryptoWarning-text">
{isMobile ? `
{isMobile
? `
MyEtherWallet requires certain features for secure wallet generation
that your browser doesn't offer. You can still securely use the site
otherwise. To generate a wallet, please use your device's default
browser, or switch to a laptop or desktop computer.
` : `
`
: `
MyEtherWallet requires certain features for secure wallet generation
that your browser doesn't offer. You can still securely use the site
otherwise. To generate a wallet, upgrade to one of the following
@ -42,7 +48,7 @@ const CryptoWarning: React.SFC<{}> = () =>
</p>
<div className="CryptoWarning-browsers">
{BROWSERS.map((browser) =>
{BROWSERS.map(browser => (
<NewTabLink
key={browser.href}
href={browser.href}
@ -58,9 +64,10 @@ const CryptoWarning: React.SFC<{}> = () =>
</div>
</div>
</NewTabLink>
)}
))}
</div>
</div>
</div>
);
export default CryptoWarning;

View File

@ -1,6 +1,7 @@
import { ContinueToPaperAction } from 'actions/generateWallet';
import { getV3Filename, UtcKeystore } from 'libs/keystore';
import PrivKeyWallet from 'libs/wallet/privkey';
import { IFullWallet, IV3Wallet } from 'ethereumjs-wallet';
import { toChecksumAddress } from 'ethereumjs-util';
import { NewTabLink } from 'components/ui';
import React, { Component } from 'react';
import translate from 'translations';
import { makeBlob } from 'utils/blob';
@ -8,46 +9,35 @@ import './DownloadWallet.scss';
import Template from './Template';
interface Props {
wallet: PrivKeyWallet;
wallet: IFullWallet;
password: string;
continueToPaper(): ContinueToPaperAction;
}
interface State {
hasDownloadedWallet: boolean;
address: string;
keystore: UtcKeystore | null;
keystore: IV3Wallet | null;
}
export default class DownloadWallet extends Component<Props, State> {
public state: State = {
hasDownloadedWallet: false,
address: '',
keystore: null
};
public componentDidMount() {
this.props.wallet.getAddress().then(address => {
this.setState({ address });
});
public componentWillMount() {
this.setWallet(this.props.wallet, this.props.password);
}
public componentWillMount() {
this.props.wallet.toKeystore(this.props.password).then(utcKeystore => {
this.setState({ keystore: utcKeystore });
});
}
public componentWillUpdate(nextProps: Props) {
if (this.props.wallet !== nextProps.wallet) {
nextProps.wallet.toKeystore(nextProps.password).then(utcKeystore => {
this.setState({ keystore: utcKeystore });
});
this.setWallet(nextProps.wallet, nextProps.password);
}
}
public render() {
const { hasDownloadedWallet } = this.state;
const filename = this.getFilename();
const filename = this.props.wallet.getV3Filename();
const content = (
<div className="DlWallet">
@ -112,22 +102,14 @@ export default class DownloadWallet extends Component<Props, State> {
<h4>{translate('GEN_Help_4')}</h4>
<ul>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-save-slash-backup-my-wallet"
target="_blank"
rel="noopener"
>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-save-slash-backup-my-wallet">
<strong>{translate('GEN_Help_13')}</strong>
</a>
</NewTabLink>
</li>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-are-the-different-formats-of-a-private-key"
target="_blank"
rel="noopener"
>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-are-the-different-formats-of-a-private-key">
<strong>{translate('GEN_Help_14')}</strong>
</a>
</NewTabLink>
</li>
</ul>
</div>
@ -136,28 +118,23 @@ export default class DownloadWallet extends Component<Props, State> {
return <Template content={content} help={help} />;
}
public getBlob() {
if (this.state.keystore) {
return makeBlob('text/json;charset=UTF-8', this.state.keystore);
}
public getBlob = () =>
(this.state.keystore &&
makeBlob('text/json;charset=UTF-8', this.state.keystore)) ||
undefined;
private markDownloaded = () =>
this.state.keystore && this.setState({ hasDownloadedWallet: true });
private handleContinue = () =>
this.state.hasDownloadedWallet && this.props.continueToPaper();
private setWallet(wallet: IFullWallet, password: string) {
const keystore = wallet.toV3(password, { n: 1024 });
keystore.address = toChecksumAddress(keystore.address);
this.setState({ keystore });
}
public getFilename() {
return getV3Filename(this.state.address);
}
private markDownloaded = () => {
if (this.state.keystore) {
this.setState({ hasDownloadedWallet: true });
}
};
private handleContinue = () => {
if (this.state.hasDownloadedWallet) {
this.props.continueToPaper();
}
};
private handleDownloadKeystore = (e): void => {
private handleDownloadKeystore = e =>
this.state.keystore ? this.markDownloaded() : e.preventDefault();
};
}

View File

@ -1,113 +1,91 @@
import PrintableWallet from 'components/PrintableWallet';
import PrivKeyWallet from 'libs/wallet/privkey';
import React, { Component } from 'react';
import { IFullWallet } from 'ethereumjs-wallet';
import { NewTabLink } from 'components/ui';
import React from 'react';
import { Link } from 'react-router-dom';
import translate from 'translations';
import './PaperWallet.scss';
import Template from './Template';
interface Props {
wallet: PrivKeyWallet;
}
const content = (wallet: IFullWallet) => (
<div className="GenPaper">
{/* Private Key */}
<h1 className="GenPaper-title">{translate('GEN_Label_5')}</h1>
<input
className="GenPaper-private form-control"
value={wallet.getPrivateKeyString()}
aria-label={translate('x_PrivKey')}
aria-describedby="x_PrivKeyDesc"
type="text"
readOnly={true}
/>
export default class PaperWallet extends Component<Props, {}> {
public render() {
const { wallet } = this.props;
{/* Download Paper Wallet */}
<h1 className="GenPaper-title">{translate('x_Print')}</h1>
<div className="GenPaper-paper">
<PrintableWallet wallet={wallet} />
</div>
const content = (
<div className="GenPaper">
{/* Private Key */}
<h1 className="GenPaper-title">{translate('GEN_Label_5')}</h1>
<input
className="GenPaper-private form-control"
value={wallet.getPrivateKey()}
aria-label={translate('x_PrivKey')}
aria-describedby="x_PrivKeyDesc"
type="text"
readOnly={true}
/>
{/* Warning */}
<div className="GenPaper-warning">
<p>
<strong>Do not lose it!</strong> It cannot be recovered if you lose it.
</p>
<p>
<strong>Do not share it!</strong> Your funds will be stolen if you use
this file on a malicious/phishing site.
</p>
<p>
<strong>Make a backup!</strong> Secure it like the millions of dollars
it may one day be worth.
</p>
</div>
{/* Download Paper Wallet */}
<h1 className="GenPaper-title">{translate('x_Print')}</h1>
<div className="GenPaper-paper">
<PrintableWallet wallet={wallet} />
</div>
{/* Continue button */}
<Link className="GenPaper-continue btn btn-default" to="/view-wallet">
{translate('NAV_ViewWallet')}
</Link>
</div>
);
{/* Warning */}
<div className="GenPaper-warning">
<p>
<strong>Do not lose it!</strong> It cannot be recovered if you lose
it.
</p>
<p>
<strong>Do not share it!</strong> Your funds will be stolen if you
use this file on a malicious/phishing site.
</p>
<p>
<strong>Make a backup!</strong> Secure it like the millions of
dollars it may one day be worth.
</p>
</div>
const help = (
<div>
<h4>{translate('GEN_Help_4')}</h4>
<ul>
<li>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-save-slash-backup-my-wallet">
<strong>{translate('HELP_2a_Title')}</strong>
</NewTabLink>
</li>
<li>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/protecting-yourself-and-your-funds">
<strong>{translate('GEN_Help_15')}</strong>
</NewTabLink>
</li>
<li>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-are-the-different-formats-of-a-private-key">
<strong>{translate('GEN_Help_16')}</strong>
</NewTabLink>
</li>
</ul>
{/* Continue button */}
<Link className="GenPaper-continue btn btn-default" to="/view-wallet">
{translate('NAV_ViewWallet')}
</Link>
</div>
);
<h4>{translate('GEN_Help_17')}</h4>
<ul>
<li>{translate('GEN_Help_18')}</li>
<li>{translate('GEN_Help_19')}</li>
<li>
<NewTabLink href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-safely-slash-offline-slash-cold-storage-with-myetherwallet">
{translate('GEN_Help_20')}
</NewTabLink>
</li>
</ul>
const help = (
<div>
<h4>{translate('GEN_Help_4')}</h4>
<ul>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-save-slash-backup-my-wallet"
target="_blank"
rel="noopener"
>
<strong>{translate('HELP_2a_Title')}</strong>
</a>
</li>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/protecting-yourself-and-your-funds"
target="_blank"
rel="noopener"
>
<strong>{translate('GEN_Help_15')}</strong>
</a>
</li>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/what-are-the-different-formats-of-a-private-key"
target="_blank"
rel="noopener"
>
<strong>{translate('GEN_Help_16')}</strong>
</a>
</li>
</ul>
<h4>{translate('x_PrintDesc')}</h4>
</div>
);
<h4>{translate('GEN_Help_17')}</h4>
<ul>
<li>{translate('GEN_Help_18')}</li>
<li>{translate('GEN_Help_19')}</li>
<li>
<a
href="https://myetherwallet.groovehq.com/knowledge_base/topics/how-do-i-safely-slash-offline-slash-cold-storage-with-myetherwallet"
target="_blank"
rel="noopener"
>
{translate('GEN_Help_20')}
</a>
</li>
</ul>
const PaperWallet: React.SFC<{
wallet: IFullWallet;
}> = ({ wallet }) => <Template content={content(wallet)} help={help} />;
<h4>{translate('x_PrintDesc')}</h4>
</div>
);
return <Template content={content} help={help} />;
}
}
export default PaperWallet;

View File

@ -6,7 +6,7 @@ import {
TGenerateNewWallet,
TResetGenerateWallet
} from 'actions/generateWallet';
import PrivKeyWallet from 'libs/wallet/privkey';
import { IFullWallet } from 'ethereumjs-wallet';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
@ -20,7 +20,7 @@ interface Props {
// Redux state
activeStep: string; // FIXME union actual steps
password: string;
wallet: PrivKeyWallet | null | undefined;
wallet: IFullWallet | null | undefined;
walletPasswordForm: any;
// Actions
generateNewWallet: TGenerateNewWallet;
@ -73,9 +73,8 @@ class GenerateWallet extends Component<Props, {}> {
default:
content = <h1>Uh oh. Not sure how you got here.</h1>;
}
}
else {
content = <CryptoWarning/>;
} else {
content = <CryptoWarning />;
}
return (

View File

@ -1,10 +1,10 @@
import { GenerateWalletAction } from 'actions/generateWallet';
import { TypeKeys } from 'actions/generateWallet/constants';
import PrivateKeyWallet from 'libs/wallet/privkey';
import { IFullWallet } from 'ethereumjs-wallet';
export interface State {
activeStep: string;
wallet?: PrivateKeyWallet | null;
wallet?: IFullWallet | null;
password?: string | null;
}

View File

@ -1,4 +1,6 @@
const isMobile = window && window.navigator ?
/iPhone|iPad|iPod|Android/i.test(window.navigator.userAgent) : false;
const isMobile =
window && window.navigator
? /iPhone|iPad|iPod|Android/i.test(window.navigator.userAgent)
: false;
export default isMobile;

6187
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -55,7 +55,7 @@
"@types/qrcode": "^0.8.0",
"@types/qrcode.react": "^0.6.2",
"@types/react": "^16.0.5",
"@types/react-dom": "^15.5.4",
"@types/react-dom": "16.0.2",
"@types/react-redux": "^5.0.9",
"@types/react-router": "^4.0.15",
"@types/react-router-dom": "^4.0.8",
@ -126,16 +126,14 @@
"predev": "check-node-version --package",
"dev:https": "HTTPS=true node webpack_config/server.js",
"predev:https": "check-node-version --package",
"derivation-checker": "webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
"derivation-checker":
"webpack --config=./webpack_config/webpack.derivation-checker.js && node ./dist/derivation-checker.js",
"tslint": "tslint --project . --exclude common/vendor/**/*",
"postinstall": "webpack --config=./webpack_config/webpack.dll.js",
"start": "npm run dev",
"precommit": "lint-staged"
},
"lint-staged": {
"*.{ts,tsx}": [
"prettier --write --single-quote",
"git add"
]
"*.{ts,tsx}": ["prettier --write --single-quote", "git add"]
}
}