EthereumJS-Wallet (Part 3) (#316)

* 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

* Strip out wallet classes for ethereumjs-wallet, stuff wallet types in privkey for now

* Seperate wallets into deterministic and non-deterministic, change IWallet and deterministic wallets to adhere to getAddressString

* Fix broken test, remove scryptsy

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

* Remove uuid from deps and keystore test

* Add ethereumjs-wallet to DLL

* Wrap mnemonic wallet

* Fix definition module for thirdparty wallets

* Fix MewV1 wallet not loading due to wrong library

* Fix tsc error

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

* Fix isKeystorePassRequired

* Fix tsc errors

* Merge package lock

* Update package lock

* regenerate lock file

* Lock typescript to 2.5.2

* Merge develop
This commit is contained in:
HenryNguyen5 2017-11-08 13:16:43 -05:00 committed by Daniel Ternyak
parent 5766735a5a
commit a00269507c
31 changed files with 181 additions and 501 deletions

View File

@ -26,7 +26,7 @@ export default class AccountInfo extends React.Component<Props, State> {
}; };
public async setAddressFromWallet() { public async setAddressFromWallet() {
const address = await this.props.wallet.getAddress(); const address = await this.props.wallet.getAddressString();
if (address !== this.state.address) { if (address !== this.state.address) {
this.setState({ address }); this.setState({ address });
} }

View File

@ -1,4 +1,4 @@
import { isKeystorePassRequired } from 'libs/keystore'; import { isKeystorePassRequired } from 'libs/wallet';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
@ -32,9 +32,7 @@ export default class KeystoreDecrypt extends Component {
return ( return (
<section className="col-md-4 col-sm-6"> <section className="col-md-4 col-sm-6">
<div id="selectedUploadKey"> <div id="selectedUploadKey">
<h4> <h4>{translate('ADD_Radio_2_alt')}</h4>
{translate('ADD_Radio_2_alt')}
</h4>
<div className="form-group"> <div className="form-group">
<input <input
@ -54,9 +52,7 @@ export default class KeystoreDecrypt extends Component {
</a> </a>
</label> </label>
<div className={file.length && passReq ? '' : 'hidden'}> <div className={file.length && passReq ? '' : 'hidden'}>
<p> <p>{translate('ADD_Label_3')}</p>
{translate('ADD_Label_3')}
</p>
<input <input
className={`form-control ${password.length > 0 className={`form-control ${password.length > 0
? 'is-valid' ? 'is-valid'

View File

@ -2,7 +2,7 @@ import './LedgerNano.scss';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
import DeterministicWalletsModal from './DeterministicWalletsModal'; import DeterministicWalletsModal from './DeterministicWalletsModal';
import LedgerWallet from 'libs/wallet/ledger'; import { LedgerWallet } from 'libs/wallet';
import Ledger3 from 'vendor/ledger3'; import Ledger3 from 'vendor/ledger3';
import LedgerEth from 'vendor/ledger-eth'; import LedgerEth from 'vendor/ledger-eth';
import DPATHS from 'config/dpaths'; import DPATHS from 'config/dpaths';

View File

@ -1,5 +1,5 @@
import DPATHS from 'config/dpaths'; import DPATHS from 'config/dpaths';
import TrezorWallet from 'libs/wallet/trezor'; import { TrezorWallet } from 'libs/wallet';
import React, { Component } from 'react'; import React, { Component } from 'react';
import translate, { translateRaw } from 'translations'; import translate, { translateRaw } from 'translations';
import TrezorConnect from 'vendor/trezor-connect'; import TrezorConnect from 'vendor/trezor-connect';

View File

@ -151,7 +151,7 @@ export const deployHOC = PassedComponent => {
}; };
private getAddressAndNonce = async () => { private getAddressAndNonce = async () => {
const address = await this.props.wallet.getAddress(); const address = await this.props.wallet.getAddressString();
const nonce = await this.props.nodeLib const nonce = await this.props.nodeLib
.getTransactionCount(address) .getTransactionCount(address)
.then(n => new Big(n).toString()); .then(n => new Big(n).toString());

View File

@ -151,8 +151,8 @@ class ConfirmationModal extends React.Component<Props, State> {
</li> </li>
<li className="ConfModal-details-detail"> <li className="ConfModal-details-detail">
You are interacting with the{' '} You are interacting with the{' '}
<strong>{node.network}</strong>{' '} <strong>{node.network}</strong> network provided by{' '}
network provided by <strong>{node.service}</strong> <strong>{node.service}</strong>
</li> </li>
{!token && ( {!token && (
<li className="ConfModal-details-detail"> <li className="ConfModal-details-detail">

View File

@ -201,7 +201,7 @@ export class SendTransaction extends React.Component<Props, State> {
const { hasSetDefaultNonce, nonce } = this.state; const { hasSetDefaultNonce, nonce } = this.state;
const unlocked = !!wallet; const unlocked = !!wallet;
if (unlocked) { if (unlocked) {
const from = await wallet.getAddress(); const from = await wallet.getAddressString();
if (forceOffline && !offline && !hasSetDefaultNonce) { if (forceOffline && !offline && !hasSetDefaultNonce) {
const nonceHex = await nodeLib.getTransactionCount(from); const nonceHex = await nodeLib.getTransactionCount(from);
const newNonce = parseInt(stripHexPrefix(nonceHex), 10); const newNonce = parseInt(stripHexPrefix(nonceHex), 10);
@ -222,7 +222,7 @@ export class SendTransaction extends React.Component<Props, State> {
public async setWalletAddressOnUpdate() { public async setWalletAddressOnUpdate() {
if (this.props.wallet) { if (this.props.wallet) {
const walletAddress = await this.props.wallet.getAddress(); const walletAddress = await this.props.wallet.getAddressString();
if (walletAddress !== this.state.walletAddress) { if (walletAddress !== this.state.walletAddress) {
this.setState({ walletAddress }); this.setState({ walletAddress });
} }

View File

@ -95,7 +95,7 @@ export class SignMessage extends Component<Props, State> {
try { try {
const signedMessage: ISignedMessage = { const signedMessage: ISignedMessage = {
address: await wallet.getAddress(), address: await wallet.getAddressString(),
message, message,
signature: await wallet.signMessage(message), signature: await wallet.signMessage(message),
version: '2' version: '2'

View File

@ -1,5 +1,5 @@
import assert from 'assert'; import assert from 'assert';
import PrivKeyWallet from './libs/wallet/privkey'; import { generate, IFullWallet } from 'ethereumjs-wallet';
const { exec } = require('child_process'); const { exec } = require('child_process');
const ProgressBar = require('progress'); const ProgressBar = require('progress');
@ -17,8 +17,8 @@ function promiseFromChildProcess(command): Promise<any> {
}); });
} }
async function privToAddrViaDocker(privKeyWallet) { async function privToAddrViaDocker(privKeyWallet: IFullWallet) {
const command = `docker run -e key=${privKeyWallet.getPrivateKey()} ${dockerImage}:${dockerTag}`; const command = `docker run -e key=${privKeyWallet.getPrivateKeyString()} ${dockerImage}:${dockerTag}`;
const dockerOutput = await promiseFromChildProcess(command); const dockerOutput = await promiseFromChildProcess(command);
const newlineStrippedDockerOutput = dockerOutput.replace( const newlineStrippedDockerOutput = dockerOutput.replace(
/(\r\n|\n|\r)/gm, /(\r\n|\n|\r)/gm,
@ -28,8 +28,8 @@ async function privToAddrViaDocker(privKeyWallet) {
} }
async function testDerivation() { async function testDerivation() {
const privKeyWallet = PrivKeyWallet.generate(); const privKeyWallet = generate();
const privKeyWalletAddress = await privKeyWallet.getAddress(); const privKeyWalletAddress = await privKeyWallet.getAddressString();
const dockerAddr = await privToAddrViaDocker(privKeyWallet); const dockerAddr = await privToAddrViaDocker(privKeyWallet);
// strip the checksum // strip the checksum
const lowerCasedPrivKeyWalletAddress = privKeyWalletAddress.toLowerCase(); const lowerCasedPrivKeyWalletAddress = privKeyWalletAddress.toLowerCase();

View File

@ -1,234 +0,0 @@
import {
createCipheriv,
createDecipheriv,
pbkdf2Sync,
randomBytes
} from 'crypto';
import { privateToAddress, sha3 } from 'ethereumjs-util';
import scrypt from 'scryptsy';
import uuid from 'uuid';
import { decipherBuffer, decodeCryptojsSalt, evp_kdf } from './decrypt';
export interface UtcKeystore {
version: number;
id: string;
address: string;
Crypto: object;
}
// adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L342
export function determineKeystoreType(file: string): string {
const parsed = JSON.parse(file);
if (parsed.encseed) {
return 'presale';
} else if (parsed.Crypto || parsed.crypto) {
return 'v2-v3-utc';
} else if (parsed.hash && parsed.locked === true) {
return 'v1-encrypted';
} else if (parsed.hash && parsed.locked === false) {
return 'v1-unencrypted';
} else if (parsed.publisher === 'MyEtherWallet') {
return 'v2-unencrypted';
} else {
throw new Error('Invalid keystore');
}
}
export function isKeystorePassRequired(file: string): boolean {
switch (determineKeystoreType(file)) {
case 'presale':
return true;
case 'v1-unencrypted':
return false;
case 'v1-encrypted':
return true;
case 'v2-unencrypted':
return false;
case 'v2-v3-utc':
return true;
default:
return false;
}
}
// adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L218
export function decryptPresaleToPrivKey(
file: string,
password: string
): Buffer {
const json = JSON.parse(file);
const encseed = new Buffer(json.encseed, 'hex');
const derivedKey = pbkdf2Sync(
new Buffer(password),
new Buffer(password),
2000,
32,
'sha256'
).slice(0, 16);
const decipher = createDecipheriv(
'aes-128-cbc',
derivedKey,
encseed.slice(0, 16)
);
const seed = decipherBuffer(decipher, encseed.slice(16));
const privkey = sha3(seed);
const address = privateToAddress(privkey);
if (address.toString('hex') !== json.ethaddr) {
throw new Error('Decoded key mismatch - possibly wrong passphrase');
}
return privkey;
}
// adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L179
export function decryptMewV1ToPrivKey(file: string, password: string): Buffer {
const json = JSON.parse(file);
let privkey;
let address;
if (typeof password !== 'string') {
throw new Error('Password required');
}
if (password.length < 7) {
throw new Error('Password must be at least 7 characters');
}
let cipher = json.encrypted ? json.private.slice(0, 128) : json.private;
cipher = decodeCryptojsSalt(cipher);
const evp = evp_kdf(new Buffer(password), cipher.salt, {
keysize: 32,
ivsize: 16
});
const decipher = createDecipheriv('aes-256-cbc', evp.key, evp.iv);
privkey = decipherBuffer(decipher, new Buffer(cipher.ciphertext));
privkey = new Buffer(privkey.toString(), 'hex');
address = '0x' + privateToAddress(privkey).toString('hex');
if (address !== json.address) {
throw new Error('Invalid private key or address');
}
return privkey;
}
export const scryptSettings = {
n: 1024
};
export const kdf = 'scrypt';
export function pkeyToKeystore(
pkey: Buffer,
address: string,
password: string
): UtcKeystore {
const salt = randomBytes(32);
const iv = randomBytes(16);
let derivedKey;
const kdfparams: any = {
dklen: 32,
salt: salt.toString('hex')
};
if (kdf === 'scrypt') {
// FIXME: support progress reporting callback
kdfparams.n = 1024;
kdfparams.r = 8;
kdfparams.p = 1;
derivedKey = scrypt(
new Buffer(password),
salt,
kdfparams.n,
kdfparams.r,
kdfparams.p,
kdfparams.dklen
);
} else {
throw new Error('Unsupported kdf');
}
const cipher = createCipheriv('aes-128-ctr', derivedKey.slice(0, 16), iv);
if (!cipher) {
throw new Error('Unsupported cipher');
}
const ciphertext = Buffer.concat([cipher.update(pkey), cipher.final()]);
const mac = sha3(
Buffer.concat([
derivedKey.slice(16, 32),
new Buffer(ciphertext as any, 'hex')
])
);
return {
version: 3,
id: uuid.v4({
random: randomBytes(16) as any
}),
address,
Crypto: {
ciphertext: ciphertext.toString('hex'),
cipherparams: {
iv: iv.toString('hex')
},
cipher: 'aes-128-ctr',
kdf,
kdfparams,
mac: mac.toString('hex')
}
};
}
export function getV3Filename(address: string) {
const ts = new Date();
return ['UTC--', ts.toJSON().replace(/:/g, '-'), '--', address].join('');
}
export function decryptUtcKeystoreToPkey(
input: string,
password: string
): Buffer {
const kstore = JSON.parse(input.toLowerCase());
if (kstore.version !== 3) {
throw new Error('Not a V3 wallet');
}
let derivedKey;
let kdfparams;
if (kstore.crypto.kdf === 'scrypt') {
kdfparams = kstore.crypto.kdfparams;
derivedKey = scrypt(
new Buffer(password),
new Buffer(kdfparams.salt, 'hex'),
kdfparams.n,
kdfparams.r,
kdfparams.p,
kdfparams.dklen
);
} else if (kstore.crypto.kdf === 'pbkdf2') {
kdfparams = kstore.crypto.kdfparams;
if (kdfparams.prf !== 'hmac-sha256') {
throw new Error('Unsupported parameters to PBKDF2');
}
derivedKey = pbkdf2Sync(
new Buffer(password),
new Buffer(kdfparams.salt, 'hex'),
kdfparams.c,
kdfparams.dklen,
'sha256'
);
} else {
throw new Error('Unsupported key derivation scheme');
}
const ciphertext = new Buffer(kstore.crypto.ciphertext, 'hex');
const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
if (mac.toString('hex') !== kstore.crypto.mac) {
throw new Error('Key derivation failed - possibly wrong passphrase');
}
const decipher = createDecipheriv(
kstore.crypto.cipher,
derivedKey.slice(0, 16),
new Buffer(kstore.crypto.cipherparams.iv, 'hex')
);
let seed = decipherBuffer(decipher, ciphertext);
while (seed.length < 32) {
const nullBuff = new Buffer([0x00]);
seed = Buffer.concat([nullBuff, seed]);
}
return seed;
}

View File

@ -224,7 +224,7 @@ export async function formatTxInput(
if (unit === 'ether') { if (unit === 'ether') {
return { return {
to, to,
from: await wallet.getAddress(), from: await wallet.getAddressString(),
value: valueToHex(new Ether(value)), value: valueToHex(new Ether(value)),
data data
}; };
@ -236,7 +236,7 @@ export async function formatTxInput(
const ERC20Data = ERC20.transfer(to, bigAmount); const ERC20Data = ERC20.transfer(to, bigAmount);
return { return {
to: token.address, to: token.address,
from: await wallet.getAddress(), from: await wallet.getAddressString(),
value: '0x0', value: '0x0',
data: ERC20Data data: ERC20Data
}; };

View File

@ -1,7 +1,7 @@
import { RawTransaction } from 'libs/transaction'; import { RawTransaction } from 'libs/transaction';
export interface IWallet { export interface IWallet {
getAddress(): Promise<string>; getAddressString(): Promise<string> | string;
signRawTransaction(tx: RawTransaction): Promise<string>; signRawTransaction(tx: RawTransaction): Promise<string> | string;
signMessage(msg: string): Promise<string>; signMessage(msg: string): Promise<string> | string;
} }

View File

@ -1,4 +1,4 @@
export default class DeterministicWallet { export class DeterministicWallet {
private address: string; private address: string;
private dPath: string; private dPath: string;
private index: number; private index: number;
@ -9,7 +9,7 @@ export default class DeterministicWallet {
this.index = index; this.index = index;
} }
public getAddress(): Promise<string> { public getAddressString(): Promise<string> {
return Promise.resolve(this.address); return Promise.resolve(this.address);
} }

View File

@ -0,0 +1,3 @@
export * from './ledger';
export * from './mnemonic';
export * from './trezor';

View File

@ -2,12 +2,11 @@ import Ledger3 from 'vendor/ledger3';
import LedgerEth from 'vendor/ledger-eth'; import LedgerEth from 'vendor/ledger-eth';
import EthTx from 'ethereumjs-tx'; import EthTx from 'ethereumjs-tx';
import { addHexPrefix, rlp } from 'ethereumjs-util'; import { addHexPrefix, rlp } from 'ethereumjs-util';
import DeterministicWallet from './deterministic'; import { DeterministicWallet } from './deterministic';
import { IWallet } from './IWallet'; import { IWallet } from '../IWallet';
import { RawTransaction } from 'libs/transaction'; import { RawTransaction } from 'libs/transaction';
export default class LedgerWallet extends DeterministicWallet export class LedgerWallet extends DeterministicWallet implements IWallet {
implements IWallet {
private ledger: any; private ledger: any;
private ethApp: any; private ethApp: any;

View File

@ -0,0 +1,13 @@
import { decryptMnemonicToPrivKey } from 'libs/decrypt';
import { fromPrivateKey } from 'ethereumjs-wallet';
import { signWrapper } from 'libs/wallet';
export const MnemonicWallet = (
phrase: string,
pass: string,
path: string,
address: string
) =>
signWrapper(
fromPrivateKey(decryptMnemonicToPrivKey(phrase, pass, path, address))
);

View File

@ -4,10 +4,9 @@ import { addHexPrefix } from 'ethereumjs-util';
import { RawTransaction } from 'libs/transaction'; import { RawTransaction } from 'libs/transaction';
import { stripHexPrefixAndLower } from 'libs/values'; import { stripHexPrefixAndLower } from 'libs/values';
import TrezorConnect from 'vendor/trezor-connect'; import TrezorConnect from 'vendor/trezor-connect';
import DeterministicWallet from './deterministic'; import { DeterministicWallet } from './deterministic';
import { IWallet } from './IWallet'; import { IWallet } from '../IWallet';
export default class TrezorWallet extends DeterministicWallet export class TrezorWallet extends DeterministicWallet implements IWallet {
implements IWallet {
public signRawTransaction(tx: RawTransaction): Promise<string> { public signRawTransaction(tx: RawTransaction): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(TrezorConnect as any).ethereumSignTx( (TrezorConnect as any).ethereumSignTx(

View File

@ -1,8 +0,0 @@
import { decryptPrivKey } from 'libs/decrypt';
import PrivKeyWallet from './privkey';
export default class EncryptedPrivKeyWallet extends PrivKeyWallet {
constructor(encprivkey: string, password: string) {
super(decryptPrivKey(encprivkey, password));
}
}

View File

@ -1,8 +1,3 @@
export { IWallet } from './IWallet'; export { IWallet } from './IWallet';
export { default as PrivKeyWallet } from './privkey'; export * from './deterministic';
export { default as EncryptedPrivKeyWallet } from './encprivkey'; export * from './non-deterministic';
export { default as PresaleWallet } from './presale';
export { default as MewV1Wallet } from './mewv1';
export { default as UtcWallet } from './utc';
export { default as MnemonicWallet } from './mnemonic';
export { default as LedgerWallet } from './ledger';

View File

@ -1,8 +0,0 @@
import { decryptMewV1ToPrivKey } from 'libs/keystore';
import PrivKeyWallet from './privkey';
export default class MewV1Wallet extends PrivKeyWallet {
constructor(keystore: string, password: string) {
super(decryptMewV1ToPrivKey(keystore, password));
}
}

View File

@ -1,8 +0,0 @@
import { decryptMnemonicToPrivKey } from 'libs/decrypt';
import PrivKeyWallet from './privkey';
export default class MnemonicWallet extends PrivKeyWallet {
constructor(phrase: string, pass: string, path: string, address: string) {
super(decryptMnemonicToPrivKey(phrase, pass, path, address));
}
}

View File

@ -0,0 +1,92 @@
import { IFullWallet } from 'ethereumjs-wallet';
import { RawTransaction } from 'libs/transaction';
import { signMessageWithPrivKeyV2, signRawTxWithPrivKey } from 'libs/signing';
import {
EncryptedPrivateKeyWallet,
MewV1Wallet,
PresaleWallet,
PrivKeyWallet,
UtcWallet
} from './wallets';
enum KeystoreTypes {
presale = 'presale',
utc = 'v2-v3-utc',
v1Unencrypted = 'v1-unencrypted',
v1Encrypted = 'v1-encrypted',
v2Unencrypted = 'v2-unencrypted'
}
interface ISignWrapper {
signRawTransaction(rawTx: RawTransaction): string;
signMessage(msg: string): string;
unlock();
}
type WrappedWallet = IFullWallet & ISignWrapper;
export const signWrapper = (walletToWrap: IFullWallet): WrappedWallet =>
Object.assign(walletToWrap, {
signRawTransaction: (rawTx: RawTransaction) =>
signRawTxWithPrivKey(walletToWrap.getPrivateKey(), rawTx),
signMessage: (msg: string) =>
signMessageWithPrivKeyV2(walletToWrap.getPrivateKey(), msg),
unlock: () => Promise.resolve()
});
function determineKeystoreType(file: string): string {
const parsed = JSON.parse(file);
if (parsed.encseed) {
return KeystoreTypes.presale;
} else if (parsed.Crypto || parsed.crypto) {
return KeystoreTypes.utc;
} else if (parsed.hash && parsed.locked === true) {
return KeystoreTypes.v1Encrypted;
} else if (parsed.hash && parsed.locked === false) {
return KeystoreTypes.v1Unencrypted;
} else if (parsed.publisher === 'MyEtherWallet') {
return KeystoreTypes.v2Unencrypted;
} else {
throw new Error('Invalid keystore');
}
}
const isKeystorePassRequired = (file: string): boolean => {
const keystoreType = determineKeystoreType(file);
return (
keystoreType === KeystoreTypes.presale ||
keystoreType === KeystoreTypes.v1Encrypted ||
keystoreType === KeystoreTypes.utc
);
};
const getPrivKeyWallet = (key: string, password: string) =>
key.length === 64
? PrivKeyWallet(Buffer.from(key, 'hex'))
: EncryptedPrivateKeyWallet(key, password);
const getKeystoreWallet = (file: string, password: string) => {
const parsed = JSON.parse(file);
switch (determineKeystoreType(file)) {
case KeystoreTypes.presale:
return PresaleWallet(file, password);
case KeystoreTypes.v1Unencrypted:
return PrivKeyWallet(Buffer.from(parsed.private, 'hex'));
case KeystoreTypes.v1Encrypted:
return MewV1Wallet(file, password);
case KeystoreTypes.v2Unencrypted:
return PrivKeyWallet(Buffer.from(parsed.privKey, 'hex'));
case KeystoreTypes.utc:
return UtcWallet(file, password);
default:
throw Error('Unknown wallet');
}
};
export { isKeystorePassRequired, getPrivKeyWallet, getKeystoreWallet };

View File

@ -0,0 +1,2 @@
export * from './helpers';
export * from './wallets';

View File

@ -0,0 +1,28 @@
import { fromPrivateKey, fromEthSale, fromV3 } from 'ethereumjs-wallet';
import { fromEtherWallet } from 'ethereumjs-wallet/thirdparty';
import { signWrapper } from './helpers';
import { decryptPrivKey } from 'libs/decrypt';
const EncryptedPrivateKeyWallet = (
encryptedPrivateKey: string,
password: string
) => signWrapper(fromPrivateKey(decryptPrivKey(encryptedPrivateKey, password)));
const PresaleWallet = (keystore: string, password: string) =>
signWrapper(fromEthSale(keystore, password));
const MewV1Wallet = (keystore: string, password: string) =>
signWrapper(fromEtherWallet(keystore, password));
const PrivKeyWallet = (privkey: Buffer) => signWrapper(fromPrivateKey(privkey));
const UtcWallet = (keystore: string, password: string) =>
signWrapper(fromV3(keystore, password, true));
export {
EncryptedPrivateKeyWallet,
PresaleWallet,
MewV1Wallet,
PrivKeyWallet,
UtcWallet
};

View File

@ -1,8 +0,0 @@
import { decryptPresaleToPrivKey } from 'libs/keystore';
import PrivKeyWallet from './privkey';
export default class PresaleWallet extends PrivKeyWallet {
constructor(keystore: string, password: string) {
super(decryptPresaleToPrivKey(keystore, password));
}
}

View File

@ -1,74 +0,0 @@
import { randomBytes } from 'crypto';
import {
privateToPublic,
publicToAddress,
toChecksumAddress
} from 'ethereumjs-util';
import { pkeyToKeystore, UtcKeystore } from 'libs/keystore';
import { signMessageWithPrivKeyV2, signRawTxWithPrivKey } from 'libs/signing';
import { RawTransaction } from 'libs/transaction';
import { isValidPrivKey } from 'libs/validators';
import { stripHexPrefixAndLower } from 'libs/values';
import { IWallet } from './IWallet';
export default class PrivKeyWallet implements IWallet {
public static generate() {
return new PrivKeyWallet(randomBytes(32));
}
private privKey: Buffer;
private pubKey: Buffer;
private address: Buffer;
constructor(privkey: Buffer) {
if (!isValidPrivKey(privkey)) {
throw new Error('Invalid private key');
}
this.privKey = privkey;
this.pubKey = privateToPublic(this.privKey);
this.address = publicToAddress(this.pubKey);
}
public getAddress(): Promise<string> {
return Promise.resolve(
toChecksumAddress(`0x${this.address.toString('hex')}`)
);
}
public getPrivateKey() {
return this.privKey.toString('hex');
}
public getNakedAddress(): Promise<string> {
return new Promise(resolve => {
this.getAddress().then(address => {
resolve(stripHexPrefixAndLower(address));
});
});
}
public toKeystore(password: string): Promise<UtcKeystore> {
return new Promise(resolve => {
this.getNakedAddress().then(address => {
resolve(pkeyToKeystore(this.privKey, address, password));
});
});
}
public unlock(): Promise<any> {
return Promise.resolve();
}
public signRawTransaction(rawTx: RawTransaction): Promise<string> {
return new Promise((resolve, reject) => {
try {
resolve(signRawTxWithPrivKey(this.privKey, rawTx));
} catch (err) {
reject(err);
}
});
}
public signMessage = async (msg: string) =>
signMessageWithPrivKeyV2(this.privKey, msg);
}

View File

@ -1,8 +0,0 @@
import { decryptUtcKeystoreToPkey } from 'libs/keystore';
import PrivKeyWallet from './privkey';
export default class UtcWallet extends PrivKeyWallet {
constructor(keystore: string, password: string) {
super(decryptUtcKeystoreToPkey(keystore, password));
}
}

View File

@ -11,17 +11,13 @@ import {
UnlockPrivateKeyAction UnlockPrivateKeyAction
} from 'actions/wallet'; } from 'actions/wallet';
import TransactionSucceeded from 'components/ExtendedNotifications/TransactionSucceeded'; import TransactionSucceeded from 'components/ExtendedNotifications/TransactionSucceeded';
import { determineKeystoreType } from 'libs/keystore';
import { INode } from 'libs/nodes/INode'; import { INode } from 'libs/nodes/INode';
import { Wei } from 'libs/units'; import { Wei } from 'libs/units';
import { import {
EncryptedPrivKeyWallet,
IWallet, IWallet,
MewV1Wallet,
MnemonicWallet, MnemonicWallet,
PresaleWallet, getPrivKeyWallet,
PrivKeyWallet, getKeystoreWallet
UtcWallet
} from 'libs/wallet'; } from 'libs/wallet';
import React from 'react'; import React from 'react';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
@ -37,7 +33,7 @@ function* updateAccountBalance(): SagaIterator {
return; return;
} }
const node: INode = yield select(getNodeLib); const node: INode = yield select(getNodeLib);
const address = yield apply(wallet, wallet.getAddress); const address = yield apply(wallet, wallet.getAddressString);
// network request // network request
const balance: Wei = yield apply(node, node.getBalance, [address]); const balance: Wei = yield apply(node, node.getBalance, [address]);
yield put(setBalance(balance)); yield put(setBalance(balance));
@ -55,7 +51,7 @@ function* updateTokenBalances(): SagaIterator {
return; return;
} }
// FIXME handle errors // FIXME handle errors
const address = yield apply(wallet, wallet.getAddress); const address = yield apply(wallet, wallet.getAddressString);
// network request // network request
const tokenBalances = yield apply(node, node.getTokenBalances, [ const tokenBalances = yield apply(node, node.getTokenBalances, [
@ -86,16 +82,10 @@ export function* unlockPrivateKey(
action: UnlockPrivateKeyAction action: UnlockPrivateKeyAction
): SagaIterator { ): SagaIterator {
let wallet: IWallet | null = null; let wallet: IWallet | null = null;
const { key, password } = action.payload;
try { try {
if (action.payload.key.length === 64) { wallet = getPrivKeyWallet(key, password);
wallet = new PrivKeyWallet(Buffer.from(action.payload.key, 'hex'));
} else {
wallet = new EncryptedPrivKeyWallet(
action.payload.key,
action.payload.password
);
}
} catch (e) { } catch (e) {
yield put(showNotification('danger', translate('INVALID_PKEY'))); yield put(showNotification('danger', translate('INVALID_PKEY')));
return; return;
@ -104,33 +94,11 @@ export function* unlockPrivateKey(
} }
export function* unlockKeystore(action: UnlockKeystoreAction): SagaIterator { export function* unlockKeystore(action: UnlockKeystoreAction): SagaIterator {
const file = action.payload.file; const { file, password } = action.payload;
const pass = action.payload.password;
let wallet: null | IWallet = null; let wallet: null | IWallet = null;
try { try {
const parsed = JSON.parse(file); wallet = getKeystoreWallet(file, password);
switch (determineKeystoreType(file)) {
case 'presale':
wallet = new PresaleWallet(file, pass);
break;
case 'v1-unencrypted':
wallet = new PrivKeyWallet(Buffer.from(parsed.private, 'hex'));
break;
case 'v1-encrypted':
wallet = new MewV1Wallet(file, pass);
break;
case 'v2-unencrypted':
wallet = new PrivKeyWallet(Buffer.from(parsed.privKey, 'hex'));
break;
case 'v2-v3-utc':
wallet = new UtcWallet(file, pass);
break;
default:
yield put(showNotification('danger', translate('ERROR_6')));
return;
}
} catch (e) { } catch (e) {
yield put(showNotification('danger', translate('ERROR_6'))); yield put(showNotification('danger', translate('ERROR_6')));
return; return;
@ -145,7 +113,7 @@ function* unlockMnemonic(action: UnlockMnemonicAction): SagaIterator {
const { phrase, pass, path, address } = action.payload; const { phrase, pass, path, address } = action.payload;
try { try {
wallet = new MnemonicWallet(phrase, pass, path, address); wallet = MnemonicWallet(phrase, pass, path, address);
} catch (err) { } catch (err) {
// TODO: use better error than 'ERROR_14' (wallet not found) // TODO: use better error than 'ERROR_14' (wallet not found)
yield put(showNotification('danger', translate('ERROR_14'))); yield put(showNotification('danger', translate('ERROR_14')));

View File

@ -1,5 +1,6 @@
require('ethereumjs-abi'); require('ethereumjs-abi');
require('ethereumjs-util'); require('ethereumjs-util');
require('ethereumjs-wallet');
require('hdkey'); require('hdkey');
require('idna-uts46'); require('idna-uts46');
require('lodash'); require('lodash');
@ -14,9 +15,7 @@ require('redux-form');
require('redux-logger'); require('redux-logger');
require('redux-saga'); require('redux-saga');
require('wallet-address-validator'); require('wallet-address-validator');
require('scryptsy');
require('store2'); require('store2');
require('uuid');
require('whatwg-fetch'); require('whatwg-fetch');
require('moment'); require('moment');
require('prop-types'); require('prop-types');

View File

@ -1,66 +0,0 @@
import {
decryptMewV1ToPrivKey,
decryptUtcKeystoreToPkey
} from '../../common/libs/keystore';
const mewV1Keystore = {
address: '0x15bd5b09f42ddd49a266570f165d2732f3372e7d',
encrypted: true,
locked: true,
hash: '5927d16b10d5d1df8a678a6f7d4770f2ac4eafe71387126fff6c1b1e93876d7a',
private:
'U2FsdGVkX19us8qXfYyeQhxyzV7aFlXckG/KrRLajoCGBKO4/saefxGs/3PrCLWxZEbx2vn6V0VDWrkDUkL+8S4MK7FL9LCiIKxeCq/ciwX9YQepsRRetG2MExuUWkQ6365d',
public:
'U2FsdGVkX1/egEFLhHiGKzn08x+MovElanAzvwcvMEf7FUSAjDEKKt0Jc+Cnz3fsVlO0nNXDG7i4sP7gEyqdEj+vlwyMXv7ir9mwCwQ1+XWz7k5BFUg0Bw9xh2ygtnGDOBjF3TDm0YL+Gdtf9WS7rcOBD0tQWHJ7N5DIBUM5WKOa0bwdCqJgrTKX73XI5mjX/kR9VFnvv+nezVkSvb66nQ=='
};
const mewV1PrivKey =
'a56d4f23449a10ddcdd94bad56f895640097800406840aa8fe545d324d422c02';
const utcKeystore = {
version: 3,
id: 'cb788af4-993d-43ad-851b-0d2031e52c61',
address: '25a24679f35e447f778cf54a3823facf39904a63',
Crypto: {
ciphertext:
'4193915c560835d00b2b9ff5dd20f3e13793b2a3ca8a97df649286063f27f707',
cipherparams: {
iv: 'dccb8c009b11d1c6226ba19b557dce4c'
},
cipher: 'aes-128-ctr',
kdf: 'scrypt',
kdfparams: {
dklen: 32,
salt: '037a53e520f2d00fb70f02f39b31b77374de9e0e1d35fd7cbe9c8a8b21d6b0ab',
n: 1024,
r: 8,
p: 1
},
mac: '774fbe4bf35e7e28df15cd6c3546e74ce6608e9ab68a88d50227858a3b05769a'
}
};
const utcPrivKey =
'8bcb4456ef0356ce062c857cefdd3ed1bab45432cf76d6d5340899cfd0f702e8';
const password = 'testtesttest';
describe('decryptMewV1ToPrivKey', () => {
it('should derive the correct private key', () => {
const result = decryptMewV1ToPrivKey(
JSON.stringify(mewV1Keystore),
password
);
expect(result).toBeInstanceOf(Buffer);
expect(result.toString('hex')).toEqual(mewV1PrivKey);
});
});
describe('decryptUtcKeystoreToPkey', () => {
it('should derive the correct private key', () => {
const result = decryptUtcKeystoreToPkey(
JSON.stringify(utcKeystore),
password
);
expect(result).toBeInstanceOf(Buffer);
expect(result.toString('hex')).toEqual(utcPrivKey);
});
});

View File

@ -9,7 +9,7 @@ describe('wallet reducer', () => {
}); });
const walletInstance = { const walletInstance = {
getAddress: () => doSomething, getAddressString: () => doSomething,
signRawTransaction: () => doSomething, signRawTransaction: () => doSomething,
signMessage: () => doSomething signMessage: () => doSomething
}; };