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 * as interfaces from './actionTypes';
import { TypeKeys } from './constants'; import { TypeKeys } from './constants';
@ -8,7 +8,7 @@ export function generateNewWallet(
): interfaces.GenerateNewWalletAction { ): interfaces.GenerateNewWalletAction {
return { return {
type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET, type: TypeKeys.GENERATE_WALLET_GENERATE_WALLET,
wallet: PrivKeyWallet.generate(), wallet: generate(),
password password
}; };
} }

View File

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

View File

@ -1,71 +1,53 @@
import { PaperWallet } from 'components'; import { PaperWallet } from 'components';
import PrivKeyWallet from 'libs/wallet/privkey'; import { IFullWallet } from 'ethereumjs-wallet';
import React, { Component } from 'react'; import React from 'react';
import translate from 'translations'; import translate from 'translations';
import printElement from 'utils/printElement'; import printElement from 'utils/printElement';
interface Props { const print = (address: string, privateKey: string) => () =>
wallet: PrivKeyWallet; address &&
} privateKey &&
printElement(<PaperWallet address={address} privateKey={privateKey} />, {
popupFeatures: {
scrollbars: 'no'
},
styles: `
* {
box-sizing: border-box;
}
interface State { body {
address: string | null; font-family: Lato, sans-serif;
privateKey: string | null; font-size: 1rem;
} line-height: 1.4;
margin: 0;
}
`
});
const initialState = { const PrintableWallet: React.SFC<{ wallet: IFullWallet }> = ({ wallet }) => {
address: null, const address = wallet.getAddressString();
privateKey: null 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, {}> { export default PrintableWallet;
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,6 @@
const isMobile = window && window.navigator ? const isMobile =
/iPhone|iPad|iPod|Android/i.test(window.navigator.userAgent) : false; window && window.navigator
? /iPhone|iPad|iPod|Android/i.test(window.navigator.userAgent)
: false;
export default isMobile; 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": "^0.8.0",
"@types/qrcode.react": "^0.6.2", "@types/qrcode.react": "^0.6.2",
"@types/react": "^16.0.5", "@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-redux": "^5.0.9",
"@types/react-router": "^4.0.15", "@types/react-router": "^4.0.15",
"@types/react-router-dom": "^4.0.8", "@types/react-router-dom": "^4.0.8",
@ -126,16 +126,14 @@
"predev": "check-node-version --package", "predev": "check-node-version --package",
"dev:https": "HTTPS=true node webpack_config/server.js", "dev:https": "HTTPS=true node webpack_config/server.js",
"predev:https": "check-node-version --package", "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/**/*", "tslint": "tslint --project . --exclude common/vendor/**/*",
"postinstall": "webpack --config=./webpack_config/webpack.dll.js", "postinstall": "webpack --config=./webpack_config/webpack.dll.js",
"start": "npm run dev", "start": "npm run dev",
"precommit": "lint-staged" "precommit": "lint-staged"
}, },
"lint-staged": { "lint-staged": {
"*.{ts,tsx}": [ "*.{ts,tsx}": ["prettier --write --single-quote", "git add"]
"prettier --write --single-quote",
"git add"
]
} }
} }