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