diff --git a/bench/coins.js b/bench/coins.js index c8d31d0a..44d6b000 100644 --- a/bench/coins.js +++ b/bench/coins.js @@ -7,7 +7,7 @@ var bench = require('./bench'); var raw = fs.readFileSync(__dirname + '/../test/data/wtx.hex', 'utf8'); var wtx = TX.fromRaw(raw.trim(), 'hex'); -var coins = Coins.fromTX(wtx); +var coins = Coins.fromTX(wtx, 1); var i, j, end, hash; end = bench('serialize'); diff --git a/lib/coins/coins.js b/lib/coins/coins.js index 70c19aa9..c10412df 100644 --- a/lib/coins/coins.js +++ b/lib/coins/coins.js @@ -12,7 +12,8 @@ var constants = require('../protocol/constants'); var Coin = require('../primitives/coin'); var Output = require('../primitives/output'); var BufferReader = require('../utils/reader'); -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); +var encoding = require('../utils/encoding'); var compressor = require('./compress'); var compress = compressor.compress; var decompress = compressor.decompress; @@ -306,18 +307,17 @@ Coins.prototype.isEmpty = function isEmpty() { */ /** - * Write the coins object to writer. - * @param {BufferWriter} bw - * @returns {BufferWriter} + * Calculate header code. + * @param {Number} len + * @param {Number} size + * @returns {Number} */ -Coins.prototype.toWriter = function toWriter(bw) { - var len = this.length(); - var size = Math.floor((len + 5) / 8); +Coins.prototype.header = function header(len, size) { var first = this.isUnspent(0); var second = this.isUnspent(1); var offset = 0; - var i, j, code, ch, output; + var code; // Throw if we're fully spent. assert(len !== 0, 'Cannot serialize fully-spent coins.'); @@ -341,6 +341,22 @@ Coins.prototype.toWriter = function toWriter(bw) { if (second) code += 4; + return code; +}; + +/** + * Serialize the coins object. + * @returns {Buffer} + */ + +Coins.prototype.toRaw = function toRaw() { + var len = this.length(); + var size = Math.floor((len + 5) / 8); + var code = this.header(len, size); + var total = this.getSize(len, size, code); + var bw = new StaticWriter(total); + var i, j, ch, output; + // Write headers. bw.writeVarint(this.version); bw.writeU32(this.height); @@ -366,27 +382,49 @@ Coins.prototype.toWriter = function toWriter(bw) { output.toWriter(bw); } - return bw; + return bw.render(); }; /** - * Serialize the coins object. - * @returns {Buffer} + * Calculate coins size. + * @param {Number} code + * @param {Number} size + * @param {Number} len + * @returns {Number} */ -Coins.prototype.toRaw = function toRaw() { - return this.toWriter(new BufferWriter()).render(); +Coins.prototype.getSize = function getSize(len, size, code) { + var total = 0; + var i, output; + + total += encoding.sizeVarint(this.version); + total += 4; + total += encoding.sizeVarint(code); + total += size; + + // Write the compressed outputs. + for (i = 0; i < len; i++) { + output = this.outputs[i]; + + if (!output || output.spent) + continue; + + total += output.getSize(); + } + + return total; }; /** - * Inject data from buffer reader. + * Inject data from serialized coins. * @private - * @param {BufferReader} br + * @param {Buffer} data * @param {Hash} hash * @returns {Coins} */ -Coins.prototype.fromReader = function fromReader(br, hash) { +Coins.prototype.fromRaw = function fromRaw(data, hash) { + var br = new BufferReader(data); var first = null; var second = null; var i, j, code, size, offset, ch; @@ -437,18 +475,6 @@ Coins.prototype.fromReader = function fromReader(br, hash) { return this; }; -/** - * Inject data from serialized coins. - * @private - * @param {Buffer} data - * @param {Hash} hash - * @returns {Coins} - */ - -Coins.prototype.fromRaw = function fromRaw(data, hash) { - return this.fromReader(new BufferReader(data), hash); -}; - /** * Parse a single serialized coin. * @param {Buffer} data @@ -519,17 +545,6 @@ Coins.parseCoin = function parseCoin(data, hash, index) { } }; -/** - * Instantiate coins from a buffer reader. - * @param {Buffer} data - * @param {Hash} hash - Transaction hash. - * @returns {Coins} - */ - -Coins.fromReader = function fromReader(br, hash) { - return new Coins().fromReader(br, hash); -}; - /** * Instantiate coins from a buffer. * @param {Buffer} data @@ -668,6 +683,18 @@ CoinEntry.prototype.toOutput = function toOutput() { return this.output; }; +/** + * Calculate coin entry size. + * @returns {Number} + */ + +CoinEntry.prototype.getSize = function getSize() { + if (!this.raw) + return compress.size(this.output); + + return this.size; +}; + /** * Slice off the part of the buffer * relevant to this particular coin. diff --git a/lib/coins/compress.js b/lib/coins/compress.js index 4467c720..1f842e73 100644 --- a/lib/coins/compress.js +++ b/lib/coins/compress.js @@ -8,6 +8,7 @@ var assert = require('assert'); var ec = require('../crypto/ec'); +var encoding = require('../utils/encoding'); /* * Constants @@ -115,6 +116,33 @@ function decompressScript(script, br) { return script; } +/** + * Calculate script size. + * @returns {Number} + */ + +function sizeScript(script) { + var size, data; + + if (script.isPubkeyhash(true)) + return 21; + + if (script.isScripthash()) + return 21; + + if (script.isPubkey(true)) { + data = script.code[0].data; + if (publicKeyVerify(data)) + return 33; + } + + size = 0; + size += encoding.sizeVarint(script.raw.length + COMPRESS_TYPES); + size += script.raw.length; + + return size; +} + /** * Compress an output. * @param {Output} output @@ -139,6 +167,18 @@ function decompressOutput(output, br) { return output; } +/** + * Calculate output size. + * @returns {Number} + */ + +function sizeOutput(output) { + var size = 0; + size += encoding.sizeVarint(output.value); + size += sizeScript(output.script); + return size; +} + /** * Compress an output. * @param {Coin} coin @@ -356,6 +396,7 @@ function decompressKey(key) { exports.compress = { output: compressOutput, coin: compressCoin, + size: sizeOutput, script: compressScript, value: compressValue, key: compressKey diff --git a/lib/coins/undocoins.js b/lib/coins/undocoins.js index b00c7706..022ed55f 100644 --- a/lib/coins/undocoins.js +++ b/lib/coins/undocoins.js @@ -8,7 +8,8 @@ var assert = require('assert'); var BufferReader = require('../utils/reader'); -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); +var encoding = require('../utils/encoding'); var Output = require('../primitives/output'); var Coins = require('./coins'); var compressor = require('./compress'); @@ -43,13 +44,33 @@ UndoCoins.prototype.push = function push(entry) { this.items.push(undo); }; +/** + * Calculate undo coins size. + * @returns {Number} + */ + +UndoCoins.prototype.getSize = function getSize() { + var size = 0; + var i, coin; + + size += 4; + + for (i = 0; i < this.items.length; i++) { + coin = this.items[i]; + size += coin.getSize(); + } + + return size; +}; + /** * Serialize all undo coins. * @returns {Buffer} */ UndoCoins.prototype.toRaw = function toRaw() { - var bw = new BufferWriter(); + var size = this.getSize(); + var bw = new StaticWriter(size); var i, coin; bw.writeU32(this.items.length); @@ -184,6 +205,33 @@ UndoCoin.prototype.toOutput = function toOutput() { return this.output; }; +/** + * Calculate undo coin size. + * @returns {Number} + */ + +UndoCoin.prototype.getSize = function getSize() { + var height = this.height; + var size = 0; + + if (height === -1) + height = 0; + + size += encoding.sizeVarint(height * 2 + (this.coinbase ? 1 : 0)); + + if (this.height !== -1) + size += encoding.sizeVarint(this.version); + + if (this.entry) { + // Cached from spend. + size += this.entry.getSize(); + } else { + size += compress.size(this.output); + } + + return size; +}; + /** * Write the undo coin to a buffer writer. * @param {BufferWriter} bw @@ -220,7 +268,8 @@ UndoCoin.prototype.toWriter = function toWriter(bw) { */ UndoCoin.prototype.toRaw = function toRaw() { - return this.toWriter(new BufferWriter()).render(); + var size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); }; /** diff --git a/lib/primitives/abstractblock.js b/lib/primitives/abstractblock.js index 08be9641..78e7c514 100644 --- a/lib/primitives/abstractblock.js +++ b/lib/primitives/abstractblock.js @@ -13,7 +13,7 @@ var util = require('../utils/util'); var crypto = require('../crypto/crypto'); var btcutils = require('../btc/utils'); var VerifyResult = require('../btc/errors').VerifyResult; -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); var InvItem = require('./invitem'); /** @@ -160,7 +160,7 @@ AbstractBlock.prototype.hash = function hash(enc) { */ AbstractBlock.prototype.abbr = function abbr() { - var bw = new BufferWriter(); + var bw = new StaticWriter(80); bw.writeU32(this.version); bw.writeHash(this.prevBlock); bw.writeHash(this.merkleRoot); diff --git a/lib/primitives/input.js b/lib/primitives/input.js index 99783022..afa18e4e 100644 --- a/lib/primitives/input.js +++ b/lib/primitives/input.js @@ -14,7 +14,7 @@ var Network = require('../protocol/network'); var Script = require('../script/script'); var Witness = require('../script/witness'); var Outpoint = require('./outpoint'); -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); var BufferReader = require('../utils/reader'); /** @@ -335,7 +335,8 @@ Input.prototype.getSize = function getSize() { */ Input.prototype.toRaw = function toRaw() { - return this.toWriter(new BufferWriter()).render(); + var size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); }; /** diff --git a/lib/primitives/outpoint.js b/lib/primitives/outpoint.js index 335560fd..e2687390 100644 --- a/lib/primitives/outpoint.js +++ b/lib/primitives/outpoint.js @@ -9,7 +9,7 @@ var util = require('../utils/util'); var assert = require('assert'); var constants = require('../protocol/constants'); -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/writer'); var BufferReader = require('../utils/reader'); /** @@ -124,7 +124,7 @@ Outpoint.prototype.getSize = function getSize() { */ Outpoint.prototype.toRaw = function toRaw() { - return this.toWriter(new BufferWriter()).render(); + return this.toWriter(new StaticWriter(36)).render(); }; /** diff --git a/lib/primitives/output.js b/lib/primitives/output.js index f4015add..aa3195b5 100644 --- a/lib/primitives/output.js +++ b/lib/primitives/output.js @@ -13,7 +13,7 @@ var btcutils = require('../btc/utils'); var Amount = require('../btc/amount'); var Network = require('../protocol/network'); var Script = require('../script/script'); -var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); var BufferReader = require('../utils/reader'); var assert = require('assert'); @@ -246,7 +246,8 @@ Output.prototype.toWriter = function toWriter(bw) { */ Output.prototype.toRaw = function toRaw() { - return this.toWriter(new BufferWriter()).render(); + var size = this.getSize(); + return this.toWriter(new StaticWriter(size)).render(); }; /** diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 4706525a..371b95dd 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -9,6 +9,7 @@ var assert = require('assert'); var util = require('../utils/util'); +var encoding = require('../utils/encoding'); var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var btcutils = require('../btc/utils'); @@ -17,6 +18,7 @@ var constants = require('../protocol/constants'); var Network = require('../protocol/network'); var Script = require('../script/script'); var BufferWriter = require('../utils/writer'); +var StaticWriter = require('../utils/staticwriter'); var VerifyResult = require('../btc/errors').VerifyResult; var Input = require('./input'); var Output = require('./output'); @@ -432,8 +434,7 @@ TX.prototype.signatureHash = function signatureHash(index, prev, value, type, ve */ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { - var i, input, output; - var bw = new BufferWriter(); + var i, size, bw, input, output; var hashType = type & 0x1f; if (hashType === constants.hashType.SINGLE) { @@ -446,8 +447,14 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { // Remove all code separators. prev = prev.removeSeparators(); + // Calculate buffer size. + size = this.hashSize(index, prev, type); + + bw = new StaticWriter(size); + bw.writeU32(this.version); + // Serialize inputs. if (type & constants.hashType.ANYONECANPAY) { bw.writeVarint(1); @@ -482,48 +489,56 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { bw.writeVarint(0); // Sequences are 0 if NONE or SINGLE. - if (hashType === constants.hashType.NONE - || hashType === constants.hashType.SINGLE) { - bw.writeU32(0); - } else { - bw.writeU32(input.sequence); + switch (hashType) { + case constants.hashType.NONE: + case constants.hashType.SINGLE: + bw.writeU32(0); + break; + default: + bw.writeU32(input.sequence); + break; } } } - if (hashType === constants.hashType.NONE) { - // No outputs if NONE. - bw.writeVarint(0); - } else if (hashType === constants.hashType.SINGLE) { - // Drop all outputs after the - // current input index if SINGLE. - bw.writeVarint(index + 1); + // Serialize outputs. + switch (hashType) { + case constants.hashType.NONE: + // No outputs if NONE. + bw.writeVarint(0); + break; + case constants.hashType.SINGLE: + // Drop all outputs after the + // current input index if SINGLE. + bw.writeVarint(index + 1); - for (i = 0; i < index + 1; i++) { - output = this.outputs[i]; + for (i = 0; i < index + 1; i++) { + output = this.outputs[i]; - // Regular serialization if - // at current input index. - if (i === index) { + // Regular serialization if + // at current input index. + if (i === index) { + bw.write64(output.value); + bw.writeVarBytes(output.script.toRaw()); + continue; + } + + // Null all outputs not at + // current input index. + bw.write64(-1); + bw.writeVarint(0); + } + break; + default: + // Regular output serialization if ALL. + bw.writeVarint(this.outputs.length); + + for (i = 0; i < this.outputs.length; i++) { + output = this.outputs[i]; bw.write64(output.value); bw.writeVarBytes(output.script.toRaw()); - continue; } - - // Null all outputs not at - // current input index. - bw.write64(-1); - bw.writeVarint(0); - } - } else { - // Regular output serialization if ALL. - bw.writeVarint(this.outputs.length); - - for (i = 0; i < this.outputs.length; i++) { - output = this.outputs[i]; - bw.write64(output.value); - bw.writeVarBytes(output.script.toRaw()); - } + break; } bw.writeU32(this.locktime); @@ -534,6 +549,57 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { return crypto.hash256(bw.render()); }; +/** + * Calculate sighash size. + * @private + * @param {Number} index + * @param {Script} prev + * @param {Number} type + * @returns {Number} + */ + +TX.prototype.hashSize = function hashSize(index, prev, type) { + var size = 0; + var i, output; + + size += 4; + + if (type & constants.hashType.ANYONECANPAY) { + size += 1; + size += 36; + size += prev.getVarSize(); + size += 4; + } else { + size += encoding.sizeVarint(this.inputs.length); + size += 41 * (this.inputs.length - 1); + size += 36; + size += prev.getVarSize(); + size += 4; + } + + switch (type & 0x1f) { + case constants.hashType.NONE: + size += 1; + break; + case constants.hashType.SINGLE: + size += encoding.sizeVarint(index + 1); + size += 9 * index; + size += this.outputs[index].getSize(); + break; + default: + size += encoding.sizeVarint(this.outputs.length); + for (i = 0; i < this.outputs.length; i++) { + output = this.outputs[i]; + size += output.getSize(); + } + break; + } + + size += 8; + + return size; +}; + /** * Witness sighashing -- O(n). * @private @@ -544,13 +610,13 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { */ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type) { - var i, bw, input, output, prevouts, sequences, outputs; + var i, bw, size, input, output, prevouts, sequences, outputs; if (!(type & constants.hashType.ANYONECANPAY)) { if (this._hashPrevouts) { prevouts = this._hashPrevouts; } else { - bw = new BufferWriter(); + bw = new StaticWriter(this.inputs.length * 36); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; @@ -572,7 +638,7 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type if (this._hashSequence) { sequences = this._hashSequence; } else { - bw = new BufferWriter(); + bw = new StaticWriter(this.inputs.length * 4); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; @@ -593,7 +659,14 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type if (this._hashOutputs) { outputs = this._hashOutputs; } else { - bw = new BufferWriter(); + size = 0; + + for (i = 0; i < this.outputs.length; i++) { + output = this.outputs[i]; + size += output.getSize(); + } + + bw = new StaticWriter(size); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; @@ -614,7 +687,8 @@ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type input = this.inputs[index]; - bw = new BufferWriter(); + size = 156 + prev.getVarSize(); + bw = new StaticWriter(size); bw.writeU32(this.version); bw.writeBytes(prevouts); diff --git a/lib/script/opcode.js b/lib/script/opcode.js index dc11600d..2e568fcd 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -13,7 +13,6 @@ var constants = require('../protocol/constants'); var util = require('../utils/util'); var encoding = require('./encoding'); var BufferReader = require('../utils/reader'); -var BufferWriter = require('../utils/writer'); var StaticWriter = require('../utils/staticwriter'); var opcodes = constants.opcodes; diff --git a/lib/script/witness.js b/lib/script/witness.js index 3b09ad6e..b4651d07 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -19,9 +19,8 @@ var Script = require('./script'); var encoding = require('./encoding'); var enc = require('../utils/encoding'); var Opcode = require('./opcode'); -var BufferWriter = require('../utils/writer'); -var StaticWriter = require('../utils/staticwriter'); var BufferReader = require('../utils/reader'); +var StaticWriter = require('../utils/staticwriter'); var Address = require('../primitives/address'); var Stack = require('./stack'); diff --git a/lib/utils/staticwriter.js b/lib/utils/staticwriter.js index aac3ee9c..5fa8e23e 100644 --- a/lib/utils/staticwriter.js +++ b/lib/utils/staticwriter.js @@ -1,6 +1,5 @@ /*! - * writer.js - buffer writer for bcoin - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * staticwriter.js - buffer writer for bcoin * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ @@ -43,7 +42,7 @@ function StaticWriter(size) { StaticWriter.prototype.render = function render(keep) { var data = this.data; - assert.equal(this.written, data.length); + assert(this.written === data.length); if (!keep) this.destroy();