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() {
const address = await this.props.wallet.getAddress();
const address = await this.props.wallet.getAddressString();
if (address !== this.state.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 translate, { translateRaw } from 'translations';
@ -32,9 +32,7 @@ export default class KeystoreDecrypt extends Component {
return (
<section className="col-md-4 col-sm-6">
<div id="selectedUploadKey">
<h4>
{translate('ADD_Radio_2_alt')}
</h4>
<h4>{translate('ADD_Radio_2_alt')}</h4>
<div className="form-group">
<input
@ -54,9 +52,7 @@ export default class KeystoreDecrypt extends Component {
</a>
</label>
<div className={file.length && passReq ? '' : 'hidden'}>
<p>
{translate('ADD_Label_3')}
</p>
<p>{translate('ADD_Label_3')}</p>
<input
className={`form-control ${password.length > 0
? 'is-valid'

View File

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

View File

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

View File

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

View File

@ -151,8 +151,8 @@ class ConfirmationModal extends React.Component<Props, State> {
</li>
<li className="ConfModal-details-detail">
You are interacting with the{' '}
<strong>{node.network}</strong>{' '}
network provided by <strong>{node.service}</strong>
<strong>{node.network}</strong> network provided by{' '}
<strong>{node.service}</strong>
</li>
{!token && (
<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 unlocked = !!wallet;
if (unlocked) {
const from = await wallet.getAddress();
const from = await wallet.getAddressString();
if (forceOffline && !offline && !hasSetDefaultNonce) {
const nonceHex = await nodeLib.getTransactionCount(from);
const newNonce = parseInt(stripHexPrefix(nonceHex), 10);
@ -222,7 +222,7 @@ export class SendTransaction extends React.Component<Props, State> {
public async setWalletAddressOnUpdate() {
if (this.props.wallet) {
const walletAddress = await this.props.wallet.getAddress();
const walletAddress = await this.props.wallet.getAddressString();
if (walletAddress !== this.state.walletAddress) {
this.setState({ walletAddress });
}

View File

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

View File

@ -1,5 +1,5 @@
import assert from 'assert';
import PrivKeyWallet from './libs/wallet/privkey';
import { generate, IFullWallet } from 'ethereumjs-wallet';
const { exec } = require('child_process');
const ProgressBar = require('progress');
@ -17,8 +17,8 @@ function promiseFromChildProcess(command): Promise<any> {
});
}
async function privToAddrViaDocker(privKeyWallet) {
const command = `docker run -e key=${privKeyWallet.getPrivateKey()} ${dockerImage}:${dockerTag}`;
async function privToAddrViaDocker(privKeyWallet: IFullWallet) {
const command = `docker run -e key=${privKeyWallet.getPrivateKeyString()} ${dockerImage}:${dockerTag}`;
const dockerOutput = await promiseFromChildProcess(command);
const newlineStrippedDockerOutput = dockerOutput.replace(
/(\r\n|\n|\r)/gm,
@ -28,8 +28,8 @@ async function privToAddrViaDocker(privKeyWallet) {
}
async function testDerivation() {
const privKeyWallet = PrivKeyWallet.generate();
const privKeyWalletAddress = await privKeyWallet.getAddress();
const privKeyWallet = generate();
const privKeyWalletAddress = await privKeyWallet.getAddressString();
const dockerAddr = await privToAddrViaDocker(privKeyWallet);
// strip the checksum
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') {
return {
to,
from: await wallet.getAddress(),
from: await wallet.getAddressString(),
value: valueToHex(new Ether(value)),
data
};
@ -236,7 +236,7 @@ export async function formatTxInput(
const ERC20Data = ERC20.transfer(to, bigAmount);
return {
to: token.address,
from: await wallet.getAddress(),
from: await wallet.getAddressString(),
value: '0x0',
data: ERC20Data
};

View File

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

View File

@ -1,4 +1,4 @@
export default class DeterministicWallet {
export class DeterministicWallet {
private address: string;
private dPath: string;
private index: number;
@ -9,7 +9,7 @@ export default class DeterministicWallet {
this.index = index;
}
public getAddress(): Promise<string> {
public getAddressString(): Promise<string> {
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 EthTx from 'ethereumjs-tx';
import { addHexPrefix, rlp } from 'ethereumjs-util';
import DeterministicWallet from './deterministic';
import { IWallet } from './IWallet';
import { DeterministicWallet } from './deterministic';
import { IWallet } from '../IWallet';
import { RawTransaction } from 'libs/transaction';
export default class LedgerWallet extends DeterministicWallet
implements IWallet {
export class LedgerWallet extends DeterministicWallet implements IWallet {
private ledger: 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 { stripHexPrefixAndLower } from 'libs/values';
import TrezorConnect from 'vendor/trezor-connect';
import DeterministicWallet from './deterministic';
import { IWallet } from './IWallet';
export default class TrezorWallet extends DeterministicWallet
implements IWallet {
import { DeterministicWallet } from './deterministic';
import { IWallet } from '../IWallet';
export class TrezorWallet extends DeterministicWallet implements IWallet {
public signRawTransaction(tx: RawTransaction): Promise<string> {
return new Promise((resolve, reject) => {
(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 { default as PrivKeyWallet } from './privkey';
export { default as EncryptedPrivKeyWallet } from './encprivkey';
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';
export * from './deterministic';
export * from './non-deterministic';

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

View File

@ -1,5 +1,6 @@
require('ethereumjs-abi');
require('ethereumjs-util');
require('ethereumjs-wallet');
require('hdkey');
require('idna-uts46');
require('lodash');
@ -14,9 +15,7 @@ require('redux-form');
require('redux-logger');
require('redux-saga');
require('wallet-address-validator');
require('scryptsy');
require('store2');
require('uuid');
require('whatwg-fetch');
require('moment');
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 = {
getAddress: () => doSomething,
getAddressString: () => doSomething,
signRawTransaction: () => doSomething,
signMessage: () => doSomething
};