bitcore-lib
Version:
A pure and powerful JavaScript Bitcoin library.
544 lines (456 loc) • 23.1 kB
JavaScript
/* jshint maxstatements: 30 */
const chai = require('chai');
const taprootAccounts = require('./data/taproot_accounts');
const bitcore = require('..');
const should = chai.should();
const expect = chai.expect;
const PublicKey = bitcore.PublicKey;
const Address = bitcore.Address;
const Script = bitcore.Script;
const Networks = bitcore.Networks;
describe('Witness Address', function() {
var pubkeyhash = Buffer.from('2a9540f5cd929bf742d16b4e1bf1b0e874c907c9', 'hex');
var str = 'bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl';
var wrappedStr = '3LfTZncZYsaxGBWYfDg8MTTFVKHmUHoZyA';
var buf = Buffer.from(str, 'utf8');
it('should throw an error because of bad network param', function() {
(function() {
return new Address(P2WPKHLivenet[0], 'main', 'witnesspubkeyhash');
}).should.throw('Second argument must be "livenet" or "testnet".');
});
it('should throw an error because of bad type param', function() {
(function() {
return new Address(P2WPKHLivenet[0], 'livenet', 'pubkey');
}).should.throw('Third argument must be "pubkeyhash", "scripthash", "witnesspubkeyhash", "witnessscripthash", or "taproot".');
});
// livenet valid
var P2WPKHLivenet = [
'bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl',
'bc1q9225pawtn2dlwsk3dd8phudsap6vjp7f2h4044',
'bc1q9225pewdn2dlwsk3dd8phudsap6vjp7f8umq0y',
'bc1q9225rawdn2dlwsk3dd8phudsap6vjp7fgwh45q',
' bc1q92z5pawdn2dlwsk3dd8phudsap6vjp7f82uucr \t\n'
];
// livenet p2wsh
var P2WSHLivenet = [
'bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvsdffnze',
'bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvqcgpth2',
'bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0yctsfgd86t',
'bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0yctquf9l0c',
'\t \nbc1qr225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0yctqtdwmes \r'
];
// testnet p2Wsh
var P2WSHTestnet = [
'tb1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvs6pluck',
'tb1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvq0qhyd9',
'tb1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycts7qmgqy',
'tb1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0yctqtpns4h'
];
//livenet bad checksums
var badChecksums = [
'bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdd',
'bc1q9225pawtn2dlwsk3dd8phudsap6vjp7f2h4040',
'bc1q9225pewdn2dlwsk3dd8phudsap6vjp7f8umq00',
'bc1q9225rawdn2dlwsk3dd8phudsap6vjp7fgwh455'
];
// incorrect witness version
var incorrectWitnessVersions = [
['tb1lnuql3d5r8fpkezf9jyvfjcczrwtfjndksaquvfr782uf7xvmpeuq3dyfg2', 'testnet', 'taproot'], // version 31
];
// incorrect witness encoding
var incorrectWitnessEncoding = [
['bc1p9225pawdj2dlwsk3dd8phudsap6vjp7fr0y9q5', 'livenet', 'witnesspubkeyhash'],
['bc1p9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvsjzekl8', 'livenet', 'witnesspubkeyhash']
];
//testnet valid
var P2WPKHTestnet = [
'tb1q5lrlddcjejvu0qyx0f5fg59zj89danlxtt058g',
'tb1qrqsut4l6payxr9zda6s74jsgupc096t40k234h',
'tb1qkjxpx3kzdqj3qydxfsd88rj8vwzy2ry9luturg',
'tb1qa38kkwah0mncpn29j6xlzv4xa5m3wrr0juyt2j'
];
// taproot addresses generated using instructions here: https://bitcoin.stackexchange.com/questions/108006/how-to-make-a-taproot-transaction-with-bitcoin-cli
// "desc" used in descriptor.txt: "tr([8868ab13/86'/1'/0']tprv8ZgxMBicQKsPe7EVJZjbgFcxVx51KrdMU8MicmR6KBbtTYhuzzdFK8Q7B6GXaTJxaAfpw1gF1dXDjg1DD3K8dchjMVeS214MSFj1bA5iAnH/0/*)#l425xznh"
// testnet taproot valid
var P2TRTestnet = [
'tb1pnuql3d5r8fpkezf9jyvfjcczrwtfjndksaquvfr782uf7xvmpeuqnqg5lk',
'tb1p6qef90ncxz25pq59c0dfjfezjlk5l0fq7n3w0axh63usmtugtvhqylnyxq',
'tb1palsux05ufcpg25al0krew5szfj03vejkqfxpz9kd26ghvruw33qq8q9ykg',
'tb1p9g2t30jj3djsn0tlaf6en2pq5qu7vgknyhjg3n54zcmglzy2t52qemw4dy',
'tb1pkadknwnukaxnpkg9wwp3430dd0w0shfw4f6ry3e68my0vyz3we5qh6qwhf',
'tb1pwjhr3ttlpvshcgrwz7h4asfusyj6angdxx2yvx6q8ds08upk699s44d4ck'
];
describe('validation', function() {
it('getValidationError detects network mismatchs', function() {
var error = Address.getValidationError('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl', 'testnet');
should.exist(error);
});
it('isValid returns true on a valid address', function() {
var valid = Address.isValid('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl', 'livenet');
valid.should.equal(true);
});
it('isValid returns false on network mismatch', function() {
var valid = Address.isValid('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl', 'testnet');
valid.should.equal(false);
});
it('validates correctly the P2WPKH test vector', function() {
for (var i = 0; i < P2WPKHLivenet.length; i++) {
var error = Address.getValidationError(P2WPKHLivenet[i]);
should.not.exist(error);
}
});
it('validates correctly the P2WSH test vector', function() {
for (var i = 0; i < P2WSHLivenet.length; i++) {
var error = Address.getValidationError(P2WSHLivenet[i]);
should.not.exist(error);
}
});
it('validates correctly the P2WSH testnet test vector', function() {
for (var i = 0; i < P2WSHTestnet.length; i++) {
var error = Address.getValidationError(P2WSHTestnet[i], 'testnet');
should.not.exist(error);
}
});
it('rejects correctly the P2WPKH livenet test vector with "testnet" parameter', function() {
for (var i = 0; i < P2WPKHLivenet.length; i++) {
var error = Address.getValidationError(P2WPKHLivenet[i], 'testnet');
should.exist(error);
}
});
it('validates correctly the P2WPKH livenet test vector with "livenet" parameter', function() {
for (var i = 0; i < P2WPKHLivenet.length; i++) {
var error = Address.getValidationError(P2WPKHLivenet[i], 'livenet');
should.not.exist(error);
}
});
it('validates correctly the P2TR testnet vector', function() {
for (var i = 0; i < P2TRTestnet.length; i++) {
var error = Address.getValidationError(P2TRTestnet[i], 'testnet');
should.not.exist(error);
}
});
it('should not validate if checksum is invalid', function() {
for (var i = 0; i < badChecksums.length; i++) {
var error = Address.getValidationError(badChecksums[i], 'livenet', 'witnesspubkeyhash');
should.exist(error);
error.message.should.equal('Invalid checksum for ' + badChecksums[i]);
}
});
it('should not validate if wrong witness version', function() {
for (var i = 0; i < incorrectWitnessVersions.length; i++) {
var [address, network, type] = incorrectWitnessVersions[i];
var error = Address.getValidationError(address, network, type);
should.exist(error);
error.message.should.equal('Only witness v0 and v1 addresses are supported.');
}
});
it('should not validate if wrong witness encoding', function() {
for (var i = 0; i < incorrectWitnessEncoding.length; i++) {
var [address, network, type] = incorrectWitnessEncoding[i];
var error = Address.getValidationError(address, network, type);
should.exist(error);
error.message.should.equal('Version 1+ witness address must use Bech32m checksum');
}
});
it('should not validate on a network mismatch', function() {
var error, i;
for (i = 0; i < P2WPKHLivenet.length; i++) {
error = Address.getValidationError(P2WPKHLivenet[i], 'testnet', 'witnesspubkeyhash');
should.exist(error);
error.message.should.equal('Address has mismatched network type.');
}
for (i = 0; i < P2WPKHTestnet.length; i++) {
error = Address.getValidationError(P2WPKHTestnet[i], 'livenet', 'witnesspubkeyhash');
should.exist(error);
error.message.should.equal('Address has mismatched network type.');
}
});
it('should not validate on a type mismatch', function() {
for (var i = 0; i < P2WPKHLivenet.length; i++) {
var error = Address.getValidationError(P2WPKHLivenet[i], 'livenet', 'witnessscripthash');
should.exist(error);
error.message.should.equal('Address has mismatched type.');
}
});
it('testnet addresses are validated correctly', function() {
for (var i = 0; i < P2WPKHTestnet.length; i++) {
var error = Address.getValidationError(P2WPKHTestnet[i], 'testnet');
should.not.exist(error);
}
});
it('addresses with whitespace are validated correctly', function() {
var ws = ' \r \t \n bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl \t \n \r';
var error = Address.getValidationError(ws);
should.not.exist(error);
Address.fromString(ws).toString().should.equal('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl');
});
});
describe('instantiation', function() {
it('can be instantiated from another address', function() {
var address = Address.fromBuffer(buf);
var address2 = new Address({
hashBuffer: address.hashBuffer,
network: address.network,
type: address.type
});
address.toString().should.equal(address2.toString());
});
it('can be instantiated from a taproot address', function() {
for (const account of taprootAccounts) {
const address = new Address(account.address);
address.toString().should.equal(account.address);
}
});
});
describe('encodings', function() {
it('should make an address from a buffer', function() {
Address.fromBuffer(buf).toString().should.equal(str);
new Address(buf).toString().should.equal(str);
new Address(buf).toString().should.equal(str);
});
it('should make an address from a string', function() {
Address.fromString(str).toString().should.equal(str);
new Address(str).toString().should.equal(str);
});
it('should make an address using a non-string network', function() {
Address.fromString(str, Networks.livenet).toString().should.equal(str);
});
it('should throw with bad network param', function() {
(function(){
Address.fromString(str, 'somenet');
}).should.throw('Unknown network');
});
it('should error because of incorrect format for script hash', function() {
(function() {
return new Address.fromScriptHash('notascript', null, Address.PayToWitnessScriptHash);
}).should.throw('Address supplied is not a buffer.');
});
it('should error because of incorrect type for pubkey transform', function() {
(function() {
return Address._transformPublicKey(Buffer.alloc(20), null, Address.PayToWitnessPublicKeyHash);
}).should.throw('Address must be an instance of PublicKey.');
(function() {
return Address._transformPublicKey(Buffer.alloc(20), null, Address.PayToScriptHash);
}).should.throw('Address must be an instance of PublicKey.');
});
it('should make this address from a compressed pubkey', function() {
var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004');
var address = Address.fromPublicKey(pubkey, 'livenet', Address.PayToWitnessPublicKeyHash);
address.toString().should.equal('bc1qtuh205nkztchej8r84k8vna9upsjh7q8dvy576');
});
it('should make this wrapped address from a compressed pubkey', function() {
var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004');
var address = Address.fromPublicKey(pubkey, 'livenet', Address.PayToScriptHash);
address.toString().should.equal('3GNVVBik6S9Ux5ccS6ymmEeQELXGJdP8p8');
});
it('should use the default network for pubkey', function() {
var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004');
var address = Address.fromPublicKey(pubkey, null, Address.PayToWitnessPublicKeyHash);
address.network.should.equal(Networks.defaultNetwork);
});
it('should use the default network for pubkey', function() {
var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004');
var address = Address.fromPublicKey(pubkey, null, Address.PayToScriptHash);
address.network.should.equal(Networks.defaultNetwork);
});
it('should fail to make an address with an uncompressed pubkey', function() {
var pubkey = new PublicKey('0485e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b00' +
'4833fef26c8be4c4823754869ff4e46755b85d851077771c220e2610496a29d98');
(function() {
return Address.fromPublicKey(pubkey, 'livenet', Address.PayToWitnessPublicKeyHash);
}).should.throw('Witness addresses must use compressed public keys.');
});
it('should fail to make a wrapped address with an uncompressed pubkey', function() {
var pubkey = new PublicKey('0485e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b00' +
'4833fef26c8be4c4823754869ff4e46755b85d851077771c220e2610496a29d98');
(function() {
return Address.fromPublicKey(pubkey, 'livenet', Address.PayToScriptHash);
}).should.throw('Witness addresses must use compressed public keys.');
});
it('should classify from a custom network', function() {
var custom = {
name: 'customnetwork2',
pubkeyhash: 0x1c,
privatekey: 0x1e,
scripthash: 0x28,
bech32prefix: 'abc',
xpubkey: 0x02e8de8f,
xprivkey: 0x02e8da54,
networkMagic: 0x0c110907,
port: 7333
};
var addressString = 'abc1q9225pawdj2dlwsk3dd8phudsap6vjp7fzfr9m9';
Networks.add(custom);
var network = Networks.get('customnetwork2');
var address = Address.fromString(addressString);
address.type.should.equal(Address.PayToWitnessPublicKeyHash);
address.network.should.equal(network);
Networks.remove(network);
});
describe('from a script', function() {
it('should make this address from a p2wpkh output script', function() {
var s = new Script('OP_0 20 ' +
'0x2a9540f5cd929bf742d16b4e1bf1b0e874c907c9');
var buf = s.toBuffer();
var a = Address.fromScript(s, 'livenet');
a.toString().should.equal('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl');
var b = new Address(s, 'livenet');
b.toString().should.equal('bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl');
});
it('should make this address from a p2wsh input script', function() {
var s = Script.fromString('OP_0 32 0x2a9540f5cd9a9bf742d16b4e1bf1b0e874c907c9b825474c614569e0480f2619');
var a = Address.fromScript(s, 'livenet');
a.toString().should.equal('bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvsdffnze');
var b = new Address(s, 'livenet');
b.toString().should.equal('bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvsdffnze');
});
it('returns the same address if the script is a pay to witness public key hash out', function() {
var address = 'bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl';
var script = Script.buildWitnessV0Out(new Address(address));
Address(script, Networks.livenet).toString().should.equal(address);
});
it('returns the same address if the script is a pay to witness script hash out', function() {
var address = 'bc1q9225pawdn2dlwsk3dd8phudsap6vjp7fhqj5wnrpg457qjq0ycvsdffnze';
var script = Script.buildWitnessV0Out(new Address(address));
Address(script, Networks.livenet).toString().should.equal(address);
});
});
it('should derive from this known address string livenet', function() {
var address = new Address(str);
var buffer = address.toBuffer();
buffer.toString().should.equal(Buffer.from(str, 'utf8').toString());
});
it('should derive from this known address string testnet', function() {
var a = new Address(P2WPKHTestnet[0], 'testnet');
var b = new Address(a.toString());
b.toString().should.equal(P2WPKHTestnet[0]);
b.network.should.equal(Networks.testnet);
});
it('should derive from this known address string livenet witness scripthash', function() {
var a = new Address(P2WSHLivenet[0], 'livenet', 'witnessscripthash');
var b = new Address(a.toString());
b.toString().should.equal(P2WSHLivenet[0]);
});
it('should derive from this known address string testnet witness scripthash', function() {
var address = new Address(P2WSHTestnet[0], 'testnet', 'witnessscripthash');
address = new Address(address.toString());
address.toString().should.equal(P2WSHTestnet[0]);
});
});
describe('#toBuffer', function() {
it('2a9540f5cd929bf742d16b4e1bf1b0e874c907c9 corresponds to hash bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl', function() {
var address = new Address(str);
var fromBuffer = new Address(address.toBuffer())
address.hashBuffer.toString('hex').should.equal(pubkeyhash.toString('hex'));
});
});
describe('#object', function() {
it('roundtrip to-from-to', function() {
var obj = new Address(str).toObject();
var address = Address.fromObject(obj);
address.toString().should.equal(str);
});
});
describe('#toString', function() {
it('livenet witnesspubkeyhash address', function() {
var address = new Address(str);
address.toString().should.equal(str);
});
it('witnessscripthash address', function() {
var address = new Address(P2WSHLivenet[0]);
address.toString().should.equal(P2WSHLivenet[0]);
});
it('testnet witnessscripthash address', function() {
var address = new Address(P2WSHTestnet[0]);
address.toString().should.equal(P2WSHTestnet[0]);
});
it('testnet witnesspubkeyhash address', function() {
var address = new Address(P2WPKHTestnet[0]);
address.toString().should.equal(P2WPKHTestnet[0]);
});
});
describe('#inspect', function() {
it('should output formatted output correctly', function() {
var address = new Address(str);
var output = '<Address: bc1q9225pawdj2dlwsk3dd8phudsap6vjp7fg3nwdl, type: witnesspubkeyhash, network: livenet>';
address.inspect().should.equal(output);
});
});
describe('questions about the address', function() {
it('should detect a P2WSH address', function() {
new Address(P2WSHLivenet[0]).isPayToWitnessScriptHash().should.equal(true);
new Address(P2WSHLivenet[0]).isPayToWitnessPublicKeyHash().should.equal(false);
new Address(P2WSHTestnet[0]).isPayToWitnessScriptHash().should.equal(true);
new Address(P2WSHTestnet[0]).isPayToWitnessPublicKeyHash().should.equal(false);
});
it('should detect a Pay To Witness PubkeyHash address', function() {
new Address(P2WPKHLivenet[0]).isPayToWitnessPublicKeyHash().should.equal(true);
new Address(P2WPKHLivenet[0]).isPayToWitnessScriptHash().should.equal(false);
new Address(P2WPKHTestnet[0]).isPayToWitnessPublicKeyHash().should.equal(true);
new Address(P2WPKHTestnet[0]).isPayToWitnessScriptHash().should.equal(false);
});
});
it('can roundtrip from/to a object', function() {
var address = new Address(P2WSHLivenet[0]);
expect(new Address(address.toObject()).toString()).to.equal(P2WSHLivenet[0]);
});
it('will use the default network for an object', function() {
var obj = {
hash: '2a9540f5cd9a9bf742d16b4e1bf1b0e874c907c9b825474c614569e0480f2619',
type: 'witnessscripthash'
};
var address = new Address(obj);
address.network.should.equal(Networks.defaultNetwork);
});
describe('creating a P2WSH address from public keys', function() {
var public1 = '02da5798ed0c055e31339eb9b5cef0d3c0ccdec84a62e2e255eb5c006d4f3e7f5b';
var public2 = '0272073bf0287c4469a2a011567361d42529cd1a72ab0d86aa104ecc89342ffeb0';
var public3 = '02738a516a78355db138e8119e58934864ce222c553a5407cf92b9c1527e03c1a2';
var publics = [public1, public2, public3];
it('can create an address from a set of public keys', function() {
var address = Address.createMultisig(publics, 2, Networks.livenet, null, Address.PayToWitnessScriptHash);
address.toString().should.equal('bc1qukwqyzxcjdykr0cfxghwkrx9rkmdvapc08syez75q5ewg3j5umvsu5w9yf');
address = new Address(publics, 2, Networks.livenet, Address.PayToWitnessScriptHash);
address.toString().should.equal('bc1qukwqyzxcjdykr0cfxghwkrx9rkmdvapc08syez75q5ewg3j5umvsu5w9yf');
});
it('works on testnet also', function() {
var address = Address.createMultisig(publics, 2, Networks.testnet, null, Address.PayToWitnessScriptHash);
address.toString().should.equal('tb1qukwqyzxcjdykr0cfxghwkrx9rkmdvapc08syez75q5ewg3j5umvstuc27x');
});
it('can also be created by Address.createMultisig', function() {
var address = Address.createMultisig(publics, 2, null, null, Address.PayToWitnessScriptHash);
var address2 = Address.createMultisig(publics, 2, null, null, Address.PayToWitnessScriptHash);
address.toString().should.equal(address2.toString());
});
it('fails if invalid array is provided', function() {
expect(function() {
return Address.createMultisig([], 3, 'testnet', null, Address.PayToWitnessScriptHash);
}).to.throw('Number of required signatures must be less than or equal to the number of public keys');
});
});
describe('taproot', function() {
const priv = new bitcore.HDPrivateKey('xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu');
before(function() {
priv.hdPublicKey.toString().should.equal('xpub661MyMwAqRbcFkPHucMnrGNzDwb6teAX1RbKQmqtEF8kK3Z7LZ59qafCjB9eCRLiTVG3uxBxgKvRgbubRhqSKXnGGb1aoaqLrpMBDrVxga8');
});
for (let i = 0; i < taprootAccounts.length; i++) {
const account = taprootAccounts[i];
it('should create taproot address from pub key - vector ' + i, function() {
const newPriv = priv.deriveChild(account.path);
newPriv.hdPublicKey.publicKey.toAddress('livenet', 'taproot').toString().should.equal(account.address);
});
it('should create taproot address from pub key - step-by-step - vector ' + i, function() {
const newPriv = priv.deriveChild(account.path);
newPriv.xprivkey.should.equal(account.xprv);
newPriv.hdPublicKey.xpubkey.should.equal(account.xpub);
newPriv.hdPublicKey.publicKey.toString().slice(2).should.equal(account.internal_key);
const tweakedKey = newPriv.hdPublicKey.publicKey.createTapTweak();
tweakedKey.tweakedPubKey.toString('hex').should.equal(account.output_key);
const scriptPubKey = new Script().add(81).add(tweakedKey.tweakedPubKey);
scriptPubKey.toHex().should.equal(account.scriptPubKey);
scriptPubKey.toAddress('livenet').toString().should.equal(account.address);
});
}
});
});
;