Browse Source

Add support for Bitpay and CashAddr formats.

master
Emilio Almansi 7 years ago
parent
commit
9b0b4e3fc0
  1. 11
      gulpfile.js
  2. 8
      package-lock.json
  3. 1
      package.json
  4. 74
      src/address.js
  5. 185
      test/address.js

11
gulpfile.js

@ -44,10 +44,19 @@ gulp.task(
gulp.task(
'test:mocha',
shell.task([
'npx nyc --reporter=html --reporter=text npx mocha',
`npx nyc --reporter=html --reporter=text npx mocha ${getTaskArgs()}`,
])
);
function getTaskArgs() {
if (process.argv.length < 4) {
return '';
}
const args = process.argv.splice(3);
const argsWithQuotes = args.map(a => a.indexOf(' ') !== -1 ? `"${a}"` : a);
return argsWithQuotes.join(' ');
}
gulp.task(
'test:karma',
['build:tests'],

8
package-lock.json

@ -1689,6 +1689,14 @@
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
"cashaddrjs": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.1.4.tgz",
"integrity": "sha512-JWBY7IUwOX0h+s5F0xheVTD58qUa1SE1AUFasWUFNLxy/7N9B+LpLzFYb4UCMd/Fy5inRJGY36LJ9FjeuwTBLg==",
"requires": {
"big-integer": "1.6.26"
}
},
"catharsis": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz",

1
package.json

@ -39,6 +39,7 @@
"bn.js": "=2.0.4",
"bs58": "=2.0.0",
"buffer-compare": "=1.0.0",
"cashaddrjs": "^0.1.4",
"elliptic": "=3.0.3",
"inherits": "=2.0.1",
"lodash": "^4.17.4"

74
src/address.js

@ -2,6 +2,7 @@
var _ = require('lodash');
var $ = require('./util/preconditions');
var cashaddr = require('cashaddrjs');
var errors = require('./errors');
var Base58Check = require('./encoding/base58check');
var Networks = require('./networks');
@ -9,6 +10,9 @@ var Hash = require('./crypto/hash');
var JSUtil = require('./util/js');
var PublicKey = require('./publickey');
const BITPAY_P2PKH_VERSION_BYTE = 28;
const BITPAY_P2SH_VERSION_BYTE = 40;
/**
* Instantiate an address from an address String or Buffer, a public key or script hash Buffer,
* or an instance of {@link PublicKey} or {@link Script}.
@ -104,7 +108,7 @@ Address.prototype._classifyArguments = function(data, network, type) {
} else if (data instanceof Script) {
return Address._transformScript(data, network);
} else if (typeof(data) === 'string') {
return Address._transformString(data, network, type);
return Address._transformString(data, network, type, Address.DefaultFormat);
} else if (_.isObject(data)) {
return Address._transformObject(data);
} else {
@ -112,6 +116,15 @@ Address.prototype._classifyArguments = function(data, network, type) {
}
};
/** @static */
Address.LegacyFormat = 'legacy';
/** @static */
Address.BitpayFormat = 'bitpay';
/** @static */
Address.CashAddrFormat = 'cashaddr';
/** @static */
Address.DefaultFormat = Address.LegacyFormat;
/** @static */
Address.PayToPublicKeyHash = 'pubkeyhash';
/** @static */
@ -271,14 +284,34 @@ Address.createMultisig = function(publicKeys, threshold, network) {
* @returns {Object} An object with keys: hashBuffer, network and type
* @private
*/
Address._transformString = function(data, network, type) {
Address._transformString = function(data, network, type, format) {
if (typeof(data) !== 'string') {
throw new TypeError('data parameter supplied is not a string.');
}
data = data.trim();
var addressBuffer = Base58Check.decode(data);
var info = Address._transformBuffer(addressBuffer, network, type);
return info;
if (format === Address.LegacyFormat) {
return Address._transformBuffer(Base58Check.decode(data), network, type);
}
else if (format === Address.BitpayFormat) {
var addressBuffer = Base58Check.decode(data);
if (format === Address.BitpayFormat) {
if (addressBuffer[0] === BITPAY_P2PKH_VERSION_BYTE) {
addressBuffer[0] = 0;
}
else if (addressBuffer[0] === BITPAY_P2SH_VERSION_BYTE) {
addressBuffer[0] = 5;
}
}
return Address._transformBuffer(addressBuffer, network, type);
}
else if (format === Address.CashAddrFormat) {
var networkObject = Networks.get(network);
var version = new Buffer([networkObject[type]]);
var hashBuffer = new Buffer(cashaddr.decode(data).hash);
var addressBuffer = Buffer.concat([version, hashBuffer]);
return Address._transformBuffer(addressBuffer, network, type);
}
throw new TypeError('Unrecognized address format.');
};
/**
@ -375,8 +408,9 @@ Address.fromBuffer = function(buffer, network, type) {
* @param {string=} type - The type of address: 'script' or 'pubkey'
* @returns {Address} A new valid and frozen instance of an Address
*/
Address.fromString = function(str, network, type) {
var info = Address._transformString(str, network, type);
Address.fromString = function(str, network, type, format) {
format = format || Address.DefaultFormat;
var info = Address._transformString(str, network, type, format);
return new Address(info.hashBuffer, info.network, info.type);
};
@ -480,8 +514,30 @@ Address.prototype.toObject = Address.prototype.toJSON = function toObject() {
*
* @returns {string} Bitcoin address
*/
Address.prototype.toString = function() {
return Base58Check.encode(this.toBuffer());
Address.prototype.toString = function(format) {
format = format || Address.DefaultFormat;
if (format === Address.LegacyFormat) {
return Base58Check.encode(this.toBuffer());
}
else if (format === Address.BitpayFormat) {
var buffer = this.toBuffer();
if (this.network.toString() === 'livenet') {
if (this.type === Address.PayToPublicKeyHash) {
buffer[0] = BITPAY_P2PKH_VERSION_BYTE;
}
else if (this.type === Address.PayToScriptHash) {
buffer[0] = BITPAY_P2SH_VERSION_BYTE;
}
}
return Base58Check.encode(buffer);
}
else if (format === Address.CashAddrFormat) {
var prefix = this.network.toString() === 'livenet' ? 'bitcoincash' : 'bchtest';
var type = this.type === Address.PayToPublicKeyHash ? 'P2PKH' : 'P2SH';
var hash = [...this.hashBuffer];
return cashaddr.encode(prefix, type, hash);
}
throw new TypeError('Unrecognized address format.');
};
/**

185
test/address.js

@ -116,6 +116,66 @@ describe('Address', function() {
'mtX8nPZZdJ8d3QNLRJ1oJTiEi26Sj6LQXS'
];
const PKHLivenetBitpay = [
'CMPeBN1BZDzaqU5DF66X5QykLcS1voucT9',
'CRZoT4EafXoYLNJm3bPpTjK3h4q1FSxet4',
'CTHVPhghRAmiLHajoKYTGRyiU8RomQmAfZ',
'CaSvYEmgxVRYiAauWzW1XP4SHkyTiS78yy',
'CaSvYEmgxVRYiAauWzW1XP4SHkyTiS78yy',
];
const P2SHLivenetBitpay = [
'H8rnMErHmZWKpp8H3beDwL8BsSEwzDFSjJ',
'H8kzbJ9Mw46WdAxC8SAFadHn1oNqp6jEsu',
'HCGvZEM8pNyAFBfRrz9Eo4N4eGJPuFahd9',
'HVZezVtqnDwoTZTZ997fZUUGZMetDFUDLf',
'HVZezVtqnDwoTZTZ997fZUUGZMetDFUDLf',
];
const PKHTestnetBitpay = [
'n28S35tqEMbt6vNad7A5K3mZ7vdn8dZ86X',
'n45x3R2w2jaSC62BMa9MeJCd3TXxgvDEmm',
'mursDVxqNQmmwWHACpM9VHwVVSfTddGsEM',
'mtX8nPZZdJ8d3QNLRJ1oJTiEi26Sj6LQXS',
];
const P2SHTestnetBitpay = [
'2N7FuwuUuoTBrDFdrAZ9KxBmtqMLxce9i1C',
'2NEWDzHWwY5ZZp8CQWbB7ouNMLqCia6YRda',
'2MxgPqX1iThW3oZVk9KoFcE5M4JpiETssVN',
'2NB72XtkjpnATMggui83aEtPawyyKvnbX2o',
];
const PKHLivenetCashAddr = [
'bitcoincash:qqmq4ua630cqumzt29ml2jmy8gesega95cjctx4j02',
'bitcoincash:qp3awknl3dz8ezu3rmapff3phnzz95kansf0r3rs4x',
'bitcoincash:qpmtrcpj2fgwfn7f5fxxg7xrx08nayzquuv62srvq4',
'bitcoincash:qrz5xl0qmwxj8q7k57cfgn0avfuf86sr85x2056k40',
'bitcoincash:qrz5xl0qmwxj8q7k57cfgn0avfuf86sr85x2056k40',
];
const P2SHLivenetCashAddr = [
'bitcoincash:pqv60krfqv3k3lglrcnwtee6ftgwgaykpccr8hujjz',
'bitcoincash:pqvglydfxx28ahwhgvkkuc2rsl3jkfz8py95pldjhu',
'bitcoincash:pqljzrnjwlyfnsap2hxpey85zpktmhhvdcjse2m6lm',
'bitcoincash:pr7v23sd6m3yslrawkcevd39mg8g7nzew5zmfd0lrt',
'bitcoincash:pr7v23sd6m3yslrawkcevd39mg8g7nzew5zmfd0lrt',
];
const PKHTestnetCashAddr = [
'bchtest:qr3pswmv0t332gwaedmuhqcp59gswsu2ysdn664dvs',
'bchtest:qrmeqjy9l3me7tchp3szxuu692a9uhwu4ugltjlulm',
'bchtest:qzw4t46ref4lm73d5l4ht3nzse987ee2tsv9zydr5v',
'bchtest:qz82yclajj49kq3cnqk5khs9h2qx5drfruglvwmnac',
];
const P2SHTestnetCashAddr = [
'bchtest:pzvmx80heyrg69ypkkt90rwmknfmmy96av8f02lrrf',
'bchtest:pr5npcvrffxjx3czwuu4r438en5zlw6a9cm22jfau6',
'bchtest:pqaek07h55x57zx35kc0vtmyf7n3zkhz7vxlq6zven',
'bchtest:prp72h7we64y8y0d92t80a9y6d82e5pp5qafr2whk4',
];
describe('validation', function() {
it('getValidationError detects network mismatchs', function() {
@ -558,4 +618,129 @@ describe('Address', function() {
});
});
describe('Address formats', function() {
it('should throw an error if given an invalid format', function() {
(function() {
new Address(PKHLivenet[0]).toString('some invalid format');
}).should.throw('Unrecognized address format.');
});
it('should successfully convert address into Bitpay format', function() {
for (const i in PKHLivenet) {
const output = new Address(PKHLivenet[i]).toString(Address.BitpayFormat);
output.should.equal(PKHLivenetBitpay[i]);
}
for (const i in P2SHLivenet) {
const output = new Address(P2SHLivenet[i]).toString(Address.BitpayFormat);
output.should.equal(P2SHLivenetBitpay[i]);
}
for (const i in PKHTestnet) {
const output = new Address(PKHTestnet[i]).toString(Address.BitpayFormat);
output.should.equal(PKHTestnetBitpay[i]);
}
for (const i in P2SHTestnet) {
const output = new Address(P2SHTestnet[i]).toString(Address.BitpayFormat);
output.should.equal(P2SHTestnetBitpay[i]);
}
});
it('should successfully decode address in Bitpay format', function() {
for (const i in PKHLivenetBitpay) {
const address = Address.fromString(
PKHLivenetBitpay[i],
'livenet' ,
Address.PayToPublicKeyHash,
Address.BitpayFormat
);
address.toString().should.equal(PKHLivenet[i].trim());
}
for (const i in P2SHLivenetBitpay) {
const address = Address.fromString(
P2SHLivenetBitpay[i],
'livenet' ,
Address.PayToScriptHash,
Address.BitpayFormat
);
address.toString().should.equal(P2SHLivenet[i].trim());
}
for (const i in PKHTestnetBitpay) {
const address = Address.fromString(
PKHTestnetBitpay[i],
'testnet' ,
Address.PayToPublicKeyHash,
Address.BitpayFormat
);
address.toString().should.equal(PKHTestnet[i].trim());
}
for (const i in P2SHTestnetBitpay) {
const address = Address.fromString(
P2SHTestnetBitpay[i],
'testnet' ,
Address.PayToScriptHash,
Address.BitpayFormat
);
address.toString().should.equal(P2SHTestnet[i].trim());
}
});
it('should successfully convert address into CashAddr format', function() {
for (const i in PKHLivenet) {
const output = new Address(PKHLivenet[i]).toString(Address.CashAddrFormat);
output.should.equal(PKHLivenetCashAddr[i]);
}
for (const i in P2SHLivenet) {
const output = new Address(P2SHLivenet[i]).toString(Address.CashAddrFormat);
output.should.equal(P2SHLivenetCashAddr[i]);
}
for (const i in PKHTestnet) {
const output = new Address(PKHTestnet[i]).toString(Address.CashAddrFormat);
output.should.equal(PKHTestnetCashAddr[i]);
}
for (const i in P2SHTestnet) {
const output = new Address(P2SHTestnet[i]).toString(Address.CashAddrFormat);
output.should.equal(P2SHTestnetCashAddr[i]);
}
});
it('should successfully decode address in CashAddr format', function() {
for (const i in PKHLivenetCashAddr) {
const address = Address.fromString(
PKHLivenetCashAddr[i],
'livenet' ,
Address.PayToPublicKeyHash,
Address.CashAddrFormat
);
address.toString().should.equal(PKHLivenet[i].trim());
}
for (const i in P2SHLivenetCashAddr) {
const address = Address.fromString(
P2SHLivenetCashAddr[i],
'livenet' ,
Address.PayToScriptHash,
Address.CashAddrFormat
);
address.toString().should.equal(P2SHLivenet[i].trim());
}
for (const i in PKHTestnetCashAddr) {
const address = Address.fromString(
PKHTestnetCashAddr[i],
'testnet' ,
Address.PayToPublicKeyHash,
Address.CashAddrFormat
);
address.toString().should.equal(PKHTestnet[i].trim());
}
for (const i in P2SHTestnetCashAddr) {
const address = Address.fromString(
P2SHTestnetCashAddr[i],
'testnet' ,
Address.PayToScriptHash,
Address.CashAddrFormat
);
address.toString().should.equal(P2SHTestnet[i].trim());
}
});
});
});

Loading…
Cancel
Save