happyucjs-lightwallet
Version:
A lightweight happyuc javascript wallet.
435 lines (346 loc) • 14.2 kB
JavaScript
var expect = require('chai').expect
var keyStore = require('../lib/keystore')
var upgrade = require('../lib/upgrade')
var fixtures = require('./fixtures/keystore')
var Promise = require('bluebird')
var defaultHdPathString = "m/0'/0'/0'";
// Test with 100 private keys
var addrprivkeyvector = require('./fixtures/addrprivkey100.json')
// Test with 10000 private keys - takes about 40 seconds to run
// var addrprivkeyvector = require('./fixtures/addrprivkey10000.json')
var Transaction = require('happyucjs-tx');
var createVaultProm = Promise.promisify(keyStore.createVault)
describe("Keystore", function() {
describe("createVault constructor", function() {
it('accepts a variety of options', function(done) {
var 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);
var 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);
var fixture = fixtures.valid[0];
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
hdPathString: fixture.hdPathString
}, function(err, ks) {
var salt0 = ks.salt;
expect(ks.salt).to.not.equal(undefined);
ks.keyFromPassword(fixture.password, function(err, derivedKey) {
var 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) {
var 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) {
var encryptedString = keyStore._encryptString(f.mnSeed, Uint8Array.from(f.pwDerivedKey))
var 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) {
var encryptedKey = keyStore._encryptKey(f.privKeyHex, Uint8Array.from(f.pwDerivedKey))
var 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) {
var derKeyProm = Promise.promisify(keyStore.deriveKeyFromPasswordAndSalt);
var promArray = [];
fixtures.valid.forEach(function (f) {
promArray.push(derKeyProm(f.password, f.salt));
})
Promise.all(promArray).then(function(derived) {
for(var 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) {
var derKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey)
var derKey1 = Uint8Array.from(fixtures.valid[1].pwDerivedKey)
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
var isDerKeyCorrect = ks.isDerivedKeyCorrect(derKey);
expect(isDerKeyCorrect).to.equal(true);
var 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) {
var 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) {
var 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) {
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, origKS) {
var serKS = origKS.serialize()
var 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) {
var 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)
var serKS = origKS.serialize()
var deserKS = keyStore.deserialize(serKS)
// Retains all attributes properly
expect(deserKS).to.deep.equal(origKS)
done();
})
})
});
describe("generateNewAddress", function() {
var N = fixtures.valid.length;
it("returns a new address, next in hd wallet", function(done) {
this.timeout(10000);
var 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 (var i=0; i<N; i++) {
var ks = keystores[i]
var numAddresses = fixtures.valid[i].hdIndex+1;
ks.generateNewAddress(Uint8Array.from(fixtures.valid[i].pwDerivedKey), numAddresses);
var addresses = ks.getAddresses();
var addr = addresses[addresses.length-1];
var 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) {
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
var add0x = function (addr) {return ('0x' + addr)}
var 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) {
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
var pwKey = Uint8Array.from(fixtures.valid[0].pwDerivedKey)
expect(ks.getSeed(pwKey)).to.equal(fixture.mnSeed)
done();
})
});
it('checks if seed is valid', function(done) {
var 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) {
var N = fixtures.sha256Test.length;
for (var i=0; i<N; i++) {
var ent0 = new Buffer(fixtures.sha256Test[i].ent0);
var ent1 = new Buffer(fixtures.sha256Test[i].ent1);
var 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) {
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
var pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey)
ks.generateNewAddress(pw, 2)
var addr = ks.getAddresses();
var exportedPriv0 = ks.exportPrivateKey(addr[0], pw)
var exportedPriv1 = ks.exportPrivateKey(addr[1], pw)
var addrFromExported0 = '0x' + keyStore._computeAddressFromPrivKey(exportedPriv0)
var addrFromExported1 = '0x' + keyStore._computeAddressFromPrivKey(exportedPriv1)
expect(addrFromExported0).to.equal(addr[0])
expect(addrFromExported1).to.equal(addr[1])
done();
})
});
});
describe("hooked webu-provider", function() {
it('implements hasAddress() correctly', function(done) {
var fixture = fixtures.valid[0]
keyStore.createVault({
password: fixture.password,
seedPhrase: fixture.mnSeed,
salt: fixture.salt,
hdPathString: fixture.hdPathString
}, function (err, ks) {
var pw = Uint8Array.from(fixtures.valid[0].pwDerivedKey)
ks.generateNewAddress(pw, 5)
var addr = ks.getAddresses();
for (var 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) {
var 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)
var addr = ks.getAddresses()[0]
// console.log(addr);
// Trivial passwordProvider
ks.passwordProvider = function(callback) {callback(null, fixture.password)}
var txParams = fixture.webuTxParams
// console.log('txParams',txParams);
ks.signTransaction(txParams, function (err, signedTx) {
// console.log('err' ,err);
// console.log('signedTx',signedTx.slice(2));
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);
var oldKS = require('./fixtures/lightwallet.json')
var oldSerialized = JSON.stringify(oldKS);
upgrade(oldSerialized, 'test', function(err, upgradedKeystore) {
var newKS = keyStore.deserialize(upgradedKeystore);
expect(newKS.addresses).to.deep.equal(oldKS.addresses);
done();
})
})
it('upgrades a version 2 keystore', function (done) {
this.timeout(10000);
var oldKS = require('./fixtures/lightwalletv2.json')
var oldSerialized = JSON.stringify(oldKS);
// console.log(upgrade)
upgrade(oldSerialized, 'PHveKjhQ&8dwWEdhu]q6', function(err, upgradedKeystore) {
var newKS = keyStore.deserialize(upgradedKeystore);
expect(newKS.addresses).to.deep.equal(oldKS.ksData[newKS.hdPathString].addresses);
done();
})
})
})
});