eth-lightwallet
Version:
A lightweight ethereum javascript wallet.
405 lines (343 loc) • 14.2 kB
JavaScript
const { expect } = require('chai');
const Promise = require('bluebird');
const KeyStore = require('../lib/keystore');
const Upgrade = require('../lib/upgrade');
const fixtures = require('./fixtures/keystore');
// Test with 100 private keys
const addrPrivKeyVector = require('./fixtures/addrprivkey100.json');
// Test with 10000 private keys - takes about 40 seconds to run
// const addrPrivKeyVector = require('./fixtures/addrprivkey10000.json')
const createVaultProm = Promise.promisify(KeyStore.createVault);
describe('Keystore', function () {
describe('createVault constructor', function () {
it('accepts a variety of options', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
expect(ks.encSeed).to.not.equal(undefined);
const decryptedPaddedSeed = KeyStore._decryptString(ks.encSeed, Uint8Array.from(fixtures.valid[0].pwDerivedKey));
// Check padding
expect(decryptedPaddedSeed.length).to.equal(120);
expect(decryptedPaddedSeed.trim()).to.equal(fixtures.valid[0].mnSeed);
done();
});
});
it('generates a random salt for key generation', function (done) {
this.timeout(10000);
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const salt0 = ks.salt;
expect(ks.salt).to.not.equal(undefined);
ks.keyFromPassword(fixture.password, function (err, derivedKey) {
const decryptedPaddedSeed = KeyStore._decryptString(ks.encSeed, derivedKey);
// Check padding
expect(decryptedPaddedSeed.length).to.equal(120);
expect(decryptedPaddedSeed.trim()).to.equal(fixtures.valid[0].mnSeed);
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
hdPathString: fixture.hdPathString
}, function (err, ks1) {
const salt1 = ks1.salt;
expect(salt0).to.not.equal(salt1);
done();
});
});
});
});
});
// Can't directly test the encrypt/decrypt functions
// since salt and iv is used.
describe('_encryptString _decryptString', function () {
fixtures.valid.forEach(function (f) {
it('encrypts the seed then returns same seed decrypted ' + '"' + f.mnSeed.substring(0, 25) + '..."', function (done) {
const encryptedString = KeyStore._encryptString(f.mnSeed, Uint8Array.from(f.pwDerivedKey));
const decryptedString = KeyStore._decryptString(encryptedString, Uint8Array.from(f.pwDerivedKey));
expect(decryptedString).to.equal(f.mnSeed);
done();
});
});
});
describe('_encryptKey _decryptKey', function () {
fixtures.valid.forEach(function (f) {
it('encrypts the key then returns same key decrypted ' + '"' + f.privKeyHex.substring(0, 15) + '..."', function (done) {
const encryptedKey = KeyStore._encryptKey(f.privKeyHex, Uint8Array.from(f.pwDerivedKey));
const decryptedKey = KeyStore._decryptKey(encryptedKey, Uint8Array.from(f.pwDerivedKey));
expect(decryptedKey).to.equal(f.privKeyHex);
done();
});
});
});
describe('deriveKeyFromPassword', function () {
it('derives a key correctly from the password', function (done) {
const derKeyProm = Promise.promisify(KeyStore.deriveKeyFromPasswordAndSalt);
const promArray = [];
fixtures.valid.forEach(function (f) {
promArray.push(derKeyProm(f.password, f.salt));
});
Promise.all(promArray).then(function (derived) {
for (let i = 0; i < derived.length; i++) {
expect(derived[i]).to.deep.equal(Uint8Array.from(fixtures.valid[i].pwDerivedKey));
}
done();
});
});
it('Checks if a derived key is correct or not', function (done) {
const derKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
const derKey1 = Uint8Array.from(fixtures.valid[1].pwDerivedKey);
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const isDerKeyCorrect = ks.isDerivedKeyCorrect(derKey);
expect(isDerKeyCorrect).to.equal(true);
const isDerKey1Correct = ks.isDerivedKeyCorrect(derKey1);
expect(isDerKey1Correct).to.equal(false);
done();
});
});
});
describe('_computeAddressFromPrivKey', function () {
fixtures.valid.forEach(function (f) {
it('generates valid address from private key ' + '"' + f.privKeyHex.substring(0, 15) + '..."', function (done) {
const address = '0x' + KeyStore._computeAddressFromPrivKey(f.privKeyHex);
expect(address).to.equal(f.address);
done();
});
});
addrPrivKeyVector.forEach(function (f) {
it('generates valid address from private key ' + '"' + f.key.substring(0, 15) + '..."', function (done) {
const address = KeyStore._computeAddressFromPrivKey(f.key);
expect(address).to.equal(f.addr);
done();
});
});
});
describe('serialize deserialize', function () {
it('serializes empty keystore and returns same non-empty keystore when deserialized ', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, origKS) {
const serKS = origKS.serialize();
const deserKS = KeyStore.deserialize(serKS);
// Retains all attributes properly
expect(deserKS).to.deep.equal(origKS);
done();
});
});
it('serializes non-empty keystore and returns same non-empty keystore when deserialized ', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, origKS) {
//Add Keys
origKS.generateNewAddress(Uint8Array.from(fixtures.valid[0].pwDerivedKey), 20);
const serKS = origKS.serialize();
const deserKS = KeyStore.deserialize(serKS);
// Retains all attributes properly
expect(deserKS).to.deep.equal(origKS);
done();
});
});
});
describe('generateNewAddress', function () {
const N = fixtures.valid.length;
it('returns a new address, next in hd wallet', function (done) {
this.timeout(10000);
const promArray = [];
fixtures.valid.forEach(function (fixture) {
promArray.push(createVaultProm({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}));
});
Promise.all(promArray).then(function (keystores) {
for (let i = 0; i < N; i++) {
const ks = keystores[i];
const numAddresses = fixtures.valid[i].hdIndex + 1;
ks.generateNewAddress(Uint8Array.from(fixtures.valid[i].pwDerivedKey), numAddresses);
const addresses = ks.getAddresses();
const addr = addresses[addresses.length - 1];
const priv = ks.exportPrivateKey(addr, Uint8Array.from(fixtures.valid[i].pwDerivedKey));
expect(addr).to.equal(fixtures.valid[i].address);
expect(priv).to.equal(fixtures.valid[i].privKeyHex);
}
done();
}).catch(done);
});
});
describe('getAddresses', function () {
it('returns the object\'s address attribute', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const add0x = function (addr) {
return ('0x' + addr);
};
const pwKey = Uint8Array.from(fixture.pwDerivedKey);
expect(ks.addresses.length).to.equal(0);
expect(ks.getAddresses()).to.deep.equal(ks.addresses);
ks.generateNewAddress(pwKey);
expect(ks.addresses.length).to.equal(1);
expect(ks.getAddresses()).to.deep.equal(ks.addresses.map(add0x));
ks.generateNewAddress(pwKey, 5);
expect(ks.addresses.length).to.equal(6);
expect(ks.getAddresses()).to.deep.equal(ks.addresses.map(add0x));
done();
});
});
});
describe('Seed functions', function () {
it('returns the unencrypted seed', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const pwKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
expect(ks.getSeed(pwKey)).to.equal(fixture.mnSeed);
done();
});
});
it('checks if seed is valid', function (done) {
let isValid = KeyStore.isSeedValid(fixtures.valid[0].mnSeed);
expect(isValid).to.equal(true);
isValid = KeyStore.isSeedValid(fixtures.invalid[0].mnSeed);
expect(isValid).to.equal(false);
done();
});
it('concatenates and hashes entropy sources', function (done) {
const N = fixtures.sha256Test.length;
for (let i = 0; i < N; i++) {
const ent0 = new Buffer(fixtures.sha256Test[i].ent0);
const ent1 = new Buffer(fixtures.sha256Test[i].ent1);
const outputString = KeyStore._concatAndSha256(ent0, ent1).toString('hex');
expect(outputString).to.equal(fixtures.sha256Test[i].targetHash);
}
done();
});
});
describe('exportPrivateKey', function () {
it('exports the private key corresponding to an address', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
ks.generateNewAddress(pw, 2);
const addr = ks.getAddresses();
const exportedPriv0 = ks.exportPrivateKey(addr[0], pw);
const exportedPriv1 = ks.exportPrivateKey(addr[1], pw);
const addrFromExported0 = '0x' + KeyStore._computeAddressFromPrivKey(exportedPriv0);
const addrFromExported1 = '0x' + KeyStore._computeAddressFromPrivKey(exportedPriv1);
expect(addrFromExported0).to.equal(addr[0]);
expect(addrFromExported1).to.equal(addr[1]);
done();
});
});
});
describe('hooked web3-provider', function () {
it('implements hasAddress() correctly', function (done) {
const fixture = fixtures.valid[0];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
const pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey);
ks.generateNewAddress(pw, 5);
const addr = ks.getAddresses();
for (let i = 0; i < addr.length; i++) {
ks.hasAddress(addr[i], function (err, hasAddr) {
expect(hasAddr).to.equal(true);
});
ks.hasAddress(addr[i], function (err, hasAddr) {
expect(hasAddr).to.equal(true);
});
}
ks.hasAddress('abcdef0123456', function (err, hasAddr) {
expect(hasAddr).to.equal(false);
});
ks.hasAddress('0xabcdef0123456', function (err, hasAddr) {
expect(hasAddr).to.equal(false);
});
done();
});
});
it('implements signTransaction correctly', function (done) {
const fixture = fixtures.valid[1];
KeyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
ks.keyFromPassword(fixture.password, function (err, pwDerivedKey) {
ks.generateNewAddress(pwDerivedKey, 1);
const addr = ks.getAddresses()[0];
// Trivial passwordProvider
ks.passwordProvider = function (callback) {
callback(null, fixture.password);
};
const txParams = fixture.web3TxParams;
ks.signTransaction(txParams, function (err, signedTx) {
expect(signedTx.slice(2)).to.equal(fixture.rawSignedTx);
done();
});
});
});
});
});
describe('upgrade old serialized keystore', function () {
it('upgrades a keystore older than version 2', function (done) {
this.timeout(10000);
const oldKS = require('./fixtures/lightwallet.json');
const oldSerialized = JSON.stringify(oldKS);
Upgrade.upgradeOldSerialized(oldSerialized, 'test', function (err, upgradedKeystore) {
const newKS = KeyStore.deserialize(upgradedKeystore);
expect(newKS.addresses).to.deep.equal(oldKS.addresses);
done();
});
});
it('upgrades a version 2 keystore', function (done) {
this.timeout(10000);
const oldKS = require('./fixtures/lightwalletv2.json');
const oldSerialized = JSON.stringify(oldKS);
Upgrade.upgradeOldSerialized(oldSerialized, 'PHveKjhQ&8dwWEdhu]q6', function (err, upgradedKeystore) {
const newKS = KeyStore.deserialize(upgradedKeystore);
expect(newKS.addresses).to.deep.equal(oldKS.ksData[newKS.hdPathString].addresses);
done();
});
});
});
});