strict scripting.

This commit is contained in:
Christopher Jeffrey 2016-04-17 19:34:03 -07:00
parent ea7fb8f476
commit a4f0807c50
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 268 additions and 108 deletions

View File

@ -387,7 +387,7 @@ Block.prototype._verify = function _verify(ret) {
*/
Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
var coinbase, code, height;
var coinbase, height;
if (this.version < 2)
return -1;
@ -400,13 +400,7 @@ Block.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
if (!coinbase || coinbase.inputs.length === 0)
return -1;
code = coinbase.inputs[0].script.code;
if (Buffer.isBuffer(code[0]) && code[0].length <= 6)
height = new bn(code[0], 'le').toNumber();
else
height = -1;
height = coinbase.inputs[0].script.getCoinbaseHeight();
this._cbHeight = height;
return height;

View File

@ -55,12 +55,7 @@ function CompactBlock(data) {
bcoin.abstractblock.call(this, data);
this.type = 'compactblock';
this.coinbaseHeight = -1;
if (this.version >= 2) {
if (Buffer.isBuffer(data.coinbaseHeight) && data.coinbaseHeight.length <= 6)
this.coinbaseHeight = new bn(data.coinbaseHeight, 'le').toNumber();
}
this.coinbaseHeight = data.coinbaseHeight;
}
utils.inherits(CompactBlock, bcoin.abstractblock);

View File

@ -18,7 +18,7 @@ var BufferWriter = require('./writer');
* @exports Input
* @constructor
* @param {NakedInput} options
* @param {TX?} tx
* @param {Boolean?} mutable
* @property {Object} prevout - Outpoint.
* @property {Hash} prevout.hash - Previous transaction hash.
* @property {Number} prevout.index - Previous output index.
@ -28,17 +28,18 @@ var BufferWriter = require('./writer');
* @property {Coin?} coin - Previous output.
* @property {String} type - Script type.
* @property {String?} address - Input address.
* @property {Boolean} mutable
*/
function Input(options, tx) {
function Input(options, mutable) {
if (!(this instanceof Input))
return new Input(options);
this.mutable = !!mutable;
this.prevout = options.prevout;
this.script = bcoin.script(options.script);
this.script = bcoin.script(options.script, this.mutable);
this.sequence = options.sequence == null ? 0xffffffff : options.sequence;
this.witness = bcoin.script.witness(options.witness);
this._mutable = !tx || (tx instanceof bcoin.mtx);
this.witness = bcoin.script.witness(options.witness, this.mutable);
if (options.coin)
this.coin = bcoin.coin(options.coin);
@ -83,7 +84,7 @@ Input.prototype.getType = function getType() {
if (!type || type === 'unknown')
type = this.script.getInputType();
if (!this._mutable)
if (!this.mutable)
this._type = type;
return type;
@ -160,7 +161,7 @@ Input.prototype.getAddress = function getAddress() {
if (!address)
address = this.script.getInputAddress();
if (!this._mutable)
if (!this.mutable)
this._address = address;
return address;

View File

@ -252,7 +252,7 @@ MTX.prototype.addInput = function addInput(options, index) {
assert(options.prevout);
input = bcoin.input(options, this);
input = bcoin.input(options, true);
if (options.script instanceof Script)
input.script = options.script.clone();
@ -386,7 +386,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) {
vector[1] = addr.publicKey;
} else if (prev.isMultisig()) {
// Multisig
if (utils.indexOf(prev.code, addr.publicKey) === -1)
if (prev.code.indexOf(addr.publicKey) === -1)
return false;
// Already has a script template (at least)
@ -405,7 +405,7 @@ MTX.prototype.scriptInput = function scriptInput(index, addr) {
for (i = 0; i < n; i++)
vector[i + 1] = dummy;
} else {
if (utils.indexOf(prev.code, addr.publicKey) === -1)
if (prev.code.indexOf(addr.publicKey) === -1)
return false;
// Already has a script template (at least)
@ -799,7 +799,7 @@ MTX.prototype.addOutput = function addOutput(obj, value) {
options = obj;
}
output = bcoin.output(options, this);
output = bcoin.output(options, true);
this.outputs.push(output);

View File

@ -16,14 +16,15 @@ var assert = utils.assert;
* @exports Output
* @constructor
* @param {NakedOutput} options
* @param {TX?} tx
* @param {Boolean?} mutable
* @property {BN} value - Value in satoshis.
* @property {Script} script
* @property {String} type - Script type.
* @property {String?} address - Input address.
* @property {Boolean} mutable
*/
function Output(options, tx) {
function Output(options, mutable) {
var value;
if (!(this instanceof Output))
@ -36,12 +37,12 @@ function Output(options, tx) {
value = new bn(value);
}
this.mutable = !!mutable;
this.value = utils.satoshi(value || new bn(0));
this.script = bcoin.script(options.script);
this._mutable = !tx || (tx instanceof bcoin.mtx);
this.script = bcoin.script(options.script, this.mutable);
assert(typeof value !== 'number');
assert(!this._mutable || !this.value.isNeg());
assert(!this.mutable || !this.value.isNeg());
}
Output.prototype.__defineGetter__('type', function() {
@ -65,7 +66,7 @@ Output.prototype.getType = function getType() {
type = this.script.getType();
if (!this._mutable)
if (!this.mutable)
this._type = type;
return type;
@ -84,7 +85,7 @@ Output.prototype.getAddress = function getAddress() {
address = this.script.getAddress();
if (!this._mutable)
if (!this.mutable)
this._address = address;
return address;

View File

@ -937,10 +937,10 @@ Parser.parseBlockCompact = function parseBlockCompact(p) {
input = Parser.parseInput(p);
}
if (input) {
if (Buffer.isBuffer(input.script.code[0]))
height = input.script.code[0];
}
if (input)
height = bcoin.script.getCoinbaseHeight(input.script.code);
else
height = -1;
raw = p.data;

View File

@ -35,15 +35,25 @@ var STACK_NEGATE = new Buffer([0xff]);
* @constructor
* @param {Buffer[]|Buffer|NakedWitness} items - Array of
* stack items or raw witness buffer.
* @param {Boolean} mutable - Whether the script will
* be changed in the future.
* @property {Buffer[]} items
* @property {Script?} redeem
* @property {Boolean} mutable
*/
function Witness(items) {
if (items instanceof Witness)
function Witness(items, mutable) {
if (items instanceof Witness) {
items.mutable = !!mutable;
items.redeem = null;
return items;
}
if (!(this instanceof Witness))
return new Witness(items);
this.mutable = !!mutable;
if (!items)
items = [];
@ -167,12 +177,25 @@ Witness.prototype.isUnknownInput = function isUnknownInput() {
*/
Witness.prototype.getRedeem = function getRedeem() {
if (this.mutable)
return Script.getRedeem(this.items);
if (!this.redeem)
this.redeem = Script.getRedeem(this.items);
return this.redeem;
};
/**
* Find a data element in a witness.
* @param {Buffer} data - Data element to match against.
* @returns {Number} Index (`-1` if not present).
*/
Witness.prototype.indexOf = function indexOf(data) {
return utils.indexOf(this.items, data);
};
/**
* Parse a formatted script
* string into a witness object. _Must_
@ -761,18 +784,27 @@ Stack.isStack = function isStack(obj) {
* @constructor
* @param {Buffer|Array|Object|NakedScript} code - Array
* of script code or a serialized script Buffer.
* @param {Boolean} mutable - Whether the script will
* be changed in the future.
* @property {Array} code - Script code.
* @property {Buffer} raw - Serialized script.
* @property {Script} redeem - Redeem script.
* @property {Buffer?} raw - Serialized script.
* @property {Script?} redeem - Redeem script.
* @property {Boolean} mutable
*/
function Script(code) {
if (code instanceof Script)
function Script(code, mutable) {
if (code instanceof Script) {
code.mutable = !!mutable;
code.raw = null;
code.redeem = null;
return code;
}
if (!(this instanceof Script))
return new Script(code);
this.mutable = !!mutable;
if (Buffer.isBuffer(code)) {
this.raw = code;
this.code = Script.decode(code);
@ -791,6 +823,9 @@ function Script(code) {
}
}
if (this.mutable)
this.raw = null;
this.redeem = null;
}
@ -820,8 +855,12 @@ Script.prototype.inspect = function inspect() {
*/
Script.prototype.encode = function encode() {
if (this.mutable)
return Script.encode(this.code);
if (!this.raw)
this.raw = Script.encode(this.code);
return this.raw;
};
@ -920,12 +959,14 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
op = this.code[ip];
if (Buffer.isBuffer(op)) {
if (!Script.checkPush(op))
throw new ScriptError('Pushdata out of range.', op, ip);
if (op.length > constants.script.MAX_PUSH)
throw new ScriptError('Push data too large.', op, ip);
throw new ScriptError('Pushdata too large.', op, ip);
// Note that minimaldata is not checked
// on unexecuted branches of code.
if (stack.negate === 0) {
if (!Script.checkPush(op, flags))
if (!Script.checkMinimal(op, flags))
throw new ScriptError('Push verification failed.', op, ip);
stack.push(op);
}
@ -1366,7 +1407,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
type = sig[sig.length - 1];
subscript = this.getSubscript(lastSep);
subscript.removeData(sig);
if (version === 0)
subscript.removeData(sig);
hash = tx.signatureHash(index, subscript, type, version);
@ -1418,7 +1460,8 @@ Script.prototype.interpret = function interpret(stack, flags, tx, index, version
for (i = 0; i < m; i++) {
sig = stack.get(stack.length - 1 - i);
subscript.removeData(sig);
if (version === 0)
subscript.removeData(sig);
}
succ = 0;
@ -1680,7 +1723,6 @@ Script.checkSequence = function checkSequence(sequence, tx, i) {
/**
* Cast a big number or Buffer to a bool.
* @see CastToBool
* @static
* @param {BN|Buffer} value
* @returns {Boolean}
*/
@ -1707,7 +1749,6 @@ Script.bool = function bool(value) {
/**
* Create a CScriptNum.
* @static
* @param {Buffer} value
* @param {Number?} flags - Script standard flags.
* @param {Number?} size - Max size in bytes.
@ -1763,7 +1804,6 @@ Script.num = function num(value, flags, size) {
* Create a script array. Will convert Numbers and big
* numbers to a little-endian buffer while taking into
* account negative zero, minimaldata, etc.
* @static
* @example
* assert.deepEqual(Script.array(0), new Buffer([]));
* assert.deepEqual(Script.array(0xffee), new Buffer([0xee, 0xff]));
@ -1801,27 +1841,76 @@ Script.array = function(value) {
* a script's code (used to remove signatures
* before verification).
* @param {Buffer} data - Data element to match against.
* @returns {Number} Total.
*/
Script.prototype.removeData = function removeData(data) {
for (var i = this.code.length - 1; i >= 0; i--) {
var total = 0;
var i;
for (i = this.code.length - 1; i >= 0; i--) {
if (!Buffer.isBuffer(this.code[i]))
continue;
if (utils.isEqual(this.code[i], data))
if (utils.isEqual(this.code[i], data)) {
this.code.splice(i, 1);
total++;
}
}
return total;
};
/**
* Find a data element in a script.
* @param {Buffer} data - Data element to match against.
* @returns {Number} Index (`-1` if not present).
*/
Script.prototype.indexOf = function indexOf(data) {
return utils.indexOf(this.code, data);
};
/**
* Test whether an op is a buffer, also
* check for buffer underflows.
* @param {Buffer?} value
* @returns {Boolean}
*/
Script.isPush = function isPush(value) {
return Buffer.isBuffer(value) && Script.checkPush(value);
};
/**
* Perform some range checking on the pushdatas
* (exactly what GetOp2 does). Note that this
* _must_ be done during execution, not parsing.
* @see GetOp2
* @param {Buffer} value - Pushdata op from script code
* (must be from a deserialized script).
* @returns {Boolean}
*/
Script.checkPush = function checkPush(value) {
var pushdata = value.pushdata;
if (!pushdata)
return true;
// The pushdata size can never
// be greater than the buffer.
return pushdata.size === value.length;
};
/**
* Check to see if a pushdata Buffer abides by minimaldata.
* @static
* @param {Buffer} value - Pushdata op from script code
* (must be from a deserialized script).
* @param {Number?} flags
* @returns {Boolean}
*/
Script.checkPush = function checkPush(value, flags) {
Script.checkMinimal = function checkMinimal(value, flags) {
var pushdata = value.pushdata;
if (flags == null)
@ -2012,6 +2101,9 @@ Script.createCommitment = function createCommitment(hash) {
*/
Script.prototype.getRedeem = function getRedeem() {
if (this.mutable)
return Script.getRedeem(this.code);
if (!this.redeem)
this.redeem = Script.getRedeem(this.code);
@ -2020,7 +2112,6 @@ Script.prototype.getRedeem = function getRedeem() {
/**
* Grab and deserialize the redeem script from script code.
* @static
* @param {Array} code
* @returns {Script|null} Redeem script.
*/
@ -2028,7 +2119,7 @@ Script.prototype.getRedeem = function getRedeem() {
Script.getRedeem = function getRedeem(code) {
var redeem = code[code.length - 1];
if (!Buffer.isBuffer(redeem))
if (!Script.isPush(redeem))
return;
return new Script(redeem);
@ -2086,9 +2177,6 @@ Script.prototype.isStandard = function isStandard() {
if (m < 1 || m > n)
return false;
} else if (type === 'nulldata') {
if (this.getSize() > constants.script.MAX_OP_RETURN_BYTES)
return false;
}
return type !== 'unknown';
@ -2136,10 +2224,14 @@ Script.prototype.isStandardProgram = function isStandardProgram(witness, flags)
};
/**
* @returns {Number} Size of script excluding the varint size bytes.
* Calculate size of script excluding the varint size bytes.
* @returns {Number}
*/
Script.prototype.getSize = function getSize() {
if (this.mutable)
return Script.encode(this.code, new BufferWriter()).written;
return this.encode().length;
};
@ -2365,9 +2457,29 @@ Script.prototype.isScripthash = function isScripthash() {
*/
Script.prototype.isNulldata = function isNulldata() {
return this.code.length === 2
&& this.code[0] === opcodes.OP_RETURN
&& Script.isData(this.code[1]);
var i, op;
if (this.raw && this.raw.length > constants.script.MAX_OP_RETURN_BYTES)
return false;
if (this.code.length === 0)
return false;
if (this.code[0] !== opcodes.OP_RETURN)
return false;
for (i = 1; i < this.code.length; i++) {
op = this.code[i];
if (Buffer.isBuffer(op)) {
if (!Script.checkPush(op))
return false;
continue;
}
if (op > opcodes.OP_16)
return false;
}
return true;
};
/**
@ -2380,9 +2492,20 @@ Script.prototype.isNulldata = function isNulldata() {
*/
Script.prototype.isCommitment = function isCommitment() {
if (this.raw) {
if (this.raw.length < 38)
return false;
if (this.raw[0] !== opcodes.OP_RETURN)
return false;
if (this.raw[1] !== 0x24)
return false;
if (utils.readU32BE(this.raw, 2) !== 0xaa21a9ed)
return false;
return true;
}
return this.code.length >= 2
&& this.code[0] === opcodes.OP_RETURN
&& Buffer.isBuffer(this.code[1])
&& Script.isPush(this.code[1])
&& this.code[1].length === 36
&& utils.readU32BE(this.code[1], 0) === 0xaa21a9ed;
};
@ -2407,13 +2530,29 @@ Script.prototype.getCommitmentHash = function getCommitmentHash() {
*/
Script.prototype.isWitnessProgram = function isWitnessProgram() {
// Witness programs are strict minimaldata.
if (this.raw) {
if (!(this.raw.length >= 4 && this.raw.length <= 34))
return false;
if (this.raw[0] !== opcodes.OP_0
&& !(this.raw[0] >= opcodes.OP_1 && this.raw[0] <= opcodes.OP_16)) {
return false;
}
if (this.raw[1] + 2 !== this.raw.length)
return false;
return true;
}
if (this.code.length !== 2)
return false;
if (typeof this.code[0] !== 'number')
return false;
if (!Buffer.isBuffer(this.code[1]))
if (!Script.isPush(this.code[1]))
return false;
return (this.code[0] === opcodes.OP_0
@ -2531,7 +2670,6 @@ Script.isUnknownInput = function isUnknownInput(code, isWitness) {
/**
* Automatically build an output script from any number of options.
* @static
* @example
* Script.createOutputScript({ address: '1HT7xU2Ngenf7D4yocz2SAcnNLW7rK8d4E' });
* @param {Object} options
@ -2712,7 +2850,7 @@ Script.isScripthashInput = function isScripthashInput(code, isWitness) {
// Last data element should be an array
// for the redeem script.
if (!Buffer.isBuffer(raw))
if (!Script.isPush(raw))
return false;
// Testing for scripthash inputs requires
@ -2739,6 +2877,39 @@ Script.isScripthashInput = function isScripthashInput(code, isWitness) {
return true;
};
/**
* Get coinbase height.
* @returns {Number} `-1` if not present.
*/
Script.prototype.getCoinbaseHeight = function getCoinbaseHeight() {
return Script.getCoinbaseHeight(this.code);
};
/**
* Get coinbase height.
* @returns {Number} `-1` if not present.
*/
Script.getCoinbaseHeight = function getCoinbaseHeight(code) {
if (code.length === 0)
return -1;
if (!Buffer.isBuffer(code[0]))
return -1;
if (!Script.checkPush(code[0]))
return -1;
if (!Script.checkMinimal(code[0]))
return -1;
if (code[0].length > 6)
return -1;
return new bn(code[0], 'le').toNumber();
};
/**
* Get info about a coinbase script.
* @returns {Object} Object containing `height`,
@ -2749,10 +2920,7 @@ Script.prototype.getCoinbaseData = function getCoinbaseData() {
var coinbase = {};
var flags;
if (Buffer.isBuffer(this.code[0]) && this.code[0].length <= 6)
coinbase.height = new bn(this.code[0], 'le').toNumber();
else
coinbase.height = -1;
coinbase.height = this.getCoinbaseHeight();
if (Buffer.isBuffer(this.code[1]))
coinbase.extraNonce = new bn(this.code[1], 'le');
@ -2781,7 +2949,7 @@ Script.prototype.getCoinbaseData = function getCoinbaseData() {
*/
Script.isHash = function isHash(hash) {
return Buffer.isBuffer(hash) && hash.length === 20;
return Script.isPush(hash) && hash.length === 20;
};
/**
@ -2792,7 +2960,7 @@ Script.isHash = function isHash(hash) {
*/
Script.isKey = function isKey(key) {
return Buffer.isBuffer(key) && key.length >= 33 && key.length <= 65;
return Script.isPush(key) && key.length >= 33 && key.length <= 65;
};
/**
@ -2803,7 +2971,7 @@ Script.isKey = function isKey(key) {
*/
Script.isSignature = function isSignature(sig) {
return Buffer.isBuffer(sig) && sig.length >= 9 && sig.length <= 73;
return Script.isPush(sig) && sig.length >= 9 && sig.length <= 73;
};
/**
@ -2813,7 +2981,7 @@ Script.isSignature = function isSignature(sig) {
*/
Script.isDummy = function isDummy(data) {
return Buffer.isBuffer(data) && data.length === 0;
return Script.isPush(data) && data.length === 0;
};
/**
@ -2830,16 +2998,6 @@ Script.isZero = function isZero(op) {
return Script.isDummy(op);
};
/**
* Test whether the data element is a valid nulldata push.
* @param {Buffer?} data
* @returns {Boolean}
*/
Script.isData = function isData(data) {
return Buffer.isBuffer(data) && data.length <= constants.script.MAX_OP_RETURN;
};
/**
* Test whether the data element is a valid key if VERIFY_STRICTENC is enabled.
* @param {Buffer} key
@ -2851,7 +3009,7 @@ Script.isValidKey = function isValidKey(key, flags) {
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (!Buffer.isBuffer(key))
if (!Script.isPush(key))
return false;
if (flags & constants.flags.VERIFY_STRICTENC) {
@ -2871,7 +3029,7 @@ Script.isValidKey = function isValidKey(key, flags) {
*/
Script.isKeyEncoding = function isKeyEncoding(key) {
if (!Buffer.isBuffer(key))
if (!Script.isPush(key))
return false;
if (key.length < 33)
@ -2905,7 +3063,7 @@ Script.isValidSignature = function isValidSignature(sig, flags) {
if (flags == null)
flags = constants.flags.STANDARD_VERIFY_FLAGS;
if (!Buffer.isBuffer(sig))
if (!Script.isPush(sig))
return false;
// Allow empty sigs
@ -2948,7 +3106,7 @@ Script.isValidSignature = function isValidSignature(sig, flags) {
Script.isSignatureEncoding = function isSignatureEncoding(sig) {
var lenR, lenS;
if (!Buffer.isBuffer(sig))
if (!Script.isPush(sig))
return false;
// Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] [sighash]
@ -3039,7 +3197,7 @@ Script.isSignatureEncoding = function isSignatureEncoding(sig) {
Script.isHashType = function isHashType(sig) {
var type;
if (!Buffer.isBuffer(sig))
if (!Script.isPush(sig))
return false;
if (sig.length === 0)
@ -3061,7 +3219,7 @@ Script.isHashType = function isHashType(sig) {
Script.isLowDER = function isLowDER(sig) {
if (!sig.s) {
if (!Buffer.isBuffer(sig))
if (!Script.isPush(sig))
return false;
if (!Script.isSignatureEncoding(sig))
@ -3130,13 +3288,13 @@ Script.prototype.isPushOnly = function isPushOnly() {
var i, op;
for (i = 0; i < this.code.length; i++) {
op = this.code[i];
if (Buffer.isBuffer(op)
|| op === opcodes.OP_1NEGATE
|| op === opcodes.OP_0
|| (op >= opcodes.OP_1 && op <= opcodes.OP_16)) {
if (Buffer.isBuffer(op)) {
if (!Script.checkPush(op))
return false;
continue;
}
return false;
if (op > opcodes.OP_16)
return false;
}
return true;
};
@ -3156,7 +3314,7 @@ Script.prototype.getSigops = function getSigops(accurate) {
for (i = 0; i < this.code.length; i++) {
op = this.code[i];
if (Buffer.isBuffer(op))
if (Script.isPush(op))
continue;
if (constants.opcodesByVal[op] == null)
@ -3407,7 +3565,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) {
// Grab the real redeem script
raw = stack.pop();
if (!Buffer.isBuffer(raw))
if (!Script.isPush(raw))
return false;
redeem = new Script(raw);
@ -3628,6 +3786,20 @@ Script.parseScript = function parseScript(buf) {
* this will apply hidden `pushdata` properties
* to each Buffer if the buffer was created from
* a non-standard pushdata.
*
* This function does not do bounds checking
* on buffers because some jackass could do a
* direct push of 30 bytes with only 20 bytes
* after it. That script would be perfectly
* fine _until_ it is executed. There are
* output scripts on the blockchain that can
* never be redeemed due to this, but they are
* in valid blocks, therefore we cannot fail
* parsing them.
*
* Also note that this function uses reference
* Buffer slices. Larger buffer slices should
* _never_ be passed in here.
* @param {Buffer} buf - Serialized script.
* @returns {Array} Script code.
*/
@ -3639,12 +3811,6 @@ Script.decode = function decode(buf) {
assert(Buffer.isBuffer(buf));
// NOTE: We can't use a BufferReader here since
// script parsing was originally non-strict/ridiculous.
// Something could do a direct push of 30 bytes with
// only 20 bytes after it.
// NOTE 2: We use reference Buffer slices here. Larger
// buffer slices should _never_ be passed in here.
while (off < buf.length) {
op = buf[off++];
if (op >= 0x01 && op <= 0x4b) {
@ -3708,8 +3874,8 @@ Script.decode = function decode(buf) {
* @returns {Buffer} Serialized script.
*/
Script.encode = function encode(code) {
var p = new BufferWriter();
Script.encode = function encode(code, writer) {
var p = new BufferWriter(writer);
var i = 0;
var op;
@ -3772,7 +3938,10 @@ Script.encode = function encode(code) {
p.writeU8(op);
}
return p.render();
if (!writer)
p = p.render();
return p;
};
/**

View File

@ -82,12 +82,12 @@ function TX(data, block, index) {
if (data.inputs) {
for (i = 0; i < data.inputs.length; i++)
this.inputs.push(new bcoin.input(data.inputs[i], this));
this.inputs.push(new bcoin.input(data.inputs[i]));
}
if (data.outputs) {
for (i = 0; i < data.outputs.length; i++)
this.outputs.push(new bcoin.output(data.outputs[i], this));
this.outputs.push(new bcoin.output(data.outputs[i]));
}
if (block && this.ts === 0) {