From 17c89fdc5eb9e508c372f91996bda8f1b183f3f3 Mon Sep 17 00:00:00 2001 From: junderw Date: Wed, 26 Sep 2018 14:54:53 +0900 Subject: [PATCH] Move Transaction to ES6 --- src/transaction.js | 773 ++++++++++++++++++++++++--------------------- 1 file changed, 418 insertions(+), 355 deletions(-) diff --git a/src/transaction.js b/src/transaction.js index 751446f..c6a9909 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -7,35 +7,20 @@ const typeforce = require('typeforce') const types = require('./types') const varuint = require('varuint-bitcoin') -function varSliceSize (someScript) { +const varSliceSize = (someScript) => { const length = someScript.length return varuint.encodingLength(length) + length } -function vectorSize (someVector) { +const vectorSize = (someVector) => { const length = someVector.length - return varuint.encodingLength(length) + someVector.reduce(function (sum, witness) { + return varuint.encodingLength(length) + someVector.reduce((sum, witness) => { return sum + varSliceSize(witness) }, 0) } -function Transaction () { - this.version = 1 - this.locktime = 0 - this.ins = [] - this.outs = [] -} - -Transaction.DEFAULT_SEQUENCE = 0xffffffff -Transaction.SIGHASH_ALL = 0x01 -Transaction.SIGHASH_NONE = 0x02 -Transaction.SIGHASH_SINGLE = 0x03 -Transaction.SIGHASH_ANYONECANPAY = 0x80 -Transaction.ADVANCED_TRANSACTION_MARKER = 0x00 -Transaction.ADVANCED_TRANSACTION_FLAG = 0x01 - const EMPTY_SCRIPT = Buffer.allocUnsafe(0) const EMPTY_WITNESS = [] const ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex') @@ -46,447 +31,525 @@ const BLANK_OUTPUT = { valueBuffer: VALUE_UINT64_MAX } -Transaction.fromBuffer = function (buffer, __noStrict) { - let offset = 0 - function readSlice (n) { - offset += n - return buffer.slice(offset - n, offset) +class Transaction { + constructor () { + this.version = 1 + this.locktime = 0 + this.ins = [] + this.outs = [] } - function readUInt32 () { - const i = buffer.readUInt32LE(offset) - offset += 4 - return i + static get DEFAULT_SEQUENCE () { + return 0xffffffff } - - function readInt32 () { - const i = buffer.readInt32LE(offset) - offset += 4 - return i + static get SIGHASH_ALL () { + return 0x01 } - - function readUInt64 () { - const i = bufferutils.readUInt64LE(buffer, offset) - offset += 8 - return i + static get SIGHASH_NONE () { + return 0x02 } - - function readVarInt () { - const vi = varuint.decode(buffer, offset) - offset += varuint.decode.bytes - return vi + static get SIGHASH_SINGLE () { + return 0x03 } - - function readVarSlice () { - return readSlice(readVarInt()) + static get SIGHASH_ANYONECANPAY () { + return 0x80 + } + static get ADVANCED_TRANSACTION_MARKER () { + return 0x00 + } + static get ADVANCED_TRANSACTION_FLAG () { + return 0x01 } - function readVector () { - const count = readVarInt() - const vector = [] - for (var i = 0; i < count; i++) vector.push(readVarSlice()) - return vector + isCoinbase () { + return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) } - const tx = new Transaction() - tx.version = readInt32() + addInput (hash, index, sequence, scriptSig) { + typeforce(types.tuple( + types.Hash256bit, + types.UInt32, + types.maybe(types.UInt32), + types.maybe(types.Buffer) + ), arguments) - const marker = buffer.readUInt8(offset) - const flag = buffer.readUInt8(offset + 1) + if (types.Null(sequence)) { + sequence = Transaction.DEFAULT_SEQUENCE + } - let hasWitnesses = false - if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && - flag === Transaction.ADVANCED_TRANSACTION_FLAG) { - offset += 2 - hasWitnesses = true + // Add the input and return the input's index + return (this.ins.push({ + hash: hash, + index: index, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence, + witness: EMPTY_WITNESS + }) - 1) } - const vinLen = readVarInt() - for (var i = 0; i < vinLen; ++i) { - tx.ins.push({ - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32(), - witness: EMPTY_WITNESS - }) + addOutput (scriptPubKey, value) { + typeforce(types.tuple(types.Buffer, types.Satoshi), arguments) + + // Add the output and return the output's index + return (this.outs.push({ + script: scriptPubKey, + value: value + }) - 1) } - const voutLen = readVarInt() - for (i = 0; i < voutLen; ++i) { - tx.outs.push({ - value: readUInt64(), - script: readVarSlice() + hasWitnesses () { + return this.ins.some((x) => { + return x.witness.length !== 0 }) } - if (hasWitnesses) { - for (i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector() - } + weight () { + const base = this.__byteLength(false) + const total = this.__byteLength(true) + return base * 3 + total + } - // was this pointless? - if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data') + virtualSize () { + return Math.ceil(this.weight() / 4) } - tx.locktime = readUInt32() + byteLength () { + return this.__byteLength(true) + } - if (__noStrict) return tx - if (offset !== buffer.length) throw new Error('Transaction has unexpected data') + __byteLength (__allowWitness) { + const hasWitnesses = __allowWitness && this.hasWitnesses() + + return ( + (hasWitnesses ? 10 : 8) + + varuint.encodingLength(this.ins.length) + + varuint.encodingLength(this.outs.length) + + this.ins.reduce((sum, input) => { + return sum + 40 + varSliceSize(input.script) + }, 0) + + this.outs.reduce((sum, output) => { + return sum + 8 + varSliceSize(output.script) + }, 0) + + (hasWitnesses ? this.ins.reduce((sum, input) => { + return sum + vectorSize(input.witness) + }, 0) : 0) + ) + } - return tx -} + clone () { + const newTx = new Transaction() + newTx.version = this.version + newTx.locktime = this.locktime + + newTx.ins = this.ins.map((txIn) => { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence, + witness: txIn.witness + } + }) -Transaction.fromHex = function (hex) { - return Transaction.fromBuffer(Buffer.from(hex, 'hex')) -} + newTx.outs = this.outs.map((txOut) => { + return { + script: txOut.script, + value: txOut.value + } + }) -Transaction.isCoinbaseHash = function (buffer) { - typeforce(types.Hash256bit, buffer) - for (var i = 0; i < 32; ++i) { - if (buffer[i] !== 0) return false + return newTx } - return true -} -Transaction.prototype.isCoinbase = function () { - return this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) -} + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. + * This method copies the transaction, makes the necessary changes based on the + * hashType, and then hashes the result. + * This hash can then be used to sign the provided transaction input. + */ + hashForSignature (inIndex, prevOutScript, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments) + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 + if (inIndex >= this.ins.length) return ONE + + // ignore OP_CODESEPARATOR + const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter((x) => { + return x !== opcodes.OP_CODESEPARATOR + })) + + const txTmp = this.clone() + + // SIGHASH_NONE: ignore all outputs? (wildcard payee) + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { + txTmp.outs = [] + + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, i) => { + if (i === inIndex) return + + input.sequence = 0 + }) + + // SIGHASH_SINGLE: ignore all outputs, except at the same index? + } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 + if (inIndex >= this.outs.length) return ONE + + // truncate outputs after + txTmp.outs.length = inIndex + 1 + + // "blank" outputs before + for (var i = 0; i < inIndex; i++) { + txTmp.outs[i] = BLANK_OUTPUT + } + + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, y) => { + if (y === inIndex) return + + input.sequence = 0 + }) + } -Transaction.prototype.addInput = function (hash, index, sequence, scriptSig) { - typeforce(types.tuple( - types.Hash256bit, - types.UInt32, - types.maybe(types.UInt32), - types.maybe(types.Buffer) - ), arguments) - - if (types.Null(sequence)) { - sequence = Transaction.DEFAULT_SEQUENCE - } - - // Add the input and return the input's index - return (this.ins.push({ - hash: hash, - index: index, - script: scriptSig || EMPTY_SCRIPT, - sequence: sequence, - witness: EMPTY_WITNESS - }) - 1) -} + // SIGHASH_ANYONECANPAY: ignore inputs entirely? + if (hashType & Transaction.SIGHASH_ANYONECANPAY) { + txTmp.ins = [txTmp.ins[inIndex]] + txTmp.ins[0].script = ourScript + + // SIGHASH_ALL: only ignore input scripts + } else { + // "blank" others input scripts + txTmp.ins.forEach((input) => { + input.script = EMPTY_SCRIPT + }) + txTmp.ins[inIndex].script = ourScript + } -Transaction.prototype.addOutput = function (scriptPubKey, value) { - typeforce(types.tuple(types.Buffer, types.Satoshi), arguments) + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4) + buffer.writeInt32LE(hashType, buffer.length - 4) + txTmp.__toBuffer(buffer, 0, false) - // Add the output and return the output's index - return (this.outs.push({ - script: scriptPubKey, - value: value - }) - 1) -} + return bcrypto.hash256(buffer) + } -Transaction.prototype.hasWitnesses = function () { - return this.ins.some(function (x) { - return x.witness.length !== 0 - }) -} + hashForWitnessV0 (inIndex, prevOutScript, value, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) -Transaction.prototype.weight = function () { - const base = this.__byteLength(false) - const total = this.__byteLength(true) - return base * 3 + total -} + let tbuffer, toffset -Transaction.prototype.virtualSize = function () { - return Math.ceil(this.weight() / 4) -} + const writeSlice = (slice) => { + toffset += slice.copy(tbuffer, toffset) + } -Transaction.prototype.byteLength = function () { - return this.__byteLength(true) -} + const writeUInt32 = (i) => { + toffset = tbuffer.writeUInt32LE(i, toffset) + } -Transaction.prototype.__byteLength = function (__allowWitness) { - const hasWitnesses = __allowWitness && this.hasWitnesses() - - return ( - (hasWitnesses ? 10 : 8) + - varuint.encodingLength(this.ins.length) + - varuint.encodingLength(this.outs.length) + - this.ins.reduce(function (sum, input) { return sum + 40 + varSliceSize(input.script) }, 0) + - this.outs.reduce(function (sum, output) { return sum + 8 + varSliceSize(output.script) }, 0) + - (hasWitnesses ? this.ins.reduce(function (sum, input) { return sum + vectorSize(input.witness) }, 0) : 0) - ) -} + const writeUInt64 = (i) => { + toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) + } -Transaction.prototype.clone = function () { - const newTx = new Transaction() - newTx.version = this.version - newTx.locktime = this.locktime - - newTx.ins = this.ins.map(function (txIn) { - return { - hash: txIn.hash, - index: txIn.index, - script: txIn.script, - sequence: txIn.sequence, - witness: txIn.witness + const writeVarInt = (i) => { + varuint.encode(i, tbuffer, toffset) + toffset += varuint.encode.bytes } - }) - newTx.outs = this.outs.map(function (txOut) { - return { - script: txOut.script, - value: txOut.value + const writeVarSlice = (slice) => { + writeVarInt(slice.length) + writeSlice(slice) } - }) - return newTx -} + let hashOutputs = ZERO + let hashPrevouts = ZERO + let hashSequence = ZERO -/** - * Hash transaction for signing a specific input. - * - * Bitcoin uses a different hash for each signed transaction input. - * This method copies the transaction, makes the necessary changes based on the - * hashType, and then hashes the result. - * This hash can then be used to sign the provided transaction input. - */ -Transaction.prototype.hashForSignature = function (inIndex, prevOutScript, hashType) { - typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments) + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + tbuffer = Buffer.allocUnsafe(36 * this.ins.length) + toffset = 0 - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 - if (inIndex >= this.ins.length) return ONE + this.ins.forEach((txIn) => { + writeSlice(txIn.hash) + writeUInt32(txIn.index) + }) - // ignore OP_CODESEPARATOR - const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(function (x) { - return x !== opcodes.OP_CODESEPARATOR - })) + hashPrevouts = bcrypto.hash256(tbuffer) + } - const txTmp = this.clone() + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + tbuffer = Buffer.allocUnsafe(4 * this.ins.length) + toffset = 0 - // SIGHASH_NONE: ignore all outputs? (wildcard payee) - if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { - txTmp.outs = [] + this.ins.forEach((txIn) => { + writeUInt32(txIn.sequence) + }) - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach(function (input, i) { - if (i === inIndex) return + hashSequence = bcrypto.hash256(tbuffer) + } - input.sequence = 0 - }) + if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + const txOutsSize = this.outs.reduce((sum, output) => { + return sum + 8 + varSliceSize(output.script) + }, 0) + + tbuffer = Buffer.allocUnsafe(txOutsSize) + toffset = 0 - // SIGHASH_SINGLE: ignore all outputs, except at the same index? - } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 - if (inIndex >= this.outs.length) return ONE + this.outs.forEach((out) => { + writeUInt64(out.value) + writeVarSlice(out.script) + }) - // truncate outputs after - txTmp.outs.length = inIndex + 1 + hashOutputs = bcrypto.hash256(tbuffer) + } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { + const output = this.outs[inIndex] - // "blank" outputs before - for (var i = 0; i < inIndex; i++) { - txTmp.outs[i] = BLANK_OUTPUT + tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)) + toffset = 0 + writeUInt64(output.value) + writeVarSlice(output.script) + + hashOutputs = bcrypto.hash256(tbuffer) } - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach(function (input, y) { - if (y === inIndex) return + tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)) + toffset = 0 - input.sequence = 0 - }) + const input = this.ins[inIndex] + writeUInt32(this.version) + writeSlice(hashPrevouts) + writeSlice(hashSequence) + writeSlice(input.hash) + writeUInt32(input.index) + writeVarSlice(prevOutScript) + writeUInt64(value) + writeUInt32(input.sequence) + writeSlice(hashOutputs) + writeUInt32(this.locktime) + writeUInt32(hashType) + return bcrypto.hash256(tbuffer) } - // SIGHASH_ANYONECANPAY: ignore inputs entirely? - if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - txTmp.ins = [txTmp.ins[inIndex]] - txTmp.ins[0].script = ourScript + getHash () { + return bcrypto.hash256(this.__toBuffer(undefined, undefined, false)) + } - // SIGHASH_ALL: only ignore input scripts - } else { - // "blank" others input scripts - txTmp.ins.forEach(function (input) { input.script = EMPTY_SCRIPT }) - txTmp.ins[inIndex].script = ourScript + getId () { + // transaction hash's are displayed in reverse order + return this.getHash().reverse().toString('hex') } - // serialize and hash - const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false) + 4) - buffer.writeInt32LE(hashType, buffer.length - 4) - txTmp.__toBuffer(buffer, 0, false) + toBuffer (buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true) + } - return bcrypto.hash256(buffer) -} + __toBuffer (buffer, initialOffset, __allowWitness) { + if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness)) -Transaction.prototype.hashForWitnessV0 = function (inIndex, prevOutScript, value, hashType) { - typeforce(types.tuple(types.UInt32, types.Buffer, types.Satoshi, types.UInt32), arguments) + let offset = initialOffset || 0 - let tbuffer, toffset - function writeSlice (slice) { toffset += slice.copy(tbuffer, toffset) } - function writeUInt32 (i) { toffset = tbuffer.writeUInt32LE(i, toffset) } - function writeUInt64 (i) { toffset = bufferutils.writeUInt64LE(tbuffer, i, toffset) } - function writeVarInt (i) { - varuint.encode(i, tbuffer, toffset) - toffset += varuint.encode.bytes - } - function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } + const writeSlice = (slice) => { + offset += slice.copy(buffer, offset) + } - let hashOutputs = ZERO - let hashPrevouts = ZERO - let hashSequence = ZERO + const writeUInt8 = (i) => { + offset = buffer.writeUInt8(i, offset) + } - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - tbuffer = Buffer.allocUnsafe(36 * this.ins.length) - toffset = 0 + const writeUInt32 = (i) => { + offset = buffer.writeUInt32LE(i, offset) + } + + const writeInt32 = (i) => { + offset = buffer.writeInt32LE(i, offset) + } + + const writeUInt64 = (i) => { + offset = bufferutils.writeUInt64LE(buffer, i, offset) + } + + const writeVarInt = (i) => { + varuint.encode(i, buffer, offset) + offset += varuint.encode.bytes + } + + const writeVarSlice = (slice) => { + writeVarInt(slice.length) + writeSlice(slice) + } + + const writeVector = (vector) => { + writeVarInt(vector.length) + vector.forEach(writeVarSlice) + } + + writeInt32(this.version) + + const hasWitnesses = __allowWitness && this.hasWitnesses() - this.ins.forEach(function (txIn) { + if (hasWitnesses) { + writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) + writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) + } + + writeVarInt(this.ins.length) + + this.ins.forEach((txIn) => { writeSlice(txIn.hash) writeUInt32(txIn.index) + writeVarSlice(txIn.script) + writeUInt32(txIn.sequence) }) - hashPrevouts = bcrypto.hash256(tbuffer) - } - - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && - (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - tbuffer = Buffer.allocUnsafe(4 * this.ins.length) - toffset = 0 + writeVarInt(this.outs.length) + this.outs.forEach((txOut) => { + if (!txOut.valueBuffer) { + writeUInt64(txOut.value) + } else { + writeSlice(txOut.valueBuffer) + } - this.ins.forEach(function (txIn) { - writeUInt32(txIn.sequence) + writeVarSlice(txOut.script) }) - hashSequence = bcrypto.hash256(tbuffer) + if (hasWitnesses) { + this.ins.forEach((input) => { + writeVector(input.witness) + }) + } + + writeUInt32(this.locktime) + + // avoid slicing unless necessary + if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) + return buffer } - if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - const txOutsSize = this.outs.reduce(function (sum, output) { - return sum + 8 + varSliceSize(output.script) - }, 0) + toHex () { + return this.toBuffer().toString('hex') + } - tbuffer = Buffer.allocUnsafe(txOutsSize) - toffset = 0 + setInputScript (index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments) - this.outs.forEach(function (out) { - writeUInt64(out.value) - writeVarSlice(out.script) - }) + this.ins[index].script = scriptSig + } - hashOutputs = bcrypto.hash256(tbuffer) - } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && inIndex < this.outs.length) { - const output = this.outs[inIndex] + setWitness (index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments) - tbuffer = Buffer.allocUnsafe(8 + varSliceSize(output.script)) - toffset = 0 - writeUInt64(output.value) - writeVarSlice(output.script) - - hashOutputs = bcrypto.hash256(tbuffer) - } - - tbuffer = Buffer.allocUnsafe(156 + varSliceSize(prevOutScript)) - toffset = 0 - - const input = this.ins[inIndex] - writeUInt32(this.version) - writeSlice(hashPrevouts) - writeSlice(hashSequence) - writeSlice(input.hash) - writeUInt32(input.index) - writeVarSlice(prevOutScript) - writeUInt64(value) - writeUInt32(input.sequence) - writeSlice(hashOutputs) - writeUInt32(this.locktime) - writeUInt32(hashType) - return bcrypto.hash256(tbuffer) + this.ins[index].witness = witness + } } -Transaction.prototype.getHash = function () { - return bcrypto.hash256(this.__toBuffer(undefined, undefined, false)) -} +Transaction.fromBuffer = (buffer, __noStrict) => { + let offset = 0 -Transaction.prototype.getId = function () { - // transaction hash's are displayed in reverse order - return this.getHash().reverse().toString('hex') -} + const readSlice = (n) => { + offset += n + return buffer.slice(offset - n, offset) + } -Transaction.prototype.toBuffer = function (buffer, initialOffset) { - return this.__toBuffer(buffer, initialOffset, true) -} + const readUInt32 = () => { + const i = buffer.readUInt32LE(offset) + offset += 4 + return i + } -Transaction.prototype.__toBuffer = function (buffer, initialOffset, __allowWitness) { - if (!buffer) buffer = Buffer.allocUnsafe(this.__byteLength(__allowWitness)) + const readInt32 = () => { + const i = buffer.readInt32LE(offset) + offset += 4 + return i + } - let offset = initialOffset || 0 - function writeSlice (slice) { offset += slice.copy(buffer, offset) } - function writeUInt8 (i) { offset = buffer.writeUInt8(i, offset) } - function writeUInt32 (i) { offset = buffer.writeUInt32LE(i, offset) } - function writeInt32 (i) { offset = buffer.writeInt32LE(i, offset) } - function writeUInt64 (i) { offset = bufferutils.writeUInt64LE(buffer, i, offset) } - function writeVarInt (i) { - varuint.encode(i, buffer, offset) - offset += varuint.encode.bytes + const readUInt64 = () => { + const i = bufferutils.readUInt64LE(buffer, offset) + offset += 8 + return i } - function writeVarSlice (slice) { writeVarInt(slice.length); writeSlice(slice) } - function writeVector (vector) { writeVarInt(vector.length); vector.forEach(writeVarSlice) } - writeInt32(this.version) + const readVarInt = () => { + const vi = varuint.decode(buffer, offset) + offset += varuint.decode.bytes + return vi + } - const hasWitnesses = __allowWitness && this.hasWitnesses() + const readVarSlice = () => { + return readSlice(readVarInt()) + } - if (hasWitnesses) { - writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER) - writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG) + const readVector = () => { + const count = readVarInt() + const vector = [] + for (var i = 0; i < count; i++) vector.push(readVarSlice()) + return vector } - writeVarInt(this.ins.length) + const tx = new Transaction() + tx.version = readInt32() - this.ins.forEach(function (txIn) { - writeSlice(txIn.hash) - writeUInt32(txIn.index) - writeVarSlice(txIn.script) - writeUInt32(txIn.sequence) - }) + const marker = buffer.readUInt8(offset) + const flag = buffer.readUInt8(offset + 1) - writeVarInt(this.outs.length) - this.outs.forEach(function (txOut) { - if (!txOut.valueBuffer) { - writeUInt64(txOut.value) - } else { - writeSlice(txOut.valueBuffer) - } + let hasWitnesses = false + if (marker === Transaction.ADVANCED_TRANSACTION_MARKER && + flag === Transaction.ADVANCED_TRANSACTION_FLAG) { + offset += 2 + hasWitnesses = true + } - writeVarSlice(txOut.script) - }) + const vinLen = readVarInt() + for (var i = 0; i < vinLen; ++i) { + tx.ins.push({ + hash: readSlice(32), + index: readUInt32(), + script: readVarSlice(), + sequence: readUInt32(), + witness: EMPTY_WITNESS + }) + } - if (hasWitnesses) { - this.ins.forEach(function (input) { - writeVector(input.witness) + const voutLen = readVarInt() + for (i = 0; i < voutLen; ++i) { + tx.outs.push({ + value: readUInt64(), + script: readVarSlice() }) } - writeUInt32(this.locktime) + if (hasWitnesses) { + for (i = 0; i < vinLen; ++i) { + tx.ins[i].witness = readVector() + } - // avoid slicing unless necessary - if (initialOffset !== undefined) return buffer.slice(initialOffset, offset) - return buffer -} + // was this pointless? + if (!tx.hasWitnesses()) throw new Error('Transaction has superfluous witness data') + } -Transaction.prototype.toHex = function () { - return this.toBuffer().toString('hex') -} + tx.locktime = readUInt32() -Transaction.prototype.setInputScript = function (index, scriptSig) { - typeforce(types.tuple(types.Number, types.Buffer), arguments) + if (__noStrict) return tx + if (offset !== buffer.length) throw new Error('Transaction has unexpected data') - this.ins[index].script = scriptSig + return tx } -Transaction.prototype.setWitness = function (index, witness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments) +Transaction.fromHex = (hex) => { + return Transaction.fromBuffer(Buffer.from(hex, 'hex')) +} - this.ins[index].witness = witness +Transaction.isCoinbaseHash = (buffer) => { + typeforce(types.Hash256bit, buffer) + for (var i = 0; i < 32; ++i) { + if (buffer[i] !== 0) return false + } + return true } module.exports = Transaction