bitcore-wallet-client
Version:
Client for bitcore-wallet-service
596 lines (537 loc) • 22.2 kB
JavaScript
'use strict';
const should = require('chai').should();
const { Key } = require('../ts_build/lib/key');
describe('Key', function() {
describe('#create', function() {
it('Should create', function() {
const key = new Key();
const c = key.get();
should.exist(c.xPrivKey, 'xpriv');
should.exist(c.mnemonic);
});
it('Should create random keys', function() {
var all = {};
for (var i = 0; i < 10; i++) {
const key = new Key();
const c = key.get();
var exist = all[c.xPrivKey];
should.not.exist(exist);
all[c.xPrivKey] = 1;
}
});
it('Should create keys from mnemonic (no passphrase) ', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
key.fingerPrint.should.equal('73c5da0a');
const c = key.get();
c.xPrivKey.should.equal(
'xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'
);
});
it('Should create keys from mnemonic (with passphrase) ', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
passphrase: 'pepe'
});
const c = key.get();
key.fingerPrint.should.equal('8b64040c');
c.xPrivKey.should.equal(
'xprv9s21ZrQH143K4C14pRZ5fTForcjAuRXLHs7Td28XuG2JMEC17Xm6JMGpNMRdNgfKZnyT3nmfeH8yVzxp6jnhmpVQAEmNBxLBh6t6t5UTVxo'
);
});
it('Should return priv key', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
key
.get()
.xPrivKey.should.be.equal(
'xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'
);
});
it('Should return mnemonic', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
key
.get()
.mnemonic.should.be.equal(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
);
});
});
describe('#checkPassword', function() {
it('Should return null', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
should.not.exist(key.checkPassword('xx'));
});
it('Should return true/false', function() {
const key = new Key({
seedType: 'mnemonic',
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
});
key.encrypt('xx');
key.checkPassword('xx').should.equal(true);
key.checkPassword('yy').should.equal(false);
});
});
describe('#match', function() {
it('Should match', function() {
const c = new Key();
Key.match(c, c).should.equal(true);
});
it('Should match (after import)', function() {
const c = new Key();
const c2 = new Key({ seedType: 'object', seedData: c.toObj() });
Key.match(c, c2).should.equal(true);
});
it("Shouldn't match", function() {
var c = new Key();
var c2 = new Key();
Key.match(c, c2).should.equal(false);
});
});
describe('Encryption', function() {
describe('#encrypt', function() {
it('should create encrypted private key and remove cleartext', function() {
var c = new Key({ seedType: 'new', password: 'password' });
c.isPrivKeyEncrypted().should.be.true;
c = c.toObj();
should.exist(c.xPrivKeyEncrypted);
should.exist(c.mnemonicEncrypted);
should.not.exist(c.xPrivKey);
should.not.exist(c.mnemonic);
});
it('should create encrypted private key and get', function() {
var c = new Key({ seedType: 'new', password: 'password' });
c.isPrivKeyEncrypted().should.be.true;
const keys2 = c.get('password');
should.exist(keys2);
should.exist(keys2.mnemonic);
should.exist(keys2.xPrivKey);
c = c.toObj();
should.not.exist(c.xPrivKey);
should.not.exist(c.mnemonic);
should.exist(c.xPrivKeyEncrypted);
should.exist(c.mnemonicEncrypted);
});
it('should encrypt private key and remove cleartext (2 steps)', function() {
var c = new Key({ seedType: 'new' });
c.encrypt('password');
c.isPrivKeyEncrypted().should.be.true;
c = c.toObj();
should.exist(c.xPrivKeyEncrypted);
should.exist(c.mnemonicEncrypted);
should.not.exist(c.xPrivKey);
should.not.exist(c.mnemonic);
});
it('should fail to encrypt private key if already encrypted', function() {
var c = new Key({ seedType: 'new' });
c.encrypt('password');
var err;
try {
c.encrypt('password');
} catch (ex) {
err = ex;
}
should.exist(err);
});
});
describe('#decryptPrivateKey', function() {
it('should decrypt private key', function() {
var c = new Key({ seedType: 'new' });
c.encrypt('password');
c.isPrivKeyEncrypted().should.be.true;
c.decrypt('password');
c.isPrivKeyEncrypted().should.be.false;
c = c.toObj();
should.exist(c.xPrivKey);
should.exist(c.mnemonic);
should.not.exist(c.xPrivKeyEncrypted);
should.not.exist(c.mnemonicEncrypted);
});
it('should fail to decrypt private key with wrong password', function() {
var c = new Key();
c.encrypt('password');
var err;
try {
c.decrypt('wrong');
} catch (ex) {
// ex.toString().should.match(/Could not decrypt/); TODO
err = ex;
}
should.exist(err);
c.isPrivKeyEncrypted().should.be.true;
c = c.toObj();
should.exist(c.mnemonicEncrypted);
should.not.exist(c.mnemonic);
});
it('should fail to decrypt private key when not encrypted', function() {
var c = new Key();
var err;
try {
c.decrypt('password');
} catch (ex) {
ex.toString().should.match(/not encrypted/);
err = ex;
}
should.exist(err);
c.isPrivKeyEncrypted().should.be.false;
});
});
describe('#getKeys', function() {
it('should get keys regardless of encryption', function() {
var c = new Key();
var keys = c.get();
should.exist(keys.xPrivKey);
should.exist(keys.mnemonic);
let o = c.toObj();
keys.xPrivKey.should.equal(o.xPrivKey);
keys.mnemonic.should.equal(o.mnemonic);
c.encrypt('password');
c.isPrivKeyEncrypted().should.be.true;
var keys2 = c.get('password');
should.exist(keys2);
keys2.should.deep.equal(keys);
c.decrypt('password');
c.isPrivKeyEncrypted().should.be.false;
var keys3 = c.get();
should.exist(keys3);
keys3.should.deep.equal(keys);
});
it('should get derived keys regardless of encryption', function() {
var c = new Key();
var xPrivKey = c.derive(null, 'm/44');
should.exist(xPrivKey);
c.encrypt('password');
c.isPrivKeyEncrypted().should.be.true;
var xPrivKey2 = c.derive('password', 'm/44');
should.exist(xPrivKey2);
xPrivKey2.toString('hex').should.equal(xPrivKey.toString('hex'));
c.decrypt('password');
c.isPrivKeyEncrypted().should.be.false;
var xPrivKey3 = c.derive(null, 'm/44');
should.exist(xPrivKey3);
xPrivKey3.toString('hex').should.equal(xPrivKey.toString('hex'));
});
});
});
describe('#fromExtendedPrivateKey', function() {
it('Should create credentials from seed', function() {
var xPriv =
'xprv9s21ZrQH143K2TjT3rF4m5AJcMvCetfQbVjFEx1Rped8qzcMJwbqxv21k3ftL69z7n3gqvvHthkdzbW14gxEFDYQdrRQMub3XdkJyt3GGGc';
var k = new Key({ seedType:'extendedPrivateKey', seedData: xPriv});
var c = k.createCredentials(null, {
coin: 'btc',
network: 'livenet',
account: 0,
n: 1
});
k = k.toObj();
k.xPrivKey.should.equal(
'xprv9s21ZrQH143K2TjT3rF4m5AJcMvCetfQbVjFEx1Rped8qzcMJwbqxv21k3ftL69z7n3gqvvHthkdzbW14gxEFDYQdrRQMub3XdkJyt3GGGc'
);
c.xPubKey.should.equal(
'xpub6DUean44k773kxbUq8QpSmAPFaNCpk5AzrxbFRAMsNCZBGD15XQVnRJCgNd8GtJVmDyDZh89NPZz1XPQeX5w6bAdLGfSTUuPDEQwBgKxfh1'
);
c.copayerId.should.equal('bad66ef88ad8dec08e36d576c29b4f091d30197f04e166871e64bf969d08a958');
c.network.should.equal('livenet');
c.personalEncryptingKey.should.equal('M4MTmfRZaTtX6izAAxTpJg==');
should.not.exist(c.walletPrivKey);
});
it('Should create credentials from seed and walletPrivateKey', function() {
var xPriv =
'xprv9s21ZrQH143K2TjT3rF4m5AJcMvCetfQbVjFEx1Rped8qzcMJwbqxv21k3ftL69z7n3gqvvHthkdzbW14gxEFDYQdrRQMub3XdkJyt3GGGc';
var wKey = 'a28840e18650b1de8cb83bcd2213672a728be38a63e70680b0c2be9c452e2d4d';
var k = new Key({ seedType: 'extendedPrivateKey', seedData:xPriv });
var c = k.createCredentials(null, {
coin: 'btc',
network: 'livenet',
account: 0,
n: 1,
walletPrivKey: wKey
});
k = k.toObj();
k.xPrivKey.should.equal(
'xprv9s21ZrQH143K2TjT3rF4m5AJcMvCetfQbVjFEx1Rped8qzcMJwbqxv21k3ftL69z7n3gqvvHthkdzbW14gxEFDYQdrRQMub3XdkJyt3GGGc'
);
c.walletPrivKey.should.equal(wKey);
});
describe('Compliant derivation', function() {
it('Should create compliant base address derivation key', function() {
var xPriv =
'xprv9s21ZrQH143K4HHBKb6APEoa5i58fxeFWP1x5AGMfr6zXB3A6Hjt7f9LrPXp9P7CiTCA3Hk66cS4g8enUHWpYHpNhtufxSrSpcbaQyVX163';
var k = new Key({ seedType: 'extendedPrivateKey', seedData:xPriv });
var c = k.createCredentials(null, {
coin: 'btc',
network: 'livenet',
account: 0,
n: 1
});
c.xPubKey.should.equal(
'xpub6CUtFEwZKBEyX6xF4ECdJdfRBBo69ufVgmRpy7oqzWJBSadSZ3vaqvCPNFsarga4UWcgTuoDQL7ZnpgWkUVUAX3oc7ej8qfLEuhMALGvFwX'
);
});
it('Should create compliant request key', function() {
var xPriv =
'xprv9s21ZrQH143K3xMCR1BNaUrTuh1XJnsj8KjEL5VpQty3NY8ufgbR8SjZS8B4offHq6Jj5WhgFpM2dcYxeqLLCuj1wgMnSfmZuPUtGk8rWT7';
var k = new Key({ seedType: 'extendedPrivateKey', seedData:xPriv });
var c = k.createCredentials(null, {
coin: 'btc',
network: 'livenet',
account: 0,
n: 1
});
c.requestPrivKey.should.equal('559371263eb0b2fd9cd2aa773ca5fea69ed1f9d9bdb8a094db321f02e9d53cec');
});
it('should accept non-compliant derivation as a parameter when importing', function() {
var xPriv =
'tprv8ZgxMBicQKsPd8U9aBBJ5J2v8XMwKwZvf8qcu2gLK5FRrsrPeSgkEcNHqKx4zwv6cP536m68q2UD7wVM24zdSCpaJRmpowaeJTeVMXL5v5k';
var k = new Key({ seedType: 'extendedPrivateKey', seedData:xPriv, nonCompliantDerivation: true });
var c = k.createCredentials(null, {
coin: 'btc',
network: 'testnet',
account: 0,
n: 1
});
k = k.toObj();
k.xPrivKey.should.equal(
'tprv8ZgxMBicQKsPd8U9aBBJ5J2v8XMwKwZvf8qcu2gLK5FRrsrPeSgkEcNHqKx4zwv6cP536m68q2UD7wVM24zdSCpaJRmpowaeJTeVMXL5v5k'
);
k.compliantDerivation.should.be.false;
c.xPubKey.should.equal(
'tpubDD919WKKqmh2CqKnSsfUAJWB9bnLbcry6r61tBuY8YEaTBBpvXSpwdXXBGAB1n4JRFDC7ebo7if3psUAMpvQJUBe3LcjuMNA6Y4nP8U9SNg'
);
});
});
});
describe('#derive', function() {
it('should derive extended private key from master livenet', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var xpk = c.derive(null, "m/44'/0'/0'").toString();
xpk.should.equal(
'xprv9xud2WztGSSBPDPDL9RQ3rG3vucRA4BmEnfAdP76bTqtkGCK8VzWjevLw9LsdqwH1PEWiwcjymf1T2FLp12XjwjuCRvcSBJvxDgv1BDTbWY'
);
});
it('should derive extended private key from master BIP48 livenet', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var xpk = c.derive(null, "m/48'/0'/0'").toString();
xpk.should.equal(
'xprv9yaGCLKPS2ovEGw987MZr4DCkfZHGh518ndVk3Jb6eiUdPwCQu7nYru59WoNkTEQvmhnv5sPbYxeuee5k8QASWRnGV2iFX4RmKXEQse8KnQ'
);
});
it('should derive compliant child', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'tprv8ZgxMBicQKsPd8U9aBBJ5J2v8XMwKwZvf8qcu2gLK5FRrsrPeSgkEcNHqKx4zwv6cP536m68q2UD7wVM24zdSCpaJRmpowaeJTeVMXL5v5k'});
c.compliantDerivation.should.be.true;
var xpk = c.derive(null, "m/44'/1'/0'").toString();
xpk.should.equal(
'tprv8gXvQvjGt7oYCTRD3d4oeQr9B7JLuC2B6S854F4XWCQ4pr9NcjokH9kouWMAp1MJKy4Y8QLBgbmPtk3i7RegVzaWhWsnVPi4ZmykJXt4HeV'
);
});
it('should derive non-compliant child', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'tprv8ZgxMBicQKsPd8U9aBBJ5J2v8XMwKwZvf8qcu2gLK5FRrsrPeSgkEcNHqKx4zwv6cP536m68q2UD7wVM24zdSCpaJRmpowaeJTeVMXL5v5k', nonCompliantDerivation: true});
c.compliantDerivation.should.be.false;
var xpk = c.derive(null, "m/44'/1'/0'").toString();
xpk.should.equal(
'tprv8gSy16H5hQ1MKNHzZDzsktr4aaGQSHg4XYVEbfsEiGSBcgw4J8dEm8uf19FH4L9h6W47VBKtc3bbYyjb6HAm6QdyRLpB6fsA7bW19RZnby2'
);
});
});
describe('#createCredentials', function() {
it('should create 1-1 credentials', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var cred = c.createCredentials(null, {
coin: 'btc',
network: 'testnet',
account: 0,
n: 1
});
c.compliantDerivation.should.equal(true);
cred.addressType.should.equal('P2PKH');
cred.rootPath.should.equal("m/44'/1'/0'");
});
it('should create 2-2 credentials', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var cred = c.createCredentials(null, {
coin: 'bch',
network: 'livenet',
account: 1,
n: 2,
nonCompliantDerivation: true
});
cred.account.should.equal(1);
cred.addressType.should.equal('P2SH');
cred.n.should.equal(2);
cred.rootPath.should.equal("m/48'/145'/1'");
c.compliantDerivation.should.equal(true);
});
});
describe('#getBaseAddressDerivationPath', function() {
it('should return path for livenet', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var path = c.getBaseAddressDerivationPath({
account: 0,
coin: 'btc',
n: 1
});
path.should.equal("m/44'/0'/0'");
});
it('should return path for testnet account 2', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var path = c.getBaseAddressDerivationPath({
account: 2,
coin: 'btc',
network: 'testnet',
n: 1
});
path.should.equal("m/44'/1'/2'");
});
it('should return path for testnet account 1', function() {
var c = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
var path = c.getBaseAddressDerivationPath({
account: 1,
coin: 'btc',
network: 'testnet',
n: 1
});
path.should.equal("m/44'/1'/1'");
});
});
describe('#createCredentials 2', function() {
it('should return different copayerId for different coin / accounts', function() {
var k = new Key({ seedType: 'extendedPrivateKey', seedData: 'xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi' });
let c = k.createCredentials(null, {
coin: 'btc',
account: 0,
network: 'livenet',
n: 1
});
let c1 = k.createCredentials(null, {
coin: 'btc',
account: 1,
network: 'livenet',
n: 1
});
let c2 = k.createCredentials(null, {
coin: 'bch',
account: 1,
network: 'livenet',
n: 1
});
c.copayerId.should.equal('4abffe3e0e52a4cec11ebf966675cb526566919a8a0d5de36d9b2898ee804a58');
c1.copayerId.should.equal('911867838cffffc2bbd05e519f1932d56c49b93a908136ce7a17b70573c1c428');
c2.copayerId.should.equal('dc9577aa5054563f31047463e25ec52f96c5b1fa93c4b567f2329eb6a66517d0');
});
it('should return different copayerId for different network', function() {
var words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
var k = new Key({ seedType: 'mnemonic', seedData: words });
var c = k.createCredentials(null, {
coin: 'btc',
account: 0,
network: 'livenet',
n: 1
});
c.copayerId.should.equal('af4e120530f26ffa834739b0eb030093c881bf73f8f893fc6837823325da83f2');
var c2 = k.createCredentials(null, {
coin: 'btc',
account: 0,
network: 'testnet',
n: 1
});
c2.copayerId.should.equal('51d883fcd4ec010a89503c4b64e0cf22fe706495a9cf086bec69194c1c8f8952');
});
});
describe('#createWithMnemonic #fromMnemonic roundtrip', function() {
for (const lang of ['en', 'es', 'ja', 'zh', 'fr']) {
it('Should verify roundtrip create/from with ' + lang + '/passphrase', function() {
var c = new Key({ seedType: 'new', language: lang });
c = c.toObj();
should.exist(c.mnemonic);
var words = c.mnemonic;
var xPriv = c.xPrivKey;
var c2 = new Key({ seedType: 'mnemonic', seedData: words });
c2 = c2.toObj();
should.exist(c2.mnemonic);
words.should.be.equal(c2.mnemonic);
c2.xPrivKey.should.equal(c.xPrivKey);
});
}
it('Should fail roundtrip create/from with ES/passphrase with wrong passphrase', function() {
var c = new Key({ seedType: 'new', language: 'es', passphrase: 'holamundo' });
c = c.toObj();
should.exist(c.mnemonic);
var words = c.mnemonic;
var xPriv = c.xPrivKey;
var c2 = new Key({ seedType: 'mnemonic', seedData: c.mnemonic, passphrase: 'chaumundo' });
c2.toObj().xPrivKey.should.not.equal(c.xPrivKey);
});
it('Should fail roundtrip create/from with ES/passphrase with null passphrase', function() {
var c = new Key({ seedType: 'new', language: 'es', passphrase: 'holamundo' });
c = c.toObj();
should.exist(c.mnemonic);
var words = c.mnemonic;
var xPriv = c.xPrivKey;
var c2 = new Key({ seedType: 'mnemonic', seedData: c.mnemonic });
c2 = c2.toObj();
c2.xPrivKey.should.not.equal(c.xPrivKey);
});
it('Should verify roundtrip create/from with ES/passphrase with ok passphrase', function() {
var c = new Key({ seedType: 'new', language: 'es', passphrase: 'holamundo' });
c = c.toObj();
should.exist(c.mnemonic);
var words = c.mnemonic;
var xPriv = c.xPrivKey;
var c2 = new Key({ seedType: 'mnemonic', seedData: c.mnemonic, passphrase: 'holamundo' });
c2 = c2.toObj();
c2.xPrivKey.should.equal(c.xPrivKey);
});
});
describe('from/toObj', () => {
it('should export & import', function() {
var c = new Key();
var exported = c.toObj();
let imported = new Key({ seedType: 'object', seedData: exported} );
imported.get().xPrivKey.should.equal(c.get().xPrivKey);
imported.get().mnemonic.should.equal(c.get().mnemonic);
});
it('should export & import encrypted and fail if password not supplied', function() {
var c = new Key();
let x = c.get().xPrivKey;
c.encrypt('pepe');
var exported = c.toObj();
let imported = new Key({ seedType: 'object', seedData: exported} );
(() => {
imported.get().xPrivKey.should.equal(x);
}).should.throw('encrypted');
imported = imported.toObj();
should.not.exist(imported.xPrivKey);
should.not.exist(imported.mnemonic);
should.exist(imported.xPrivKeyEncrypted);
should.exist(imported.mnemonicEncrypted);
});
it('should export & import encrypted and restore if password supplied', function() {
var c = new Key();
let x = c.get().xPrivKey;
let m = c.get().mnemonic;
c.encrypt('pepe');
var exported = c.toObj();
let imported = new Key({ seedType: 'object', seedData: exported} );
imported.get('pepe').xPrivKey.should.equal(x);
imported.get('pepe').mnemonic.should.equal(m);
imported = imported.toObj();
should.not.exist(imported.xPrivKey);
should.not.exist(imported.mnemonic);
should.exist(imported.xPrivKeyEncrypted);
should.exist(imported.mnemonicEncrypted);
should.exist(imported.fingerPrint);
});
});
});