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:
parent
5766735a5a
commit
a00269507c
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export * from './ledger';
|
||||
export * from './mnemonic';
|
||||
export * from './trezor';
|
|
@ -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;
|
||||
|
|
@ -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))
|
||||
);
|
|
@ -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(
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 };
|
|
@ -0,0 +1,2 @@
|
|||
export * from './helpers';
|
||||
export * from './wallets';
|
|
@ -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
|
||||
};
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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')));
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -9,7 +9,7 @@ describe('wallet reducer', () => {
|
|||
});
|
||||
|
||||
const walletInstance = {
|
||||
getAddress: () => doSomething,
|
||||
getAddressString: () => doSomething,
|
||||
signRawTransaction: () => doSomething,
|
||||
signMessage: () => doSomething
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue