UNPKG

@ducatus/ducatus-wallet-client-rev

Version:

Client for @ducatus/ducatus-wallet-service-rev

514 lines (443 loc) 19.6 kB
'use strict'; var _ = require('lodash'); var chai = chai || require('chai'); var sinon = sinon || require('sinon'); var should = chai.should(); var { Key } = require('../ts_build/lib/key'); describe('Key', function () { describe('#create', function () { it('Should create', function () { var c = Key.create(); should.exist(c.xPrivKey); should.exist(c.mnemonic); }); it('Should create random keys', function () { var all = {}; for (var i = 0; i < 10; i++) { var c = Key.create(); var exist = all[c.xPrivKey]; should.not.exist(exist); all[c.xPrivKey] = 1; } }); it('Should create keys from mnemonic (no passphrase) ', function () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); c.xPrivKey.should.equal('xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'); c.fingerPrint.should.equal('73c5da0a'); }); it('Should create keys from mnemonic (with passphrase) ', function () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', { passphrase: 'pepe' }); c.xPrivKey.should.equal('xprv9s21ZrQH143K4C14pRZ5fTForcjAuRXLHs7Td28XuG2JMEC17Xm6JMGpNMRdNgfKZnyT3nmfeH8yVzxp6jnhmpVQAEmNBxLBh6t6t5UTVxo'); }); it('Should return priv key', function () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); c.get().xPrivKey.should.be.equal('xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu'); }); it('Should return mnemonic', function () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); c.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 () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); should.not.exist(c.checkPassword('xx')); }); it('Should return true/false', function () { var c = Key.fromMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'); c.encrypt('xx'); c.checkPassword('xx').should.equal(true); c.checkPassword('yy').should.equal(false); }); }); describe('#match', function () { it('Should match', function () { var c = Key.create(); Key.match(c, c).should.equal(true); }); it('Should match (after import)', function () { var c = Key.create(); var c2 = Key.fromObj(c.toObj()); Key.match(c, c2).should.equal(true); }); it('Shouldn\'t match', function () { var c = Key.create(); var c2 = Key.create(); Key.match(c, c2).should.equal(false); }); }); describe('Encryption', function () { describe('#encrypt', function () { it('should encrypt private key and remove cleartext', function () { var c = Key.create(); c.encrypt('password'); c.isPrivKeyEncrypted().should.be.true; 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 = Key.create(); 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 = Key.create(); c.encrypt('password'); c.isPrivKeyEncrypted().should.be.true; c.decrypt('password'); c.isPrivKeyEncrypted().should.be.false; 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 = Key.create(); 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; should.exist(c.mnemonicEncrypted); should.not.exist(c.mnemonic); }); it('should fail to decrypt private key when not encrypted', function () { var c = Key.create(); 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 = Key.create(); var keys = c.get(); should.exist(keys.xPrivKey); should.exist(keys.mnemonic); keys.xPrivKey.should.equal(c.xPrivKey); keys.mnemonic.should.equal(c.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 = Key.create(); 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 = Key.fromExtendedPrivateKey(xPriv); var c = k.createCredentials(null, { coin: 'btc', network: 'livenet', account: 0, n: 1, }); 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 = Key.fromExtendedPrivateKey(xPriv); var c = k.createCredentials(null, { coin: 'btc', network: 'livenet', account: 0, n: 1, walletPrivKey: wKey, }); 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 = Key.fromExtendedPrivateKey(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 = Key.fromExtendedPrivateKey(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 = Key.fromExtendedPrivateKey(xPriv, { nonCompliantDerivation: true }); var c = k.createCredentials(null, { coin: 'btc', network: 'testnet', account: 0, n: 1, }); 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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi'); var xpk = c.derive(null, 'm/48\'/0\'/0\'').toString(); xpk.should.equal('xprv9yaGCLKPS2ovEGw987MZr4DCkfZHGh518ndVk3Jb6eiUdPwCQu7nYru59WoNkTEQvmhnv5sPbYxeuee5k8QASWRnGV2iFX4RmKXEQse8KnQ'); }); it('should derive compliant child', function () { var c = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('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 = Key.fromExtendedPrivateKey('xprv9s21ZrQH143K3zLpjtB4J4yrRfDTEfbrMa9vLZaTAv5BzASwBmA16mdBmZKpMLssw1AzTnm31HAD2pk2bsnZ9dccxaLD48mRdhtw82XoiBi'); var path = c.getBaseAddressDerivationPath({ account: 1, coin: 'btc', network: 'testnet', n: 1, }); path.should.equal("m/44'/1'/1'"); }); }); describe('#createCredentials', function () { it('should return different copayerId for different coin / accounts', function () { var k = Key.fromExtendedPrivateKey('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 = Key.fromMnemonic(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 () { _.each(['en', 'es', 'ja', 'zh', 'fr'], function (lang) { it('Should verify roundtrip create/from with ' + lang + '/passphrase', function () { var c = Key.create({ language: lang }); should.exist(c.mnemonic); var words = c.mnemonic; var xPriv = c.xPrivKey; var c2 = Key.fromMnemonic(words); 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 = Key.create({ language: 'es', passphrase: 'holamundo' }); should.exist(c.mnemonic); var words = c.mnemonic; var xPriv = c.xPrivKey; var c2 = Key.fromMnemonic(words, { passphrase: 'chaumundo' }); c2.xPrivKey.should.not.equal(c.xPrivKey); }); it('Should fail roundtrip create/from with ES/passphrase with null passphrase', function () { var c = Key.create({ language: 'es', passphrase: 'holamundo' }); should.exist(c.mnemonic); var words = c.mnemonic; var xPriv = c.xPrivKey; var c2 = Key.fromMnemonic(words); c2.xPrivKey.should.not.equal(c.xPrivKey); }); it('Should verify roundtrip create/from with ES/passphrase with ok passphrase', function () { var c = Key.create({ language: 'es', passphrase: 'holamundo' }); should.exist(c.mnemonic); var words = c.mnemonic; var xPriv = c.xPrivKey; var c2 = Key.fromMnemonic(words, { passphrase: 'holamundo' }); c2.xPrivKey.should.equal(c.xPrivKey); }); }); describe('from/toObj', () => { it('should export & import', function () { var c = Key.create(); var exported = c.toObj(); let imported = Key.fromObj(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 () { let c = Key.create(); let x = c.get().xPrivKey; c.encrypt('pepe'); var exported = c.toObj(); let imported = Key.fromObj(exported); (() => { imported.get().xPrivKey.should.equal(x); }).should.throw('encrypted'); 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 = Key.create(); let x = c.get().xPrivKey; let m = c.get().mnemonic; c.encrypt('pepe'); var exported = c.toObj(); let imported = Key.fromObj(exported); imported.get('pepe').xPrivKey.should.equal(x); imported.get('pepe').mnemonic.should.equal(m); should.not.exist(imported.xPrivKey); should.not.exist(imported.mnemonic); should.exist(imported.xPrivKeyEncrypted); should.exist(imported.mnemonicEncrypted); should.exist(imported.fingerPrint); }); }); });