Minor improvements on Address

* Update JSDocs
* Branch coverage 100%
* Removed duplicated test
* Updated names of tests
This commit is contained in:
Esteban Ordano 2014-12-19 11:59:06 -03:00
parent 85169a3874
commit 1ec4c7f512
2 changed files with 102 additions and 99 deletions

View File

@ -17,6 +17,8 @@ var JSUtil = require('./util/js');
* An address has two key properties: `network` and `type`. The type is either
* `Address.PayToPublicKeyHash` (value is the `'pubkeyhash'` string)
* or `Address.PayToScriptHash` (the string `'scripthash'`). The network is an instance of {@link Network}.
* You can quickly check whether an address is of a given kind by using the methods
* `isPayToPublicKeyHash` and `isPayToScriptHash`
*
* @example
* ```javascript
@ -40,6 +42,8 @@ var JSUtil = require('./util/js');
* @constructor
*/
function Address(data, network, type) {
/* jshint maxcomplexity: 12 */
/* jshint maxstatements: 20 */
if (!(this instanceof Address)) {
return new Address(data, network, type);
@ -49,6 +53,11 @@ function Address(data, network, type) {
return Address.createMultisig(data, network, type);
}
if (data instanceof Address) {
// Immutable instance
return data;
}
if (!data) {
throw new TypeError('First argument is required, please include address data.');
}
@ -61,24 +70,7 @@ function Address(data, network, type) {
throw new TypeError('Third argument must be "pubkeyhash" or "scripthash".');
}
var info;
// transform and validate input data
if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 20) {
info = Address._transformHash(data);
} else if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 21) {
info = Address._transformBuffer(data, network, type);
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'PublicKey')) {
info = Address._transformPublicKey(data);
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'Script')) {
info = Address._transformScript(data, network);
} else if (data instanceof Address) {
return data;
} else if (typeof(data) === 'string') {
info = Address._transformString(data, network, type);
} else {
throw new TypeError('First argument is an unrecognized data format.');
}
var info = this._classifyArguments(data, network, type);
// set defaults if not set
info.network = info.network || Networks.get(network) || Networks.defaultNetwork;
@ -102,17 +94,37 @@ function Address(data, network, type) {
return this;
}
/**
* Internal function used to split different kinds of arguments of the constructor
* @param {*} data - The encoded data in various formats
* @param {Network|String|number} [network] - The network: 'livenet' or 'testnet'
* @param {String} [type] - The type of address: 'script' or 'pubkey'
* @returns {Object} An "info" object with "type", "network", and "hashBuffer"
*/
Address.prototype._classifyArguments = function(data, network, type) {
/* jshint maxcomplexity: 10 */
// transform and validate input data
if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 20) {
return Address._transformHash(data);
} else if ((data instanceof Buffer || data instanceof Uint8Array) && data.length === 21) {
return Address._transformBuffer(data, network, type);
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'PublicKey')) {
return Address._transformPublicKey(data);
} else if (data.constructor && (data.constructor.name && data.constructor.name === 'Script')) {
return Address._transformScript(data, network);
} else if (typeof(data) === 'string') {
return Address._transformString(data, network, type);
} else {
throw new TypeError('First argument is an unrecognized data format.');
}
};
/** @static */
Address.PayToPublicKeyHash = 'pubkeyhash';
/**
* @static
* @value 'scripthash'
*/
/** @static */
Address.PayToScriptHash = 'scripthash';
/**
* Internal function to transform a hash buffer
*
* @param {Buffer} hash - An instance of a hash Buffer
* @returns {Object} An object with keys: hashBuffer
* @private
@ -130,7 +142,7 @@ Address._transformHash = function(hash){
};
/**
* Internal function to discover the network and type
* Internal function to discover the network and type based on the first data byte
*
* @param {Buffer} buffer - An instance of a hex encoded address Buffer
* @returns {Object} An object with keys: network and type
@ -138,24 +150,21 @@ Address._transformHash = function(hash){
*/
Address._classifyFromVersion = function(buffer){
var version = {};
switch(buffer[0]){ // the version byte
version.network = Networks.get(buffer[0]);
switch (buffer[0]) { // the version byte
case Networks.livenet.pubkeyhash:
version.network = Networks.livenet;
version.type = Address.PayToPublicKeyHash;
break;
case Networks.livenet.scripthash:
version.network = Networks.livenet;
version.type = Address.PayToScriptHash;
break;
case Networks.testnet.pubkeyhash:
version.network = Networks.testnet;
version.type = Address.PayToPublicKeyHash;
break;
case Networks.testnet.scripthash:
version.network = Networks.testnet;
version.type = Address.PayToScriptHash;
break;
}
@ -172,6 +181,7 @@ Address._classifyFromVersion = function(buffer){
* @private
*/
Address._transformBuffer = function(buffer, network, type){
/* jshint maxcomplexity: 9 */
var info = {};
if (!(buffer instanceof Buffer) && !(buffer instanceof Uint8Array)) {
throw new TypeError('Address supplied is not a buffer.');
@ -198,7 +208,7 @@ Address._transformBuffer = function(buffer, network, type){
};
/**
* Internal function to transform a PublicKey
* Internal function to transform a {@link PublicKey}
*
* @param {PublicKey} pubkey - An instance of PublicKey
* @returns {Object} An object with keys: hashBuffer, type
@ -215,7 +225,7 @@ Address._transformPublicKey = function(pubkey){
};
/**
* Internal function to transform a Script
* Internal function to transform a {@link Script} into a `info` object.
*
* @param {Script} script - An instance of Script
* @returns {Object} An object with keys: hashBuffer, type
@ -235,9 +245,13 @@ Address._transformScript = function(script, network){
/**
* Creates a P2SH address from a set of public keys and a threshold.
*
* @param {Array} publicKeys
* @param {number} threshold
* @param {Network} network
* The addresses will be sorted lexicographically, as that is the trend in bitcoin.
* To create an address from unsorted public keys, use the {@link Script#buildMultisigOut}
* interface.
*
* @param {Array} publicKeys - a set of public keys to create an address
* @param {number} threshold - the number of signatures needed to release the funds
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @return {Address}
*/
Address.createMultisig = function(publicKeys, threshold, network) {
@ -248,8 +262,8 @@ Address.createMultisig = function(publicKeys, threshold, network) {
/**
* Internal function to transform a bitcoin address string
*
* @param {String} data - An instance of PublicKey
* @param {String} [network] - The network: 'livenet' or 'testnet'
* @param {String} data
* @param {String|Network} [network] - either a Network instance, 'livenet', or 'testnet'
* @param {String} [type] - The type: 'pubkeyhash' or 'scripthash'
* @returns {Object} An object with keys: hashBuffer, network and type
* @private
@ -266,8 +280,8 @@ Address._transformString = function(data, network, type){
/**
* Instantiate an address from a PublicKey instance
*
* @param {PublicKey} data - An instance of PublicKey
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {PublicKey} data
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @returns {Address} A new valid and frozen instance of an Address
*/
Address.fromPublicKey = function(data, network){
@ -280,7 +294,7 @@ Address.fromPublicKey = function(data, network){
* Instantiate an address from a ripemd160 public key hash
*
* @param {Buffer} hash - An instance of buffer of the hash
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @returns {Address} A new valid and frozen instance of an Address
*/
Address.fromPublicKeyHash = function(hash, network) {
@ -292,7 +306,7 @@ Address.fromPublicKeyHash = function(hash, network) {
* Instantiate an address from a ripemd160 script hash
*
* @param {Buffer} hash - An instance of buffer of the hash
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @returns {Address} A new valid and frozen instance of an Address
*/
Address.fromScriptHash = function(hash, network) {
@ -304,7 +318,7 @@ Address.fromScriptHash = function(hash, network) {
* Instantiate an address from a Script
*
* @param {Script} script - An instance of Script
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @returns {Address} A new valid and frozen instance of an Address
*/
Address.fromScript = function(script, network) {
@ -316,7 +330,7 @@ Address.fromScript = function(script, network) {
* Instantiate an address from a buffer of the address
*
* @param {Buffer} buffer - An instance of buffer of the address
* @param {String} [network] - The network: 'livenet' or 'testnet'
* @param {String|Network} [network] - either a Network instance, 'livenet', or 'testnet'
* @param {String} [type] - The type of address: 'script' or 'pubkey'
* @returns {Address} A new valid and frozen instance of an Address
*/
@ -329,7 +343,7 @@ Address.fromBuffer = function(buffer, network, type) {
* Instantiate an address from an address string
*
* @param {String} str - An string of the bitcoin address
* @param {String} [network] - The network: 'livenet' or 'testnet'
* @param {String|Network} [network] - either a Network instance, 'livenet', or 'testnet'
* @param {String} [type] - The type of address: 'script' or 'pubkey'
* @returns {Address} A new valid and frozen instance of an Address
*/
@ -361,19 +375,19 @@ Address.fromJSON = function fromJSON(json) {
*
* @example
* ```javascript
*
* var error = Address.getValidationError('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'testnet');
* // a network mismatch error
* var error = Address.getValidationError('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'testnet');
* ```
*
* @param {String} data - The encoded data
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @param {String} type - The type of address: 'script' or 'pubkey'
* @returns {null|Error} The corresponding error message
*/
Address.getValidationError = function(data, network, type) {
var error;
try {
/* jshint nonew: false */
new Address(data, network, type);
} catch (e) {
error = e;
@ -386,13 +400,11 @@ Address.getValidationError = function(data, network, type) {
*
* @example
* ```javascript
*
* var valid = Address.isValid('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'livenet');
* // true
* assert(Address.isValid('15vkcKf7gB23wLAnZLmbVuMiiVDc1Nm4a2', 'livenet'));
* ```
*
* @param {String} data - The encoded data
* @param {String} network - The network: 'livenet' or 'testnet'
* @param {String|Network} network - either a Network instance, 'livenet', or 'testnet'
* @param {String} type - The type of address: 'script' or 'pubkey'
* @returns {boolean} The corresponding error message
*/
@ -428,7 +440,7 @@ Address.prototype.toBuffer = function() {
};
/**
* @returns {Object} An object of the address
* @returns {Object} A plain object with the address information
*/
Address.prototype.toObject = function toObject() {
return {
@ -439,7 +451,7 @@ Address.prototype.toObject = function toObject() {
};
/**
* @returns {Object} An object of the address
* @returns {String} A JSON representation of a plain object with the address information
*/
Address.prototype.toJSON = function toJSON() {
return JSON.stringify(this.toObject());

View File

@ -21,7 +21,7 @@ describe('Address', function() {
var buf = Buffer.concat([new Buffer([0]), pubkeyhash]);
var str = '16VZnHwRhwrExfeHFHGjwrgEMq8VcYPs9r';
it('should throw an error because of missing data', function() {
it('can\'t build without data', function() {
(function() {
return new Address();
}).should.throw('First argument is required, please include address data.');
@ -116,57 +116,57 @@ describe('Address', function() {
describe('validation', function() {
it('should describe this livenet address as an invalid testnet address', function() {
it('getValidationError detects network mismatchs', function() {
var error = Address.getValidationError('37BahqRsFrAd3qLiNNwLNV3AWMRD7itxTo', 'testnet');
should.exist(error);
});
it('should should return a true boolean', function(){
it('isValid returns true on a valid address', function(){
var valid = Address.isValid('37BahqRsFrAd3qLiNNwLNV3AWMRD7itxTo', 'livenet');
valid.should.equal(true);
});
it('should should return a false boolean', function(){
it('isValid returns false on network mismatch', function(){
var valid = Address.isValid('37BahqRsFrAd3qLiNNwLNV3AWMRD7itxTo', 'testnet');
valid.should.equal(false);
});
it('should validate addresses', function() {
it('validates correctly the P2PKH test vector', function() {
for (var i = 0; i < PKHLivenet.length; i++) {
var error = Address.getValidationError(PKHLivenet[i]);
should.not.exist(error);
}
});
it('should validate p2sh addresses', function() {
it('validates correctly the P2SH test vector', function() {
for (var i = 0; i < P2SHLivenet.length; i++) {
var error = Address.getValidationError(P2SHLivenet[i]);
should.not.exist(error);
}
});
it('should validate testnet p2sh addresses', function() {
it('validates correctly the P2SH testnet test vector', function() {
for (var i = 0; i < P2SHTestnet.length; i++) {
var error = Address.getValidationError(P2SHTestnet[i], 'testnet');
should.not.exist(error);
}
});
it('should not validate addresses with params', function() {
it('rejects correctly the P2PKH livenet test vector with "testnet" parameter', function() {
for (var i = 0; i < PKHLivenet.length; i++) {
var error = Address.getValidationError(PKHLivenet[i], 'testnet');
should.exist(error);
}
});
it('should validate addresses with params', function() {
it('validates correctly the P2PKH livenet test vector with "livenet" parameter', function() {
for (var i = 0; i < PKHLivenet.length; i++) {
var error = Address.getValidationError(PKHLivenet[i], 'livenet');
should.not.exist(error);
}
});
it('should not validate because of an invalid checksum', function() {
it('should not validate if checksum is invalid', function() {
for (var i = 0; i < badChecksums.length; i++) {
var error = Address.getValidationError(badChecksums[i], 'livenet', 'pubkeyhash');
should.exist(error);
@ -174,16 +174,21 @@ describe('Address', function() {
}
});
it('should not validate because of mismatched network', function() {
for (var i = 0; i < PKHLivenet.length; i++) {
var error = Address.getValidationError(PKHLivenet[i], 'testnet', 'pubkeyhash');
it('should not validate on a network mismatch', function() {
var error, i;
for (i = 0; i < PKHLivenet.length; i++) {
error = Address.getValidationError(PKHLivenet[i], 'testnet', 'pubkeyhash');
should.exist(error);
error.message.should.equal('Address has mismatched network type.');
}
for (i = 0; i < PKHTestnet.length; i++) {
error = Address.getValidationError(PKHTestnet[i], 'livenet', 'pubkeyhash');
should.exist(error);
error.message.should.equal('Address has mismatched network type.');
}
});
it('should not validate because of a mismatched type', function() {
it('should not validate on a type mismatch', function() {
for (var i = 0; i < PKHLivenet.length; i++) {
var error = Address.getValidationError(PKHLivenet[i], 'livenet', 'scripthash');
should.exist(error);
@ -191,7 +196,7 @@ describe('Address', function() {
}
});
it('should not validate because of non-base58 characters', function() {
it('should not validate on non-base58 characters', function() {
for (var i = 0; i < nonBase58.length; i++) {
var error = Address.getValidationError(nonBase58[i], 'livenet', 'pubkeyhash');
should.exist(error);
@ -199,28 +204,13 @@ describe('Address', function() {
}
});
it('should not validate addresses', function() {
for (var i = 0; i < badChecksums.length; i++) {
var error = Address.getValidationError(badChecksums[i]);
should.exist(error);
}
});
it('should validate testnet addresses', function() {
it('testnet addresses are validated correctly', function() {
for (var i = 0; i < PKHTestnet.length; i++) {
var error = Address.getValidationError(PKHTestnet[i], 'testnet');
should.not.exist(error);
}
});
it('should not validate testnet addresses because of mismatched network', function() {
for (var i = 0; i < PKHTestnet.length; i++) {
var error = Address.getValidationError(PKHTestnet[i], 'livenet', 'pubkeyhash');
should.exist(error);
error.message.should.equal('Address has mismatched network type.');
}
});
});
describe('encodings', function() {
@ -228,7 +218,7 @@ describe('Address', function() {
it('should make an address from a buffer', function() {
Address.fromBuffer(buf).toString().should.equal(str);
new Address(buf).toString().should.equal(str);
Address(buf).toString().should.equal(str);
new Address(buf).toString().should.equal(str);
});
it('should make an address from a string', function() {
@ -316,13 +306,14 @@ describe('Address', function() {
});
it('should make this address from a compressed pubkey', function() {
var pubkey = PublicKey.fromDER(new Buffer('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004', 'hex'));
var pubkey = new PublicKey('0285e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004');
var address = Address.fromPublicKey(pubkey);
address.toString().should.equal('19gH5uhqY6DKrtkU66PsZPUZdzTd11Y7ke');
});
it('should make this address from an uncompressed pubkey', function() {
var pubkey = PublicKey.fromDER(new Buffer('0485e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b004833fef26c8be4c4823754869ff4e46755b85d851077771c220e2610496a29d98', 'hex'));
var pubkey = new PublicKey('0485e9737a74c30a873f74df05124f2aa6f53042c2fc0a130d6cbd7d16b944b00' +
'4833fef26c8be4c4823754869ff4e46755b85d851077771c220e2610496a29d98');
var a = Address.fromPublicKey(pubkey, 'livenet');
a.toString().should.equal('16JXnhxjJUhxfyx4y6H4sFcxrgt8kQ8ewX');
var b = new Address(pubkey, 'livenet', 'pubkeyhash');
@ -379,7 +370,7 @@ describe('Address', function() {
describe('#toBuffer', function() {
it('should output this known hash', function() {
it('3c3fa3d4adcaf8f52d5b1843975e122548269937 corresponds to hash 16VZnHwRhwrExfeHFHGjwrgEMq8VcYPs9r', function() {
var address = new Address(str);
address.toBuffer().slice(1).toString('hex').should.equal(pubkeyhash.toString('hex'));
});
@ -388,37 +379,37 @@ describe('Address', function() {
describe('#json', function() {
it('should output/input a JSON object', function() {
var address = Address.fromJSON(new Address(str).toJSON());
address.toString().should.equal(str);
});
it('should output/input a JSON string', function() {
it('roundtrip to-from-to', function() {
var json = new Address(str).toJSON();
var address = Address.fromJSON(json);
address.toString().should.equal(str);
});
it('checks that the string parameter is valid JSON', function() {
expect(function() {
return Address.fromJSON('¹');
}).to.throw();
});
});
describe('#toString', function() {
it('should output a livenet pubkeyhash address', function() {
it('livenet pubkeyhash address', function() {
var address = new Address(str);
address.toString().should.equal(str);
});
it('should output a scripthash address', function() {
it('scripthash address', function() {
var address = new Address(P2SHLivenet[0]);
address.toString().should.equal(P2SHLivenet[0]);
});
it('should output a testnet scripthash address', function() {
it('testnet scripthash address', function() {
var address = new Address(P2SHTestnet[0]);
address.toString().should.equal(P2SHTestnet[0]);
});
it('should output a testnet pubkeyhash address', function() {
it('testnet pubkeyhash address', function() {
var address = new Address(PKHTestnet[0]);
address.toString().should.equal(PKHTestnet[0]);
});