/*! * tx.js - transaction object for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin */ 'use strict'; 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 Amount = require('../btc/amount'); var Network = require('../protocol/network'); var Script = require('../script/script'); var BufferReader = require('../utils/reader'); var StaticWriter = require('../utils/staticwriter'); var VerifyResult = require('../protocol/errors').VerifyResult; var Input = require('./input'); var Output = require('./output'); var Outpoint = require('./outpoint'); var InvItem = require('./invitem'); var workerPool = require('../workers/workerpool').pool; var Bloom = require('../utils/bloom'); var consensus = require('../protocol/consensus'); var policy = require('../protocol/policy'); /** * A static transaction object. * @alias module:primitives.TX * @constructor * @param {Object} options - Transaction fields. * @property {Number} version - Transaction version. Note that Bcoin reads * versions as unsigned even though they are signed at the protocol level. * This value will never be negative. * @property {Number} flag - Flag field for segregated witness. * Always non-zero (1 if not present). * @property {Input[]} inputs * @property {Output[]} outputs * @property {Number} locktime - nLockTime */ function TX(options) { if (!(this instanceof TX)) return new TX(options); this.version = 1; this.flag = 1; this.inputs = []; this.outputs = []; this.locktime = 0; this.mutable = false; this._hash = null; this._hhash = null; this._whash = null; this._raw = null; this._size = -1; this._witness = -1; this._hashPrevouts = null; this._hashSequence = null; this._hashOutputs = null; if (options) this.fromOptions(options); } /** * Inject properties from options object. * @private * @param {NakedTX} options */ TX.prototype.fromOptions = function fromOptions(options) { var i; assert(options, 'TX data is required.'); if (options.version != null) { assert(util.isUInt32(options.version), 'Version must be a uint32.'); this.version = options.version; } if (options.flag != null) { assert(util.isUInt8(options.flag), 'Flag must be a uint8.'); this.flag = options.flag; } if (options.inputs) { assert(Array.isArray(options.inputs), 'Inputs must be an array.'); for (i = 0; i < options.inputs.length; i++) this.inputs.push(new Input(options.inputs[i])); } if (options.outputs) { assert(Array.isArray(options.outputs), 'Outputs must be an array.'); for (i = 0; i < options.outputs.length; i++) this.outputs.push(new Output(options.outputs[i])); } if (options.locktime != null) { assert(util.isUInt32(options.locktime), 'Locktime must be a uint32.'); this.locktime = options.locktime; } return this; }; /** * Instantiate TX from options object. * @param {NakedTX} options * @returns {TX} */ TX.fromOptions = function fromOptions(options) { return new TX().fromOptions(options); }; /** * Clone the transaction. * @returns {TX} */ TX.prototype.clone = function clone() { return new TX().inject(this); }; /** * Inject properties from tx. * Used for cloning. * @private * @param {TX} tx * @returns {TX} */ TX.prototype.inject = function inject(tx) { var i, input, output; this.version = tx.version; this.flag = tx.flag; for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; this.inputs.push(input.clone()); } for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; this.outputs.push(output.clone()); } this.locktime = tx.locktime; return this; }; /** * Clear any cached values. */ TX.prototype.refresh = function refresh() { this._hash = null; this._hhash = null; this._whash = null; this._raw = null; this._size = -1; this._witness = -1; this._hashPrevouts = null; this._hashSequence = null; this._hashOutputs = null; }; /** * Hash the transaction with the non-witness serialization. * @param {String?} enc - Can be `'hex'` or `null`. * @returns {Hash|Buffer} hash */ TX.prototype.hash = function _hash(enc) { var hash = this._hash; var hex; if (!hash) { hash = crypto.hash256(this.toNormal()); if (!this.mutable) this._hash = hash; } if (enc === 'hex') { hex = this._hhash; if (!hex) { hex = hash.toString('hex'); if (!this.mutable) this._hhash = hex; } hash = hex; } return hash; }; /** * Hash the transaction with the witness * serialization, return the wtxid (normal * hash if no witness is present, all zeroes * if coinbase). * @param {String?} enc - Can be `'hex'` or `null`. * @returns {Hash|Buffer} hash */ TX.prototype.witnessHash = function witnessHash(enc) { var hash = this._whash; if (!this.hasWitness()) return this.hash(enc); if (!hash) { hash = crypto.hash256(this.toRaw()); if (!this.mutable) this._whash = hash; } return enc === 'hex' ? hash.toString('hex') : hash; }; /** * Serialize the transaction. Note * that this is cached. This will use * the witness serialization if a * witness is present. * @returns {Buffer} Serialized transaction. */ TX.prototype.toRaw = function toRaw() { return this.frame().data; }; /** * Serialize the transaction without the * witness vector, regardless of whether it * is a witness transaction or not. * @returns {Buffer} Serialized transaction. */ TX.prototype.toNormal = function toNormal() { if (this.hasWitness()) return this.frameNormal().data; return this.toRaw(); }; /** * Write the transaction to a buffer writer. * @param {BufferWriter} bw */ TX.prototype.toWriter = function toWriter(bw) { if (this.mutable) { if (this.hasWitness()) return this.writeWitness(bw); return this.writeNormal(bw); } bw.writeBytes(this.toRaw()); return bw; }; /** * Write the transaction to a buffer writer. * Uses non-witness serialization. * @param {BufferWriter} bw */ TX.prototype.toNormalWriter = function toNormalWriter(bw) { if (this.hasWitness()) { this.writeNormal(bw); return bw; } return this.toWriter(bw); }; /** * Serialize the transaction. Note * that this is cached. This will use * the witness serialization if a * witness is present. * @private * @returns {RawTX} */ TX.prototype.frame = function frame() { var raw; if (this.mutable) { assert(!this._raw); if (this.hasWitness()) return this.frameWitness(); return this.frameNormal(); } if (this._raw) { assert(this._size > 0); assert(this._witness >= 0); raw = new RawTX(this._size, this._witness); raw.data = this._raw; return raw; } if (this.hasWitness()) raw = this.frameWitness(); else raw = this.frameNormal(); this._raw = raw.data; this._size = raw.total; this._witness = raw.witness; return raw; }; /** * Calculate total size and size of the witness bytes. * @returns {Object} Contains `total` and `witness`. */ TX.prototype.getSizes = function getSizes() { if (this.mutable) { if (this.hasWitness()) return this.getWitnessSizes(); return this.getNormalSizes(); } return this.frame(); }; /** * Calculate the virtual size of the transaction. * Note that this is cached. * @returns {Number} vsize */ TX.prototype.getVirtualSize = function getVirtualSize() { var scale = consensus.WITNESS_SCALE_FACTOR; return (this.getWeight() + scale - 1) / scale | 0; }; /** * Calculate the virtual size of the transaction * (weighted against bytes per sigop cost). * @param {Number} sigops - Sigops cost. * @returns {Number} vsize */ TX.prototype.getSigopsSize = function getSigopsSize(sigops) { var scale = consensus.WITNESS_SCALE_FACTOR; var bytes = policy.BYTES_PER_SIGOP; var weight = Math.max(this.getWeight(), sigops * bytes); return (weight + scale - 1) / scale | 0; }; /** * Calculate the weight of the transaction. * Note that this is cached. * @returns {Number} weight */ TX.prototype.getWeight = function getWeight() { var raw = this.getSizes(); var base = raw.total - raw.witness; return base * (consensus.WITNESS_SCALE_FACTOR - 1) + raw.total; }; /** * Calculate the real size of the transaction * with the witness included. * @returns {Number} size */ TX.prototype.getSize = function getSize() { return this.getSizes().total; }; /** * Calculate the size of the transaction * without the witness. * with the witness included. * @returns {Number} size */ TX.prototype.getBaseSize = function getBaseSize() { var raw = this.getSizes(); return raw.total - raw.witness; }; /** * Test whether the transaction has a non-empty witness. * @returns {Boolean} */ TX.prototype.hasWitness = function hasWitness() { var i, input; if (this._witness !== -1) return this._witness !== 0; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.witness.items.length > 0) return true; } return false; }; /** * Get the signature hash of the transaction for signing verifying. * @param {Number} index - Index of input being signed/verified. * @param {Script} prev - Previous output script or redeem script * (in the case of witnesspubkeyhash, this should be the generated * p2pkh script). * @param {Amount} value - Previous output value. * @param {SighashType} type - Sighash type. * @param {Number} version - Sighash version (0=legacy, 1=segwit). * @returns {Buffer} Signature hash. */ TX.prototype.signatureHash = function signatureHash(index, prev, value, type, version) { if (typeof type === 'string') { type = Script.hashType[type.toUpperCase()]; assert(type != null, 'Unknown sighash type.'); } assert(index >= 0 && index < this.inputs.length); assert(prev instanceof Script); assert(typeof value === 'number'); assert(typeof type === 'number'); // Traditional sighashing if (version === 0) return this.signatureHashV0(index, prev, type); // Segwit sighashing if (version === 1) return this.signatureHashV1(index, prev, value, type); assert(false, 'Unknown sighash version.'); }; /** * Legacy sighashing -- O(n^2). * @private * @param {Number} index * @param {Script} prev * @param {SighashType} type * @returns {Buffer} */ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { var i, size, bw, input, output; var hashType = type & 0x1f; if (hashType === Script.hashType.SINGLE) { // Bitcoind used to return 1 as an error code: // it ended up being treated like a hash. if (index >= this.outputs.length) return util.copy(encoding.ONE_HASH); } // 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 & Script.hashType.ANYONECANPAY) { bw.writeVarint(1); // Serialize only the current // input if ANYONECANPAY. input = this.inputs[index]; // Outpoint. input.prevout.toWriter(bw); // Replace script with previous // output script if current index. bw.writeVarBytes(prev.toRaw()); bw.writeU32(input.sequence); } else { bw.writeVarint(this.inputs.length); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; // Outpoint. input.prevout.toWriter(bw); // Replace script with previous // output script if current index. if (i === index) { bw.writeVarBytes(prev.toRaw()); bw.writeU32(input.sequence); continue; } // Script is null. bw.writeVarint(0); // Sequences are 0 if NONE or SINGLE. switch (hashType) { case Script.hashType.NONE: case Script.hashType.SINGLE: bw.writeU32(0); break; default: bw.writeU32(input.sequence); break; } } } // Serialize outputs. switch (hashType) { case Script.hashType.NONE: // No outputs if NONE. bw.writeVarint(0); break; case Script.hashType.SINGLE: // Drop all outputs after the // current input index if SINGLE. bw.writeVarint(index + 1); for (i = 0; i < index; i++) { // Null all outputs not at // current input index. bw.write64(-1); bw.writeVarint(0); } // Regular serialization // at current input index. output = this.outputs[index]; output.toWriter(bw); break; default: // Regular output serialization if ALL. bw.writeVarint(this.outputs.length); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; output.toWriter(bw); } break; } bw.writeU32(this.locktime); // Append the hash type. bw.writeU32(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 & Script.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 Script.hashType.NONE: size += 1; break; case Script.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 * @param {Number} index * @param {Script} prev * @param {Amount} value * @param {SighashType} type * @returns {Buffer} */ TX.prototype.signatureHashV1 = function signatureHashV1(index, prev, value, type) { var prevouts = encoding.ZERO_HASH; var sequences = encoding.ZERO_HASH; var outputs = encoding.ZERO_HASH; var i, bw, size, input, output; if (!(type & Script.hashType.ANYONECANPAY)) { if (this._hashPrevouts) { prevouts = this._hashPrevouts; } else { bw = new StaticWriter(this.inputs.length * 36); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; input.prevout.toWriter(bw); } prevouts = crypto.hash256(bw.render()); if (!this.mutable) this._hashPrevouts = prevouts; } } if (!(type & Script.hashType.ANYONECANPAY) && (type & 0x1f) !== Script.hashType.SINGLE && (type & 0x1f) !== Script.hashType.NONE) { if (this._hashSequence) { sequences = this._hashSequence; } else { bw = new StaticWriter(this.inputs.length * 4); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; bw.writeU32(input.sequence); } sequences = crypto.hash256(bw.render()); if (!this.mutable) this._hashSequence = sequences; } } if ((type & 0x1f) !== Script.hashType.SINGLE && (type & 0x1f) !== Script.hashType.NONE) { if (this._hashOutputs) { outputs = this._hashOutputs; } else { 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]; output.toWriter(bw); } outputs = crypto.hash256(bw.render()); if (!this.mutable) this._hashOutputs = outputs; } } else if ((type & 0x1f) === Script.hashType.SINGLE && index < this.outputs.length) { output = this.outputs[index]; outputs = crypto.hash256(output.toRaw()); } input = this.inputs[index]; size = 156 + prev.getVarSize(); bw = new StaticWriter(size); bw.writeU32(this.version); bw.writeBytes(prevouts); bw.writeBytes(sequences); bw.writeHash(input.prevout.hash); bw.writeU32(input.prevout.index); bw.writeVarBytes(prev.toRaw()); bw.write64(value); bw.writeU32(input.sequence); bw.writeBytes(outputs); bw.writeU32(this.locktime); bw.writeU32(type); return crypto.hash256(bw.render()); }; /** * Verify all transaction inputs. * @param {CoinView} view * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] * @returns {Boolean} Whether the inputs are valid. */ TX.prototype.verify = function verify(view, flags) { var i, input, coin; if (this.inputs.length === 0) return false; if (this.isCoinbase()) return true; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) return false; if (!this.verifyInput(i, coin, flags)) return false; } return true; }; /** * Verify a transaction input. * @param {Number} index - Index of output being * verified. * @param {Coin|Output} coin - Previous output. * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] * @returns {Boolean} Whether the input is valid. */ TX.prototype.verifyInput = function verifyInput(index, coin, flags) { var input = this.inputs[index]; assert(input, 'Input does not exist.'); assert(coin, 'No coin passed.'); try { Script.verify( input.script, input.witness, coin.script, this, index, coin.value, flags ); } catch (e) { if (e.type === 'ScriptError') return false; throw e; } return true; }; /** * Verify the transaction inputs on the worker pool * (if workers are enabled). * @param {CoinView} view * @param {VerifyFlags?} [flags=STANDARD_VERIFY_FLAGS] * @returns {Promise} */ TX.prototype.verifyAsync = co(function* verifyAsync(view, flags) { if (this.inputs.length === 0) return false; if (this.isCoinbase()) return true; return yield workerPool.verify(this, view, flags); }); /** * Verify a transaction input asynchronously. * @param {Number} index - Index of output being * verified. * @param {Coin|Output} coin - Previous output. * @param {VerifyFlags} [flags=STANDARD_VERIFY_FLAGS] * @returns {Promise} */ TX.prototype.verifyInputAsync = co(function* verifyInputAsync(index, coin, flags) { var input = this.inputs[index]; assert(input, 'Input does not exist.'); return yield workerPool.verifyInput(this, index, coin, flags); }); /** * Test whether the transaction is a coinbase * by examining the inputs. * @returns {Boolean} */ TX.prototype.isCoinbase = function isCoinbase() { return this.inputs.length === 1 && this.inputs[0].prevout.isNull(); }; /** * Test whether the transaction is replaceable. * @returns {Boolean} */ TX.prototype.isRBF = function isRBF() { var i, input; // Core doesn't do this, but it should: if (this.version === 2) return false; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.isRBF()) return true; } return false; }; /** * Calculate the fee for the transaction. * @param {CoinView} view * @returns {Amount} fee (zero if not all coins are available). */ TX.prototype.getFee = function getFee(view) { if (!this.hasCoins(view)) return 0; return this.getInputValue(view) - this.getOutputValue(); }; /** * Calculate the total input value. * @param {CoinView} view * @returns {Amount} value */ TX.prototype.getInputValue = function getInputValue(view) { var total = 0; var i, input, coin; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) return 0; total += coin.value; } return total; }; /** * Calculate the total output value. * @returns {Amount} value */ TX.prototype.getOutputValue = function getOutputValue() { var total = 0; var i, output; for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; total += output.value; } return total; }; /** * Get all input addresses. * @private * @param {CoinView} view * @returns {Object} */ TX.prototype._getInputAddresses = function getInputAddresses(view) { var table = {}; var addrs = []; var i, address, hash, input, coin; if (this.isCoinbase()) return new AddrResult(addrs, table); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view ? view.getOutput(input) : null; address = input.getAddress(coin); if (!address) continue; hash = address.getHash('hex'); if (!table[hash]) { table[hash] = true; addrs.push(address); } } return new AddrResult(addrs, table); }; /** * Get all output addresses. * @private * @returns {Object} */ TX.prototype._getOutputAddresses = function getOutputAddresses() { var table = {}; var addrs = []; var i, output, address, hash; for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; address = output.getAddress(); if (!address) continue; hash = address.getHash('hex'); if (!table[hash]) { table[hash] = true; addrs.push(address); } } return new AddrResult(addrs, table); }; /** * Get all addresses. * @private * @param {CoinView} view * @returns {Object} */ TX.prototype._getAddresses = function getAddresses(view) { var input = this._getInputAddresses(view); var output = this.getOutputAddresses(); var addrs = input.addrs; var table = input.table; var i, address, hash; for (i = 0; i < output.length; i++) { address = output[i]; hash = address.getHash('hex'); if (!table[hash]) { table[hash] = true; addrs.push(address); } } return input; }; /** * Get all input addresses. * @param {CoinView|null} view * @returns {Address[]} addresses */ TX.prototype.getInputAddresses = function getInputAddresses(view) { return this._getInputAddresses(view).addrs; }; /** * Get all output addresses. * @returns {Address[]} addresses */ TX.prototype.getOutputAddresses = function getOutputAddresses() { return this._getOutputAddresses().addrs; }; /** * Get all addresses. * @param {CoinView|null} view * @returns {Address[]} addresses */ TX.prototype.getAddresses = function getAddresses(view) { return this._getAddresses(view).addrs; }; /** * Get all input address hashes. * @param {CoinView|null} view * @returns {Hash[]} hashes */ TX.prototype.getInputHashes = function getInputHashes(view, enc) { var hashes = []; var i, input, address; if (enc === 'hex') { input = this._getInputAddresses(view); return Object.keys(input.table); } input = this.getInputAddresses(view); for (i = 0; i < input.length; i++) { address = input[i]; hashes.push(address.getHash()); } return hashes; }; /** * Get all output address hashes. * @returns {Hash[]} hashes */ TX.prototype.getOutputHashes = function getOutputHashes(enc) { var hashes = []; var i, output, address; if (enc === 'hex') { output = this._getOutputAddresses(); return Object.keys(output.table); } output = this.getOutputAddresses(); for (i = 0; i < output.length; i++) { address = output[i]; hashes.push(address.getHash()); } return hashes; }; /** * Get all address hashes. * @param {CoinView|null} view * @returns {Hash[]} hashes */ TX.prototype.getHashes = function getHashes(view, enc) { var hashes = []; var i, result, address; if (enc === 'hex') { result = this._getAddresses(view); return Object.keys(result.table); } result = this.getAddresses(view); for (i = 0; i < result.length; i++) { address = result[i]; hashes.push(address.getHash()); } return hashes; }; /** * Test whether the transaction has * all coins available. * @param {CoinView} view * @returns {Boolean} */ TX.prototype.hasCoins = function hasCoins(view) { var i, input; if (this.inputs.length === 0) return false; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (!view.hasEntry(input)) return false; } return true; }; /** * Check finality of transaction by examining * nLocktime and nSequence values. * @example * tx.isFinal(chain.height + 1, network.now()); * @param {Number} height - Height at which to test. This * is usually the chain height, or the chain height + 1 * when the transaction entered the mempool. * @param {Number} ts - Time at which to test. This is * usually the chain tip's parent's median time, or the * time at which the transaction entered the mempool. If * MEDIAN_TIME_PAST is enabled this will be the median * time of the chain tip's previous entry's median time. * @returns {Boolean} */ TX.prototype.isFinal = function isFinal(height, ts) { var THRESHOLD = consensus.LOCKTIME_THRESHOLD; var i, input; if (this.locktime === 0) return true; if (this.locktime < (this.locktime < THRESHOLD ? height : ts)) return true; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.sequence !== 0xffffffff) return false; } return true; }; /** * Verify the absolute locktime of a transaction. * Called by OP_CHECKLOCKTIMEVERIFY. * @param {Number} index - Index of input being verified. * @param {Number} locktime - Locktime to verify against. * @returns {Boolean} */ TX.prototype.verifyLocktime = function verifyLocktime(index, locktime) { var THRESHOLD = consensus.LOCKTIME_THRESHOLD; var input = this.inputs[index]; assert(input, 'Input does not exist.'); assert(locktime >= 0, 'Locktime must be non-negative.'); if (!((this.locktime < THRESHOLD && locktime < THRESHOLD) || (this.locktime >= THRESHOLD && locktime >= THRESHOLD))) { return false; } if (locktime > this.locktime) return false; if (input.sequence === 0xffffffff) return false; return true; }; /** * Verify the relative locktime of an input. * Called by OP_CHECKSEQUENCEVERIFY. * @param {Number} index - Index of input being verified. * @param {Number} locktime - Sequence locktime to verify against. * @returns {Boolean} */ TX.prototype.verifySequence = function verifySequence(index, locktime) { var DISABLE_FLAG = consensus.SEQUENCE_DISABLE_FLAG; var TYPE_FLAG = consensus.SEQUENCE_TYPE_FLAG; var SEQUENCE_MASK = consensus.SEQUENCE_MASK; var input = this.inputs[index]; var mask, sequence, predicate; assert(input, 'Input does not exist.'); assert(locktime >= 0, 'Locktime must be non-negative.'); if ((locktime & DISABLE_FLAG) !== 0) return true; if (this.version < 2) return false; if ((input.sequence & DISABLE_FLAG) !== 0) return false; mask = TYPE_FLAG | SEQUENCE_MASK; sequence = input.sequence & mask; predicate = locktime & mask; if (!((sequence < TYPE_FLAG && predicate < TYPE_FLAG) || (sequence >= TYPE_FLAG && predicate >= TYPE_FLAG))) { return false; } if (predicate > sequence) return false; return true; }; /** * Calculate legacy (inaccurate) sigop count. * @returns {Number} sigop count */ TX.prototype.getLegacySigops = function getLegacySigops() { var total = 0; var i, input, output; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; total += input.script.getSigops(false); } for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; total += output.script.getSigops(false); } return total; }; /** * Calculate accurate sigop count, taking into account redeem scripts. * @param {CoinView} view * @returns {Number} sigop count */ TX.prototype.getScripthashSigops = function getScripthashSigops(view) { var total = 0; var i, input, coin; if (this.isCoinbase()) return 0; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) continue; if (!coin.script.isScripthash()) continue; total += coin.script.getScripthashSigops(input.script); } return total; }; /** * Calculate sigops cost, taking into account witness programs. * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Number} sigop weight */ TX.prototype.getSigopsCost = function getSigopsCost(view, flags) { var scale = consensus.WITNESS_SCALE_FACTOR; var cost = this.getLegacySigops() * scale; var i, input, coin; if (flags == null) flags = Script.flags.STANDARD_VERIFY_FLAGS; if (this.isCoinbase()) return cost; if (flags & Script.flags.VERIFY_P2SH) cost += this.getScripthashSigops(view) * scale; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) continue; cost += Script.getWitnessSigops( input.script, coin.script, input.witness, flags); } return cost; }; /** * Calculate virtual sigop count. * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Number} sigop count */ TX.prototype.getSigops = function getSigops(view, flags) { var scale = consensus.WITNESS_SCALE_FACTOR; if (flags == null) flags = Script.flags.STANDARD_VERIFY_FLAGS; return (this.getSigopsCost(view, flags) + scale - 1) / scale | 0; }; /** * Non-contextual sanity checks for the transaction. * Will mostly verify coin and output values. * @see CheckTransaction() * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} sane */ TX.prototype.isSane = function isSane(ret) { var prevout = {}; var total = 0; var i, input, output, size, key; if (!ret) ret = new VerifyResult(); if (this.inputs.length === 0) { ret.reason = 'bad-txns-vin-empty'; ret.score = 100; return false; } if (this.outputs.length === 0) { ret.reason = 'bad-txns-vout-empty'; ret.score = 100; return false; } if (this.getBaseSize() > consensus.MAX_BLOCK_SIZE) { ret.reason = 'bad-txns-oversize'; ret.score = 100; return false; } for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; if (output.value < 0) { ret.reason = 'bad-txns-vout-negative'; ret.score = 100; return false; } if (output.value > consensus.MAX_MONEY) { ret.reason = 'bad-txns-vout-toolarge'; ret.score = 100; return false; } total += output.value; if (total < 0 || total > consensus.MAX_MONEY) { ret.reason = 'bad-txns-txouttotal-toolarge'; ret.score = 100; return false; } } for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; key = input.prevout.toKey(); if (prevout[key]) { ret.reason = 'bad-txns-inputs-duplicate'; ret.score = 100; return false; } prevout[key] = true; } if (this.isCoinbase()) { size = this.inputs[0].script.getSize(); if (size < 2 || size > 100) { ret.reason = 'bad-cb-length'; ret.score = 100; return false; } } else { for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.prevout.isNull()) { ret.reason = 'bad-txns-prevout-null'; ret.score = 10; return false; } } } return true; }; /** * Non-contextual checks to determine whether the * transaction has all standard output script * types and standard input script size with only * pushdatas in the code. * Will mostly verify coin and output values. * @see IsStandardTx() * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ TX.prototype.isStandard = function isStandard(ret) { var i, input, output; var nulldata = 0; if (!ret) ret = new VerifyResult(); if (this.version < 1 || this.version > policy.MAX_TX_VERSION) { ret.reason = 'version'; ret.score = 0; return false; } if (this.getWeight() >= policy.MAX_TX_WEIGHT) { ret.reason = 'tx-size'; ret.score = 0; return false; } for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; if (input.script.getSize() > 1650) { ret.reason = 'scriptsig-size'; ret.score = 0; return false; } if (!input.script.isPushOnly()) { ret.reason = 'scriptsig-not-pushonly'; ret.score = 0; return false; } } for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; if (!output.script.isStandard()) { ret.reason = 'scriptpubkey'; ret.score = 0; return false; } if (output.script.isNulldata()) { nulldata++; continue; } if (output.script.isMultisig() && !policy.BARE_MULTISIG) { ret.reason = 'bare-multisig'; ret.score = 0; return false; } if (output.isDust(policy.MIN_RELAY)) { ret.reason = 'dust'; ret.score = 0; return false; } } if (nulldata > 1) { ret.reason = 'multi-op-return'; ret.score = 0; return false; } return true; }; /** * Perform contextual checks to verify coin and input * script standardness (including the redeem script). * @see AreInputsStandard() * @param {CoinView} view * @param {VerifyFlags?} flags * @returns {Boolean} */ TX.prototype.hasStandardInputs = function hasStandardInputs(view) { var i, input, coin, redeem; if (this.isCoinbase()) return true; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) return false; if (coin.script.isPubkeyhash()) continue; if (coin.script.isScripthash()) { redeem = input.script.getRedeem(); if (!redeem) return false; if (redeem.getSigops(true) > policy.MAX_P2SH_SIGOPS) return false; continue; } if (coin.script.isUnknown()) return false; } return true; }; /** * Perform contextual checks to verify coin and witness standardness. * @see IsBadWitness() * @param {CoinView} view * @returns {Boolean} */ TX.prototype.hasStandardWitness = function hasStandardWitness(view) { var i, j, input, witness, coin, prev, redeem, m; if (this.isCoinbase()) return true; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; witness = input.witness; coin = view.getOutput(input); if (!coin) continue; if (witness.items.length === 0) continue; prev = coin.script; if (prev.isScripthash()) { prev = input.script.getRedeem(); if (!prev) return false; } if (!prev.isProgram()) return false; if (prev.isWitnessPubkeyhash()) { if (witness.items.length !== 2) return false; if (witness.items[0].length > 73) return false; if (witness.items[1].length > 65) return false; continue; } if (prev.isWitnessScripthash()) { if (witness.items.length - 1 > policy.MAX_P2WSH_STACK) return false; for (j = 0; j < witness.items.length - 1; j++) { if (witness.items[j].length > policy.MAX_P2WSH_PUSH) return false; } redeem = witness.items[witness.items.length - 1]; if (redeem.length > policy.MAX_P2WSH_SIZE) return false; prev = new Script(redeem); if (prev.isPubkey()) { if (witness.items.length - 1 !== 1) return false; if (witness.items[0].length > 73) return false; continue; } if (prev.isPubkeyhash()) { if (input.witness.length - 1 !== 2) return false; if (witness.items[0].length > 73) return false; if (witness.items[1].length > 65) return false; continue; } if (prev.isMultisig()) { m = prev.getSmall(0); if (witness.items.length - 1 !== m + 1) return false; if (witness.items[0].length !== 0) return false; for (j = 1; j < witness.items.length - 1; j++) { if (witness.items[j].length > 73) return false; } } continue; } if (witness.items.length > policy.MAX_P2WSH_STACK) return false; for (j = 0; j < witness.items.length; j++) { if (witness.items[j].length > policy.MAX_P2WSH_PUSH) return false; } } return true; }; /** * Perform contextual checks to verify input, output, * and fee values, as well as coinbase spend maturity * (coinbases can only be spent 100 blocks or more * after they're created). Note that this function is * consensus critical. * @param {CoinView} view * @param {Number} height - Height at which the * transaction is being spent. In the mempool this is * the chain height plus one at the time it entered the pool. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Boolean} */ TX.prototype.checkInputs = function checkInputs(view, height, ret) { return this.checkContext(view, height, ret) !== -1; }; /** * Perform contextual checks to verify input, output, * and fee values, as well as coinbase spend maturity * (coinbases can only be spent 100 blocks or more * after they're created). Note that this function is * consensus critical. * @param {CoinView} view * @param {Number} height - Height at which the * transaction is being spent. In the mempool this is * the chain height plus one at the time it entered the pool. * @param {Object?} ret - Return object, may be * set with properties `reason` and `score`. * @returns {Amount} */ TX.prototype.checkContext = function checkContext(view, height, ret) { var total = 0; var i, input, coins, coin, fee, value; if (!ret) ret = new VerifyResult(); assert(typeof height === 'number'); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coins = view.get(input.prevout.hash); if (!coins) { ret.reason = 'bad-txns-inputs-missingorspent'; ret.score = 0; return -1; } if (coins.coinbase) { if (height - coins.height < consensus.COINBASE_MATURITY) { ret.reason = 'bad-txns-premature-spend-of-coinbase'; ret.score = 0; return -1; } } coin = coins.getOutput(input.prevout.index); if (!coin) { ret.reason = 'bad-txns-inputs-missingorspent'; ret.score = 0; return -1; } if (coin.value < 0 || coin.value > consensus.MAX_MONEY) { ret.reason = 'bad-txns-inputvalues-outofrange'; ret.score = 100; return -1; } total += coin.value; if (total < 0 || total > consensus.MAX_MONEY) { ret.reason = 'bad-txns-inputvalues-outofrange'; ret.score = 100; return -1; } } // Overflows already checked in `isSane()`. value = this.getOutputValue(); if (total < value) { ret.reason = 'bad-txns-in-belowout'; ret.score = 100; return -1; } fee = total - value; if (fee < 0) { ret.reason = 'bad-txns-fee-negative'; ret.score = 100; return -1; } if (fee > consensus.MAX_MONEY) { ret.reason = 'bad-txns-fee-outofrange'; ret.score = 100; return -1; } return fee; }; /** * Calculate the modified size of the transaction. This * is used in the mempool for calculating priority. * @param {Number?} size - The size to modify. If not present, * virtual size will be used. * @returns {Number} Modified size. */ TX.prototype.getModifiedSize = function getModifiedSize(size) { var i, input, offset; if (size == null) size = this.getVirtualSize(); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; offset = 41 + Math.min(110, input.script.getSize()); if (size > offset) size -= offset; } return size; }; /** * Calculate the transaction priority. * @param {CoinView} view * @param {Number} height * @param {Number?} size - Size to calculate priority * based on. If not present, virtual size will be used. * @returns {Number} */ TX.prototype.getPriority = function getPriority(view, height, size) { var sum = 0; var i, input, age, coin, coinHeight; assert(typeof height === 'number', 'Must pass in height.'); if (this.isCoinbase()) return sum; if (size == null) size = this.getVirtualSize(); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) continue; coinHeight = view.getHeight(input); if (coinHeight === -1) continue; if (coinHeight <= height) { age = height - coinHeight; sum += coin.value * age; } } return Math.floor(sum / size); }; /** * Calculate the transaction's on-chain value. * @param {CoinView} view * @returns {Number} */ TX.prototype.getChainValue = function getChainValue(view) { var value = 0; var i, input, coin, coinHeight; if (this.isCoinbase()) return value; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; coin = view.getOutput(input); if (!coin) continue; coinHeight = view.getHeight(input); if (coinHeight === -1) continue; value += coin.value; } return value; }; /** * Determine whether the transaction is above the * free threshold in priority. A transaction which * passed this test is most likely relayable * without a fee. * @param {CoinView} view * @param {Number?} height - If not present, tx * height or network height will be used. * @param {Number?} size - If not present, modified * size will be calculated and used. * @returns {Boolean} */ TX.prototype.isFree = function isFree(view, height, size) { var priority = this.getPriority(view, height, size); return priority > policy.FREE_THRESHOLD; }; /** * Calculate minimum fee in order for the transaction * to be relayable (not the constant min relay fee). * @param {Number?} size - If not present, max size * estimation will be calculated and used. * @param {Rate?} rate - Rate of satoshi per kB. * @returns {Amount} fee */ TX.prototype.getMinFee = function getMinFee(size, rate) { if (size == null) size = this.getVirtualSize(); return policy.getMinFee(size, rate); }; /** * Calculate the minimum fee in order for the transaction * to be relayable, but _round to the nearest kilobyte * when taking into account size. * @param {Number?} size - If not present, max size * estimation will be calculated and used. * @param {Rate?} rate - Rate of satoshi per kB. * @returns {Amount} fee */ TX.prototype.getRoundFee = function getRoundFee(size, rate) { if (size == null) size = this.getVirtualSize(); return policy.getRoundFee(size, rate); }; /** * Calculate the transaction's rate based on size * and fees. Size will be calculated if not present. * @param {CoinView} view * @param {Number?} size * @returns {Rate} */ TX.prototype.getRate = function getRate(view, size) { var fee = this.getFee(view); if (fee < 0) return 0; if (size == null) size = this.getVirtualSize(); return policy.getRate(size, fee); }; /** * Get all unique outpoint hashes. * @returns {Hash[]} Outpoint hashes. */ TX.prototype.getPrevout = function getPrevout() { var prevout = {}; var i, input; if (this.isCoinbase()) return []; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; prevout[input.prevout.hash] = true; } return Object.keys(prevout); }; /** * Test a transaction against a bloom filter using * the BIP37 matching algorithm. Note that this may * update the filter depending on what the `update` * value is. * @see "Filter matching algorithm": * @see https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki * @param {Bloom} filter * @returns {Boolean} True if the transaction matched. */ TX.prototype.isWatched = function isWatched(filter) { var found = false; var i, input, output, prevout; // 1. Test the tx hash if (filter.test(this.hash())) found = true; // 2. Test data elements in output scripts // (may need to update filter on match) for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; // Test the output script if (output.script.test(filter)) { if (filter.update === Bloom.flags.ALL) { prevout = Outpoint.fromTX(this, i); filter.add(prevout.toRaw()); } else if (filter.update === Bloom.flags.PUBKEY_ONLY) { if (output.script.isPubkey() || output.script.isMultisig()) { prevout = Outpoint.fromTX(this, i); filter.add(prevout.toRaw()); } } found = true; } } if (found) return found; // 3. Test prev_out structure // 4. Test data elements in input scripts for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; prevout = input.prevout; // Test the COutPoint structure if (filter.test(prevout.toRaw())) return true; // Test the input script if (input.script.test(filter)) return true; // Test the witness if (input.witness.test(filter)) return true; } // 5. No match return false; }; /** * Get little-endian tx hash. * @returns {Hash} */ TX.prototype.rhash = function() { return util.revHex(this.hash('hex')); }; /** * Get little-endian wtx hash. * @returns {Hash} */ TX.prototype.rwhash = function() { return util.revHex(this.witnessHash('hex')); }; /** * Get little-endian tx hash. * @returns {Hash} */ TX.prototype.txid = function() { return this.rhash(); }; /** * Get little-endian wtx hash. * @returns {Hash} */ TX.prototype.wtxid = function() { return this.rwhash(); }; /** * Convert the tx to an inv item. * @returns {InvItem} */ TX.prototype.toInv = function toInv() { return new InvItem(InvItem.types.TX, this.hash('hex')); }; /** * Inspect the transaction and return a more * user-friendly representation of the data. * @returns {Object} */ TX.prototype.inspect = function inspect() { return this.format(); }; /** * Inspect the transaction and return a more * user-friendly representation of the data. * @param {CoinView} view * @param {ChainEntry} entry * @param {Number} index * @returns {Object} */ TX.prototype.format = function format(view, entry, index) { var rate = 0; var fee = 0; var height = -1; var block = null; var ts = 0; var date = null; if (view) { fee = this.getFee(view); rate = this.getRate(view); // Rate can exceed 53 bits in testing. if (!util.isSafeInteger(rate)) rate = 0; } if (entry) { height = entry.height; block = util.revHex(entry.hash); ts = entry.ts; date = util.date(ts); } if (index == null) index = -1; return { hash: this.txid(), witnessHash: this.wtxid(), size: this.getSize(), virtualSize: this.getVirtualSize(), value: Amount.btc(this.getOutputValue()), fee: Amount.btc(fee), rate: Amount.btc(rate), minFee: Amount.btc(this.getMinFee()), height: height, block: block, ts: ts, date: date, index: index, version: this.version, flag: this.flag, inputs: this.inputs.map(function(input) { var coin = view ? view.getOutput(input) : null; return input.format(coin); }), outputs: this.outputs, locktime: this.locktime }; }; /** * Convert the transaction to an object suitable * for JSON serialization. * @returns {Object} */ TX.prototype.toJSON = function toJSON() { return this.getJSON(); }; /** * Convert the transaction to an object suitable * for JSON serialization. Note that the hashes * will be reversed to abide by bitcoind's legacy * of little-endian uint256s. * @param {Network} network * @param {CoinView} view * @param {ChainEntry} entry * @param {Number} index * @returns {Object} */ TX.prototype.getJSON = function getJSON(network, view, entry, index) { var rate, fee, height, block, ts, date; if (view) { fee = this.getFee(view); rate = this.getRate(view); // Rate can exceed 53 bits in testing. if (!util.isSafeInteger(rate)) rate = 0; fee = Amount.btc(fee); rate = Amount.btc(rate); } if (entry) { height = entry.height; block = util.revHex(entry.hash); ts = entry.ts; date = util.date(ts); } network = Network.get(network); return { hash: this.txid(), witnessHash: this.wtxid(), fee: fee, rate: rate, ps: util.now(), height: height, block: block, ts: ts, date: date, index: index, version: this.version, flag: this.flag, inputs: this.inputs.map(function(input) { var coin = view ? view.getCoin(input) : null; return input.getJSON(network, coin); }), outputs: this.outputs.map(function(output) { return output.getJSON(network); }), locktime: this.locktime }; }; /** * Inject properties from a json object. * @private * @param {Object} json */ TX.prototype.fromJSON = function fromJSON(json) { var i, input, output; assert(json, 'TX data is required.'); assert(util.isUInt32(json.version), 'Version must be a uint32.'); assert(util.isUInt8(json.flag), 'Flag must be a uint8.'); assert(Array.isArray(json.inputs), 'Inputs must be an array.'); assert(Array.isArray(json.outputs), 'Outputs must be an array.'); assert(util.isUInt32(json.locktime), 'Locktime must be a uint32.'); this.version = json.version; this.flag = json.flag; for (i = 0; i < json.inputs.length; i++) { input = json.inputs[i]; this.inputs.push(Input.fromJSON(input)); } for (i = 0; i < json.outputs.length; i++) { output = json.outputs[i]; this.outputs.push(Output.fromJSON(output)); } this.locktime = json.locktime; return this; }; /** * Instantiate a transaction from a * jsonified transaction object. * @param {Object} json - The jsonified transaction object. * @returns {TX} */ TX.fromJSON = function fromJSON(json) { return new TX().fromJSON(json); }; /** * Instantiate a transaction from a serialized Buffer. * @param {Buffer} data * @param {String?} enc - Encoding, can be `'hex'` or null. * @returns {TX} */ TX.fromRaw = function fromRaw(data, enc) { if (typeof data === 'string') data = new Buffer(data, enc); return new TX().fromRaw(data); }; /** * Instantiate a transaction from a buffer reader. * @param {BufferReader} br * @returns {TX} */ TX.fromReader = function fromReader(br) { return new TX().fromReader(br); }; /** * Inject properties from serialized data. * @private * @param {Buffer} data */ TX.prototype.fromRaw = function fromRaw(data) { return this.fromReader(new BufferReader(data)); }; /** * Inject properties from buffer reader. * @private * @param {BufferReader} br */ TX.prototype.fromReader = function fromReader(br) { var i, count; if (TX.isWitness(br)) return this.fromWitnessReader(br); br.start(); this.version = br.readU32(); count = br.readVarint(); for (i = 0; i < count; i++) this.inputs.push(Input.fromReader(br)); count = br.readVarint(); for (i = 0; i < count; i++) this.outputs.push(Output.fromReader(br)); this.locktime = br.readU32(); if (!this.mutable) { this._raw = br.endData(); this._size = this._raw.length; this._witness = 0; } else { br.end(); } return this; }; /** * Inject properties from serialized * buffer reader (witness serialization). * @private * @param {BufferReader} br */ TX.prototype.fromWitnessReader = function fromWitnessReader(br) { var flag = 0; var witness = 0; var hasWitness = false; var i, count, input; br.start(); this.version = br.readU32(); assert(br.readU8() === 0, 'Non-zero marker.'); flag = br.readU8(); assert(flag !== 0, 'Flag byte is zero.'); this.flag = flag; count = br.readVarint(); for (i = 0; i < count; i++) this.inputs.push(Input.fromReader(br)); count = br.readVarint(); for (i = 0; i < count; i++) this.outputs.push(Output.fromReader(br)); if (flag & 1) { flag ^= 1; witness = br.offset; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; input.witness.fromReader(br); if (input.witness.items.length > 0) hasWitness = true; } witness = (br.offset - witness) + 2; } if (flag !== 0) throw new Error('Unknown witness flag.'); // We'll never be able to reserialize // this to get the regular txid, and // there's no way it's valid anyway. if (this.inputs.length === 0 && this.outputs.length !== 0) throw new Error('Zero input witness tx.'); this.locktime = br.readU32(); if (!this.mutable && hasWitness) { this._raw = br.endData(); this._size = this._raw.length; this._witness = witness; } else { br.end(); } return this; }; /** * Serialize transaction without witness. * @private * @returns {RawTX} */ TX.prototype.frameNormal = function frameNormal() { var sizes = this.getNormalSizes(); var bw = new StaticWriter(sizes.total); this.writeNormal(bw); sizes.data = bw.render(); return sizes; }; /** * Serialize transaction with witness. Calculates the witness * size as it is framing (exposed on return value as `witness`). * @private * @returns {RawTX} */ TX.prototype.frameWitness = function frameWitness() { var sizes = this.getWitnessSizes(); var bw = new StaticWriter(sizes.total); this.writeWitness(bw); sizes.data = bw.render(); return sizes; }; /** * Serialize transaction without witness. * @private * @param {BufferWriter} bw * @returns {RawTX} */ TX.prototype.writeNormal = function writeNormal(bw) { var i, input, output; if (this.inputs.length === 0 && this.outputs.length !== 0) throw new Error('Cannot serialize zero-input tx.'); bw.writeU32(this.version); bw.writeVarint(this.inputs.length); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; input.toWriter(bw); } bw.writeVarint(this.outputs.length); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; output.toWriter(bw); } bw.writeU32(this.locktime); return bw; }; /** * Serialize transaction with witness. Calculates the witness * size as it is framing (exposed on return value as `witness`). * @private * @param {BufferWriter} bw * @returns {RawTX} */ TX.prototype.writeWitness = function writeWitness(bw) { var i, witness, input, output; if (this.inputs.length === 0 && this.outputs.length !== 0) throw new Error('Cannot serialize zero-input tx.'); bw.writeU32(this.version); bw.writeU8(0); bw.writeU8(this.flag); bw.writeVarint(this.inputs.length); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; input.toWriter(bw); } bw.writeVarint(this.outputs.length); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; output.toWriter(bw); } witness = bw.written; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; input.witness.toWriter(bw); } witness = bw.written - witness; bw.writeU32(this.locktime); if (witness === this.inputs.length) throw new Error('Cannot serialize empty-witness tx.'); return bw; }; /** * Calculate the real size of the transaction * without the witness vector. * @returns {RawTX} */ TX.prototype.getNormalSizes = function getNormalSizes() { var base = 0; var i, input, output; base += 4; base += encoding.sizeVarint(this.inputs.length); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; base += input.getSize(); } base += encoding.sizeVarint(this.outputs.length); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; base += output.getSize(); } base += 4; return new RawTX(base, 0); }; /** * Calculate the real size of the transaction * with the witness included. * @returns {RawTX} */ TX.prototype.getWitnessSizes = function getWitnessSizes() { var base = 0; var witness = 0; var i, input, output; base += 4; witness += 2; base += encoding.sizeVarint(this.inputs.length); for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; base += input.getSize(); witness += input.witness.getVarSize(); } base += encoding.sizeVarint(this.outputs.length); for (i = 0; i < this.outputs.length; i++) { output = this.outputs[i]; base += output.getSize(); } base += 4; return new RawTX(base + witness, witness); }; /** * Test whether data is a witness transaction. * @param {Buffer|BufferReader} data * @returns {Boolean} */ TX.isWitness = function isWitness(br) { if (br.left() < 6) return false; return br.data[br.offset + 4] === 0 && br.data[br.offset + 5] !== 0; }; /** * Test whether an object is a TX. * @param {Object} obj * @returns {Boolean} */ TX.isTX = function isTX(obj) { return obj && Array.isArray(obj.inputs) && typeof obj.locktime === 'number' && typeof obj.witnessHash === 'function'; }; /* * Helpers */ function RawTX(total, witness) { this.data = null; this.total = total; this.witness = witness; } function AddrResult(addrs, table) { this.addrs = addrs; this.table = table; } /* * Expose */ module.exports = TX;