UNPKG

12.2 kBJavaScriptView Raw
1/***
2 * @license
3 * https://github.com/bitcoincashjs/bchaddr
4 * Copyright (c) 2018 Emilio Almansi
5 * Distributed under the MIT software license, see the accompanying
6 * file LICENSE or http://www.opensource.org/licenses/mit-license.php.
7 */
8
9var bs58check = require('bs58check')
10var cashaddr = require('cashaddrjs')
11
12/**
13 * General purpose Bitcoin Cash address detection and translation.<br />
14 * Supports all major Bitcoin Cash address formats.<br />
15 * Currently:
16 * <ul>
17 * <li> Legacy format </li>
18 * <li> Bitpay format </li>
19 * <li> Cashaddr format </li>
20 * </ul>
21 * @module bchaddr
22 */
23
24/**
25 * @static
26 * Supported Bitcoin Cash address formats.
27 */
28var Format = {}
29Format.Legacy = 'legacy'
30Format.Bitpay = 'bitpay'
31Format.Cashaddr = 'cashaddr'
32
33/**
34 * @static
35 * Supported networks.
36 */
37var Network = {}
38Network.Mainnet = 'mainnet'
39Network.Testnet = 'testnet'
40
41/**
42 * @static
43 * Supported address types.
44 */
45var Type = {}
46Type.P2PKH = 'p2pkh'
47Type.P2SH = 'p2sh'
48
49/**
50 * Detects what is the given address' format.
51 * @static
52 * @param {string} address - A valid Bitcoin Cash address in any format.
53 * @return {string}
54 * @throws {InvalidAddressError}
55 */
56function detectAddressFormat (address) {
57 return decodeAddress(address).format
58}
59
60/**
61 * Detects what is the given address' network.
62 * @static
63 * @param {string} address - A valid Bitcoin Cash address in any format.
64 * @return {string}
65 * @throws {InvalidAddressError}
66 */
67function detectAddressNetwork (address) {
68 return decodeAddress(address).network
69}
70
71/**
72 * Detects what is the given address' type.
73 * @static
74 * @param {string} address - A valid Bitcoin Cash address in any format.
75 * @return {string}
76 * @throws {InvalidAddressError}
77 */
78function detectAddressType (address) {
79 return decodeAddress(address).type
80}
81
82/**
83 * Translates the given address into legacy format.
84 * @static
85 * @param {string} address - A valid Bitcoin Cash address in any format.
86 * @return {string}
87 * @throws {InvalidAddressError}
88 */
89function 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 * Translates the given address into bitpay format.
99 * @static
100 * @param {string} address - A valid Bitcoin Cash address in any format.
101 * @return {string}
102 * @throws {InvalidAddressError}
103 */
104function 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 * Translates the given address into cashaddr format.
114 * @static
115 * @param {string} address - A valid Bitcoin Cash address in any format.
116 * @return {string}
117 * @throws {InvalidAddressError}
118 */
119function toCashAddress (address) {
120 var decoded = decodeAddress(address)
121 return encodeAsCashaddr(decoded)
122}
123
124/**
125 * Version byte table for base58 formats.
126 * @private
127 */
128var VERSION_BYTE = {}
129VERSION_BYTE[Format.Legacy] = {}
130VERSION_BYTE[Format.Legacy][Network.Mainnet] = {}
131VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2PKH] = 0
132VERSION_BYTE[Format.Legacy][Network.Mainnet][Type.P2SH] = 5
133VERSION_BYTE[Format.Legacy][Network.Testnet] = {}
134VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2PKH] = 111
135VERSION_BYTE[Format.Legacy][Network.Testnet][Type.P2SH] = 196
136VERSION_BYTE[Format.Bitpay] = {}
137VERSION_BYTE[Format.Bitpay][Network.Mainnet] = {}
138VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2PKH] = 28
139VERSION_BYTE[Format.Bitpay][Network.Mainnet][Type.P2SH] = 40
140VERSION_BYTE[Format.Bitpay][Network.Testnet] = {}
141VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2PKH] = 111
142VERSION_BYTE[Format.Bitpay][Network.Testnet][Type.P2SH] = 196
143
144/**
145 * Decodes the given address into its constituting hash, format, network and type.
146 * @private
147 * @param {string} address - A valid Bitcoin Cash address in any format.
148 * @return {object}
149 * @throws {InvalidAddressError}
150 */
151function 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 * Length of a valid base58check encoding payload: 1 byte for
165 * the version byte plus 20 bytes for a RIPEMD-160 hash.
166 * @private
167 */
168var BASE_58_CHECK_PAYLOAD_LENGTH = 21
169
170/**
171 * Attempts to decode the given address assuming it is a base58 address.
172 * @private
173 * @param {string} address - A valid Bitcoin Cash address in any format.
174 * @return {object}
175 * @throws {InvalidAddressError}
176 */
177function 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 * Attempts to decode the given address assuming it is a cashaddr address.
236 * @private
237 * @param {string} address - A valid Bitcoin Cash address in any format.
238 * @return {object}
239 * @throws {InvalidAddressError}
240 */
241function 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 * Attempts to decode the given address assuming it is a cashaddr address with explicit prefix.
262 * @private
263 * @param {string} address - A valid Bitcoin Cash address in any format.
264 * @return {object}
265 * @throws {InvalidAddressError}
266 */
267function 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 * Encodes the given decoded address into legacy format.
296 * @private
297 * @param {object} decoded
298 * @returns {string}
299 */
300function 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 * Encodes the given decoded address into bitpay format.
310 * @private
311 * @param {object} decoded
312 * @returns {string}
313 */
314function 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 * Encodes the given decoded address into cashaddr format.
324 * @private
325 * @param {object} decoded
326 * @returns {string}
327 */
328function 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 * Returns a boolean indicating whether the address is in legacy format.
337 * @static
338 * @param {string} address - A valid Bitcoin Cash address in any format.
339 * @returns {boolean}
340 * @throws {InvalidAddressError}
341 */
342function isLegacyAddress (address) {
343 return detectAddressFormat(address) === Format.Legacy
344}
345
346/**
347 * Returns a boolean indicating whether the address is in bitpay format.
348 * @static
349 * @param {string} address - A valid Bitcoin Cash address in any format.
350 * @returns {boolean}
351 * @throws {InvalidAddressError}
352 */
353function isBitpayAddress (address) {
354 return detectAddressFormat(address) === Format.Bitpay
355}
356
357/**
358 * Returns a boolean indicating whether the address is in cashaddr format.
359 * @static
360 * @param {string} address - A valid Bitcoin Cash address in any format.
361 * @returns {boolean}
362 * @throws {InvalidAddressError}
363 */
364function isCashAddress (address) {
365 return detectAddressFormat(address) === Format.Cashaddr
366}
367
368/**
369 * Returns a boolean indicating whether the address is a mainnet address.
370 * @static
371 * @param {string} address - A valid Bitcoin Cash address in any format.
372 * @returns {boolean}
373 * @throws {InvalidAddressError}
374 */
375function isMainnetAddress (address) {
376 return detectAddressNetwork(address) === Network.Mainnet
377}
378
379/**
380 * Returns a boolean indicating whether the address is a testnet address.
381 * @static
382 * @param {string} address - A valid Bitcoin Cash address in any format.
383 * @returns {boolean}
384 * @throws {InvalidAddressError}
385 */
386function isTestnetAddress (address) {
387 return detectAddressNetwork(address) === Network.Testnet
388}
389
390/**
391 * Returns a boolean indicating whether the address is a p2pkh address.
392 * @static
393 * @param {string} address - A valid Bitcoin Cash address in any format.
394 * @returns {boolean}
395 * @throws {InvalidAddressError}
396 */
397function isP2PKHAddress (address) {
398 return detectAddressType(address) === Type.P2PKH
399}
400
401/**
402 * Returns a boolean indicating whether the address is a p2sh address.
403 * @static
404 * @param {string} address - A valid Bitcoin Cash address in any format.
405 * @returns {boolean}
406 * @throws {InvalidAddressError}
407 */
408function isP2SHAddress (address) {
409 return detectAddressType(address) === Type.P2SH
410}
411
412/**
413 * Error thrown when the address given as input is not a valid Bitcoin Cash address.
414 * @constructor
415 * InvalidAddressError
416 */
417function 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
424InvalidAddressError.prototype = Object.create(Error.prototype)
425
426module.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}