MyCrypto/common/libs/keystore.ts
HenryNguyen5 5d4b36d453 Migrate to Typescript (#224)
* Refactor babel/types

* Refactor entry point

* Refactor actions

* Refactor api

* Full project refactor -- Broad type fixing sweep

* - completely fix merge conflicts
- handle various type errors

* Add tslint to package.json

* Dependency cleanup

* Fix module resolution

* Work on type definitions for untyped libs

* progress commit

* Add more definition typing

* various type additions

* Add unit types

* Fix sagaiterator  + unit types

* various types added

* additional type additions

* Fix typing on Sagas

* remove flowfixmes; swap translate for translateRaw

* Get rid of contracts - awaiting Henry's contract PR

* Remove contracts from routing

* Fix most of actions/reducers

* refactor actions directory structure

* fix reducer action type imports

* Fix most of type errors pre-actions refactor

* fix action creator imports in containers

* Refactor more

* Refactor index of actions

* fix action imports; use module level index export

* package-lock.json updated

* Use action types in props

* Type up action creators

* Fix most of connect errors

* Typefixing progress

* More types

* Fix run-time errors

* Caching improvements for webpack

* Remove path resolve from webpack

* Update non-breaking packages to latest version

* Fix token typing

* Remove unused color code

* Fix wallet decrypt dispatch

* Set redux-form related props/functions to ANY, since we're stripping it out later on

* Revert BigNumber.js package changes

* Extend window to custom object for Perf

* Format Navigation

* Typecase keystore errors as any (since we shouldnt touch this)

* Push wallet context fix

* - find/replace value->payload in swap
- properly type swap state properties
- extract inline reducer into reducer function

* - type local storage retrieved items as generic

* - bind all RPCClient methods with fat arrow

* - reformat

* Change to enums for constants

* Change state into any

* Fix swap errors

* ensure that seconds are passed into state as integers

* Fix rest of errors

* use parseInt explicitly instead of type coercion

* Fix derivation-checker, remove flow command, add tslint command, add tslint-react, tell travis to use tslint instead of flow.

* Whoops, remove those tests.

* Remove unsupported (yet) config option.

* Fix precommit to target ts and tsx files.

* Fix some errors, ignore some silly rules.

* Revert jest to v19, use ts-jest and make all tests typescript. Fixes all but one.

* Get rid of saga tests

* Fix tslint errors
2017-09-24 19:06:28 -07:00

235 lines
6.2 KiB
TypeScript

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;
}