From 4c557744a21e772ee88d9e2ee0455ba70a151c9e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 9 Jan 2017 22:36:10 -0800 Subject: [PATCH] refactor: address, amount, uri, errors. --- lib/blockchain/chain.js | 7 +- lib/btc/amount.js | 208 +++++++++++++++++++++--- lib/btc/index.js | 3 +- lib/btc/uri.js | 168 +++++++++++++++++-- lib/env.js | 103 +----------- lib/mempool/mempool.js | 2 +- lib/net/peer.js | 2 +- lib/net/pool.js | 2 +- lib/primitives/abstractblock.js | 2 +- lib/primitives/address.js | 279 +++++++++++++++++++++++--------- lib/primitives/block.js | 2 +- lib/primitives/keyring.js | 36 ++--- lib/primitives/merkleblock.js | 2 +- lib/primitives/tx.js | 2 +- lib/{btc => protocol}/errors.js | 10 +- lib/protocol/index.js | 1 + lib/script/script.js | 6 +- lib/utils/util.js | 10 ++ lib/wallet/path.js | 2 +- test/script-test.js | 6 +- test/wallet-test.js | 8 +- 21 files changed, 606 insertions(+), 255 deletions(-) rename lib/{btc => protocol}/errors.js (98%) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 7f4204f8..004fedeb 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -7,6 +7,7 @@ 'use strict'; +var assert = require('assert'); var AsyncObject = require('../utils/async'); var Network = require('../protocol/network'); var Logger = require('../node/logger'); @@ -19,11 +20,10 @@ var LRU = require('../utils/lru'); var ChainEntry = require('./chainentry'); var CoinView = require('../coins/coinview'); var Script = require('../script/script'); -var assert = require('assert'); -var errors = require('../btc/errors'); +var errors = require('../protocol/errors'); +var co = require('../utils/co'); var VerifyError = errors.VerifyError; var VerifyResult = errors.VerifyResult; -var co = require('../utils/co'); /** * Represents a blockchain. @@ -1027,6 +1027,7 @@ Chain.prototype._reset = co(function* reset(block, silent) { this.synced = this.isFull(true); state = yield this.getDeploymentState(); + this.setDeploymentState(state); this.emit('tip', tip); diff --git a/lib/btc/amount.js b/lib/btc/amount.js index 602fbc69..1788c9d5 100644 --- a/lib/btc/amount.js +++ b/lib/btc/amount.js @@ -10,8 +10,12 @@ var assert = require('assert'); var util = require('../utils/util'); /** - * Amount + * Represents a bitcoin amount (satoshis internally). * @constructor + * @param {(String|Number)?} value + * @param {String?} unit + * @param {Boolean?} num + * @property {Amount} value */ function Amount(value, unit, num) { @@ -24,6 +28,15 @@ function Amount(value, unit, num) { this.fromOptions(value, unit, num); } +/** + * Inject properties from options. + * @private + * @param {(String|Number)?} value + * @param {String?} unit + * @param {Boolean?} num + * @returns {Amount} + */ + Amount.prototype.fromOptions = function fromOptions(value, unit, num) { if (typeof unit === 'string') return this.from(unit, value, num); @@ -34,10 +47,21 @@ Amount.prototype.fromOptions = function fromOptions(value, unit, num) { return this.fromBTC(value); }; +/** + * Get satoshi value. + * @returns {Amount} + */ + Amount.prototype.toValue = function toValue() { return this.value; }; +/** + * Get satoshi string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ + Amount.prototype.toSatoshis = function toSatoshis(num) { if (num) return this.value; @@ -45,18 +69,44 @@ Amount.prototype.toSatoshis = function toSatoshis(num) { return this.value.toString(10); }; +/** + * Get bits string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ + Amount.prototype.toBits = function toBits(num) { return Amount.serialize(this.value, 2, num); }; +/** + * Get mbtc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ + Amount.prototype.toMBTC = function toMBTC(num) { return Amount.serialize(this.value, 5, num); }; +/** + * Get btc string or value. + * @param {Boolean?} num + * @returns {String|Amount} + */ + Amount.prototype.toBTC = function toBTC(num) { return Amount.serialize(this.value, 8, num); }; +/** + * Get unit string or value. + * @param {String} unit - Can be `sat`, + * `ubtc`, `bits`, `mbtc`, or `btc`. + * @param {Boolean?} num + * @returns {String|Amount} + */ + Amount.prototype.to = function to(unit, num) { switch (unit) { case 'sat': @@ -72,36 +122,89 @@ Amount.prototype.to = function to(unit, num) { throw new Error('Unknown unit "' + unit + '".'); }; +/** + * Convert amount to bitcoin string. + * @returns {String} + */ + Amount.prototype.toString = function toString() { return this.toBTC(); }; +/** + * Inject properties from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ + Amount.prototype.fromValue = function fromValue(value) { assert(util.isInt53(value), 'Value must be an int64.'); this.value = value; return this; }; +/** + * Inject properties from satoshis. + * @private + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.prototype.fromSatoshis = function fromSatoshis(value, num) { this.value = Amount.parse(value, 0, num); return this; }; +/** + * Inject properties from bits. + * @private + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.prototype.fromBits = function fromBits(value, num) { this.value = Amount.parse(value, 2, num); return this; }; +/** + * Inject properties from mbtc. + * @private + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.prototype.fromMBTC = function fromMBTC(value, num) { this.value = Amount.parse(value, 5, num); return this; }; +/** + * Inject properties from btc. + * @private + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.prototype.fromBTC = function fromBTC(value, num) { this.value = Amount.parse(value, 8, num); return this; }; +/** + * Inject properties from unit. + * @private + * @param {String} unit + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.prototype.from = function from(unit, value, num) { switch (unit) { case 'sat': @@ -117,34 +220,90 @@ Amount.prototype.from = function from(unit, value, num) { throw new Error('Unknown unit "' + unit + '".'); }; +/** + * Instantiate amount from options. + * @param {(String|Number)?} value + * @param {String?} unit + * @param {Boolean?} num + * @returns {Amount} + */ + Amount.fromOptions = function fromOptions(value, unit, num) { return new Amount().fromOptions(value); }; +/** + * Instantiate amount from value. + * @private + * @param {Amount} value + * @returns {Amount} + */ + Amount.fromValue = function fromValue(value) { return new Amount().fromValue(value); }; +/** + * Instantiate amount from satoshis. + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.fromSatoshis = function fromSatoshis(value, num) { return new Amount().fromSatoshis(value, num); }; +/** + * Instantiate amount from bits. + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.fromBits = function fromBits(value, num) { return new Amount().fromBits(value, num); }; +/** + * Instantiate amount from mbtc. + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.fromMBTC = function fromMBTC(value, num) { return new Amount().fromMBTC(value, num); }; +/** + * Instantiate amount from btc. + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.fromBTC = function fromBTC(value, num) { return new Amount().fromBTC(value, num); }; +/** + * Instantiate amount from unit. + * @param {String} unit + * @param {Number|String} value + * @param {Bolean?} num + * @returns {Amount} + */ + Amount.from = function from(unit, value, num) { return new Amount().from(unit, value, num); }; +/** + * Inspect amount. + * @returns {String} + */ + Amount.prototype.inspect = function inspect() { return ''; }; @@ -169,12 +328,12 @@ Amount.btc = function btc(value, num) { * This function explicitly avoids any * floating point arithmetic. * @param {Amount} value - * @param {Number} dec - Number of decimals. + * @param {Number} exp - Exponent. * @param {Boolean} num - Return a number. * @returns {String} */ -Amount.serialize = function serialize(value, dec, num) { +Amount.serialize = function serialize(value, exp, num) { var negative = false; var hi, lo, result; @@ -189,11 +348,11 @@ Amount.serialize = function serialize(value, dec, num) { assert(value.length <= 16, 'Number exceeds 2^53-1.'); - while (value.length < dec + 1) + while (value.length < exp + 1) value = '0' + value; - hi = value.slice(0, -dec); - lo = value.slice(-dec); + hi = value.slice(0, -exp); + lo = value.slice(-exp); lo = lo.replace(/0+$/, ''); @@ -214,21 +373,21 @@ Amount.serialize = function serialize(value, dec, num) { /** * Unsafely convert satoshis to a BTC string. * @param {Amount} value - * @param {Number} dec - Number of decimals. + * @param {Number} exp - Exponent. * @param {Boolean} num - Return a number. * @returns {String} */ -Amount.serializeUnsafe = function serializeUnsafe(value, dec, num) { +Amount.serializeUnsafe = function serializeUnsafe(value, exp, num) { assert(util.isInt(value), 'Non-satoshi value for conversion.'); - value /= pow10(dec); - value = value.toFixed(dec); + value /= pow10(exp); + value = value.toFixed(exp); if (num) return +value; - if (dec !== 0) { + if (exp !== 0) { value = value.replace(/0+$/, ''); if (value[value.length - 1] === '.') value += '0'; @@ -258,15 +417,15 @@ Amount.value = function value(value, num) { * extra validation to ensure the resulting * Number will be 53 bits or less. * @param {String} value - BTC - * @param {Number} dec - Number of decimals. + * @param {Number} exp - Exponent. * @param {Boolean} num - Allow numbers. * @returns {Amount} Satoshis. * @throws on parse error */ -Amount.parse = function parse(value, dec, num) { +Amount.parse = function parse(value, exp, num) { var negative = false; - var mult = pow10(dec); + var mult = pow10(exp); var maxLo = modSafe(mult); var maxHi = divSafe(mult); var parts, hi, lo, result; @@ -293,13 +452,13 @@ Amount.parse = function parse(value, dec, num) { hi = hi.replace(/^0+/, ''); lo = lo.replace(/0+$/, ''); - assert(hi.length <= 16 - dec, 'Number exceeds 2^53-1.'); - assert(lo.length <= dec, 'Too many decimal places.'); + assert(hi.length <= 16 - exp, 'Number exceeds 2^53-1.'); + assert(lo.length <= exp, 'Too many decimal places.'); if (hi.length === 0) hi = '0'; - while (lo.length < dec) + while (lo.length < exp) lo += '0'; hi = parseInt(hi, 10); @@ -319,13 +478,13 @@ Amount.parse = function parse(value, dec, num) { /** * Unsafely convert a BTC string to satoshis. * @param {String} value - BTC - * @param {Number} dec - Number of decimals. + * @param {Number} exp - Exponent. * @param {Boolean} num - Allow numbers. * @returns {Amount} Satoshis. * @throws on parse error */ -Amount.parseUnsafe = function parseUnsafe(value, dec, num) { +Amount.parseUnsafe = function parseUnsafe(value, exp, num) { if (typeof value === 'string') { assert(util.isFloat(value), 'Non-BTC value for conversion.'); value = parseFloat(value, 10); @@ -334,7 +493,7 @@ Amount.parseUnsafe = function parseUnsafe(value, dec, num) { assert(num, 'Cannot parse number.'); } - value *= pow10(dec); + value *= pow10(exp); assert(value % 1 === 0, 'Too many decimal places.'); @@ -366,7 +525,8 @@ function pow10(exp) { case 8: return 100000000; default: - assert(false); + assert(false, 'Exponent is too large.'); + break; } } @@ -391,7 +551,8 @@ function modSafe(mod) { case 100000000: return 54740991; default: - assert(false); + assert(false, 'Exponent is too large.'); + break; } } @@ -416,7 +577,8 @@ function divSafe(div) { case 100000000: return 90071992; default: - assert(false); + assert(false, 'Exponent is too large.'); + break; } } diff --git a/lib/btc/index.js b/lib/btc/index.js index 6d19274a..d019f898 100644 --- a/lib/btc/index.js +++ b/lib/btc/index.js @@ -1,5 +1,4 @@ 'use strict'; exports.Amount = require('./amount'); -exports.errors = require('./errors'); -exports.uri = require('./uri'); +exports.URI = require('./uri'); diff --git a/lib/btc/uri.js b/lib/btc/uri.js index 165209cf..caa97d86 100644 --- a/lib/btc/uri.js +++ b/lib/btc/uri.js @@ -8,23 +8,46 @@ var util = require('../utils/util'); var Address = require('../primitives/address'); +var KeyRing = require('../primitives/keyring'); var Amount = require('./amount'); var assert = require('assert'); +/** + * Represents a bitcoin URI. + * @constructor + * @param {Object|String} options + * @property {Address} address + * @property {Number|-1} version + * @property {Amount} amount + * @property {String|null} label + * @property {String|null} message + * @property {KeyRing|null} key + * @property {String|null} request + */ + function URI(options) { if (!(this instanceof URI)) return new URI(options); this.address = new Address(); + this.version = -1; this.amount = -1; this.label = null; this.message = null; + this.key = null; this.request = null; if (options) this.fromOptions(options); } +/** + * Inject properties from options object. + * @private + * @param {Object|String} options + * @returns {URI} + */ + URI.prototype.fromOptions = function fromOptions(options) { if (typeof options === 'string') return this.fromString(options); @@ -33,63 +56,100 @@ URI.prototype.fromOptions = function fromOptions(options) { this.address.fromOptions(options.address); if (options.amount != null) { - assert(util.isNumber(options.amount)); + assert(util.isUInt53(options.amount), 'Amount must be a uint53.'); this.amount = options.amount; } + if (options.version != null) { + assert(util.isUInt32(options.version), 'Version must be a uint32.'); + this.version = options.version; + } + if (options.label) { - assert(typeof options.label === 'string'); + assert(typeof options.label === 'string', 'Label must be a string.'); this.label = options.label; } if (options.message) { - assert(typeof options.message === 'string'); + assert(typeof options.message === 'string', 'Message must be a string.'); this.message = options.message; } + if (options.key) { + if (typeof options.key === 'string') { + this.key = KeyRing.fromSecret(options.key); + } else { + this.key = KeyRing.fromOptions(options.key); + assert(this.key.privateKey, 'Key must have a private key.'); + } + } + if (options.request) { - assert(typeof options.request === 'string'); + assert(typeof options.request === 'string', 'Request must be a string.'); this.request = options.request; } return this; }; +/** + * Instantiate URI from options. + * @param {Object|String} options + * @returns {URI} + */ + URI.fromOptions = function fromOptions(options) { return new URI().fromOptions(options); }; +/** + * Parse and inject properties from string. + * @private + * @param {String} str + * @returns {URI} + */ + URI.prototype.fromString = function fromString(str) { - var prefix, index, address, query; + var prefix, index, body, query, parts, address, version; assert(typeof str === 'string'); assert(str.length > 8, 'Not a bitcoin URI.'); prefix = str.substring(0, 8); - if (prefix !== 'bitcoin:') - throw new Error('Not a bitcoin URI.'); + assert(prefix === 'bitcoin:', 'Not a bitcoin URI.'); str = str.substring(8); index = str.indexOf('?'); if (index === -1) { - address = str; + body = str; } else { - address = str.substring(0, index); + body = str.substring(0, index); query = str.substring(index + 1); } + parts = body.split(';'); + assert(parts.length <= 2, 'Too many semicolons in body.'); + + address = parts[0]; + this.address.fromBase58(address); + if (parts.length === 2) { + version = parts[1]; + assert(util.isDecimal(version), 'Version is not decimal.'); + this.version = parseInt(version, 10); + } + if (!query) return this; query = parsePairs(query); if (query.amount) - this.amount = Amount.value(query.amount); + this.amount = parseAmount(query.amount, query.size); if (query.label) this.label = query.label; @@ -97,22 +157,39 @@ URI.prototype.fromString = function fromString(str) { if (query.message) this.message = query.message; + if (query.send) + this.key = KeyRing.fromSecret(query.send); + if (query.r) this.request = query.r; return this; }; +/** + * Instantiate uri from string. + * @param {String} str + * @returns {URI} + */ + URI.fromString = function fromString(str) { return new URI().fromString(str); }; +/** + * Serialize uri to a string. + * @returns {String} + */ + URI.prototype.toString = function toString() { var str = 'bitcoin:'; var query = []; str += this.address.toBase58(); + if (this.version !== -1) + str += ';version=' + this.version; + if (this.amount !== -1) query.push('amount=' + Amount.btc(this.amount)); @@ -122,6 +199,9 @@ URI.prototype.toString = function toString() { if (this.message) query.push('message=' + escape(this.message)); + if (this.key) + query.push('send=' + this.key.toSecret()); + if (this.request) query.push('r=' + escape(this.request)); @@ -131,6 +211,11 @@ URI.prototype.toString = function toString() { return str; }; +/** + * Inspect bitcoin uri. + * @returns {String} + */ + URI.prototype.inspect = function inspect() { return ''; }; @@ -188,6 +273,69 @@ function escape(str) { } } +function parseAmount(amount, size) { + var value = amount; + var exp = 8; + var parts; + + assert(typeof amount === 'string'); + assert(amount.length > 0); + + if (size) { + assert(typeof size === 'string'); + assert(size.length > 0); + exp = size; + assert(util.isDecimal(exp), 'Exponent is not a decimal.'); + exp = parseInt(exp, 10); + } + + if (value[0] === 'x') { + exp = 4; + + assert(value.length > 1); + + value = value.substring(1); + parts = value.split('X'); + assert(parts.length <= 2, 'Too many bases.'); + + value = parts[0]; + assert(value.length > 0, 'Value is empty.'); + assert(util.isHex(value), 'Value is not hex.'); + value = parseInt(value, 16); + assert(util.isNumber(value), 'Value exceeds 2^53-1 bits.'); + + if (parts.length === 2) { + exp = parts[1]; + assert(util.isHex(exp), 'Exponent is not hex.'); + exp = parseInt(exp, 16); + } + + assert(exp <= 4, 'Exponent is too large.'); + + value *= Math.pow(16, exp); + + assert(util.isNumber(value), 'Value exceeds 2^53-1 bits.'); + + return value; + } + + parts = value.split('X'); + assert(parts.length <= 2, 'Too many bases.'); + + value = parts[0]; + assert(value.length > 0, 'Value is empty.'); + assert(value[0] !== '-', 'Value is negative.'); + assert(util.isFloat(value), 'Value is not a float.'); + + if (parts.length === 2) { + exp = parts[1]; + assert(util.isDecimal(exp), 'Exponent is not decimal.'); + exp = parseInt(exp, 10); + } + + return Amount.parse(value, exp, false); +} + /* * Expose */ diff --git a/lib/env.js b/lib/env.js index bbab52ce..a6cce8c9 100644 --- a/lib/env.js +++ b/lib/env.js @@ -10,99 +10,11 @@ var lazy = require('./utils/lazy'); /** - * A BCoin "environment" which is used for - * bootstrapping the initial `bcoin` module. - * It exposes all constructors for primitives, - * the blockchain, mempool, wallet, etc. It - * also sets the default network if there is - * one. It exposes a global {@link TimeData} - * object for adjusted time, as well as a + * A BCoin "environment" which exposes all + * constructors for primitives, the blockchain, + * mempool, wallet, etc. It also exposes a * global worker pool. - * - * @exports Environment * @constructor - * - * @param {(Object|NetworkType)?} options - Options object or network type. - * @param {(Network|NetworkType)?} options.network - * @param {String} [options.prefix=~/.bcoin] - Prefix for filesystem. - * @param {String} [options.db=leveldb] - Database backend. - * @param {Boolean} [options.debug=false] - Whether to display debug output. - * @param {String|Boolean} [options.debugFile=~/.debug.log] - A file to - * pipe debug output to. - * @param {Boolean} [options.profile=false] - Enable profiler. - * @param {Boolean} [options.useWorkers=false] - Enable workers. - * @param {Number} [options.maxWorkers=6] - Max size of - * the worker pool. - * @param {String} [options.workerUri=/bcoin-worker.js] Location of the bcoin - * worker.js file for web workers. - * @param {String} [options.proxyServer=localhost:8080] - - * Websocket->tcp proxy server for browser. - * @param {Object?} options.logger - Custom logger. - * @property {Boolean} isBrowser - * @property {NetworkType} networkType - * - * @property {Function} bn - Big number constructor - * (see {@link https://github.com/indutny/bn.js} for docs). - * @property {Object} utils - {@link module:utils}. - * @property {Function} locker - {@link Locker} constructor. - * @property {Function} reader - {@link BufferReader} constructor. - * @property {Function} writer - {@link BufferWriter} constructor. - * @property {Object} ec - {@link module:ec}. - * @property {Function} lru - {@link LRU} constructor. - * @property {Function} bloom - {@link Bloom} constructor. - * @property {Function} rbt - {@link RBT} constructor. - * @property {Function} lowlevelup - See {@link LowlevelUp}. - * @property {Function} uri - See {@link module:uri}. - * @property {Function} logger - {@link Logger} constructor. - * - * @property {Object} constants - See {@link module:constants}. - * @property {Object} networks - See {@link module:network}. - * @property {Object} errors - * @property {Function} errors.VerifyError - {@link VerifyError} constructor. - * @property {Function} errors.ScriptError - {@link ScriptError} constructor. - * @property {Function} profiler - {@link module:profiler}. - * @property {Function} ldb - See {@link module:ldb}. - * @property {Function} script - {@link Script} constructor. - * @property {Function} opcode - {@link Opcode} constructor. - * @property {Function} stack - {@link Stack} constructor. - * @property {Function} witness - {@link Witness} constructor. - * @property {Function} input - {@link Input} constructor. - * @property {Function} output - {@link Output} constructor. - * @property {Function} coin - {@link Coin} constructor. - * @property {Function} coins - {@link Coins} constructor. - * @property {Function} coinview - {@link CoinView} constructor. - * @property {Function} tx - {@link TX} constructor. - * @property {Function} mtx - {@link MTX} constructor. - * @property {Function} txdb - {@link TXDB} constructor. - * @property {Function} abstractblock - {@link AbstractBlock} constructor. - * @property {Function} memblock - {@link MemBlock} constructor. - * @property {Function} block - {@link Block} constructor. - * @property {Function} merkleblock - {@link MerkleBlock} constructor. - * @property {Function} headers - {@link Headers} constructor. - * @property {Function} node - {@link Node} constructor. - * @property {Function} spvnode - {@link SPVNode} constructor. - * @property {Function} fullnode - {@link Fullnode} constructor. - * @property {Function} chainentry - {@link ChainEntry} constructor. - * @property {Function} chaindb - {@link ChainDB} constructor. - * @property {Function} chain - {@link Chain} constructor. - * @property {Function} mempool - {@link Mempool} constructor. - * @property {Function} mempoolentry - {@link MempoolEntry} constructor. - * @property {Function} hd - {@link HD} constructor. - * @property {Function} address - {@link Address} constructor. - * @property {Function} wallet - {@link Wallet} constructor. - * @property {Function} walletdb - {@link WalletDB} constructor. - * @property {Function} peer - {@link Peer} constructor. - * @property {Function} pool - {@link Pool} constructor. - * @property {Function} miner - {@link Miner} constructor. - * @property {Function} minerblock - {@link MinerBlock} constructor. - * @property {Object} http - * @property {Function} http.client - {@link HTTPClient} constructor. - * @property {Function} http.http - {@link HTTPBase} constructor. - * @property {Function} http.request - See {@link request}. - * @property {Function} http.server - {@link HTTPServer} constructor. - * @property {Object} workers - See {@link module:workers}. - * @property {TimeData} time - For adjusted time. - * @property {Workers?} workerPool - Default global worker pool. */ function Environment() { @@ -125,7 +37,6 @@ function Environment() { // BTC this.require('btc', './btc'); this.require('amount', './btc/amount'); - this.require('errors', './btc/errors'); this.require('uri', './btc/uri'); // Coins @@ -150,7 +61,7 @@ function Environment() { this.require('rpc', './http/rpc'); // Mempool - this.require('txmempool', './mempool'); // -> txmempool? + this.require('txmempool', './mempool'); this.require('fees', './mempool/fees'); this.require('mempool', './mempool/mempool'); this.require('mempoolentry', './mempool/mempoolentry'); @@ -196,13 +107,14 @@ function Environment() { // Protocol this.require('protocol', './protocol'); this.require('consensus', './protocol/consensus'); + this.require('errors', './protocol/errors'); this.require('network', './protocol/network'); this.require('networks', './protocol/networks'); this.require('policy', './protocol/policy'); this.require('timedata', './protocol/timedata'); // Script - this.require('scripting', './script'); // -> scripting? + this.require('txscript', './script'); this.require('opcode', './script/opcode'); this.require('program', './script/program'); this.require('script', './script/script'); @@ -255,7 +167,8 @@ Environment.prototype.set = function set(options) { }; /** - * Get the adjusted time. + * Get the adjusted time of + * the default network. * @returns {Number} Adjusted time. */ diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 365c190a..1bc51617 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -13,7 +13,7 @@ var policy = require('../protocol/policy'); var util = require('../utils/util'); var co = require('../utils/co'); var crypto = require('../crypto/crypto'); -var errors = require('../btc/errors'); +var errors = require('../protocol/errors'); var Bloom = require('../utils/bloom'); var Address = require('../primitives/address'); var Coin = require('../primitives/coin'); diff --git a/lib/net/peer.js b/lib/net/peer.js index 552a4678..772fd01b 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -26,7 +26,7 @@ var BIP152 = require('./bip152'); var Block = require('../primitives/block'); var TX = require('../primitives/tx'); var encoding = require('../utils/encoding'); -var errors = require('../btc/errors'); +var errors = require('../protocol/errors'); var NetAddress = require('../primitives/netaddress'); var invTypes = InvItem.types; var packetTypes = packets.types; diff --git a/lib/net/pool.js b/lib/net/pool.js index c60d2aca..7927c80e 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -14,7 +14,7 @@ var util = require('../utils/util'); var IP = require('../utils/ip'); var co = require('../utils/co'); var common = require('./common'); -var errors = require('../btc/errors'); +var errors = require('../protocol/errors'); var NetAddress = require('../primitives/netaddress'); var Address = require('../primitives/address'); var BIP150 = require('./bip150'); diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index c83f5157..8c45b388 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -10,7 +10,7 @@ var assert = require('assert'); var util = require('../utils/util'); var crypto = require('../crypto/crypto'); -var VerifyResult = require('../btc/errors').VerifyResult; +var VerifyResult = require('../protocol/errors').VerifyResult; var StaticWriter = require('../utils/staticwriter'); var InvItem = require('./invitem'); var encoding = require('../utils/encoding'); diff --git a/lib/primitives/address.js b/lib/primitives/address.js index f96810cd..268f1fa4 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -24,12 +24,12 @@ var base58 = require('../utils/base58'); * @constructor * @param {Object} options * @param {Buffer|Hash} options.hash - Address hash. - * @param {AddressType} options.type - Address type + * @param {AddressPrefix} options.type - Address type * `{witness,}{pubkeyhash,scripthash}`. * @param {Number} [options.version=-1] - Witness program version. * @param {(Network|NetworkType)?} options.network - Network name. * @property {Buffer} hash - * @property {AddressType} type + * @property {AddressPrefix} type * @property {Number} version * @property {Network} network */ @@ -48,18 +48,31 @@ function Address(options) { } /** - * Address types. + * Address types. Note that the values + * have a direct mapping to script types. + * These also represent the "prefix type" + * as a network-agnostic version of the + * prefix byte. They DO NOT represent the + * script type. For example, script type + * `WITNESSMASTHASH` would be prefix type + * `WITNESSSCRIPTHASH` with a `version` + * of 1. * @enum {Number} */ -Address.types = common.types; +Address.types = { + PUBKEYHASH: common.types.PUBKEYHASH, + SCRIPTHASH: common.types.SCRIPTHASH, + WITNESSSCRIPTHASH: common.types.WITNESSSCRIPTHASH, + WITNESSPUBKEYHASH: common.types.WITNESSPUBKEYHASH +}; /** * Address types by value. * @const {RevMap} */ -Address.typesByVal = common.typesByVal; +exports.typesByVal = util.revMap(Address.types); /** * Inject properties from options object. @@ -130,7 +143,7 @@ Address.prototype.verifyNetwork = function verifyNetwork(network) { /** * Get the address type as a string. - * @returns {AddressType} + * @returns {AddressPrefix} */ Address.prototype.getType = function getType() { @@ -426,7 +439,7 @@ Address.fromScript = function fromScript(script) { * Inject properties from a hash. * @private * @param {Buffer|Hash} hash - * @param {AddressType} type + * @param {AddressPrefix} type * @param {Number} [version=-1] * @param {(Network|NetworkType)?} network * @throws on bad hash size @@ -436,8 +449,10 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { if (typeof hash === 'string') hash = new Buffer(hash, 'hex'); - if (typeof type === 'string') + if (typeof type === 'string') { type = Address.types[type.toUpperCase()]; + assert(type != null, 'Not a valid address type.'); + } if (type == null) type = Address.types.PUBKEYHASH; @@ -477,8 +492,8 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { /** * Create a naked address from hash/type/version. - * @param {Buffer|Hash} hash - * @param {AddressType} type + * @param {Hash} hash + * @param {AddressPrefix} type * @param {Number} [version=-1] * @param {(Network|NetworkType)?} network * @returns {Address} @@ -490,89 +505,203 @@ Address.fromHash = function fromHash(hash, type, version, network) { }; /** - * Inject properties from data. + * Inject properties from pubkeyhash. * @private - * @param {Buffer|Buffer[]} data - * @param {AddressType} type - * @param {Number} [version=-1] - * @param {(Network|NetworkType)?} network + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} */ -Address.prototype.fromData = function fromData(data, type, version, network) { - if (typeof type === 'string') - type = Address.types[type.toUpperCase()]; +Address.prototype.fromPubkeyhash = function fromPubkeyhash(hash, network) { + var type = Address.types.PUBKEYHASH; + return this.fromHash(hash, type, -1, network); +}; - if (type === Address.types.WITNESSSCRIPTHASH) { - if (version === 0) { - assert(Buffer.isBuffer(data)); - data = crypto.sha256(data); - } else if (version === 1) { - assert(Array.isArray(data)); - throw new Error('MASTv2 creation not implemented.'); - } else { - throw new Error('Cannot create from version=' + version); - } - } else if (type === Address.types.WITNESSPUBKEYHASH) { - if (version !== 0) - throw new Error('Cannot create from version=' + version); - assert(Buffer.isBuffer(data)); - data = crypto.hash160(data); - } else { - data = crypto.hash160(data); +/** + * Instantiate address from pubkeyhash. + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.fromPubkeyhash = function fromPubkeyhash(hash, network) { + return new Address().fromPubkeyhash(hash, network); +}; + +/** + * Inject properties from scripthash. + * @private + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.prototype.fromScripthash = function fromScripthash(hash, network) { + var type = Address.types.SCRIPTHASH; + return this.fromHash(hash, type, -1, network); +}; + +/** + * Instantiate address from scripthash. + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.fromScripthash = function fromScripthash(hash, network) { + return new Address().fromScripthash(hash, network); +}; + +/** + * Inject properties from witness pubkeyhash. + * @private + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.prototype.fromWitnessPubkeyhash = function fromWitnessPubkeyhash(hash, network) { + var type = Address.types.WITNESSPUBKEYHASH; + return this.fromHash(hash, type, 0, network); +}; + +/** + * Instantiate address from witness pubkeyhash. + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.fromWitnessPubkeyhash = function fromWitnessPubkeyhash(hash, network) { + return new Address().fromWitnessPubkeyhash(hash, network); +}; + +/** + * Inject properties from witness scripthash. + * @private + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.prototype.fromWitnessScripthash = function fromWitnessScripthash(hash, network) { + var type = Address.types.WITNESSSCRIPTHASH; + return this.fromHash(hash, type, 0, network); +}; + +/** + * Instantiate address from witness scripthash. + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.fromWitnessScripthash = function fromWitnessScripthash(hash, network) { + return new Address().fromScripthash(hash, network); +}; + +/** + * Inject properties from witness program. + * @private + * @param {Number} version + * @param {Buffer} hash + * @param {Network?} network + * @returns {Address} + */ + +Address.prototype.fromProgram = function fromProgram(version, hash, network) { + var type; + + assert(version >= 0, 'Bad version for witness program.'); + + if (typeof hash === 'string') + hash = new Buffer(hash, 'hex'); + + switch (hash.length) { + case 20: + type = Address.types.WITNESSPUBKEYHASH; + break; + case 32: + type = Address.types.WITNESSSCRIPTHASH; + break; + default: + assert(false, 'Unknown witness program data length.'); + break; } - return this.fromHash(data, type, version, network); + return this.fromHash(hash, type, version, network); }; /** - * Create an Address from data/type/version. - * @param {Buffer|Buffer[]} data - Data to be hashed. - * Normally a buffer, but can also be an array of - * buffers for MAST. - * @param {AddressType} type - * @param {Number} [version=-1] - * @param {(Network|NetworkType)?} network + * Instantiate address from witness program. + * @param {Number} version + * @param {Buffer} hash + * @param {Network?} network * @returns {Address} - * @throws on bad hash size */ -Address.fromData = function fromData(data, type, version, network) { - return new Address().fromData(data, type, version, network); +Address.fromProgram = function fromProgram(version, hash, network) { + return new Address().fromProgram(version, hash, network); }; /** - * Validate an address, optionally test against a type. - * @param {Base58Address} address - * @param {AddressType} + * Test whether the address is pubkeyhash. * @returns {Boolean} */ -Address.validate = function validate(address, type) { - if (!address) - return false; - - if (!Buffer.isBuffer(address) && typeof address !== 'string') - return false; - - try { - address = Address.fromBase58(address); - } catch (e) { - return false; - } - - if (typeof type === 'string') - type = Address.types[type.toUpperCase()]; - - if (type && address.type !== type) - return false; - - return true; +Address.prototype.isPubkeyhash = function isPubkeyhash() { + return this.type === Address.types.PUBKEYHASH; }; /** - * Get the hex hash of a base58 - * address or address object. - * @param {Base58Address|Address} data + * Test whether the address is scripthash. + * @returns {Boolean} + */ + +Address.prototype.isScripthash = function isScripthash() { + return this.type === Address.types.SCRIPTHASH; +}; + +/** + * Test whether the address is witness pubkeyhash. + * @returns {Boolean} + */ + +Address.prototype.isWitnessPubkeyhash = function isWitnessPubkeyhash() { + return this.version === 0 && this.type === Address.types.WITNESSPUBKEYHASH; +}; + +/** + * Test whether the address is witness scripthash. + * @returns {Boolean} + */ + +Address.prototype.isWitnessScripthash = function isWitnessScripthash() { + return this.version === 0 && this.type === Address.types.WITNESSSCRIPTHASH; +}; + +/** + * Test whether the address is witness masthash. + * @returns {Boolean} + */ + +Address.prototype.isWitnessMasthash = function isWitnessMasthash() { + return this.version === 1 && this.type === Address.types.WITNESSSCRIPTHASH; +}; + +/** + * Test whether the address is a witness program. + * @returns {Boolean} + */ + +Address.prototype.isProgram = function isProgram() { + return this.version !== -1; +}; + +/** + * Get the hash of a base58 address or address-related object. + * @param {Base58Address|Address|Hash} data + * @param {String} enc * @returns {Hash|null} */ @@ -603,7 +732,7 @@ Address.getHash = function getHash(data, enc) { /** * Get a network address prefix for a specified address type. - * @param {AddressType} type + * @param {AddressPrefix} type * @param {Network} network * @returns {Number} */ @@ -628,7 +757,7 @@ Address.getPrefix = function getPrefix(type, network) { * Get an address type for a specified network address prefix. * @param {Number} prefix * @param {Network} network - * @returns {AddressType} + * @returns {AddressPrefix} */ Address.getType = function getType(prefix, network) { @@ -649,7 +778,7 @@ Address.getType = function getType(prefix, network) { /** * Test whether an address type is a witness program. - * @param {AddressType} type + * @param {AddressPrefix} type * @returns {Boolean} */ diff --git a/lib/primitives/block.js b/lib/primitives/block.js index f1b0c6cc..cb2a441f 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -13,7 +13,7 @@ var encoding = require('../utils/encoding'); var crypto = require('../crypto/crypto'); var consensus = require('../protocol/consensus'); var AbstractBlock = require('./abstractblock'); -var VerifyResult = require('../btc/errors').VerifyResult; +var VerifyResult = require('../protocol/errors').VerifyResult; var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); var TX = require('./tx'); diff --git a/lib/primitives/keyring.js b/lib/primitives/keyring.js index cf7cf642..affc3927 100644 --- a/lib/primitives/keyring.js +++ b/lib/primitives/keyring.js @@ -477,7 +477,7 @@ KeyRing.prototype.getNestedAddress = function getNestedAddress(enc) { if (!this._nestedAddress) { hash = this.getNestedHash(); - address = this.compile(hash, Script.types.SCRIPTHASH); + address = Address.fromScripthash(hash, this.network); this._nestedAddress = address; } @@ -550,10 +550,10 @@ KeyRing.prototype.getScriptAddress = function getScriptAddress(enc) { if (!this._scriptAddress) { if (this.witness) { hash = this.getScriptHash256(); - address = this.compile(hash, Script.types.WITNESSSCRIPTHASH, 0); + address = Address.fromWitnessScripthash(hash, this.network); } else { hash = this.getScriptHash160(); - address = this.compile(hash, Script.types.SCRIPTHASH); + address = Address.fromScripthash(hash, this.network); } this._scriptAddress = address; } @@ -591,9 +591,9 @@ KeyRing.prototype.getKeyAddress = function getKeyAddress(enc) { if (!this._keyAddress) { hash = this.getKeyHash(); if (this.witness) - address = this.compile(hash, Script.types.WITNESSPUBKEYHASH, 0); + address = Address.fromWitnessPubkeyhash(hash, this.network); else - address = this.compile(hash, Script.types.PUBKEYHASH); + address = Address.fromPubkeyhash(hash, this.network); this._keyAddress = address; } @@ -603,20 +603,6 @@ KeyRing.prototype.getKeyAddress = function getKeyAddress(enc) { return this._keyAddress; }; -/** - * Compile a hash to an address. - * @private - * @param {Hash|Buffer} hash - * @param {AddressType?} type - * @param {Number?} version - Witness version. - * @returns {Address} - * @throws Error on bad hash/prefix. - */ - -KeyRing.prototype.compile = function compile(hash, type, version) { - return Address.fromHash(hash, type, version, this.network); -}; - /** * Get hash. * @param {String?} enc - `"hex"` or `null`. @@ -759,18 +745,18 @@ KeyRing.prototype.getVersion = function getVersion() { KeyRing.prototype.getType = function getType() { if (this.nested) - return Script.types.SCRIPTHASH; + return Address.types.SCRIPTHASH; if (this.witness) { if (this.script) - return Script.types.WITNESSSCRIPTHASH; - return Script.types.WITNESSPUBKEYHASH; + return Address.types.WITNESSSCRIPTHASH; + return Address.types.WITNESSPUBKEYHASH; } if (this.script) - return Script.types.SCRIPTHASH; + return Address.types.SCRIPTHASH; - return Script.types.PUBKEYHASH; + return Address.types.PUBKEYHASH; }; /** @@ -795,7 +781,7 @@ KeyRing.prototype.toJSON = function toJSON() { publicKey: this.publicKey.toString('hex'), script: this.script ? this.script.toRaw().toString('hex') : null, program: this.witness ? this.getProgram().toRaw().toString('hex') : null, - type: Script.typesByVal[this.getType()].toLowerCase(), + type: Address.typesByVal[this.getType()].toLowerCase(), address: this.getAddress('base58') }; }; diff --git a/lib/primitives/merkleblock.js b/lib/primitives/merkleblock.js index d1b3b8ee..4377bd35 100644 --- a/lib/primitives/merkleblock.js +++ b/lib/primitives/merkleblock.js @@ -11,7 +11,7 @@ var assert = require('assert'); var util = require('../utils/util'); var crypto = require('../crypto/crypto'); var AbstractBlock = require('./abstractblock'); -var VerifyResult = require('../btc/errors').VerifyResult; +var VerifyResult = require('../protocol/errors').VerifyResult; var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); var encoding = require('../utils/encoding'); diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index bc82fef9..374bfea4 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -17,7 +17,7 @@ var Network = require('../protocol/network'); var Script = require('../script/script'); var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); -var VerifyResult = require('../btc/errors').VerifyResult; +var VerifyResult = require('../protocol/errors').VerifyResult; var Input = require('./input'); var Output = require('./output'); var Outpoint = require('./outpoint'); diff --git a/lib/btc/errors.js b/lib/protocol/errors.js similarity index 98% rename from lib/btc/errors.js rename to lib/protocol/errors.js index 3ad2ffcf..489bfdde 100644 --- a/lib/btc/errors.js +++ b/lib/protocol/errors.js @@ -36,19 +36,19 @@ function VerifyError(msg, code, reason, score) { if (Error.captureStackTrace) Error.captureStackTrace(this, VerifyError); - this.type = 'VerifyError'; - assert(typeof code === 'string'); assert(typeof reason === 'string'); assert(score >= 0); - this.hash = msg.hash(); - this.malleated = false; - + this.type = 'VerifyError'; + this.message = ''; this.code = code; this.reason = reason; this.score = score; + this.hash = msg.hash(); + this.malleated = false; + this.message = 'Verification failure: ' + reason + ' (code=' + code + ', score=' + score + ', hash=' + util.revHex(this.hash.toString('hex')) diff --git a/lib/protocol/index.js b/lib/protocol/index.js index 2a17c7f3..f40b0705 100644 --- a/lib/protocol/index.js +++ b/lib/protocol/index.js @@ -1,6 +1,7 @@ 'use strict'; exports.consensus = require('./consensus'); +exports.errors = require('./errors'); exports.networks = require('./networks'); exports.Network = require('./network'); exports.policy = require('./policy'); diff --git a/lib/script/script.js b/lib/script/script.js index 9deb3ff3..1c361229 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -1822,13 +1822,13 @@ Script.prototype.fromAddress = function fromAddress(address) { assert(address instanceof Address, 'Not an address.'); - if (address.type === scriptTypes.PUBKEYHASH) + if (address.isPubkeyhash()) return this.fromPubkeyhash(address.hash); - if (address.type === scriptTypes.SCRIPTHASH) + if (address.isScripthash()) return this.fromScripthash(address.hash); - if (address.version !== -1) + if (address.isProgram()) return this.fromProgram(address.version, address.hash); throw new Error('Unknown address type.'); diff --git a/lib/utils/util.js b/lib/utils/util.js index 9d80ba0e..d2abd0c7 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -162,6 +162,16 @@ util.hrtime = function hrtime(time) { return process.hrtime(); }; +/** + * Test whether a string is decimal. + * @param {String?} obj + * @returns {Boolean} + */ + +util.isDecimal = function isDecimal(obj) { + return typeof obj === 'string' && /^\d+$/.test(obj); +}; + /** * Test whether a string is hex. Note that this * _could_ yield a false positive on base58 diff --git a/lib/wallet/path.js b/lib/wallet/path.js index cb92532a..f58ceb39 100644 --- a/lib/wallet/path.js +++ b/lib/wallet/path.js @@ -42,7 +42,7 @@ function Path(options) { this.data = null; // Currently unused. - this.type = Script.types.PUBKEYHASH; + this.type = Address.types.PUBKEYHASH; this.version = -1; this.hash = null; // Passed in by caller. diff --git a/test/script-test.js b/test/script-test.js index 3dcd3fdc..bc87a020 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -276,8 +276,8 @@ describe('Script', function() { hash: encoding.NULL_HASH, index: 0xffffffff }, - script: new Script([opcodes.OP_0, opcodes.OP_0]), - witness: new Witness(), + script: [opcodes.OP_0, opcodes.OP_0], + witness: [], sequence: 0xffffffff }], outputs: [{ @@ -301,7 +301,7 @@ describe('Script', function() { sequence: 0xffffffff }], outputs: [{ - script: new Script(), + script: [], value: amount }], locktime: 0 diff --git a/test/wallet-test.js b/test/wallet-test.js index 0654512a..603563f1 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -77,15 +77,17 @@ describe('Wallet', function() { var w = yield walletdb.create(); var addr = w.getAddress('base58'); assert(addr); - assert(Address.validate(addr)); + assert(Address.fromBase58(addr)); })); it('should validate existing address', function() { - assert(Address.validate('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc')); + assert(Address.fromBase58('1KQ1wMNwXHUYj1nV2xzsRcKUH8gVFpTFUc')); }); it('should fail to validate invalid address', function() { - assert(!Address.validate('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc')); + assert.throws(function() { + Address.fromBase58('1KQ1wMNwXHUYj1nv2xzsRcKUH8gVFpTFUc'); + }); }); it('should create and get wallet', cob(function* () {