Keystore & Private Key Wallet Decrypts (#116)

* wire up keystore decrypt & build UI

* add support for encrypted private keys

* add check for key length

* rename keystore wallet file

* rename encrypted priv key wallet file

* add support for presale, v1, & v2 JSON keystores

* clean up TODO messages, add class files

* add v3 references

* add flow type

* fix event bug

* update privkey validators to accept whole privkey

* refactor pkey/pass validation to function

* move pass req detection to function, remove unnecessary state

* add tests for decrypt & keystore libs
This commit is contained in:
skubakdj 2017-08-20 16:28:47 -04:00 committed by Daniel Ternyak
parent 11834299a2
commit f42837de68
17 changed files with 575 additions and 72 deletions

View File

@ -22,6 +22,26 @@ export function unlockPrivateKey(
};
}
/*** Unlock Keystore File ***/
export type KeystoreUnlockParams = {
file: string,
password: string
};
export type UnlockKeystoreAction = {
type: 'WALLET_UNLOCK_KEYSTORE',
payload: KeystoreUnlockParams
};
export function unlockKeystore(
value: KeystoreUnlockParams
): UnlockKeystoreAction {
return {
type: 'WALLET_UNLOCK_KEYSTORE',
payload: value
};
}
/*** Set Wallet ***/
export type SetWalletAction = {
type: 'WALLET_SET',

View File

@ -1,53 +1,51 @@
import React, { Component } from 'react';
import translate from 'translations';
import wallet from 'ethereumjs-wallet';
import ethUtil from 'ethereumjs-util';
import { isKeystorePassRequired } from 'libs/keystore';
export type KeystoreValue = {
file: string,
password: string,
valid: boolean
};
function isPassRequired(file: string): boolean {
let passReq = false;
try {
passReq = isKeystorePassRequired(file);
} catch (e) {
//TODO: communicate invalid file to user
}
return passReq;
}
export default class KeystoreDecrypt extends Component {
constructor(props) {
super(props);
}
handleFileSelection = event => {
const fileReader = new FileReader();
const inputFile = event.target.files[0];
fileReader.onload = () => {
try {
const keyStoreString = fileReader.result;
const decryptedWallet = wallet.fromV3(
keyStoreString,
'asdfasdfasdf',
true
);
const privateHex = ethUtil.bufferToHex(decryptedWallet._privKey);
const publicHex = ethUtil.bufferToHex(
ethUtil.privateToAddress(decryptedWallet._privKey)
);
console.log(privateHex, publicHex); // TODO: Remove console log, it's only here to let Travis pass
} catch (e) {
console.error('Could not parse Keystore file.', e);
}
};
fileReader.readAsText(inputFile, 'utf-8');
props: {
value: KeystoreValue,
onChange: (value: KeystoreValue) => void,
onUnlock: () => void
};
render() {
const { file, password } = this.props.value;
let passReq = isPassRequired(file);
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
className={'hidden'}
type="file"
id="fselector"
onChange={this.handleFileSelection}
/>
<label htmlFor="fselector">
<label htmlFor="fselector" style={{ width: '100%' }}>
<a
className="btn-file marg-v-sm"
className="btn btn-default btn-block"
id="aria1"
tabIndex="0"
role="button"
@ -55,9 +53,59 @@ export default class KeystoreDecrypt extends Component {
{translate('ADD_Radio_2_short')}
</a>
</label>
<div className={file.length && passReq ? '' : 'hidden'}>
<p>
{translate('ADD_Label_3')}
</p>
<input
className={`form-control ${password.length > 0
? 'is-valid'
: 'is-invalid'}`}
value={password}
onChange={this.onPasswordChange}
onKeyDown={this.onKeyDown}
placeholder={translate('x_Password')}
type="password"
/>
</div>
</div>
</div>
</section>
);
}
onKeyDown = (e: SyntheticKeyboardEvent) => {
if (e.keyCode === 13) {
e.preventDefault();
e.stopPropagation();
this.props.onUnlock();
}
};
onPasswordChange = (e: SyntheticInputEvent) => {
const valid = this.props.value.file.length && e.target.value.length;
this.props.onChange({
...this.props.value,
password: e.target.value,
valid
});
};
handleFileSelection = (e: SyntheticInputEvent) => {
const fileReader = new FileReader();
const inputFile = e.target.files[0];
fileReader.onload = () => {
const keystore = fileReader.result;
let passReq = isPassRequired(keystore);
this.props.onChange({
...this.props.value,
file: keystore,
valid: keystore.length && !passReq
});
};
fileReader.readAsText(inputFile, 'utf-8');
};
}

View File

@ -1,7 +1,7 @@
// @flow
import React, { Component } from 'react';
import translate from 'translations';
import { isValidPrivKey } from 'libs/validators';
import { isValidPrivKey, isValidEncryptedPrivKey } from 'libs/validators';
export type PrivateKeyValue = {
key: string,
@ -16,6 +16,35 @@ function fixPkey(key) {
return key;
}
type validated = {
fixedPkey: string,
isValidPkey: boolean,
isPassRequired: boolean,
valid: boolean
};
function validatePkeyAndPass(pkey: string, pass: string): validated {
const fixedPkey = fixPkey(pkey);
const validPkey = isValidPrivKey(fixedPkey);
const validEncPkey = isValidEncryptedPrivKey(fixedPkey);
const isValidPkey = validPkey || validEncPkey;
let isValidPass = false;
if (validPkey) {
isValidPass = true;
} else if (validEncPkey) {
isValidPass = pass.length > 0;
}
return {
fixedPkey,
isValidPkey,
isPassRequired: validEncPkey,
valid: isValidPkey && isValidPass
};
}
export default class PrivateKeyDecrypt extends Component {
props: {
value: PrivateKeyValue,
@ -25,9 +54,7 @@ export default class PrivateKeyDecrypt extends Component {
render() {
const { key, password } = this.props.value;
const fixedPkey = fixPkey(key);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
const { isValidPkey, isPassRequired } = validatePkeyAndPass(key, password);
return (
<section className="col-md-4 col-sm-6">
@ -38,7 +65,9 @@ export default class PrivateKeyDecrypt extends Component {
<div className="form-group">
<textarea
id="aria-private-key"
className={`form-control ${isValid ? 'is-valid' : 'is-invalid'}`}
className={`form-control ${isValidPkey
? 'is-valid'
: 'is-invalid'}`}
value={key}
onChange={this.onPkeyChange}
onKeyDown={this.onKeyDown}
@ -46,7 +75,7 @@ export default class PrivateKeyDecrypt extends Component {
rows="4"
/>
</div>
{isValid &&
{isValidPkey &&
isPassRequired &&
<div className="form-group">
<p>
@ -69,25 +98,21 @@ export default class PrivateKeyDecrypt extends Component {
}
onPkeyChange = (e: SyntheticInputEvent) => {
const fixedPkey = fixPkey(e.target.value);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
const valid =
isValid && (isPassRequired ? this.props.value.password.length > 0 : true);
const pkey = e.target.value;
const pass = this.props.value.password;
const { fixedPkey, valid } = validatePkeyAndPass(pkey, pass);
this.props.onChange({ ...this.props.value, key: e.target.value, valid });
this.props.onChange({ ...this.props.value, key: fixedPkey, valid });
};
onPasswordChange = (e: SyntheticInputEvent) => {
const fixedPkey = fixPkey(this.props.value.key);
const isValid = isValidPrivKey(fixedPkey.length);
const isPassRequired = fixedPkey.length > 64;
const valid =
isValid && (isPassRequired ? e.target.value.length > 0 : true);
const pkey = this.props.value.key;
const pass = e.target.value;
const { valid } = validatePkeyAndPass(pkey, pass);
this.props.onChange({
...this.props.value,
password: e.target.value,
password: pass,
valid
});
};

View File

@ -9,14 +9,18 @@ import LedgerNanoSDecrypt from './LedgerNano';
import TrezorDecrypt from './Trezor';
import ViewOnlyDecrypt from './ViewOnly';
import map from 'lodash/map';
import { unlockPrivateKey } from 'actions/wallet';
import { unlockPrivateKey, unlockKeystore } from 'actions/wallet';
import { connect } from 'react-redux';
const WALLETS = {
'keystore-file': {
lid: 'x_Keystore2',
component: KeystoreDecrypt,
initialParams: {}
initialParams: {
file: '',
password: ''
},
unlock: unlockKeystore
},
'private-key': {
lid: 'x_PrivKey2',

68
common/libs/decrypt.js Normal file
View File

@ -0,0 +1,68 @@
//@flow
import { createHash, createDecipheriv } from 'crypto';
//adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L230
export function decryptPrivKey(encprivkey: string, password: string): Buffer {
let cipher = encprivkey.slice(0, 128);
cipher = decodeCryptojsSalt(cipher);
let evp = evp_kdf(new Buffer(password), cipher.salt, {
keysize: 32,
ivsize: 16
});
let decipher = createDecipheriv('aes-256-cbc', evp.key, evp.iv);
let privKey = decipherBuffer(decipher, new Buffer(cipher.ciphertext));
return new Buffer(privKey.toString(), 'hex');
}
//adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L284
export function decodeCryptojsSalt(input: string): Object {
let ciphertext = new Buffer(input, 'base64');
if (ciphertext.slice(0, 8).toString() === 'Salted__') {
return {
salt: ciphertext.slice(8, 16),
ciphertext: ciphertext.slice(16)
};
} else {
return {
ciphertext: ciphertext
};
}
}
//adapted from https://github.com/kvhnuke/etherwallet/blob/de536ffebb4f2d1af892a32697e89d1a0d906b01/app/scripts/myetherwallet.js#L297
export function evp_kdf(data: Buffer, salt: Buffer, opts: Object) {
// A single EVP iteration, returns `D_i`, where block equlas to `D_(i-1)`
function iter(block) {
let hash = createHash(opts.digest || 'md5');
hash.update(block);
hash.update(data);
hash.update(salt);
block = hash.digest();
for (let i = 1; i < (opts.count || 1); i++) {
hash = createHash(opts.digest || 'md5');
hash.update(block);
block = hash.digest();
}
return block;
}
let keysize = opts.keysize || 16;
let ivsize = opts.ivsize || 16;
let ret = [];
let i = 0;
while (Buffer.concat(ret).length < keysize + ivsize) {
ret[i] = iter(i === 0 ? new Buffer(0) : ret[i - 1]);
i++;
}
let tmp = Buffer.concat(ret);
return {
key: tmp.slice(0, keysize),
iv: tmp.slice(keysize, keysize + ivsize)
};
}
export function decipherBuffer(decipher: Object, data: Buffer): Buffer {
return Buffer.concat([decipher.update(data), decipher.final()]);
}

View File

@ -5,10 +5,97 @@ import {
pbkdf2Sync,
createDecipheriv
} from 'crypto';
import { sha3 } from 'ethereumjs-util';
import { decipherBuffer, decodeCryptojsSalt, evp_kdf } from './decrypt';
import { sha3, privateToAddress } from 'ethereumjs-util';
import scrypt from 'scryptsy';
import uuid from 'uuid';
//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 {
let json = JSON.parse(file);
let encseed = new Buffer(json.encseed, 'hex');
let derivedKey = pbkdf2Sync(
new Buffer(password),
new Buffer(password),
2000,
32,
'sha256'
).slice(0, 16);
let decipher = createDecipheriv(
'aes-128-cbc',
derivedKey,
encseed.slice(0, 16)
);
let seed = decipherBuffer(decipher, encseed.slice(16));
let privkey = sha3(seed);
let 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 {
let json = JSON.parse(file);
let privkey, 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);
let evp = evp_kdf(new Buffer(password), cipher.salt, {
keysize: 32,
ivsize: 16
});
let 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
};
@ -75,7 +162,10 @@ export function getV3Filename(address: string) {
return ['UTC--', ts.toJSON().replace(/:/g, '-'), '--', address].join('');
}
export function fromV3KeystoreToPkey(input: string, password: string): Buffer {
export function decryptUtcKeystoreToPkey(
input: string,
password: string
): Buffer {
let kstore = JSON.parse(input.toLowerCase());
if (kstore.version !== 3) {
throw new Error('Not a V3 wallet');
@ -124,7 +214,3 @@ export function fromV3KeystoreToPkey(input: string, password: string): Buffer {
}
return seed;
}
function decipherBuffer(decipher, data) {
return Buffer.concat([decipher.update(data), decipher.final()]);
}

View File

@ -75,8 +75,22 @@ function validateEtherAddress(address: string): boolean {
else return isChecksumAddress(address);
}
export function isValidPrivKey(length: number): boolean {
return length === 64 || length === 128 || length === 132;
export function isValidPrivKey(privkey: string | Buffer): boolean {
if (typeof privkey === 'string') {
return privkey.length === 64;
} else if (privkey instanceof Buffer) {
return privkey.length === 32;
} else {
return false;
}
}
export function isValidEncryptedPrivKey(privkey: string): boolean {
if (typeof privkey === 'string') {
return privkey.length === 128 || privkey.length === 132;
} else {
return false;
}
}
export function isPositiveIntegerOrZero(number: number): boolean {

View File

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

View File

@ -2,3 +2,7 @@
export { default as BaseWallet } from './base';
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';

View File

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

View File

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

View File

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

View File

@ -8,6 +8,8 @@ import {
import { randomBytes } from 'crypto';
import { pkeyToKeystore } from 'libs/keystore';
import { signRawTxWithPrivKey, signMessageWithPrivKey } from 'libs/signing';
import { isValidPrivKey } from 'libs/validators';
import type { RawTransaction } from 'libs/transaction';
export default class PrivKeyWallet extends BaseWallet {
@ -15,6 +17,9 @@ export default class PrivKeyWallet extends BaseWallet {
pubKey: Buffer;
address: Buffer;
constructor(privkey: Buffer) {
if (!isValidPrivKey(privkey)) {
throw new Error('Invalid private key');
}
super();
this.privKey = privkey;
this.pubKey = privateToPublic(this.privKey);

View File

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

View File

@ -2,14 +2,26 @@
import { takeEvery, call, apply, put, select, fork } from 'redux-saga/effects';
import type { Effect } from 'redux-saga/effects';
import { setWallet, setBalance, setTokenBalances } from 'actions/wallet';
import type { UnlockPrivateKeyAction } from 'actions/wallet';
import type {
UnlockPrivateKeyAction,
UnlockKeystoreAction
} from 'actions/wallet';
import { showNotification } from 'actions/notifications';
import translate from 'translations';
import { PrivKeyWallet, BaseWallet } from 'libs/wallet';
import {
PresaleWallet,
MewV1Wallet,
UtcWallet,
EncryptedPrivKeyWallet,
PrivKeyWallet,
BaseWallet
} from 'libs/wallet';
import { BaseNode } from 'libs/nodes';
import { getNodeLib } from 'selectors/config';
import { getWalletInst, getTokens } from 'selectors/wallet';
import { determineKeystoreType } from 'libs/keystore';
function* updateAccountBalance() {
const node: BaseNode = yield select(getNodeLib);
const wallet: ?BaseWallet = yield select(getWalletInst);
@ -56,7 +68,14 @@ export function* unlockPrivateKey(
let wallet = null;
try {
wallet = new PrivKeyWallet(Buffer.from(action.payload.key, 'hex'));
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
);
}
} catch (e) {
yield put(showNotification('danger', translate('INVALID_PKEY')));
return;
@ -65,11 +84,55 @@ export function* unlockPrivateKey(
yield call(updateBalances);
}
export function* unlockKeystore(
action?: UnlockKeystoreAction
): Generator<Effect, void, any> {
if (!action) return;
const file = action.payload.file;
const pass = action.payload.password;
let wallet = 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;
}
} catch (e) {
yield put(showNotification('danger', translate('ERROR_6')));
return;
}
// TODO: provide a more descriptive error than the two 'ERROR_6' (invalid pass) messages above
yield put(setWallet(wallet));
yield call(updateBalances);
}
export default function* walletSaga(): Generator<Effect | Effect[], void, any> {
// useful for development
yield call(updateBalances);
yield [
takeEvery('WALLET_UNLOCK_PRIVATE_KEY', unlockPrivateKey),
takeEvery('WALLET_UNLOCK_KEYSTORE', unlockKeystore),
takeEvery('CUSTOM_TOKEN_ADD', updateTokenBalances)
];
}

73
spec/libs/decrypt.spec.js Normal file
View File

@ -0,0 +1,73 @@
import {
decryptPrivKey,
decodeCryptojsSalt,
evp_kdf,
decipherBuffer
} from '../../common/libs/decrypt';
//deconstructed elements of a V1 encrypted priv key
const encpkey =
'U2FsdGVkX19us8qXfYyeQhxyzV7aFlXckG/KrRLajoCGBKO4/saefxGs/3PrCLWxZEbx2vn6V0VDWrkDUkL+8S4MK7FL9LCiIKxeCq/ciwX9YQepsRRetG2MExuUWkQ6365d';
const pass = 'testtesttest';
const salt = 'brPKl32MnkI=';
const ciphertext =
'HHLNXtoWVdyQb8qtEtqOgIYEo7j+xp5/Eaz/c+sItbFkRvHa+fpXRUNauQNSQv7xLgwrsUv0sKIgrF4Kr9yLBf1hB6mxFF60bYwTG5RaRDrfrl0=';
const iv = 'k9YWF8ZBCoyuFS6CfGS+7w==';
const key = 'u9uhwRmBQDJ12MUBkIrO5EzMQZTYEf6hTBDzSJBKJ2k=';
const pkey = 'a56d4f23449a10ddcdd94bad56f895640097800406840aa8fe545d324d422c02';
describe('decryptPrivKey', () => {
it('should decrypt encrypted pkey string to pkey buffer', () => {
const decrypt = decryptPrivKey(encpkey, pass);
expect(decrypt).toBeInstanceOf(Buffer);
expect(decrypt.toString('hex')).toEqual(pkey);
expect(decrypt.length).toEqual(32);
});
});
describe('decodeCryptojsSalt', () => {
it('should derive correct salt and ciphertext from pkey string', () => {
const decode = decodeCryptojsSalt(encpkey);
expect(decode.salt).toBeInstanceOf(Buffer);
expect(decode.ciphertext).toBeInstanceOf(Buffer);
expect(decode.salt.toString('base64')).toEqual(salt);
expect(decode.ciphertext.toString('base64')).toEqual(ciphertext);
});
});
describe('evp_kdf', () => {
it('should derive correct key and iv', () => {
const result = evp_kdf(
new Buffer(pass, 'utf8'),
new Buffer(salt, 'base64'),
{ keysize: 32, ivsize: 16 }
);
expect(result.key).toBeInstanceOf(Buffer);
expect(result.iv).toBeInstanceOf(Buffer);
expect(result.key.toString('base64')).toEqual(key);
expect(result.iv.toString('base64')).toEqual(iv);
});
});
describe('decipherBuffer', () => {
const str = 'test string';
const data = new Buffer(str, 'utf8');
const decipher = {
update: jest.fn(d => d),
final: jest.fn(() => new Buffer('!', 'utf8'))
};
const result = decipherBuffer(decipher, data);
it('should call update and final on decipher', () => {
expect(decipher.update).toHaveBeenCalledWith(data);
expect(decipher.final).toHaveBeenCalled();
});
it('should return concat of update and final buffers', () => {
expect(result).toBeInstanceOf(Buffer);
expect(result.toString()).toEqual(str + '!');
});
});

View File

@ -0,0 +1,66 @@
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);
});
});