From 4ce070fad47ca27c82b92008cc6fa31ccc5bda98 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 6 Aug 2017 14:42:10 -0700 Subject: [PATCH] validation: use stricter validation for ints. --- browser/wsproxy.js | 53 +++++++++++++++++++-------------- lib/bip70/paymentdetails.js | 4 +-- lib/bip70/paymentrequest.js | 2 +- lib/blockchain/chain.js | 10 +++---- lib/blockchain/chainentry.js | 13 ++++---- lib/btc/amount.js | 4 +-- lib/hd/mnemonic.js | 4 +-- lib/hd/private.js | 7 ++--- lib/hd/public.js | 5 ++-- lib/mining/miner.js | 16 +++++----- lib/net/hostlist.js | 17 +++++------ lib/node/config.js | 42 +++++++++++++++++++++----- lib/primitives/abstractblock.js | 16 +++++----- lib/primitives/address.js | 4 +-- lib/primitives/mtx.js | 13 ++++---- lib/primitives/netaddress.js | 7 ++--- lib/primitives/txmeta.js | 18 +++++------ lib/script/common.js | 4 ++- lib/script/opcode.js | 2 +- lib/script/program.js | 4 +-- lib/script/script.js | 4 +-- lib/script/sigcache.js | 6 ++-- lib/utils/asyncemitter.js | 2 +- lib/utils/bloom.js | 8 ++--- lib/utils/encoding.js | 12 ++------ lib/utils/rollingfilter.js | 2 +- lib/utils/util.js | 11 ++++--- lib/utils/validator.js | 50 +++++++++++++++++-------------- lib/wallet/account.js | 18 +++++------ lib/wallet/masterkey.js | 8 ++--- lib/wallet/wallet.js | 6 ++-- lib/wallet/walletdb.js | 4 +-- lib/workers/workerpool.js | 6 ++-- migrate/coins-old.js | 4 +-- migrate/coins/coins.js | 2 +- 35 files changed, 208 insertions(+), 180 deletions(-) diff --git a/browser/wsproxy.js b/browser/wsproxy.js index 5aef4e16..2588b448 100644 --- a/browser/wsproxy.js +++ b/browser/wsproxy.js @@ -1,5 +1,6 @@ 'use strict'; +const assert = require('assert'); const net = require('net'); const EventEmitter = require('events').EventEmitter; const IOServer = require('socket.io'); @@ -26,27 +27,32 @@ function WSProxy(options) { this.options = options; this.target = options.target || TARGET; this.pow = options.pow === true; - this.ports = options.ports || []; + this.ports = new Set(); this.io = new IOServer(); this.sockets = new WeakMap(); - this._init(); + if (options.ports) { + for (const port of options.ports) + this.ports.add(port); + } + + this.init(); } util.inherits(WSProxy, EventEmitter); -WSProxy.prototype._init = function _init() { +WSProxy.prototype.init = function init() { this.io.on('error', (err) => { this.emit('error', err); }); this.io.on('connection', (ws) => { - this._handleSocket(ws); + this.handleSocket(ws); }); }; -WSProxy.prototype._handleSocket = function _handleSocket(ws) { - let state = new SocketState(this, ws); +WSProxy.prototype.handleSocket = function handleSocket(ws) { + const state = new SocketState(this, ws); // Use a weak map to avoid // mutating the websocket object. @@ -59,20 +65,20 @@ WSProxy.prototype._handleSocket = function _handleSocket(ws) { }); ws.on('tcp connect', (port, host, nonce) => { - this._handleConnect(ws, port, host, nonce); + this.handleConnect(ws, port, host, nonce); }); }; -WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce) { - let state = this.sockets.get(ws); - let socket, pow, raw; +WSProxy.prototype.handleConnect = function handleConnect(ws, port, host, nonce) { + const state = this.sockets.get(ws); + assert(state); if (state.socket) { this.log('Client is trying to reconnect (%s).', state.host); return; } - if (!util.isNumber(port) + if (!util.isU16(port) || typeof host !== 'string' || host.length === 0) { this.log('Client gave bad arguments (%s).', state.host); @@ -82,19 +88,20 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce } if (this.pow) { - if (!util.isNumber(nonce)) { + if (!util.isU32(nonce)) { this.log('Client did not solve proof of work (%s).', state.host); ws.emit('tcp close'); ws.disconnect(); return; } - pow = new BufferWriter(); - pow.writeU32(nonce); - pow.writeBytes(state.snonce); - pow.writeU32(port); - pow.writeString(host, 'ascii'); - pow = pow.render(); + const bw = new BufferWriter(); + bw.writeU32(nonce); + bw.writeBytes(state.snonce); + bw.writeU32(port); + bw.writeString(host, 'ascii'); + + const pow = bw.render(); if (digest.hash256(pow).compare(this.target) > 0) { this.log('Client did not solve proof of work (%s).', state.host); @@ -104,9 +111,10 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce } } + let raw, addr; try { raw = IP.toBuffer(host); - host = IP.toString(raw); + addr = IP.toString(raw); } catch (e) { this.log('Client gave a bad host: %s (%s).', host, state.host); ws.emit('tcp error', { @@ -120,7 +128,7 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce if (!IP.isRoutable(raw) || IP.isOnion(raw)) { this.log( 'Client is trying to connect to a bad ip: %s (%s).', - host, state.host); + addr, state.host); ws.emit('tcp error', { message: 'ENETUNREACH', code: 'ENETUNREACH' @@ -129,7 +137,7 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce return; } - if (this.ports.indexOf(port) === -1) { + if (!this.ports.has(port)) { this.log('Client is connecting to non-whitelist port (%s).', state.host); ws.emit('tcp error', { message: 'ENETUNREACH', @@ -139,8 +147,9 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce return; } + let socket; try { - socket = state.connect(port, host); + socket = state.connect(port, addr); this.log('Connecting to %s (%s).', state.remoteHost, state.host); } catch (e) { this.log(e.message); diff --git a/lib/bip70/paymentdetails.js b/lib/bip70/paymentdetails.js index c665e423..c33d8c42 100644 --- a/lib/bip70/paymentdetails.js +++ b/lib/bip70/paymentdetails.js @@ -64,12 +64,12 @@ PaymentDetails.prototype.fromOptions = function fromOptions(options) { } if (options.time != null) { - assert(util.isNumber(options.time)); + assert(util.isInt(options.time)); this.time = options.time; } if (options.expires != null) { - assert(util.isNumber(options.expires)); + assert(util.isInt(options.expires)); this.expires = options.expires; } diff --git a/lib/bip70/paymentrequest.js b/lib/bip70/paymentrequest.js index 6ecb810d..6fb17555 100644 --- a/lib/bip70/paymentrequest.js +++ b/lib/bip70/paymentrequest.js @@ -50,7 +50,7 @@ function PaymentRequest(options) { PaymentRequest.prototype.fromOptions = function fromOptions(options) { if (options.version != null) { - assert(util.isNumber(options.version)); + assert(util.isInt(options.version)); this.version = options.version; } diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index de26a44b..56ca0bcb 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -2455,12 +2455,12 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) { } if (options.maxFiles != null) { - assert(util.isNumber(options.maxFiles)); + assert(util.isU32(options.maxFiles)); this.maxFiles = options.maxFiles; } if (options.cacheSize != null) { - assert(util.isNumber(options.cacheSize)); + assert(util.isU64(options.cacheSize)); this.cacheSize = options.cacheSize; } @@ -2500,17 +2500,17 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) { } if (options.coinCache != null) { - assert(util.isNumber(options.coinCache)); + assert(util.isU64(options.coinCache)); this.coinCache = options.coinCache; } if (options.entryCache != null) { - assert(util.isNumber(options.entryCache)); + assert(util.isU32(options.entryCache)); this.entryCache = options.entryCache; } if (options.maxOrphans != null) { - assert(util.isNumber(options.maxOrphans)); + assert(util.isU32(options.maxOrphans)); this.maxOrphans = options.maxOrphans; } diff --git a/lib/blockchain/chainentry.js b/lib/blockchain/chainentry.js index c95dad82..67ee55cb 100644 --- a/lib/blockchain/chainentry.js +++ b/lib/blockchain/chainentry.js @@ -56,7 +56,7 @@ function ChainEntry(chain, options, prev) { this.time = 0; this.bits = 0; this.nonce = 0; - this.height = -1; + this.height = 0; this.chainwork = ZERO; if (options) @@ -88,12 +88,13 @@ ChainEntry.MEDIAN_TIMESPAN = 11; ChainEntry.prototype.fromOptions = function fromOptions(options, prev) { assert(options, 'Block data is required.'); assert(typeof options.hash === 'string'); - assert(util.isNumber(options.version)); + assert(util.isU32(options.version)); assert(typeof options.prevBlock === 'string'); assert(typeof options.merkleRoot === 'string'); - assert(util.isNumber(options.time)); - assert(util.isNumber(options.bits)); - assert(util.isNumber(options.nonce)); + assert(util.isU32(options.time)); + assert(util.isU32(options.bits)); + assert(util.isU32(options.nonce)); + assert(util.isU32(options.height)); assert(!options.chainwork || BN.isBN(options.chainwork)); this.hash = options.hash; @@ -104,7 +105,7 @@ ChainEntry.prototype.fromOptions = function fromOptions(options, prev) { this.bits = options.bits; this.nonce = options.nonce; this.height = options.height; - this.chainwork = options.chainwork; + this.chainwork = options.chainwork || ZERO; if (!this.chainwork) this.chainwork = this.getChainwork(prev); diff --git a/lib/btc/amount.js b/lib/btc/amount.js index 82033d32..45984699 100644 --- a/lib/btc/amount.js +++ b/lib/btc/amount.js @@ -371,10 +371,8 @@ Amount.encode = function encode(value, exp, num) { */ Amount.decode = function decode(str, exp, num) { - if (num && typeof str === 'number') { - assert(util.isNumber(str), 'Non-BTC value for conversion.'); + if (num && typeof str === 'number') str = str.toString(10); - } return util.fromFixed(str, exp); }; diff --git a/lib/hd/mnemonic.js b/lib/hd/mnemonic.js index a086cac1..bd0e04e8 100644 --- a/lib/hd/mnemonic.js +++ b/lib/hd/mnemonic.js @@ -77,7 +77,7 @@ Mnemonic.prototype.fromOptions = function fromOptions(options) { options = { phrase: options }; if (options.bits != null) { - assert(util.isNumber(options.bits)); + assert(util.isU16(options.bits)); assert(options.bits >= common.MIN_ENTROPY); assert(options.bits <= common.MAX_ENTROPY); assert(options.bits % 32 === 0); @@ -377,7 +377,7 @@ Mnemonic.prototype.toJSON = function toJSON() { */ Mnemonic.prototype.fromJSON = function fromJSON(json) { - assert(util.isNumber(json.bits)); + assert(util.isU16(json.bits)); assert(typeof json.language === 'string'); assert(typeof json.entropy === 'string'); assert(typeof json.phrase === 'string'); diff --git a/lib/hd/private.js b/lib/hd/private.js index f1e48755..56a3cee2 100644 --- a/lib/hd/private.js +++ b/lib/hd/private.js @@ -70,10 +70,9 @@ function HDPrivateKey(options) { HDPrivateKey.prototype.fromOptions = function fromOptions(options) { assert(options, 'No options for HD private key.'); - assert(util.isNumber(options.depth)); - assert(options.depth >= 0 && options.depth <= 0xff); + assert(util.isU8(options.depth)); assert(Buffer.isBuffer(options.parentFingerPrint)); - assert(util.isNumber(options.childIndex)); + assert(util.isU32(options.childIndex)); assert(Buffer.isBuffer(options.chainCode)); assert(Buffer.isBuffer(options.privateKey)); @@ -258,7 +257,7 @@ HDPrivateKey.prototype.getID = function getID(index) { */ HDPrivateKey.prototype.deriveBIP44 = function deriveBIP44(account, bip48) { - assert(util.isNumber(account), 'Account index must be a number.'); + assert(util.isU32(account), 'Account index must be a number.'); assert(this.isMaster(), 'Cannot derive account index.'); return this .derive(bip48 ? 48 : 44, true) diff --git a/lib/hd/public.js b/lib/hd/public.js index 33b014a0..acf262c1 100644 --- a/lib/hd/public.js +++ b/lib/hd/public.js @@ -64,10 +64,9 @@ function HDPublicKey(options) { HDPublicKey.prototype.fromOptions = function fromOptions(options) { assert(options, 'No options for HDPublicKey'); - assert(util.isNumber(options.depth)); - assert(options.depth >= 0 && options.depth <= 0xff); + assert(util.isU8(options.depth)); assert(Buffer.isBuffer(options.parentFingerPrint)); - assert(util.isNumber(options.childIndex)); + assert(util.isU32(options.childIndex)); assert(Buffer.isBuffer(options.chainCode)); assert(Buffer.isBuffer(options.publicKey)); diff --git a/lib/mining/miner.js b/lib/mining/miner.js index 22585274..21c680a1 100644 --- a/lib/mining/miner.js +++ b/lib/mining/miner.js @@ -427,7 +427,7 @@ MinerOptions.prototype.fromOptions = function fromOptions(options) { } if (options.version != null) { - assert(util.isNumber(options.version)); + assert(util.isInt(options.version)); this.version = options.version; } @@ -461,41 +461,41 @@ MinerOptions.prototype.fromOptions = function fromOptions(options) { } if (options.minWeight != null) { - assert(util.isNumber(options.minWeight)); + assert(util.isU32(options.minWeight)); this.minWeight = options.minWeight; } if (options.maxWeight != null) { - assert(util.isNumber(options.maxWeight)); + assert(util.isU32(options.maxWeight)); assert(options.maxWeight <= consensus.MAX_BLOCK_WEIGHT, 'Max weight must be below MAX_BLOCK_WEIGHT'); this.maxWeight = options.maxWeight; } if (options.maxSigops != null) { - assert(util.isNumber(options.maxSigops)); + assert(util.isU32(options.maxSigops)); assert(options.maxSigops <= consensus.MAX_BLOCK_SIGOPS_COST, 'Max sigops must be below MAX_BLOCK_SIGOPS_COST'); this.maxSigops = options.maxSigops; } if (options.priorityWeight != null) { - assert(util.isNumber(options.priorityWeight)); + assert(util.isU32(options.priorityWeight)); this.priorityWeight = options.priorityWeight; } if (options.priorityThreshold != null) { - assert(util.isNumber(options.priorityThreshold)); + assert(util.isU32(options.priorityThreshold)); this.priorityThreshold = options.priorityThreshold; } if (options.reservedWeight != null) { - assert(util.isNumber(options.reservedWeight)); + assert(util.isU32(options.reservedWeight)); this.reservedWeight = options.reservedWeight; } if (options.reservedSigops != null) { - assert(util.isNumber(options.reservedSigops)); + assert(util.isU32(options.reservedSigops)); this.reservedSigops = options.reservedSigops; } diff --git a/lib/net/hostlist.js b/lib/net/hostlist.js index e1369864..243bac48 100644 --- a/lib/net/hostlist.js +++ b/lib/net/hostlist.js @@ -1374,13 +1374,13 @@ HostEntry.prototype.fromJSON = function fromJSON(json, network) { assert(typeof json.services === 'string'); assert(json.services.length > 0); assert(json.services.length <= 32); - this.addr.services = parseInt(json.services, 2); - assert(util.isU32(this.addr.services)); + const services = parseInt(json.services, 2); + assert(util.isU32(services)); + this.addr.services = services; } if (json.time != null) { - assert(util.isNumber(json.time)); - assert(json.time >= 0); + assert(util.isU64(json.time)); this.addr.time = json.time; } @@ -1390,20 +1390,17 @@ HostEntry.prototype.fromJSON = function fromJSON(json, network) { } if (json.attempts != null) { - assert(util.isNumber(json.attempts)); - assert(json.attempts >= 0); + assert(util.isU64(json.attempts)); this.attempts = json.attempts; } if (json.lastSuccess != null) { - assert(util.isNumber(json.lastSuccess)); - assert(json.lastSuccess >= 0); + assert(util.isU64(json.lastSuccess)); this.lastSuccess = json.lastSuccess; } if (json.lastAttempt != null) { - assert(util.isNumber(json.lastAttempt)); - assert(json.lastAttempt >= 0); + assert(util.isU64(json.lastAttempt)); this.lastAttempt = json.lastAttempt; } diff --git a/lib/node/config.js b/lib/node/config.js index 29f7cce3..32244721 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -202,9 +202,14 @@ Config.prototype.get = function get(key, fallback) { if (typeof key === 'number') { assert(key >= 0, 'Index must be positive.'); + if (key >= this.argv.length) return fallback; - return this.argv[key]; + + if (this.argv[key] != null) + return this.argv[key]; + + return fallback; } assert(typeof key === 'string', 'Key must be a string.'); @@ -232,6 +237,21 @@ Config.prototype.get = function get(key, fallback) { return fallback; }; +/** + * Get a value's type. + * @param {String} key + * @returns {String} + */ + +Config.prototype.typeOf = function typeOf(key) { + const value = this.get(key); + + if (value === null) + return 'null'; + + return typeof value; +}; + /** * Get a config option (as a string). * @param {String} key @@ -275,10 +295,7 @@ Config.prototype.int = function int(key, fallback) { throw new Error(`${fmt(key)} must be an int.`); if (!Number.isSafeInteger(value)) - throw new Error(`${fmt(key)} must be an int (53 bit max).`); - - if (value % 1 !== 0) - throw new Error(`${fmt(key)} must be an int (float).`); + throw new Error(`${fmt(key)} must be an int.`); return value; } @@ -289,7 +306,7 @@ Config.prototype.int = function int(key, fallback) { value = parseInt(value, 10); if (!Number.isSafeInteger(value)) - throw new Error(`${fmt(key)} must be an int (53 bit max).`); + throw new Error(`${fmt(key)} must be an int.`); return value; }; @@ -441,6 +458,15 @@ Config.prototype.bool = function bool(key, fallback) { if (value === null) return fallback; + // Bitcoin Core compat. + if (typeof value === 'number') { + if (value === 1) + return true; + + if (value === 0) + return false; + } + if (typeof value !== 'string') { if (typeof value !== 'boolean') throw new Error(`${fmt(key)} must be a boolean.`); @@ -484,7 +510,7 @@ Config.prototype.buf = function buf(key, fallback, enc) { const data = Buffer.from(value, enc); if (data.length !== Buffer.byteLength(value, enc)) - throw new Error(`${fmt(key)} must be a hex string.`); + throw new Error(`${fmt(key)} must be a ${enc} string.`); return data; }; @@ -540,7 +566,7 @@ Config.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (!value || typeof value !== 'object') + if (typeof value !== 'object') throw new Error(`${fmt(key)} must be an object.`); return value; diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index cc2b5af8..15216c4b 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -57,12 +57,12 @@ function AbstractBlock() { AbstractBlock.prototype.parseOptions = function parseOptions(options) { assert(options, 'Block data is required.'); - assert(util.isNumber(options.version)); + assert(util.isU32(options.version)); assert(typeof options.prevBlock === 'string'); assert(typeof options.merkleRoot === 'string'); - assert(util.isNumber(options.time)); - assert(util.isNumber(options.bits)); - assert(util.isNumber(options.nonce)); + assert(util.isU32(options.time)); + assert(util.isU32(options.bits)); + assert(util.isU32(options.nonce)); this.version = options.version; this.prevBlock = options.prevBlock; @@ -85,12 +85,12 @@ AbstractBlock.prototype.parseOptions = function parseOptions(options) { AbstractBlock.prototype.parseJSON = function parseJSON(json) { assert(json, 'Block data is required.'); - assert(util.isNumber(json.version)); + assert(util.isU32(json.version)); assert(typeof json.prevBlock === 'string'); assert(typeof json.merkleRoot === 'string'); - assert(util.isNumber(json.time)); - assert(util.isNumber(json.bits)); - assert(util.isNumber(json.nonce)); + assert(util.isU32(json.time)); + assert(util.isU32(json.bits)); + assert(util.isU32(json.nonce)); this.version = json.version; this.prevBlock = util.revHex(json.prevBlock); diff --git a/lib/primitives/address.js b/lib/primitives/address.js index 483201c5..4a7f2927 100644 --- a/lib/primitives/address.js +++ b/lib/primitives/address.js @@ -579,8 +579,8 @@ Address.prototype.fromHash = function fromHash(hash, type, version, network) { network = Network.get(network); assert(Buffer.isBuffer(hash)); - assert(util.isNumber(type)); - assert(util.isNumber(version)); + assert(util.isU8(type)); + assert(util.isI8(version)); assert(type >= Address.types.PUBKEYHASH && type <= Address.types.WITNESS, 'Not a valid address type.'); diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index e0940fa5..f41c66cd 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -1519,37 +1519,36 @@ CoinSelector.prototype.fromOptions = function fromOptions(options) { } if (options.height != null) { - assert(util.isNumber(options.height)); + assert(util.isInt(options.height)); assert(options.height >= -1); this.height = options.height; } if (options.confirmations != null) { - assert(util.isNumber(options.confirmations)); + assert(util.isInt(options.confirmations)); assert(options.confirmations >= -1); this.depth = options.confirmations; } if (options.depth != null) { - assert(util.isNumber(options.depth)); + assert(util.isInt(options.depth)); assert(options.depth >= -1); this.depth = options.depth; } if (options.hardFee != null) { - assert(util.isNumber(options.hardFee)); + assert(util.isInt(options.hardFee)); assert(options.hardFee >= -1); this.hardFee = options.hardFee; } if (options.rate != null) { - assert(util.isNumber(options.rate)); - assert(options.rate >= 0); + assert(util.isU64(options.rate)); this.rate = options.rate; } if (options.maxFee != null) { - assert(util.isNumber(options.maxFee)); + assert(util.isInt(options.maxFee)); assert(options.maxFee >= -1); this.maxFee = options.maxFee; } diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js index 3ead8704..497b3c6d 100644 --- a/lib/primitives/netaddress.js +++ b/lib/primitives/netaddress.js @@ -439,10 +439,9 @@ NetAddress.prototype.toJSON = function toJSON() { */ NetAddress.prototype.fromJSON = function fromJSON(json) { - assert(util.isNumber(json.port)); - assert(json.port >= 0 && json.port <= 0xffff); - assert(util.isNumber(json.services)); - assert(util.isNumber(json.time)); + assert(util.isU16(json.port)); + assert(util.isU32(json.services)); + assert(util.isU32(json.time)); this.raw = IP.toBuffer(json.host); this.host = json.host; this.port = json.port; diff --git a/lib/primitives/txmeta.js b/lib/primitives/txmeta.js index dbc1f727..aed439bb 100644 --- a/lib/primitives/txmeta.js +++ b/lib/primitives/txmeta.js @@ -28,7 +28,7 @@ function TXMeta(options) { this.height = -1; this.block = null; this.time = 0; - this.index = 0; + this.index = -1; if (options) this.fromOptions(options); @@ -47,12 +47,12 @@ TXMeta.prototype.fromOptions = function fromOptions(options) { } if (options.mtime != null) { - assert(util.isNumber(options.mtime)); + assert(util.isU32(options.mtime)); this.mtime = options.mtime; } if (options.height != null) { - assert(util.isNumber(options.height)); + assert(util.isInt(options.height)); this.height = options.height; } @@ -62,12 +62,12 @@ TXMeta.prototype.fromOptions = function fromOptions(options) { } if (options.time != null) { - assert(util.isNumber(options.time)); + assert(util.isU32(options.time)); this.time = options.time; } if (options.index != null) { - assert(util.isNumber(options.index)); + assert(util.isInt(options.index)); this.index = options.index; } @@ -169,11 +169,11 @@ TXMeta.prototype.getJSON = function getJSON(network, view) { TXMeta.prototype.fromJSON = function fromJSON(json) { this.tx.fromJSON(json); - assert(util.isNumber(json.mtime)); - assert(util.isNumber(json.height)); + assert(util.isU32(json.mtime)); + assert(util.isInt(json.height)); assert(!json.block || typeof json.block === 'string'); - assert(util.isNumber(json.time)); - assert(util.isNumber(json.index)); + assert(util.isU32(json.time)); + assert(util.isInt(json.index)); this.mtime = json.mtime; this.height = json.height; diff --git a/lib/script/common.js b/lib/script/common.js index d6021113..72fbb45a 100644 --- a/lib/script/common.js +++ b/lib/script/common.js @@ -758,8 +758,10 @@ exports.num = function num(value, minimal, size) { */ exports.array = function array(value) { - if (util.isNumber(value)) + if (typeof value === 'number') { + assert(util.isInt(value)); value = new BN(value); + } assert(BN.isBN(value)); diff --git a/lib/script/opcode.js b/lib/script/opcode.js index ed4a9881..795717c6 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -357,7 +357,7 @@ Opcode.fromNumber = function fromNumber(num) { */ Opcode.fromSmall = function fromSmall(num) { - assert(util.isNumber(num) && num >= 0 && num <= 16); + assert(util.isU8(num) && num >= 0 && num <= 16); return Opcode.fromOp(num === 0 ? 0 : num + 0x50); }; diff --git a/lib/script/program.js b/lib/script/program.js index f29339fc..fedee361 100644 --- a/lib/script/program.js +++ b/lib/script/program.js @@ -29,9 +29,9 @@ function Program(version, data) { if (!(this instanceof Program)) return new Program(version, data); - assert(util.isNumber(version)); - assert(Buffer.isBuffer(data)); + assert(util.isU8(version)); assert(version >= 0 && version <= 16); + assert(Buffer.isBuffer(data)); assert(data.length >= 2 && data.length <= 40); this.version = version; diff --git a/lib/script/script.js b/lib/script/script.js index 3bdec829..91f9d395 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -1470,7 +1470,7 @@ Script.fromPubkeyhash = function fromPubkeyhash(hash) { */ Script.prototype.fromMultisig = function fromMultisig(m, n, keys) { - assert(util.isNumber(m) && util.isNumber(n)); + assert(util.isU8(m) && util.isU8(n)); assert(Array.isArray(keys)); assert(keys.length === n, '`n` keys are required for multisig.'); assert(m >= 1 && m <= n); @@ -1570,7 +1570,7 @@ Script.fromNulldata = function fromNulldata(flags) { */ Script.prototype.fromProgram = function fromProgram(version, data) { - assert(util.isNumber(version) && version >= 0 && version <= 16); + assert(util.isU8(version) && version >= 0 && version <= 16); assert(Buffer.isBuffer(data) && data.length >= 2 && data.length <= 40); const op = Opcode.fromSmall(version); diff --git a/lib/script/sigcache.js b/lib/script/sigcache.js index 05f90af1..4e44c855 100644 --- a/lib/script/sigcache.js +++ b/lib/script/sigcache.js @@ -27,8 +27,7 @@ function SigCache(size) { if (size == null) size = 10000; - assert(util.isNumber(size)); - assert(size >= 0); + assert(util.isU32(size)); this.size = size; this.keys = []; @@ -41,8 +40,7 @@ function SigCache(size) { */ SigCache.prototype.resize = function resize(size) { - assert(util.isNumber(size)); - assert(size >= 0); + assert(util.isU32(size)); this.size = size; this.keys.length = 0; diff --git a/lib/utils/asyncemitter.js b/lib/utils/asyncemitter.js index 32d0654c..3f1c3278 100644 --- a/lib/utils/asyncemitter.js +++ b/lib/utils/asyncemitter.js @@ -152,7 +152,7 @@ AsyncEmitter.prototype.removeListener = function removeListener(type, handler) { AsyncEmitter.prototype.setMaxListeners = function setMaxListeners(max) { assert(typeof max === 'number', '`max` must be a number.'); assert(max >= 0, '`max` must be non-negative.'); - assert(max % 1 === 0, '`max` must be an integer.'); + assert(Number.isSafeInteger(max), '`max` must be an integer.'); }; /** diff --git a/lib/utils/bloom.js b/lib/utils/bloom.js index 3d84d83b..1452bc59 100644 --- a/lib/utils/bloom.js +++ b/lib/utils/bloom.js @@ -119,7 +119,7 @@ Bloom.flagsByVal = { Bloom.prototype.fromOptions = function fromOptions(size, n, tweak, update) { assert(typeof size === 'number', '`size` must be a number.'); assert(size > 0, '`size` must be greater than zero.'); - assert(size % 1 === 0, '`size` must be an integer.'); + assert(Number.isSafeInteger(size), '`size` must be an integer.'); size -= size % 8; @@ -139,9 +139,9 @@ Bloom.prototype.fromOptions = function fromOptions(size, n, tweak, update) { assert(size > 0, '`size` must be greater than zero.'); assert(n > 0, '`n` must be greater than zero.'); - assert(n % 1 === 0, '`n` must be an integer.'); + assert(Number.isSafeInteger(n), '`n` must be an integer.'); assert(typeof tweak === 'number', '`tweak` must be a number.'); - assert(tweak % 1 === 0, '`tweak` must be an integer.'); + assert(Number.isSafeInteger(tweak), '`tweak` must be an integer.'); assert(Bloom.flagsByVal[update], 'Unknown update flag.'); this.filter = filter; @@ -258,7 +258,7 @@ Bloom.prototype.added = function added(val, enc) { Bloom.fromRate = function fromRate(items, rate, update) { assert(typeof items === 'number', '`items` must be a number.'); assert(items > 0, '`items` must be greater than zero.'); - assert(items % 1 === 0, '`items` must be an integer.'); + assert(Number.isSafeInteger(items), '`items` must be an integer.'); assert(typeof rate === 'number', '`rate` must be a number.'); assert(rate >= 0 && rate <= 1, '`rate` must be between 0.0 and 1.0.'); diff --git a/lib/utils/encoding.js b/lib/utils/encoding.js index 78d8ab9a..c46e8aac 100644 --- a/lib/utils/encoding.js +++ b/lib/utils/encoding.js @@ -12,6 +12,7 @@ */ const BN = require('bn.js'); +const util = require('./util'); const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; const encoding = exports; @@ -1037,7 +1038,7 @@ encoding.EncodingError = function EncodingError(offset, reason) { Error.captureStackTrace(this, EncodingError); }; -inherits(encoding.EncodingError, Error); +util.inherits(encoding.EncodingError, Error); /* * Helpers @@ -1048,15 +1049,6 @@ function Varint(size, value) { this.value = value; } -function inherits(child, parent) { - child.super_ = parent; - Object.setPrototypeOf(child.prototype, parent.prototype); - Object.defineProperty(child.prototype, 'constructor', { - value: child, - enumerable: false - }); -} - function enforce(value, offset, reason) { if (!value) throw new encoding.EncodingError(offset, reason); diff --git a/lib/utils/rollingfilter.js b/lib/utils/rollingfilter.js index 42b08b01..df6d8e25 100644 --- a/lib/utils/rollingfilter.js +++ b/lib/utils/rollingfilter.js @@ -50,7 +50,7 @@ function RollingFilter(items, rate) { RollingFilter.prototype.fromRate = function fromRate(items, rate) { assert(typeof items === 'number', '`items` must be a number.'); assert(items > 0, '`items` must be greater than zero.'); - assert(items % 1 === 0, '`items` must be an integer.'); + assert(Number.isSafeInteger(items), '`items` must be an integer.'); assert(typeof rate === 'number', '`rate` must be a number.'); assert(rate >= 0 && rate <= 1, '`rate` must be between 0.0 and 1.0.'); diff --git a/lib/utils/util.js b/lib/utils/util.js index e7d9288c..3d57d481 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -38,7 +38,10 @@ const inspectOptions = { */ util.isNumber = function isNumber(value) { - return Number.isSafeInteger(value); + return typeof value === 'number' + && isFinite(value) + && value >= -Number.MAX_SAFE_INTEGER + && value <= Number.MAX_SAFE_INTEGER; }; /** @@ -48,11 +51,11 @@ util.isNumber = function isNumber(value) { */ util.isInt = function isInt(value) { - return util.isNumber(value) && value % 1 === 0; + return Number.isSafeInteger(value); }; /** - * Test whether an object is an int. + * Test whether an object is a uint. * @param {Number?} value * @returns {Boolean} */ @@ -844,7 +847,7 @@ util.memoryUsage = function memoryUsage() { util.toFixed = function toFixed(num, exp) { assert(typeof num === 'number'); - assert(Number.isSafeInteger(num) && num % 1 === 0, 'Invalid integer value.'); + assert(Number.isSafeInteger(num), 'Invalid integer value.'); let sign = ''; diff --git a/lib/utils/validator.js b/lib/utils/validator.js index ea89ec91..3ec7d5f9 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -97,6 +97,21 @@ Validator.prototype.get = function get(key, fallback) { return fallback; }; +/** + * Get a value's type. + * @param {String} key + * @returns {String} + */ + +Validator.prototype.typeOf = function typeOf(key) { + const value = this.get(key); + + if (value == null) + return 'null'; + + return typeof value; +}; + /** * Get a value (as a string). * @param {String} key @@ -140,10 +155,7 @@ Validator.prototype.int = function int(key, fallback) { throw new ValidationError(key, 'int'); if (!Number.isSafeInteger(value)) - throw new ValidationError(key, 'int (53 bit max)'); - - if (value % 1 !== 0) - throw new ValidationError(key, 'int (float)'); + throw new ValidationError(key, 'int'); return value; } @@ -154,7 +166,7 @@ Validator.prototype.int = function int(key, fallback) { value = parseInt(value, 10); if (!Number.isSafeInteger(value)) - throw new ValidationError(key, 'int (53 bit max)'); + throw new ValidationError(key, 'int'); return value; }; @@ -492,15 +504,9 @@ Validator.prototype.hash = function hash(key, fallback) { */ Validator.prototype.numhash = function numhash(key, fallback) { - const value = this.get(key); - - if (value === null) - return fallback; - - if (typeof value === 'string') - return this.hash(key); - - return this.uint(key); + if (this.typeOf(key) === 'string') + return this.hash(key, fallback); + return this.uint(key, fallback); }; /** @@ -519,14 +525,14 @@ Validator.prototype.bool = function bool(key, fallback) { if (value === null) return fallback; - // Bitcoin Core mixes semantics of truthiness - // amoung rpc methods most "verbose" parameters - // are bools, but getrawtransaction is 1/0. - if (value === 1) - return true; + // Bitcoin Core compat. + if (typeof value === 'number') { + if (value === 1) + return true; - if (value === 0) - return false; + if (value === 0) + return false; + } if (typeof value !== 'string') { if (typeof value !== 'boolean') @@ -628,7 +634,7 @@ Validator.prototype.obj = function obj(key, fallback) { if (value === null) return fallback; - if (!value || typeof value !== 'object') + if (typeof value !== 'object') throw new ValidationError(key, 'object'); return value; diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 4eb12c67..38dbda7f 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -105,10 +105,10 @@ Account.typesByVal = { Account.prototype.fromOptions = function fromOptions(options) { assert(options, 'Options are required.'); - assert(util.isNumber(options.wid)); + assert(util.isU32(options.wid)); assert(common.isName(options.id), 'Bad Wallet ID.'); assert(HD.isHD(options.accountKey), 'Account key is required.'); - assert(util.isNumber(options.accountIndex), 'Account index is required.'); + assert(util.isU32(options.accountIndex), 'Account index is required.'); this.wid = options.wid; this.id = options.id; @@ -145,37 +145,37 @@ Account.prototype.fromOptions = function fromOptions(options) { } if (options.m != null) { - assert(util.isNumber(options.m)); + assert(util.isU8(options.m)); this.m = options.m; } if (options.n != null) { - assert(util.isNumber(options.n)); + assert(util.isU8(options.n)); this.n = options.n; } if (options.accountIndex != null) { - assert(util.isNumber(options.accountIndex)); + assert(util.isU32(options.accountIndex)); this.accountIndex = options.accountIndex; } if (options.receiveDepth != null) { - assert(util.isNumber(options.receiveDepth)); + assert(util.isU32(options.receiveDepth)); this.receiveDepth = options.receiveDepth; } if (options.changeDepth != null) { - assert(util.isNumber(options.changeDepth)); + assert(util.isU32(options.changeDepth)); this.changeDepth = options.changeDepth; } if (options.nestedDepth != null) { - assert(util.isNumber(options.nestedDepth)); + assert(util.isU32(options.nestedDepth)); this.nestedDepth = options.nestedDepth; } if (options.lookahead != null) { - assert(util.isNumber(options.lookahead)); + assert(util.isU32(options.lookahead)); assert(options.lookahead >= 0); assert(options.lookahead <= Account.MAX_LOOKAHEAD); this.lookahead = options.lookahead; diff --git a/lib/wallet/masterkey.js b/lib/wallet/masterkey.js index 55200c2d..5f3bd645 100644 --- a/lib/wallet/masterkey.js +++ b/lib/wallet/masterkey.js @@ -129,22 +129,22 @@ MasterKey.prototype.fromOptions = function fromOptions(options) { } if (options.rounds != null) { - assert(util.isNumber(options.rounds)); + assert(util.isU32(options.rounds)); this.N = options.rounds; } if (options.N != null) { - assert(util.isNumber(options.N)); + assert(util.isU32(options.N)); this.N = options.N; } if (options.r != null) { - assert(util.isNumber(options.r)); + assert(util.isU32(options.r)); this.r = options.r; } if (options.p != null) { - assert(util.isNumber(options.p)); + assert(util.isU32(options.p)); this.p = options.p; } diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index ea1a1c16..be7faefc 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -124,7 +124,7 @@ Wallet.prototype.fromOptions = function fromOptions(options) { this.master.fromKey(key, mnemonic); if (options.wid != null) { - assert(util.isNumber(options.wid)); + assert(util.isU32(options.wid)); this.wid = options.wid; } @@ -144,7 +144,7 @@ Wallet.prototype.fromOptions = function fromOptions(options) { } if (options.accountDepth != null) { - assert(util.isNumber(options.accountDepth)); + assert(util.isU32(options.accountDepth)); this.accountDepth = options.accountDepth; } @@ -155,7 +155,7 @@ Wallet.prototype.fromOptions = function fromOptions(options) { } if (options.tokenDepth != null) { - assert(util.isNumber(options.tokenDepth)); + assert(util.isU32(options.tokenDepth)); this.tokenDepth = options.tokenDepth; } diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index a741b229..a89fd5be 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -2220,12 +2220,12 @@ WalletOptions.prototype.fromOptions = function fromOptions(options) { } if (options.maxFiles != null) { - assert(util.isNumber(options.maxFiles)); + assert(util.isU32(options.maxFiles)); this.maxFiles = options.maxFiles; } if (options.cacheSize != null) { - assert(util.isNumber(options.cacheSize)); + assert(util.isU64(options.cacheSize)); this.cacheSize = options.cacheSize; } diff --git a/lib/workers/workerpool.js b/lib/workers/workerpool.js index a269b402..8d62e1b0 100644 --- a/lib/workers/workerpool.js +++ b/lib/workers/workerpool.js @@ -68,14 +68,14 @@ WorkerPool.prototype.set = function set(options) { } if (options.size != null) { - assert(util.isNumber(options.size)); + assert(util.isU32(options.size)); assert(options.size > 0); this.size = options.size; } if (options.timeout != null) { - assert(util.isNumber(options.timeout)); - assert(options.timeout > 0); + assert(util.isInt(options.timeout)); + assert(options.timeout >= -1); this.timeout = options.timeout; } diff --git a/migrate/coins-old.js b/migrate/coins-old.js index 5d8cb713..991cc5a7 100644 --- a/migrate/coins-old.js +++ b/migrate/coins-old.js @@ -52,7 +52,7 @@ function Coins(options) { Coins.prototype.fromOptions = function fromOptions(options) { if (options.version != null) { - assert(util.isNumber(options.version)); + assert(util.isU32(options.version)); this.version = options.version; } @@ -62,7 +62,7 @@ Coins.prototype.fromOptions = function fromOptions(options) { } if (options.height != null) { - assert(util.isNumber(options.height)); + assert(util.isInt(options.height)); this.height = options.height; } diff --git a/migrate/coins/coins.js b/migrate/coins/coins.js index bed08958..9aaf1c9b 100644 --- a/migrate/coins/coins.js +++ b/migrate/coins/coins.js @@ -64,7 +64,7 @@ Coins.prototype.fromOptions = function fromOptions(options) { } if (options.height != null) { - assert(util.isNumber(options.height)); + assert(util.isInt(options.height)); this.height = options.height; }