1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var bs58check = require('bs58check')
|
10 | var cashaddr = require('cashaddrjs')
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | var Format = {}
|
29 | Format.Legacy = 'legacy'
|
30 | Format.Bitpay = 'bitpay'
|
31 | Format.Cashaddr = 'cashaddr'
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | var Network = {}
|
38 | Network.Mainnet = 'mainnet'
|
39 | Network.Testnet = 'testnet'
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | var Type = {}
|
46 | Type.P2PKH = 'p2pkh'
|
47 | Type.P2SH = 'p2sh'
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | function detectAddressFormat (address) {
|
57 | return decodeAddress(address).format
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | function detectAddressNetwork (address) {
|
68 | return decodeAddress(address).network
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | function detectAddressType (address) {
|
79 | return decodeAddress(address).type
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | function toLegacyAddress (address) {
|
90 | var decoded = decodeAddress(address)
|
91 | if (decoded.format === Format.Legacy) {
|
92 | return address
|
93 | }
|
94 | return encodeAsLegacy(decoded)
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | function toBitpayAddress (address) {
|
105 | var decoded = decodeAddress(address)
|
106 | if (decoded.format === Format.Bitpay) {
|
107 | return address
|
108 | }
|
109 | return encodeAsBitpay(decoded)
|
110 | }
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | function toCashAddress (address) {
|
120 | var decoded = decodeAddress(address)
|
121 | return encodeAsCashaddr(decoded)
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | var VERSION_BYTE = {}
|
129 | VERSION_BYTE[Format.Legacy] = {}
|
130 | VERSION_BYTE[Format.Legacy][Network.Mainnet] = {}
|
131 | VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH] = 0
|
132 | VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH] = 5
|
133 | VERSION_BYTE[Format.Legacy][Network.Testnet] = {}
|
134 | VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH] = 111
|
135 | VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH] = 196
|
136 | VERSION_BYTE[Format.Bitpay] = {}
|
137 | VERSION_BYTE[Format.Bitpay][Network.Mainnet] = {}
|
138 | VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH] = 28
|
139 | VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH] = 40
|
140 | VERSION_BYTE[Format.Bitpay][Network.Testnet] = {}
|
141 | VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2PKH] = 111
|
142 | VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2SH] = 196
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | function decodeAddress (address) {
|
152 | try {
|
153 | return decodeBase58Address(address)
|
154 | } catch (error) {
|
155 | }
|
156 | try {
|
157 | return decodeCashAddress(address)
|
158 | } catch (error) {
|
159 | }
|
160 | throw new InvalidAddressError()
|
161 | }
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | var BASE_58_CHECK_PAYLOAD_LENGTH = 21
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | function decodeBase58Address (address) {
|
178 | try {
|
179 | var payload = bs58check.decode(address)
|
180 | if (payload.length !== BASE_58_CHECK_PAYLOAD_LENGTH) {
|
181 | throw new InvalidAddressError()
|
182 | }
|
183 | var versionByte = payload[0]
|
184 | var hash = Array.prototype.slice.call(payload, 1)
|
185 | switch (versionByte) {
|
186 | case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH]:
|
187 | return {
|
188 | hash: hash,
|
189 | format: Format.Legacy,
|
190 | network: Network.Mainnet,
|
191 | type: Type.P2PKH
|
192 | }
|
193 | case VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH]:
|
194 | return {
|
195 | hash: hash,
|
196 | format: Format.Legacy,
|
197 | network: Network.Mainnet,
|
198 | type: Type.P2SH
|
199 | }
|
200 | case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH]:
|
201 | return {
|
202 | hash: hash,
|
203 | format: Format.Legacy,
|
204 | network: Network.Testnet,
|
205 | type: Type.P2PKH
|
206 | }
|
207 | case VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH]:
|
208 | return {
|
209 | hash: hash,
|
210 | format: Format.Legacy,
|
211 | network: Network.Testnet,
|
212 | type: Type.P2SH
|
213 | }
|
214 | case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH]:
|
215 | return {
|
216 | hash: hash,
|
217 | format: Format.Bitpay,
|
218 | network: Network.Mainnet,
|
219 | type: Type.P2PKH
|
220 | }
|
221 | case VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH]:
|
222 | return {
|
223 | hash: hash,
|
224 | format: Format.Bitpay,
|
225 | network: Network.Mainnet,
|
226 | type: Type.P2SH
|
227 | }
|
228 | }
|
229 | } catch (error) {
|
230 | }
|
231 | throw new InvalidAddressError()
|
232 | }
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | function decodeCashAddress (address) {
|
242 | if (address.indexOf(':') !== -1) {
|
243 | try {
|
244 | return decodeCashAddressWithPrefix(address)
|
245 | } catch (error) {
|
246 | }
|
247 | } else {
|
248 | var prefixes = ['bitcoincash', 'bchtest', 'bchreg']
|
249 | for (var i = 0; i < prefixes.length; ++i) {
|
250 | try {
|
251 | var prefix = prefixes[i]
|
252 | return decodeCashAddressWithPrefix(prefix + ':' + address)
|
253 | } catch (error) {
|
254 | }
|
255 | }
|
256 | }
|
257 | throw new InvalidAddressError()
|
258 | }
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 | function decodeCashAddressWithPrefix (address) {
|
268 | try {
|
269 | var decoded = cashaddr.decode(address)
|
270 | var hash = Array.prototype.slice.call(decoded.hash, 0)
|
271 | var type = decoded.type === 'P2PKH' ? Type.P2PKH : Type.P2SH
|
272 | switch (decoded.prefix) {
|
273 | case 'bitcoincash':
|
274 | return {
|
275 | hash: hash,
|
276 | format: Format.Cashaddr,
|
277 | network: Network.Mainnet,
|
278 | type: type
|
279 | }
|
280 | case 'bchtest':
|
281 | case 'bchreg':
|
282 | return {
|
283 | hash: hash,
|
284 | format: Format.Cashaddr,
|
285 | network: Network.Testnet,
|
286 | type: type
|
287 | }
|
288 | }
|
289 | } catch (error) {
|
290 | }
|
291 | throw new InvalidAddressError()
|
292 | }
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 | function encodeAsLegacy (decoded) {
|
301 | var versionByte = VERSION_BYTE[Format.Legacy][decoded.network][decoded.type]
|
302 | var buffer = Buffer.alloc(1 + decoded.hash.length)
|
303 | buffer[0] = versionByte
|
304 | buffer.set(decoded.hash, 1)
|
305 | return bs58check.encode(buffer)
|
306 | }
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 | function encodeAsBitpay (decoded) {
|
315 | var versionByte = VERSION_BYTE[Format.Bitpay][decoded.network][decoded.type]
|
316 | var buffer = Buffer.alloc(1 + decoded.hash.length)
|
317 | buffer[0] = versionByte
|
318 | buffer.set(decoded.hash, 1)
|
319 | return bs58check.encode(buffer)
|
320 | }
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 | function encodeAsCashaddr (decoded) {
|
329 | var prefix = decoded.network === Network.Mainnet ? 'bitcoincash' : 'bchtest'
|
330 | var type = decoded.type === Type.P2PKH ? 'P2PKH' : 'P2SH'
|
331 | var hash = new Uint8Array(decoded.hash)
|
332 | return cashaddr.encode(prefix, type, hash)
|
333 | }
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 | function isLegacyAddress (address) {
|
343 | return detectAddressFormat(address) === Format.Legacy
|
344 | }
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 | function isBitpayAddress (address) {
|
354 | return detectAddressFormat(address) === Format.Bitpay
|
355 | }
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 | function isCashAddress (address) {
|
365 | return detectAddressFormat(address) === Format.Cashaddr
|
366 | }
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 | function isMainnetAddress (address) {
|
376 | return detectAddressNetwork(address) === Network.Mainnet
|
377 | }
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 |
|
386 | function isTestnetAddress (address) {
|
387 | return detectAddressNetwork(address) === Network.Testnet
|
388 | }
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 | function isP2PKHAddress (address) {
|
398 | return detectAddressType(address) === Type.P2PKH
|
399 | }
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 | function isP2SHAddress (address) {
|
409 | return detectAddressType(address) === Type.P2SH
|
410 | }
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 | function InvalidAddressError () {
|
418 | var error = new Error()
|
419 | this.name = error.name = 'InvalidAddressError'
|
420 | this.message = error.message = 'Received an invalid Bitcoin Cash address as input.'
|
421 | this.stack = error.stack
|
422 | }
|
423 |
|
424 | InvalidAddressError.prototype = Object.create(Error.prototype)
|
425 |
|
426 | module.exports = {
|
427 | Format: Format,
|
428 | Network: Network,
|
429 | Type: Type,
|
430 | detectAddressFormat: detectAddressFormat,
|
431 | detectAddressNetwork: detectAddressNetwork,
|
432 | detectAddressType: detectAddressType,
|
433 | toLegacyAddress: toLegacyAddress,
|
434 | toBitpayAddress: toBitpayAddress,
|
435 | toCashAddress: toCashAddress,
|
436 | isLegacyAddress: isLegacyAddress,
|
437 | isBitpayAddress: isBitpayAddress,
|
438 | isCashAddress: isCashAddress,
|
439 | isMainnetAddress: isMainnetAddress,
|
440 | isTestnetAddress: isTestnetAddress,
|
441 | isP2PKHAddress: isP2PKHAddress,
|
442 | isP2SHAddress: isP2SHAddress,
|
443 | InvalidAddressError: InvalidAddressError
|
444 | }
|