|
| 1 | +const Dashcore = require('@dashevo/dashcore-lib'); |
| 2 | +const { expect } = require('chai'); |
| 3 | +const DerivableKeyChain = require('./DerivableKeyChain'); |
| 4 | +const { mnemonicToHDPrivateKey } = require('../../utils/mnemonic'); |
| 5 | + |
| 6 | +let derivableKeyChain; |
| 7 | +let derivableKeyChain2; |
| 8 | +const mnemonic = 'during develop before curtain hazard rare job language become verb message travel'; |
| 9 | +const mnemonic2 = 'birth kingdom trash renew flavor utility donkey gasp regular alert pave layer'; |
| 10 | +const hdPublicKey = 'xpub661MyMwAqRbcFGB6XSWBsD725rJDUbFUpy4zWe2u22nJ2BxpoHFxtVDfKnTnvVQHohnY7AsVpRTHDv6PyPQTYu1KxFPKw29MAVXPEpz1G7V'; |
| 11 | +const expectedRootDIP15AccountKey_0 = 'tprv8hRzmheQujhJN5XP2dj955nAFCKeEoSifJRWuutdbwWRtusdDQ426jbp75EqErUSuTxmPyxYmP1TpcF5qdxGhXLNXRLMGsRLG6NFCv1WnaQ'; |
| 12 | +const expectedRootDIP15AccountKey_1 = 'tprv8hRzmheQujhJQyCtFTuUFHxB3Ag5VLB994zhH4CfxbA41cq73HT2mpYq5M33V54oJyn6g514saxxVJB886G55eYX56J6D6x87UNNT6iQHkR'; |
| 13 | +const expectedKeyForChild_0 = 'tprv8d4podc2Tg459CH2bwLHXj3vdJFBT2rdsk5Nr1djH7hzHdt5LRdvN6QyFwMiDy7ffRdik7fEVRKKgsHB4F18sh8xF6jFXpKq4sUgGBoSbKw'; |
| 14 | + |
| 15 | +describe('DerivableKeyChain', function suite() { |
| 16 | + this.timeout(1000); |
| 17 | + it('should create a DerivableKeyChain', () => { |
| 18 | + const expectedException1 = 'Expect one of [mnemonic, HDPrivateKey, HDPublicKey, privateKey, publicKey, address] to be provided.'; |
| 19 | + expect(() => new DerivableKeyChain()).to.throw(expectedException1); |
| 20 | + |
| 21 | + derivableKeyChain = new DerivableKeyChain({ mnemonic: mnemonic, network: 'testnet' }); |
| 22 | + expect(derivableKeyChain.rootKeyType).to.equal('HDPrivateKey'); |
| 23 | + expect(derivableKeyChain.network.toString()).to.equal('testnet'); |
| 24 | + expect(derivableKeyChain.rootKey.network.toString()).to.equal('testnet'); |
| 25 | + |
| 26 | + derivableKeyChain2 = new DerivableKeyChain({ mnemonic: mnemonic2, network: 'livenet' }); |
| 27 | + }); |
| 28 | + |
| 29 | + it('should generate key for full path', () => { |
| 30 | + const path = 'm/44\'/1\'/0\'/0/0'; |
| 31 | + const pk2 = derivableKeyChain.getForPath(path).key; |
| 32 | + const address = new Dashcore.Address(pk2.publicKey.toAddress()).toString(); |
| 33 | + expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); |
| 34 | + }); |
| 35 | + |
| 36 | + it('should get DIP15 account key', function () { |
| 37 | + const rootDIP15AccountKey_0 = derivableKeyChain.getHardenedDIP15AccountKey(0); |
| 38 | + expect(rootDIP15AccountKey_0.toString()).to.deep.equal(expectedRootDIP15AccountKey_0); |
| 39 | + const rootDIP15AccountKey_1 = derivableKeyChain.getHardenedDIP15AccountKey(1); |
| 40 | + expect(rootDIP15AccountKey_1.toString()).to.deep.equal(expectedRootDIP15AccountKey_1); |
| 41 | + }); |
| 42 | + |
| 43 | + it('should get DIP15 extended key', function () { |
| 44 | + const userUniqueId = '0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'; |
| 45 | + const contactUniqueId = '0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'; |
| 46 | + |
| 47 | + // m/9'/5'/15'/0'/0x555d3854c910b7dee436869c4724bed2fe0784e198b8a39f02bbb49d8ebcfc3a'/0xa137439f36d04a15474ff7423e4b904a14373fafb37a41db74c84f1dbb5c89b5'/0 |
| 48 | + const DIP15ExtPubKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0, 'HDPublicKey'); |
| 49 | + expect(DIP15ExtPubKey_0.toString()).to.equal('xpub6LTkTQFSb8KMgMSz4B6sMZLpkQAY6wSTDprDkHDmLwWLpnjxazuxZn13FrSLKUafitsxuaaffM5a49P6aswhpppWUuYW6eFnwBXshR2W2eY'); |
| 50 | + expect(DIP15ExtPubKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') |
| 51 | + |
| 52 | + const DIP15ExtPrivKey_0 = derivableKeyChain2.getDIP15ExtendedKey(userUniqueId, contactUniqueId, 0, 0); |
| 53 | + expect(DIP15ExtPrivKey_0.toString()).to.equal('xprvA7UQ3tiYkkm4TsNWx9ZrzRQ6CNL3hUibrbvcwtp9nbyMwzQp3Tbi1ygZQaPoigDhCf8XUjMmGK2NbnB2kLXPYg99Lp6e3iki318sdWcFN3q'); |
| 54 | + expect(DIP15ExtPrivKey_0.privateKey.toString()).to.equal('fac40790776d171ee1db90899b5eb2df2f7d2aaf35ad56f07ffb8ed2c57f8e60') |
| 55 | + expect(DIP15ExtPrivKey_0.publicKey.toString()).to.equal('038030c88ab0106e1f4af3b939db2bafc56f892554106f08da1ce1f9ef10f807bd') |
| 56 | + |
| 57 | + // This comes from the test factor of DIP-15 |
| 58 | + const userAhash = "0xa11ce14f698b32e9bb306dba7bbbee831263dcf658abeebb39930460ead117e5"; |
| 59 | + const userBhash = "0xb0b052ff075c5ca3c16c3e20e9ac8223834475cc1324ab07889cb24ce6a62793"; |
| 60 | + const DIP15ExtKey_1 = derivableKeyChain.getDIP15ExtendedKey(userAhash, userBhash, 0, 0); |
| 61 | + expect(DIP15ExtKey_1.privateKey.toString()).to.equal('60581b6dca8244d3fb3cfe619b5a22277e5423b01e5285f356981f247e0f4a60') |
| 62 | + expect(DIP15ExtKey_1.publicKey.toString()).to.equal('03deaac00f721151307fbc7bf80d7b8afab98c1f026d67e5f56b21e2013f551ce6') |
| 63 | + }); |
| 64 | + |
| 65 | + it('should derive from hardened path and get address', () => { |
| 66 | + const hardenedHDKey = derivableKeyChain.getHardenedBIP44HDKey(); |
| 67 | + const pk2 = derivableKeyChain.getForPath(`m/44'/1'`).key; |
| 68 | + expect(pk2.toString()).to.equal(hardenedHDKey.toString()); |
| 69 | + expect(hardenedHDKey.toString()).to.deep.equal('tprv8dtrJNytYHRiZY585hmHGbguS6VjGpK49puSB7oXZjLHcQfrAzQkF4ZCxM2DkEbyY85J4EYcZ8EjT5ZCU8ozB727TDdodbfXet5GkGau2RQ'); |
| 70 | + const derivedPk = hardenedHDKey.deriveChild(0, true).deriveChild(0).deriveChild(0); |
| 71 | + // m/44'/1'/0'/0/0 (this is first external address of the account 0) |
| 72 | + const address = new Dashcore.Address(derivedPk.publicKey.toAddress()).toString(); |
| 73 | + expect(address).to.equal('yNfUebksUc5HoSfg8gv98ruC3jUNJUM8pT'); |
| 74 | + }); |
| 75 | + |
| 76 | + it('should get hardened DIP9FeatureHDKey', function () { |
| 77 | + const hardenedHDKey = derivableKeyChain.getHardenedDIP9FeatureHDKey(); |
| 78 | + const pk2 = derivableKeyChain.getForPath(`m/9'/1'`).key; |
| 79 | + expect(pk2.toString()).to.equal(hardenedHDKey.toString()); |
| 80 | + expect(hardenedHDKey.toString()).to.deep.equal('tprv8fBJjWoGgCpGRCbyzE9RUA59rmoN1RUijhLnXGL4VHnLxvSe523yVg4GrGzbR6TyXtdynAEh5z8UX55EXt2Cb3xjvrsx2PgTY9BHxzFVkWn'); |
| 81 | + }); |
| 82 | + |
| 83 | + it('should get key for path using the HDPrivateKey', () => { |
| 84 | + const derivableKeyChain2 = new DerivableKeyChain({ HDPrivateKey: mnemonicToHDPrivateKey(mnemonic, 'testnet') }); |
| 85 | + const keyForChild = derivableKeyChain2.getForPath('m/0').key; |
| 86 | + expect(keyForChild.toString()).to.equal(expectedKeyForChild_0); |
| 87 | + }); |
| 88 | + |
| 89 | + it('should mark address watched and get watched addresses', function () { |
| 90 | + const key0 = derivableKeyChain.getForPath('m/0'); |
| 91 | + derivableKeyChain.getForPath('m/0').isWatched = true |
| 92 | + key0.isWatched = true; |
| 93 | + derivableKeyChain.getForPath('m/1', { isWatched: false }); |
| 94 | + derivableKeyChain.getForPath('m/2', { isWatched: true }); |
| 95 | + |
| 96 | + const watchedAddresses = derivableKeyChain.getWatchedAddresses(); |
| 97 | + let expectedWatchedAddresses = [ |
| 98 | + derivableKeyChain.getForPath('m/0').address.toString(), |
| 99 | + derivableKeyChain.getForPath('m/2').address.toString() |
| 100 | + ]; |
| 101 | + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); |
| 102 | + }); |
| 103 | + |
| 104 | + it('should get watched addresses', function () { |
| 105 | + derivableKeyChain.getForPath('m/1').isWatched = true |
| 106 | + const watchedAddresses = derivableKeyChain.getWatchedAddresses(); |
| 107 | + const expectedWatchedAddresses = [ |
| 108 | + 'ybQDfNwiDjk8ZH5UUmHQzAMEmjbrbK5dAj', |
| 109 | + 'yhFX5rseJPitV45HUCaa9haeGHtLuooBaq', |
| 110 | + 'yhqxsmYk6jfoGWf1hJKq7d4U2cGHCgzpFU' |
| 111 | + ] |
| 112 | + expect(watchedAddresses).to.deep.equal(expectedWatchedAddresses); |
| 113 | + }); |
| 114 | + |
| 115 | + it('should remove an address from watched addresses', function () { |
| 116 | + derivableKeyChain.getForPath('m/0', { isWatched: false }); |
| 117 | + derivableKeyChain.getForPath('m/1'); |
| 118 | + const data2 = derivableKeyChain.getForPath('m/2'); |
| 119 | + data2.isWatched = false; |
| 120 | + |
| 121 | + expect(derivableKeyChain.getWatchedAddresses().length).to.equal(1); |
| 122 | + }); |
| 123 | + |
| 124 | + it('should get address for path', function (){ |
| 125 | + const address0_1 = derivableKeyChain.getForPath('m/1').address; |
| 126 | + expect(address0_1.toString()).to.equal('yhFX5rseJPitV45HUCaa9haeGHtLuooBaq') |
| 127 | + }) |
| 128 | + |
| 129 | + it('should mark address as used', function () { |
| 130 | + const address0_0 = derivableKeyChain.getForPath('m/0').address; |
| 131 | + derivableKeyChain.markAddressAsUsed(address0_0); |
| 132 | + expect(derivableKeyChain.issuedPaths.get('m/0').isUsed).to.equal(true) |
| 133 | + }); |
| 134 | +}); |
| 135 | + |
| 136 | +describe('DerivableKeyChain - HDPublicKey', function suite(){ |
| 137 | + let hdpubDerivableKeyChain; |
| 138 | + it('should initiate from a HDPublicKey', function () { |
| 139 | + hdpubDerivableKeyChain = new DerivableKeyChain({ |
| 140 | + HDPublicKey: new Dashcore.HDPublicKey(hdPublicKey), |
| 141 | + network: 'testnet' |
| 142 | + }); |
| 143 | + // As the HDPublicKey starts with xpub, it's livenet and should take priority over our network being set. |
| 144 | + expect(hdpubDerivableKeyChain.network.toString()).to.equal('livenet'); |
| 145 | + expect(hdpubDerivableKeyChain.keyChainId).to.equal('kc5059442d66'); |
| 146 | + expect(hdpubDerivableKeyChain.getRootKey().toString()).to.equal(hdPublicKey); |
| 147 | + }); |
| 148 | + |
| 149 | + it('should derivate', function () { |
| 150 | + const key0_1 = hdpubDerivableKeyChain.getForPath('m/1').key; |
| 151 | + expect(key0_1.publicKey.toAddress(hdpubDerivableKeyChain.network).toString()).to.equal('XoL5LcBiDWcj6L7fFwytsFoX5Vz7BVXw9w') |
| 152 | + }); |
| 153 | + |
| 154 | + it('should get address for path', function (){ |
| 155 | + const address0_1 = hdpubDerivableKeyChain.getForPath('m/2').address; |
| 156 | + expect(address0_1.toString()).to.equal('XwAzpxQKbgebaLiadq1c6rDeFJ4FKPUufy') |
| 157 | + }) |
| 158 | +}) |
| 159 | + |
| 160 | +describe('DerivableKeyChain - single privateKey', function suite() { |
| 161 | + this.timeout(10000); |
| 162 | + |
| 163 | + it('should correctly throw errors out when not a HDPublicKey (privateKey)', () => { |
| 164 | + const privateKey = Dashcore.PrivateKey().toString(); |
| 165 | + const network = 'livenet'; |
| 166 | + const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network }); |
| 167 | + expect(pkDerivableKeyChain.network).to.equal(network); |
| 168 | + expect(pkDerivableKeyChain.rootKeyType).to.equal('privateKey'); |
| 169 | + expect(pkDerivableKeyChain.rootKey.toString()).to.equal(privateKey); |
| 170 | + |
| 171 | + const expectedException1 = 'Wallet is not loaded from a mnemonic or a HDPrivateKey, impossible to derivate keys for path m/0'; |
| 172 | + expect(() => pkDerivableKeyChain.getForPath('m/0')).to.throw(expectedException1); |
| 173 | + }); |
| 174 | + |
| 175 | + it('should get private key', () => { |
| 176 | + const privateKey = Dashcore.PrivateKey().toString(); |
| 177 | + const pkDerivableKeyChain = new DerivableKeyChain({ privateKey, network: 'livenet' }); |
| 178 | + expect(pkDerivableKeyChain.getRootKey().toString()).to.equal(privateKey); |
| 179 | + expect(pkDerivableKeyChain.rootKey.toString()).to.equal(privateKey); |
| 180 | + }); |
| 181 | +}); |
0 commit comments