From c190dd2aadb01b0576f164ca1372cfd3460ef8fc Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 2 Dec 2016 04:23:41 -0800 Subject: [PATCH] util: refactor encoding. --- lib/net/packets.js | 2 +- lib/primitives/coin.js | 2 +- lib/primitives/output.js | 2 +- lib/utils/encoding.js | 944 ++++++++++++++++++++++++--------------- lib/utils/protobuf.js | 2 +- lib/utils/reader.js | 195 +++++--- lib/utils/writer.js | 76 +++- test/chain-test.js | 2 +- test/tx-test.js | 22 +- test/utils-test.js | 26 +- 10 files changed, 791 insertions(+), 482 deletions(-) diff --git a/lib/net/packets.js b/lib/net/packets.js index c973568d..c4f9fe6a 100644 --- a/lib/net/packets.js +++ b/lib/net/packets.js @@ -2211,7 +2211,7 @@ FeeFilterPacket.prototype.toRaw = function toRaw(writer) { FeeFilterPacket.prototype.fromRaw = function fromRaw(data) { var br = BufferReader(data); - this.rate = br.read64N(); + this.rate = br.read64(); return this; }; diff --git a/lib/primitives/coin.js b/lib/primitives/coin.js index d4d67bd1..80fce7a2 100644 --- a/lib/primitives/coin.js +++ b/lib/primitives/coin.js @@ -229,7 +229,7 @@ Coin.prototype.fromRaw = function fromRaw(data) { this.version = br.readU32(); this.height = br.readU32(); - this.value = br.read64N(); + this.value = br.read64(); this.script.fromRaw(br.readVarBytes()); this.coinbase = br.readU8() === 1; diff --git a/lib/primitives/output.js b/lib/primitives/output.js index 067c1cf5..4b71d939 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -244,7 +244,7 @@ Output.prototype.toRaw = function toRaw(writer) { Output.prototype.fromRaw = function fromRaw(data) { var br = BufferReader(data); - this.value = br.read64N(); + this.value = br.read64(); this.script.fromRaw(br.readVarBytes()); return this; diff --git a/lib/utils/encoding.js b/lib/utils/encoding.js index 0a3afb6e..e1f129a9 100644 --- a/lib/utils/encoding.js +++ b/lib/utils/encoding.js @@ -25,112 +25,6 @@ encoding.U32_MAX = new BN(0xffffffff); encoding.U64_MAX = new BN('ffffffffffffffff', 'hex'); -/** - * Read uint64le. - * @param {Buffer} data - * @param {Number} off - * @returns {BN} - */ - -encoding.readU64 = function readU64(data, off) { - var num; - off = off >>> 0; - num = data.slice(off, off + 8); - return new BN(num, 'le'); -}; - -/** - * Read uint64be. - * @param {Buffer} data - * @param {Number} off - * @returns {BN} - */ - -encoding.readU64BE = function readU64BE(data, off) { - var num; - off = off >>> 0; - num = data.slice(off, off + 8); - return new BN(num, 'be'); -}; - -/** - * Read int64le. - * @param {Buffer} data - * @param {Number} off - * @returns {BN} - */ - -encoding.read64 = function read64(data, off) { - var num; - - off = off >>> 0; - - num = data.slice(off, off + 8); - - if (num[num.length - 1] & 0x80) - return new BN(num, 'le').notn(64).addn(1).neg(); - - return new BN(num, 'le'); -}; - -/** - * Read int64be. - * @param {Buffer} data - * @param {Number} off - * @returns {BN} - */ - -encoding.read64BE = function read64BE(data, off) { - var num; - - off = off >>> 0; - - num = data.slice(off, off + 8); - - if (num[0] & 0x80) - return new BN(num, 'be').notn(64).addn(1).neg(); - - return new BN(num, 'be'); -}; - -/** - * Write uint64le. - * @param {BN|Number} value - */ - -encoding.writeU64 = function writeU64(dst, num, off) { - return encoding.write64(dst, num, off); -}; - -/** - * Write uint64be. - * @param {BN|Number} value - */ - -encoding.writeU64BE = function writeU64BE(dst, num, off) { - return encoding.write64BE(dst, num, off); -}; - -/** - * Write a javascript number as a uint64le (faster than big numbers). - * @param {Number} value - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeU64N = function writeU64N(dst, num, off) { - return encoding.write64N(dst, num, off); -}; - -/** - * Write a javascript number as a uint64be (faster than big numbers). - * @param {Number} value - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.writeU64NBE = function writeU64NBE(dst, num, off) { - return encoding.write64NBE(dst, num, off); -}; - /** * Max safe integer (53 bits). * @const {Number} @@ -149,78 +43,17 @@ encoding.MAX_SAFE_INTEGER = 0x1fffffffffffff; encoding.MAX_SAFE_ADDITION = 0xfffffffffffff; /** - * Write a javascript number as an int64le (faster than big numbers). - * @param {Number} value - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.write64N = function write64N(dst, num, off, be) { - var negative, hi, lo; - - assert(typeof num === 'number'); - - off = off >>> 0; - - negative = num < 0; - - if (negative) { - num = -num; - num -= 1; - } - - assert(num <= encoding.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1'); - - lo = num % 0x100000000; - hi = (num - lo) / 0x100000000; - - if (negative) { - hi = ~hi >>> 0; - lo = ~lo >>> 0; - } - - if (be) { - dst[off + 0] = (hi >>> 24) & 0xff; - dst[off + 1] = (hi >>> 16) & 0xff; - dst[off + 2] = (hi >>> 8) & 0xff; - dst[off + 3] = (hi >>> 0) & 0xff; - dst[off + 4] = (lo >>> 24) & 0xff; - dst[off + 5] = (lo >>> 16) & 0xff; - dst[off + 6] = (lo >>> 8) & 0xff; - dst[off + 7] = (lo >>> 0) & 0xff; - } else { - dst[off + 0] = (lo >>> 0) & 0xff; - dst[off + 1] = (lo >>> 8) & 0xff; - dst[off + 2] = (lo >>> 16) & 0xff; - dst[off + 3] = (lo >>> 24) & 0xff; - dst[off + 4] = (hi >>> 0) & 0xff; - dst[off + 5] = (hi >>> 8) & 0xff; - dst[off + 6] = (hi >>> 16) & 0xff; - dst[off + 7] = (hi >>> 24) & 0xff; - } - - return off + 8; -}; - -/** - * Write a javascript number as an int64be (faster than big numbers). - * @param {Number} value - * @throws on num > MAX_SAFE_INTEGER - */ - -encoding.write64NBE = function write64NBE(dst, num, off) { - return encoding.write64N(dst, num, off, true); -}; - -/** - * Read uint64le as a js number. + * Read uint64 as a js number. + * @private * @param {Buffer} data * @param {Number} off * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. + * @param {Boolean} be * @returns {Number} * @throws on num > MAX_SAFE_INTEGER */ -encoding.readU64N = function readU64N(data, off, force53, be) { +encoding._readU64 = function _readU64(data, off, force53, be) { var hi, lo; off = off >>> 0; @@ -242,28 +75,65 @@ encoding.readU64N = function readU64N(data, off, force53, be) { }; /** - * Read uint64be as a js number. + * Read uint64le as a js number. * @param {Buffer} data * @param {Number} off - * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. * @returns {Number} * @throws on num > MAX_SAFE_INTEGER */ -encoding.readU64NBE = function readU64NBE(data, off, force53) { - return encoding.readU64N(data, off, force53, true); +encoding.readU64 = function readU64(data, off) { + return encoding._readU64(data, off, false, false); }; /** - * Read int64le as a js number. + * Read uint64be as a js number. * @param {Buffer} data * @param {Number} off - * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. * @returns {Number} * @throws on num > MAX_SAFE_INTEGER */ -encoding.read64N = function read64N(data, off, force53, be) { +encoding.readU64BE = function readU64BE(data, off) { + return encoding._readU64(data, off, false, true); +}; + +/** + * Read uint64le as a js number (limit at 53 bits). + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.readU53 = function readU53(data, off) { + return encoding._readU64(data, off, true, false); +}; + +/** + * Read uint64be as a js number (limit at 53 bits). + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.readU53BE = function readU53BE(data, off) { + return encoding._readU64(data, off, true, true); +}; + +/** + * Read int64 as a js number. + * @private + * @param {Buffer} data + * @param {Number} off + * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. + * @param {Boolean} be + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding._read64 = function _read64(data, off, force53, be) { var hi, lo; off = off >>> 0; @@ -300,84 +170,317 @@ encoding.read64N = function read64N(data, off, force53, be) { * Read int64be as a js number. * @param {Buffer} data * @param {Number} off - * @param {Boolean} force53 - Read only 53 bits, but maintain the sign. * @returns {Number} * @throws on num > MAX_SAFE_INTEGER */ -encoding.read64NBE = function read64NBE(data, off, force53) { - return encoding.read64N(data, off, force53, true); +encoding.read64 = function read64(data, off) { + return encoding._read64(data, off, false, false); +}; + +/** + * Read int64be as a js number. + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.read64BE = function read64BE(data, off) { + return encoding._read64(data, off, false, true); +}; + +/** + * Read int64be as a js number (limit at 53 bits). + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.read53 = function read53(data, off) { + return encoding._read64(data, off, true, false); +}; + +/** + * Read int64be as a js number (limit at 53 bits). + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.read53BE = function read53BE(data, off) { + return encoding._read64(data, off, true, true); +}; + +/** + * Write a javascript number as an int64. + * @private + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @param {Boolean} be + * @returns {Number} Buffer offset. + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding._write64 = function _write64(dst, num, off, be) { + var negative, hi, lo; + + assert(typeof num === 'number'); + + off = off >>> 0; + + negative = num < 0; + + if (negative) { + num = -num; + num -= 1; + } + + assert(num <= encoding.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1'); + + lo = num % 0x100000000; + hi = (num - lo) / 0x100000000; + + if (negative) { + hi = ~hi >>> 0; + lo = ~lo >>> 0; + } + + if (be) { + dst[off++] = hi >>> 24; + dst[off++] = (hi >> 16) & 0xff; + dst[off++] = (hi >> 8) & 0xff; + dst[off++] = hi & 0xff; + + dst[off++] = lo >>> 24; + dst[off++] = (lo >> 16) & 0xff; + dst[off++] = (lo >> 8) & 0xff; + dst[off++] = lo & 0xff; + } else { + dst[off++] = lo & 0xff; + dst[off++] = (lo >> 8) & 0xff; + dst[off++] = (lo >> 16) & 0xff; + dst[off++] = lo >>> 24; + + dst[off++] = hi & 0xff; + dst[off++] = (hi >> 8) & 0xff; + dst[off++] = (hi >> 16) & 0xff; + dst[off++] = hi >>> 24; + } + + return off; +}; + +/** + * Write a javascript number as a uint64le. + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @returns {Number} Buffer offset. + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.writeU64 = function writeU64(dst, num, off) { + return encoding._write64(dst, num, off, false); +}; + +/** + * Write a javascript number as a uint64be. + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @returns {Number} Buffer offset. + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.writeU64BE = function writeU64BE(dst, num, off) { + return encoding._write64(dst, num, off, true); +}; + +/** + * Write a javascript number as an int64le. + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @returns {Number} Buffer offset. + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.write64 = function write64(dst, num, off) { + return encoding._write64(dst, num, off, false); +}; + +/** + * Write a javascript number as an int64be. + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @returns {Number} Buffer offset. + * @throws on num > MAX_SAFE_INTEGER + */ + +encoding.write64BE = function write64BE(dst, num, off) { + return encoding._write64(dst, num, off, true); +}; + +/** + * Read uint64le. + * @param {Buffer} data + * @param {Number} off + * @returns {BN} + */ + +encoding.readU64BN = function readU64BN(data, off) { + var num; + off = off >>> 0; + num = data.slice(off, off + 8); + return new BN(num, 'le'); +}; + +/** + * Read uint64be. + * @param {Buffer} data + * @param {Number} off + * @returns {BN} + */ + +encoding.readU64BEBN = function readU64BEBN(data, off) { + var num; + off = off >>> 0; + num = data.slice(off, off + 8); + return new BN(num, 'be'); +}; + +/** + * Read int64le. + * @param {Buffer} data + * @param {Number} off + * @returns {BN} + */ + +encoding.read64BN = function read64BN(data, off) { + var num; + + off = off >>> 0; + + num = data.slice(off, off + 8); + + if (num[num.length - 1] & 0x80) + return new BN(num, 'le').notn(64).addn(1).neg(); + + return new BN(num, 'le'); +}; +/** + * Read int64be. + * @param {Buffer} data + * @param {Number} off + * @returns {BN} + */ + +encoding.read64BEBN = function read64BEBN(data, off) { + var num; + + off = off >>> 0; + + num = data.slice(off, off + 8); + + if (num[0] & 0x80) + return new BN(num, 'be').notn(64).addn(1).neg(); + + return new BN(num, 'be'); +}; + +/** + * Write int64le. + * @private + * @param {Buffer} dst + * @param {BN} num + * @param {Number} off + * @param {Boolean} be + * @returns {Number} Buffer offset. + */ + +encoding._write64BN = function _write64BN(dst, num, off, be) { + var i; + + if (num.bitLength() <= 53) + return encoding._write64(dst, num.toNumber(), off, be); + + off = off >>> 0; + + if (num.bitLength() > 64) + num = num.uand(encoding.U64_MAX); + + if (num.isNeg()) + num = num.neg().inotn(64).iaddn(1); + + num = num.toArray(be ? 'be' : 'le', 8); + + for (i = 0; i < num.length; i++) + dst[off++] = num[i]; + + return off; +}; + +/** + * Write uint64le. + * @param {Buffer} dst + * @param {BN} num + * @param {Number} off + * @returns {Number} Buffer offset. + */ + +encoding.writeU64BN = function writeU64BN(dst, num, off) { + return encoding._write64BN(dst, num, off, false); +}; + +/** + * Write uint64be. + * @param {Buffer} dst + * @param {BN} num + * @param {Number} off + * @returns {Number} Buffer offset. + */ + +encoding.writeU64BEBN = function writeU64BEBN(dst, num, off) { + return encoding._write64BN(dst, num, off, true); }; /** * Write int64le. * @param {Buffer} dst - * @param {BN|Number} num + * @param {BN} num * @param {Number} off - * @returns {Number} Number of bytes written. + * @returns {Number} Buffer offset. */ -encoding.write64 = function write64(dst, num, off) { - var i; - - if (typeof num === 'number') - return encoding.write64N(dst, num, off); - - off = off >>> 0; - - if (num.isNeg()) - num = num.neg().inotn(64).iaddn(1); - - if (num.bitLength() > 64) - num = num.uand(encoding.U64_MAX); - - num = num.toArray('le', 8); - - for (i = 0; i < num.length; i++) - dst[off++] = num[i]; - - return off; +encoding.write64BN = function write64BN(dst, num, off) { + return encoding._write64BN(dst, num, off, false); }; /** * Write int64be. * @param {Buffer} dst - * @param {BN|Number} num + * @param {BN} num * @param {Number} off - * @returns {Number} Number of bytes written. + * @returns {Number} Buffer offset. */ -encoding.write64BE = function write64BE(dst, num, off) { - var i; - - if (typeof num === 'number') - return encoding.write64NBE(dst, num, off); - - off = off >>> 0; - - if (num.isNeg()) - num = num.neg().inotn(64).iaddn(1); - - if (num.bitLength() > 64) - num = num.uand(encoding.U64_MAX); - - num = num.toArray('be', 8); - - for (i = 0; i < num.length; i++) - dst[off++] = num[i]; - - return off; +encoding.write64BEBN = function write64BEBN(dst, num, off) { + return encoding._write64BN(dst, num, off, true); }; /** * Read a varint. * @param {Buffer} data * @param {Number} off - * @param {Boolean?} big - Whether to read as a big number. * @returns {Object} */ -encoding.readVarint = function readVarint(data, off, big) { +encoding.readVarint = function readVarint(data, off) { var value, size; off = off >>> 0; @@ -388,39 +491,67 @@ encoding.readVarint = function readVarint(data, off, big) { case 0xff: size = 9; assert(off + size <= data.length); - if (big) { - value = encoding.readU64(data, off + 1); - assert(value.bitLength() > 32); - } else { - value = encoding.readU64N(data, off + 1); - assert(value > 0xffffffff); - } + value = encoding.readU64(data, off + 1); + assert(value > 0xffffffff); break; case 0xfe: size = 5; assert(off + size <= data.length); value = data.readUInt32LE(off + 1, true); assert(value > 0xffff); - if (big) - value = new BN(value); break; case 0xfd: size = 3; assert(off + size <= data.length); value = data[off + 1] | (data[off + 2] << 8); assert(value >= 0xfd); - if (big) - value = new BN(value); break; default: size = 1; value = data[off]; - if (big) - value = new BN(value); break; } - return { size: size, value: value }; + return new Varint(size, value); +}; + +/** + * Write a varint. + * @param {Buffer} dst + * @param {Number} num + * @param {Number} off + * @returns {Number} Buffer offset. + */ + +encoding.writeVarint = function writeVarint(dst, num, off) { + off = off >>> 0; + + num = +num; + + if (num < 0xfd) { + dst[off++] = num & 0xff; + return off; + } + + if (num <= 0xffff) { + dst[off++] = 0xfd; + dst[off++] = num & 0xff; + dst[off++] = (num >> 8) & 0xff; + return off; + } + + if (num <= 0xffffffff) { + dst[off++] = 0xfe; + dst[off++] = num & 0xff; + dst[off++] = (num >> 8) & 0xff; + dst[off++] = (num >> 16) & 0xff; + dst[off++] = num >>> 24; + return off; + } + + dst[off++] = 0xff; + off = encoding.writeU64(dst, num, off); + return off; }; /** @@ -447,68 +578,13 @@ encoding.skipVarint = function skipVarint(data, off) { } }; -/** - * Write a varint. - * @param {Buffer} dst - * @param {BN|Number} num - * @param {Number} off - * @returns {Number} Number of bytes written. - */ - -encoding.writeVarint = function writeVarint(dst, num, off) { - off = off >>> 0; - - if (BN.isBN(num)) { - if (num.bitLength() > 32) { - dst[off] = 0xff; - encoding.writeU64(dst, num, off + 1); - return off + 9; - } - num = num.toNumber(); - } - - num = +num; - - if (num < 0xfd) { - dst[off] = num & 0xff; - return off + 1; - } - - if (num <= 0xffff) { - dst[off] = 0xfd; - dst[off + 1] = num & 0xff; - dst[off + 2] = (num >>> 8) & 0xff; - return off + 3; - } - - if (num <= 0xffffffff) { - dst[off] = 0xfe; - dst[off + 1] = num & 0xff; - dst[off + 2] = (num >>> 8) & 0xff; - dst[off + 3] = (num >>> 16) & 0xff; - dst[off + 4] = (num >>> 24) & 0xff; - return off + 5; - } - - dst[off] = 0xff; - encoding.writeU64N(dst, num, off + 1); - - return off + 9; -}; - /** * Calculate size of varint. - * @param {BN|Number} num + * @param {Number} num * @returns {Number} size */ encoding.sizeVarint = function sizeVarint(num) { - if (BN.isBN(num)) { - if (num.bitLength() > 32) - return 9; - num = num.toNumber(); - } - if (num < 0xfd) return 1; @@ -522,17 +598,75 @@ encoding.sizeVarint = function sizeVarint(num) { }; /** - * Read a varint (type 2). + * Read a varint. * @param {Buffer} data * @param {Number} off - * @param {Boolean?} big - Whether to read as a big number. * @returns {Object} */ -encoding.readVarint2 = function readVarint2(data, off, big) { +encoding.readVarintBN = function readVarintBN(data, off) { + var value, size; + + off = off >>> 0; + + assert(off < data.length); + + switch (data[off]) { + case 0xff: + size = 9; + assert(off + size <= data.length); + value = encoding.readU64(data, off + 1); + assert(value.bitLength() > 32); + return new Varint(size, value); + default: + return encoding.readVarint(data, off); + } +}; + +/** + * Write a varint. + * @param {Buffer} dst + * @param {BN} num + * @param {Number} off + * @returns {Number} Buffer offset. + */ + +encoding.writeVarintBN = function writeVarintBN(dst, num, off) { + off = off >>> 0; + + if (num.bitLength() > 32) { + dst[off] = 0xff; + encoding.writeU64(dst, num, off + 1); + return off + 9; + } + + return encoding.writeVarint(dst, num.toNumber(), off); +}; + +/** + * Calculate size of varint. + * @param {BN} num + * @returns {Number} size + */ + +encoding.sizeVarintBN = function sizeVarintBN(num) { + if (num.bitLength() > 32) + return 9; + + return encoding.sizeVarint(num.toNumber()); +}; + +/** + * Read a varint (type 2). + * @param {Buffer} data + * @param {Number} off + * @returns {Object} + */ + +encoding.readVarint2 = function readVarint2(data, off) { var num = 0; var size = 0; - var bnum, ch; + var ch; off = off >>> 0; @@ -542,70 +676,31 @@ encoding.readVarint2 = function readVarint2(data, off, big) { ch = data[off++]; size++; - if (num >= 0x3fffffffffff) { - assert(big, 'Number exceeds 2^53-1.'); - bnum = new BN(num); - num = 0; - } - - if (bnum) { - assert(bnum.bitLength() <= 256); - bnum.iushln(7).iaddn(ch & 0x7f); - if ((ch & 0x80) === 0) - break; - bnum.iaddn(1); - continue; - } + assert(num < 0x3fffffffffff, 'Number exceeds 2^53-1.'); num = (num * 0x80) + (ch & 0x7f); + if ((ch & 0x80) === 0) break; + num++; } - if (bnum) - return { size: size, value: bnum }; - - if (big) - num = new BN(num); - - return { size: size, value: num }; + return new Varint(size, num); }; /** * Write a varint (type 2). * @param {Buffer} dst - * @param {BN|Number} num + * @param {Number} num * @param {Number} off - * @returns {Number} Number of bytes written. + * @returns {Number} Buffer offset. */ encoding.writeVarint2 = function writeVarint2(dst, num, off) { var tmp = []; var len = 0; - if (BN.isBN(num)) { - if (num.bitLength() > 53) { - for (;;) { - tmp[len] = (num.words[0] & 0x7f) | (len ? 0x80 : 0x00); - if (num.cmpn(0x7f) <= 0) - break; - num.iushrn(7).isubn(1); - len++; - } - - assert(off + len <= dst.length); - - do { - dst[off++] = tmp[len]; - } while (len--); - - return off; - } - - num = num.toNumber(); - } - off = off >>> 0; num = +num; @@ -626,32 +721,39 @@ encoding.writeVarint2 = function writeVarint2(dst, num, off) { return off; }; +/** + * Read a varint size. + * @param {Buffer} data + * @param {Number} off + * @returns {Number} + */ + +encoding.skipVarint2 = function skipVarint2(data, off) { + var size = 0; + var ch; + + off = off >>> 0; + + for (;;) { + assert(off < data.length); + ch = data[off++]; + size++; + if ((ch & 0x80) === 0) + break; + } + + return size; +}; + /** * Calculate size of varint (type 2). - * @param {BN|Number} num + * @param {Number} num * @returns {Number} size */ encoding.sizeVarint2 = function sizeVarint2(num) { var size = 0; - if (BN.isBN(num)) { - if (num.bitLength() > 53) { - num = num.clone(); - - for (;;) { - size++; - if (num.cmpn(0x7f) <= 0) - break; - num.iushrn(7).isubn(1); - } - - return size; - } - - num = num.toNumber(); - } - num = +num; for (;;) { @@ -664,6 +766,111 @@ encoding.sizeVarint2 = function sizeVarint2(num) { return size; }; +/** + * Read a varint (type 2). + * @param {Buffer} data + * @param {Number} off + * @returns {Object} + */ + +encoding.readVarint2BN = function readVarint2BN(data, off) { + var num = 0; + var size = 0; + var ch; + + off = off >>> 0; + + while (num < 0x3fffffffffff) { + assert(off < data.length); + + ch = data[off++]; + size++; + + num = (num * 0x80) + (ch & 0x7f); + + if ((ch & 0x80) === 0) + return new Varint(size, new BN(num)); + + num++; + } + + num = new BN(num); + + for (;;) { + assert(off < data.length); + + ch = data[off++]; + size++; + + assert(num.bitLength() <= 64, 'Number exceeds 64 bits.'); + + num.iushln(7).iaddn(ch & 0x7f); + + if ((ch & 0x80) === 0) + break; + + num.iaddn(1); + } + + return new Varint(size, num); +}; + +/** + * Write a varint (type 2). + * @param {Buffer} dst + * @param {BN} num + * @param {Number} off + * @returns {Number} Buffer offset. + */ + +encoding.writeVarint2BN = function writeVarint2BN(dst, num, off) { + var tmp = []; + var len = 0; + + if (num.bitLength() <= 53) + return encoding.writeVarint2(dst, num.toNumber()); + + for (;;) { + tmp[len] = (num.words[0] & 0x7f) | (len ? 0x80 : 0x00); + if (num.cmpn(0x7f) <= 0) + break; + num.iushrn(7).isubn(1); + len++; + } + + assert(off + len <= dst.length); + + do { + dst[off++] = tmp[len]; + } while (len--); + + return off; +}; + +/** + * Calculate size of varint (type 2). + * @param {BN} num + * @returns {Number} size + */ + +encoding.sizeVarint2BN = function sizeVarint2BN(num) { + var size = 0; + + if (num.bitLength() <= 53) + return encoding.sizeVarint(num.toNumber()); + + num = num.clone(); + + for (;;) { + size++; + if (num.cmpn(0x7f) <= 0) + break; + num.iushrn(7).isubn(1); + } + + return size; +}; + /** * Serialize number as a u8. * @param {Number} num @@ -687,3 +894,12 @@ encoding.U32 = function U32(num) { data.writeUInt32LE(num, 0, true); return data; }; + +/* + * Helpers + */ + +function Varint(size, value) { + this.size = size; + this.value = value; +} diff --git a/lib/utils/protobuf.js b/lib/utils/protobuf.js index 770b9a5a..e13423ad 100644 --- a/lib/utils/protobuf.js +++ b/lib/utils/protobuf.js @@ -105,7 +105,7 @@ ProtoReader.prototype.readField = function readField(tag, opt) { value = this.readVarint(); break; case wireType.FIXED64: - value = this.readU64N(); + value = this.readU64(); break; case wireType.DELIMITED: data = this.readVarBytes(); diff --git a/lib/utils/reader.js b/lib/utils/reader.js index ce2ab4f9..b13b75c7 100644 --- a/lib/utils/reader.js +++ b/lib/utils/reader.js @@ -200,8 +200,9 @@ BufferReader.prototype.readU32BE = function readU32BE() { }; /** - * Read uint64le. - * @returns {BN} + * Read uint64le as a js number. + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER */ BufferReader.prototype.readU64 = function readU64() { @@ -213,8 +214,9 @@ BufferReader.prototype.readU64 = function readU64() { }; /** - * Read uint64be. - * @returns {BN} + * Read uint64be as a js number. + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER */ BufferReader.prototype.readU64BE = function readU64BE() { @@ -225,34 +227,6 @@ BufferReader.prototype.readU64BE = function readU64BE() { return ret; }; -/** - * Read uint64le as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readU64N = function readU64N(force53) { - var ret; - assert(this.offset + 8 <= this.data.length); - ret = encoding.readU64N(this.data, this.offset, force53); - this.offset += 8; - return ret; -}; - -/** - * Read uint64be as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.readU64NBE = function readU64NBE(force53) { - var ret; - assert(this.offset + 8 <= this.data.length); - ret = encoding.readU64NBE(this.data, this.offset, force53); - this.offset += 8; - return ret; -}; - /** * Read first least significant 53 bits of * a uint64le as a js number. Maintain the sign. @@ -260,7 +234,11 @@ BufferReader.prototype.readU64NBE = function readU64NBE(force53) { */ BufferReader.prototype.readU53 = function readU53() { - return this.readU64N(true); + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.readU53(this.data, this.offset); + this.offset += 8; + return ret; }; /** @@ -270,7 +248,11 @@ BufferReader.prototype.readU53 = function readU53() { */ BufferReader.prototype.readU53BE = function readU53BE() { - return this.readU64NBE(true); + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.readU53BE(this.data, this.offset); + this.offset += 8; + return ret; }; /** @@ -339,8 +321,9 @@ BufferReader.prototype.read32BE = function read32BE() { }; /** - * Read int64le. - * @returns {BN} + * Read int64le as a js number. + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER */ BufferReader.prototype.read64 = function read64() { @@ -352,8 +335,9 @@ BufferReader.prototype.read64 = function read64() { }; /** - * Read int64be. - * @returns {BN} + * Read int64be as a js number. + * @returns {Number} + * @throws on num > MAX_SAFE_INTEGER */ BufferReader.prototype.read64BE = function read64BE() { @@ -364,34 +348,6 @@ BufferReader.prototype.read64BE = function read64BE() { return ret; }; -/** - * Read int64le as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.read64N = function read64N(force53) { - var ret; - assert(this.offset + 8 <= this.data.length); - ret = encoding.read64N(this.data, this.offset, force53); - this.offset += 8; - return ret; -}; - -/** - * Read int64be as a js number. - * @returns {Number} - * @throws on num > MAX_SAFE_INTEGER - */ - -BufferReader.prototype.read64NBE = function read64NBE(force53) { - var ret; - assert(this.offset + 8 <= this.data.length); - ret = encoding.read64NBE(this.data, this.offset, force53); - this.offset += 8; - return ret; -}; - /** * Read first least significant 53 bits of * a int64le as a js number. Maintain the sign. @@ -399,7 +355,11 @@ BufferReader.prototype.read64NBE = function read64NBE(force53) { */ BufferReader.prototype.read53 = function read53() { - return this.read64N(true); + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.read53(this.data, this.offset); + this.offset += 8; + return ret; }; /** @@ -409,7 +369,63 @@ BufferReader.prototype.read53 = function read53() { */ BufferReader.prototype.read53BE = function read53BE() { - return this.read64NBE(true); + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.read53BE(this.data, this.offset); + this.offset += 8; + return ret; +}; + +/** + * Read uint64le. + * @returns {BN} + */ + +BufferReader.prototype.readU64BN = function readU64BN() { + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.readU64BN(this.data, this.offset); + this.offset += 8; + return ret; +}; + +/** + * Read uint64be. + * @returns {BN} + */ + +BufferReader.prototype.readU64BEBN = function readU64BEBN() { + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.readU64BEBN(this.data, this.offset); + this.offset += 8; + return ret; +}; + +/** + * Read int64le. + * @returns {BN} + */ + +BufferReader.prototype.read64BN = function read64BN() { + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.read64BN(this.data, this.offset); + this.offset += 8; + return ret; +}; + +/** + * Read int64be. + * @returns {BN} + */ + +BufferReader.prototype.read64BEBN = function read64BEBN() { + var ret; + assert(this.offset + 8 <= this.data.length); + ret = encoding.read64BEBN(this.data, this.offset); + this.offset += 8; + return ret; }; /** @@ -466,12 +482,11 @@ BufferReader.prototype.readDoubleBE = function readDoubleBE() { /** * Read a varint. - * @param {Boolean?} big - Whether to read as a big number. * @returns {Number} */ -BufferReader.prototype.readVarint = function readVarint(big) { - var result = encoding.readVarint(this.data, this.offset, big); +BufferReader.prototype.readVarint = function readVarint() { + var result = encoding.readVarint(this.data, this.offset); this.offset += result.size; return result.value; }; @@ -487,14 +502,46 @@ BufferReader.prototype.skipVarint = function skipVarint() { this.offset += size; }; +/** + * Read a varint. + * @returns {BN} + */ + +BufferReader.prototype.readVarintBN = function readVarintBN() { + var result = encoding.readVarintBN(this.data, this.offset); + this.offset += result.size; + return result.value; +}; + /** * Read a varint (type 2). - * @param {Boolean?} big - Whether to read as a big number. * @returns {Number} */ -BufferReader.prototype.readVarint2 = function readVarint2(big) { - var result = encoding.readVarint2(this.data, this.offset, big); +BufferReader.prototype.readVarint2 = function readVarint2() { + var result = encoding.readVarint2(this.data, this.offset); + this.offset += result.size; + return result.value; +}; + +/** + * Skip past a varint (type 2). + * @returns {Number} + */ + +BufferReader.prototype.skipVarint2 = function skipVarint2() { + var size = encoding.skipVarint2(this.data, this.offset); + assert(this.offset + size <= this.data.length); + this.offset += size; +}; + +/** + * Read a varint (type 2). + * @returns {BN} + */ + +BufferReader.prototype.readVarint2BN = function readVarint2BN() { + var result = encoding.readVarint2BN(this.data, this.offset); this.offset += result.size; return result.value; }; diff --git a/lib/utils/writer.js b/lib/utils/writer.js index dd20d107..58d48e80 100644 --- a/lib/utils/writer.js +++ b/lib/utils/writer.js @@ -206,7 +206,7 @@ BufferWriter.prototype.writeU32BE = function writeU32BE(value) { /** * Write uint64le. - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.writeU64 = function writeU64(value) { @@ -216,7 +216,7 @@ BufferWriter.prototype.writeU64 = function writeU64(value) { /** * Write uint64be. - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.writeU64BE = function writeU64BE(value) { @@ -224,6 +224,24 @@ BufferWriter.prototype.writeU64BE = function writeU64BE(value) { this.ops.push(new WriteOp(UI64BE, value)); }; +/** + * Write uint64le. + * @param {BN} value + */ + +BufferWriter.prototype.writeU64BN = function writeU64BN(value) { + assert(false, 'Not implemented.'); +}; + +/** + * Write uint64be. + * @param {BN} value + */ + +BufferWriter.prototype.writeU64BEBN = function writeU64BEBN(value) { + assert(false, 'Not implemented.'); +}; + /** * Write int8. * @param {Number} value @@ -276,7 +294,7 @@ BufferWriter.prototype.write32BE = function write32BE(value) { /** * Write int64le. - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.write64 = function write64(value) { @@ -286,7 +304,7 @@ BufferWriter.prototype.write64 = function write64(value) { /** * Write int64be. - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.write64BE = function write64BE(value) { @@ -294,6 +312,24 @@ BufferWriter.prototype.write64BE = function write64BE(value) { this.ops.push(new WriteOp(I64BE, value)); }; +/** + * Write int64le. + * @param {BN} value + */ + +BufferWriter.prototype.write64BN = function write64BN(value) { + assert(false, 'Not implemented.'); +}; + +/** + * Write int64be. + * @param {BN} value + */ + +BufferWriter.prototype.write64BEBN = function write64BEBN(value) { + assert(false, 'Not implemented.'); +}; + /** * Write float le. * @param {Number} value @@ -336,34 +372,42 @@ BufferWriter.prototype.writeDoubleBE = function writeDoubleBE(value) { /** * Write a varint. - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.writeVarint = function writeVarint(value) { - if (typeof value === 'number') - assert(value >= 0); - else - assert(!value.isNeg()); - this.written += encoding.sizeVarint(value); this.ops.push(new WriteOp(VARINT, value)); }; +/** + * Write a varint. + * @param {BN} value + */ + +BufferWriter.prototype.writeVarintBN = function writeVarintBN(value) { + assert(false, 'Not implemented.'); +}; + /** * Write a varint (type 2). - * @param {BN|Number} value + * @param {Number} value */ BufferWriter.prototype.writeVarint2 = function writeVarint2(value) { - if (typeof value === 'number') - assert(value >= 0); - else - assert(!value.isNeg()); - this.written += encoding.sizeVarint2(value); this.ops.push(new WriteOp(VARINT2, value)); }; +/** + * Write a varint (type 2). + * @param {BN} value + */ + +BufferWriter.prototype.writeVarint2BN = function writeVarint2BN(value) { + assert(false, 'Not implemented.'); +}; + /** * Write bytes. * @param {Buffer} value diff --git a/test/chain-test.js b/test/chain-test.js index 90e39058..1cbc6d0f 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -15,7 +15,7 @@ describe('Chain', function() { var chain, wallet, node, miner, walletdb; var tip1, tip2, cb1, cb2, mineBlock; - this.timeout(5000); + this.timeout(8000); node = new bcoin.fullnode({ db: 'memory', apiKey: 'foo' }); // node.walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' }); diff --git a/test/tx-test.js b/test/tx-test.js index bf7d15b8..3d405346 100644 --- a/test/tx-test.js +++ b/test/tx-test.js @@ -4,6 +4,7 @@ var BN = require('bn.js'); var bcoin = require('../').set('main'); var assert = require('assert'); var util = bcoin.util; +var encoding = require('../lib/utils/encoding'); var crypto = require('../lib/crypto/crypto'); var constants = bcoin.constants; var opcodes = bcoin.constants.opcodes; @@ -492,22 +493,23 @@ describe('TX', function() { ], outputs: [{ script: [], - value: 0 + value: 0xdeadbeef }], locktime: 0 }); - tx.outputs[0].value = new BN('00ffffffffffffff', 'hex'); - assert(tx.outputs[0].value.bitLength() === 56); - var raw = tx.toRaw() + var raw = tx.toRaw(); + assert(encoding.readU64(raw, 47) === 0xdeadbeef); + raw[54] = 0x7f; assert.throws(function() { - bcoin.tx.fromRaw(raw); + console.log(bcoin.tx.fromRaw(raw)); }); - delete tx._raw; - tx.outputs[0].value = new BN('00ffffffffffffff', 'hex').ineg(); - assert(tx.outputs[0].value.bitLength() === 56); - var raw = tx.toRaw() + tx._raw = null; + tx.outputs[0].value = 0; + var raw = tx.toRaw(); + assert(encoding.readU64(raw, 47) === 0x00); + raw[54] = 0x80; assert.throws(function() { - bcoin.tx.fromRaw(raw); + console.log(bcoin.tx.fromRaw(raw)); }); }); diff --git a/test/utils-test.js b/test/utils-test.js index 3b0c5f4c..5d63b510 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -192,11 +192,11 @@ describe('Utils', function() { var buf2 = new Buffer(8); var msg = 'should write+read a ' + num.bitLength() + ' bit unsigned int'; it(msg, function() { - encoding.writeU64(buf1, num, 0); - encoding.writeU64N(buf2, num.toNumber(), 0); + encoding.writeU64BN(buf1, num, 0); + encoding.writeU64(buf2, num.toNumber(), 0); assert.deepEqual(buf1, buf2); - var n1 = encoding.readU64(buf1, 0); - var n2 = encoding.readU64N(buf2, 0); + var n1 = encoding.readU64BN(buf1, 0); + var n2 = encoding.readU64(buf2, 0); assert.equal(n1.toNumber(), n2); }); }); @@ -207,26 +207,26 @@ describe('Utils', function() { var msg = 'should write+read a ' + num.bitLength() + ' bit ' + (num.isNeg() ? 'negative' : 'positive') + ' int'; it(msg, function() { - encoding.write64(buf1, num, 0); - encoding.write64N(buf2, num.toNumber(), 0); + encoding.write64BN(buf1, num, 0); + encoding.write64(buf2, num.toNumber(), 0); assert.deepEqual(buf1, buf2); - var n1 = encoding.read64(buf1, 0); - var n2 = encoding.read64N(buf2, 0); + var n1 = encoding.read64BN(buf1, 0); + var n2 = encoding.read64(buf2, 0); assert.equal(n1.toNumber(), n2); }); var msg = 'should write+read a ' + num.bitLength() + ' bit ' + (num.isNeg() ? 'negative' : 'positive') + ' int as unsigned'; it(msg, function() { - encoding.writeU64(buf1, num, 0); - encoding.writeU64N(buf2, num.toNumber(), 0); + encoding.writeU64BN(buf1, num, 0); + encoding.writeU64(buf2, num.toNumber(), 0); assert.deepEqual(buf1, buf2); - var n1 = encoding.readU64(buf1, 0); + var n1 = encoding.readU64BN(buf1, 0); if (num.isNeg()) { assert.throws(function() { - encoding.readU64N(buf2, 0); + encoding.readU64(buf2, 0); }); } else { - var n2 = encoding.readU64N(buf2, 0); + var n2 = encoding.readU64(buf2, 0); assert.equal(n1.toNumber(), n2); } });