1 | 'use strict';
|
2 |
|
3 |
|
4 | var _ = require('lodash');
|
5 | var assert = require('assert');
|
6 | var should = require('chai').should();
|
7 | var expect = require('chai').expect;
|
8 | var bitcore = require('..');
|
9 | var buffer = require('buffer');
|
10 | var errors = bitcore.errors;
|
11 | var hdErrors = bitcore.errors.HDPublicKey;
|
12 | var BufferUtil = bitcore.util.buffer;
|
13 | var HDPrivateKey = bitcore.HDPrivateKey;
|
14 | var HDPublicKey = bitcore.HDPublicKey;
|
15 | var Base58Check = bitcore.encoding.Base58Check;
|
16 | var Networks = bitcore.Networks;
|
17 |
|
18 | var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
|
19 | var xpubkey = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8';
|
20 | var xpubkeyTestnet = 'tpubD6NzVbkrYhZ4WZaiWHz59q5EQ61bd6dUYfU4ggRWAtNAyyYRNWT6ktJ7UHJEXURvTfTfskFQmK7Ff4FRkiRN5wQH8nkGAb6aKB4Yyeqsw5m';
|
21 | var json = '{"network":"livenet","depth":0,"fingerPrint":876747070,"parentFingerPrint":0,"childIndex":0,"chainCode":"873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508","publicKey":"0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2","checksum":-1421395167,"xpubkey":"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"}';
|
22 | var derived_0_1_200000 = 'xpub6BqyndF6rkBNTV6LXwiY8Pco8aqctqq7tGEUdA8fmGDTnDJphn2fmxr3eM8Lm3m8TrNUsLbEjHvpa3adBU18YpEx4tp2Zp6nqax3mQkudhX';
|
23 |
|
24 | describe('HDPublicKey interface', function() {
|
25 |
|
26 | var expectFail = function(func, errorType) {
|
27 | (function() {
|
28 | func();
|
29 | }).should.throw(errorType);
|
30 | };
|
31 |
|
32 | var expectDerivationFail = function(argument, error) {
|
33 | (function() {
|
34 | var pubkey = new HDPublicKey(xpubkey);
|
35 | pubkey.derive(argument);
|
36 | }).should.throw(error);
|
37 | };
|
38 |
|
39 | var expectFailBuilding = function(argument, error) {
|
40 | (function() {
|
41 | return new HDPublicKey(argument);
|
42 | }).should.throw(error);
|
43 | };
|
44 |
|
45 | describe('creation formats', function() {
|
46 |
|
47 | it('returns same argument if already an instance of HDPublicKey', function() {
|
48 | var publicKey = new HDPublicKey(xpubkey);
|
49 | publicKey.should.equal(new HDPublicKey(publicKey));
|
50 | });
|
51 |
|
52 | it('returns the correct xpubkey for a xprivkey', function() {
|
53 | var publicKey = new HDPublicKey(xprivkey);
|
54 | publicKey.xpubkey.should.equal(xpubkey);
|
55 | });
|
56 |
|
57 | it('allows to call the argument with no "new" keyword', function() {
|
58 | HDPublicKey(xpubkey).xpubkey.should.equal(new HDPublicKey(xpubkey).xpubkey);
|
59 | });
|
60 |
|
61 | it('fails when user doesn\'t supply an argument', function() {
|
62 | expectFailBuilding(null, hdErrors.MustSupplyArgument);
|
63 | });
|
64 |
|
65 | it('should not be able to change read-only properties', function() {
|
66 | var publicKey = new HDPublicKey(xprivkey);
|
67 | expect(function() {
|
68 | publicKey.fingerPrint = 'notafingerprint';
|
69 | }).to.throw(TypeError);
|
70 | });
|
71 |
|
72 | it('doesn\'t recognize an invalid argument', function() {
|
73 | expectFailBuilding(1, hdErrors.UnrecognizedArgument);
|
74 | expectFailBuilding(true, hdErrors.UnrecognizedArgument);
|
75 | });
|
76 |
|
77 |
|
78 | describe('xpubkey string serialization errors', function() {
|
79 | it('fails on invalid length', function() {
|
80 | expectFailBuilding(
|
81 | Base58Check.encode(Buffer.from([1, 2, 3])),
|
82 | hdErrors.InvalidLength
|
83 | );
|
84 | });
|
85 | it('fails on invalid base58 encoding', function() {
|
86 | expectFailBuilding(
|
87 | xpubkey + '1',
|
88 | errors.InvalidB58Checksum
|
89 | );
|
90 | });
|
91 | it('user can ask if a string is valid', function() {
|
92 | (HDPublicKey.isValidSerialized(xpubkey)).should.equal(true);
|
93 | });
|
94 | });
|
95 |
|
96 | it('can be generated from a json', function() {
|
97 | expect(new HDPublicKey(JSON.parse(json)).xpubkey).to.equal(xpubkey);
|
98 | });
|
99 |
|
100 | it('can generate a json that has a particular structure', function() {
|
101 | assert(_.isEqual(
|
102 | new HDPublicKey(JSON.parse(json)).toJSON(),
|
103 | new HDPublicKey(xpubkey).toJSON()
|
104 | ));
|
105 | });
|
106 |
|
107 | it('builds from a buffer object', function() {
|
108 | (new HDPublicKey(new HDPublicKey(xpubkey)._buffers)).xpubkey.should.equal(xpubkey);
|
109 | });
|
110 |
|
111 | it('checks the checksum', function() {
|
112 | var buffers = new HDPublicKey(xpubkey)._buffers;
|
113 | buffers.checksum = BufferUtil.integerAsBuffer(1);
|
114 | expectFail(function() {
|
115 | return new HDPublicKey(buffers);
|
116 | }, errors.InvalidB58Checksum);
|
117 | });
|
118 | });
|
119 |
|
120 | describe('error checking on serialization', function() {
|
121 | var compareType = function(a, b) {
|
122 | expect(a instanceof b).to.equal(true);
|
123 | };
|
124 | it('throws invalid argument when argument is not a string or buffer', function() {
|
125 | compareType(HDPublicKey.getSerializedError(1), hdErrors.UnrecognizedArgument);
|
126 | });
|
127 | it('if a network is provided, validates that data corresponds to it', function() {
|
128 | compareType(HDPublicKey.getSerializedError(xpubkey, 'testnet'), errors.InvalidNetwork);
|
129 | });
|
130 | it('recognizes invalid network arguments', function() {
|
131 | compareType(HDPublicKey.getSerializedError(xpubkey, 'invalid'), errors.InvalidNetworkArgument);
|
132 | });
|
133 | it('recognizes a valid network', function() {
|
134 | expect(HDPublicKey.getSerializedError(xpubkey, 'livenet')).to.equal(null);
|
135 | });
|
136 | });
|
137 |
|
138 | it('toString() returns the same value as .xpubkey', function() {
|
139 | var pubKey = new HDPublicKey(xpubkey);
|
140 | pubKey.toString().should.equal(pubKey.xpubkey);
|
141 | });
|
142 |
|
143 | it('publicKey property matches network', function() {
|
144 | var livenet = new HDPublicKey(xpubkey);
|
145 | var testnet = new HDPublicKey(xpubkeyTestnet);
|
146 |
|
147 | livenet.publicKey.network.should.equal(Networks.livenet);
|
148 | testnet.publicKey.network.should.equal(Networks.testnet);
|
149 | });
|
150 |
|
151 | it('inspect() displays correctly', function() {
|
152 | var pubKey = new HDPublicKey(xpubkey);
|
153 | pubKey.inspect().should.equal('<HDPublicKey: ' + pubKey.xpubkey + '>');
|
154 | });
|
155 |
|
156 | describe('conversion to/from buffer', function() {
|
157 |
|
158 | it('should roundtrip to an equivalent object', function() {
|
159 | var pubKey = new HDPublicKey(xpubkey);
|
160 | var toBuffer = pubKey.toBuffer();
|
161 | var fromBuffer = HDPublicKey.fromBuffer(toBuffer);
|
162 | var roundTrip = new HDPublicKey(fromBuffer.toBuffer());
|
163 | roundTrip.xpubkey.should.equal(xpubkey);
|
164 | });
|
165 | });
|
166 |
|
167 | describe('conversion to different formats', function() {
|
168 | var plainObject = {
|
169 | 'network':'livenet',
|
170 | 'depth':0,
|
171 | 'fingerPrint':876747070,
|
172 | 'parentFingerPrint':0,
|
173 | 'childIndex':0,
|
174 | 'chainCode':'873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508',
|
175 | 'publicKey':'0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2',
|
176 | 'checksum':-1421395167,
|
177 | 'xpubkey':'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8'
|
178 | };
|
179 | it('roundtrips to JSON and to Object', function() {
|
180 | var pubkey = new HDPublicKey(xpubkey);
|
181 | expect(HDPublicKey.fromObject(pubkey.toJSON()).xpubkey).to.equal(xpubkey);
|
182 | });
|
183 | it('recovers state from Object', function() {
|
184 | new HDPublicKey(plainObject).xpubkey.should.equal(xpubkey);
|
185 | });
|
186 | });
|
187 |
|
188 | describe('derivation', function() {
|
189 | it('derivation is the same whether deriving with number or string', function() {
|
190 | var pubkey = new HDPublicKey(xpubkey);
|
191 | var derived1 = pubkey.derive(0).derive(1).derive(200000);
|
192 | var derived2 = pubkey.derive('m/0/1/200000');
|
193 | derived1.xpubkey.should.equal(derived_0_1_200000);
|
194 | derived2.xpubkey.should.equal(derived_0_1_200000);
|
195 | });
|
196 |
|
197 | it('allows special parameters m, M', function() {
|
198 | var expectDerivationSuccess = function(argument) {
|
199 | new HDPublicKey(xpubkey).derive(argument).xpubkey.should.equal(xpubkey);
|
200 | };
|
201 | expectDerivationSuccess('m');
|
202 | expectDerivationSuccess('M');
|
203 | });
|
204 |
|
205 | it('doesn\'t allow object arguments for derivation', function() {
|
206 | expectFail(function() {
|
207 | return new HDPublicKey(xpubkey).derive({});
|
208 | }, hdErrors.InvalidDerivationArgument);
|
209 | });
|
210 |
|
211 | it('needs first argument for derivation', function() {
|
212 | expectFail(function() {
|
213 | return new HDPublicKey(xpubkey).derive('s');
|
214 | }, hdErrors.InvalidPath);
|
215 | });
|
216 |
|
217 | it('doesn\'t allow other parameters like m\' or M\' or "s"', function() {
|
218 |
|
219 | expectDerivationFail("m'", hdErrors.InvalidIndexCantDeriveHardened);
|
220 | expectDerivationFail("M'", hdErrors.InvalidIndexCantDeriveHardened);
|
221 | expectDerivationFail("1", hdErrors.InvalidPath);
|
222 | expectDerivationFail("S", hdErrors.InvalidPath);
|
223 | });
|
224 |
|
225 | it('can\'t derive hardened keys', function() {
|
226 | expectFail(function() {
|
227 | return new HDPublicKey(xpubkey).derive(HDPublicKey.Hardened);
|
228 | }, hdErrors.InvalidIndexCantDeriveHardened);
|
229 | });
|
230 |
|
231 | it('can\'t derive hardened keys via second argument', function() {
|
232 | expectFail(function() {
|
233 | return new HDPublicKey(xpubkey).derive(5, true);
|
234 | }, hdErrors.InvalidIndexCantDeriveHardened);
|
235 | });
|
236 |
|
237 | it('validates correct paths', function() {
|
238 | var valid;
|
239 |
|
240 | valid = HDPublicKey.isValidPath('m/123/12');
|
241 | valid.should.equal(true);
|
242 |
|
243 | valid = HDPublicKey.isValidPath('m');
|
244 | valid.should.equal(true);
|
245 |
|
246 | valid = HDPublicKey.isValidPath(123);
|
247 | valid.should.equal(true);
|
248 | });
|
249 |
|
250 | it('rejects illegal paths', function() {
|
251 | var valid;
|
252 |
|
253 | valid = HDPublicKey.isValidPath('m/-1/12');
|
254 | valid.should.equal(false);
|
255 |
|
256 | valid = HDPublicKey.isValidPath("m/0'/12");
|
257 | valid.should.equal(false);
|
258 |
|
259 | valid = HDPublicKey.isValidPath("m/8000000000/12");
|
260 | valid.should.equal(false);
|
261 |
|
262 | valid = HDPublicKey.isValidPath('bad path');
|
263 | valid.should.equal(false);
|
264 |
|
265 | valid = HDPublicKey.isValidPath(-1);
|
266 | valid.should.equal(false);
|
267 |
|
268 | valid = HDPublicKey.isValidPath(8000000000);
|
269 | valid.should.equal(false);
|
270 |
|
271 | valid = HDPublicKey.isValidPath(HDPublicKey.Hardened);
|
272 | valid.should.equal(false);
|
273 | });
|
274 | });
|
275 | });
|