From 00e7fcc1ad7b811a004d70f2b73e2cd1460b754c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 Mar 2016 14:32:59 -0700 Subject: [PATCH] script parsing. --- lib/bcoin/coin.js | 4 +- lib/bcoin/input.js | 12 ++--- lib/bcoin/mtx.js | 8 +-- lib/bcoin/output.js | 4 +- lib/bcoin/protocol/framer.js | 82 ++++++++++++++++++++++++++---- lib/bcoin/protocol/parser.js | 35 +++++++++---- lib/bcoin/script.js | 98 +++++++++++++----------------------- lib/bcoin/tx.js | 48 +++++++++++++++++- 8 files changed, 195 insertions(+), 96 deletions(-) diff --git a/lib/bcoin/coin.js b/lib/bcoin/coin.js index dd93dabc..cde997ff 100644 --- a/lib/bcoin/coin.js +++ b/lib/bcoin/coin.js @@ -42,7 +42,7 @@ function Coin(tx, index) { this.version = options.version; this.height = options.height; this.value = options.value; - this.script = options.script; + this.script = bcoin.script(options.script); this.coinbase = options.coinbase; this.hash = options.hash; this.index = options.index; @@ -117,7 +117,7 @@ Coin.prototype.toJSON = function toJSON() { version: this.version, height: this.height, value: utils.btc(this.value), - script: utils.toHex(this.script.encode()), + script: utils.toHex(bcoin.protocol.framer(this.script)), coinbase: this.coinbase, hash: this.hash ? utils.revHex(this.hash) : null, index: this.index diff --git a/lib/bcoin/input.js b/lib/bcoin/input.js index 37fab8e7..2ab08238 100644 --- a/lib/bcoin/input.js +++ b/lib/bcoin/input.js @@ -21,9 +21,9 @@ function Input(options, tx) { assert(typeof options.script !== 'string'); this.prevout = options.prevout; - this.script = options.script || new bcoin.script([]); + this.script = bcoin.script(options.script); this.sequence = options.sequence == null ? 0xffffffff : options.sequence; - this.witness = options.witness || new bcoin.script.witness([]); + this.witness = bcoin.script.witness(options.witness); this._mutable = !tx || (tx instanceof bcoin.mtx); if (options.coin) @@ -205,8 +205,8 @@ Input.prototype.toJSON = function toJSON() { index: this.prevout.index }, coin: this.coin ? this.coin.toJSON() : null, - script: utils.toHex(this.script.encode()), - witness: utils.toHex(this.witness.encode()), + script: utils.toHex(bcoin.protocol.framer.script(this.script)), + witness: utils.toHex(bcoin.protocol.framer.witness(this.witness)), sequence: this.sequence }; }; @@ -218,8 +218,8 @@ Input._fromJSON = function _fromJSON(json) { index: json.prevout.index }, coin: json.coin ? bcoin.coin._fromJSON(json.coin) : null, - script: new bcoin.script(new Buffer(json.script, 'hex')), - witness: new bcoin.script.witness(new Buffer(json.witness, 'hex')), + script: bcoin.protocol.parser.parseScript(new Buffer(json.script, 'hex')), + witness: bcoin.protocol.parser.parseWitness(new Buffer(json.witness, 'hex')), sequence: json.sequence }; }; diff --git a/lib/bcoin/mtx.js b/lib/bcoin/mtx.js index 396000fb..88dddb5e 100644 --- a/lib/bcoin/mtx.js +++ b/lib/bcoin/mtx.js @@ -140,10 +140,10 @@ MTX.prototype.addInput = function addInput(options, index) { input = bcoin.input(options, this); - if (options.script) + if (options.script instanceof bcoin.script) input.script = options.script.clone(); - if (options.witness) + if (options.witness instanceof bcoin.script.witness) input.witness = options.witness.clone(); this.inputs.push(input); @@ -666,8 +666,10 @@ MTX.prototype.scriptOutput = function scriptOutput(index, options) { output = this.outputs[index]; assert(output); - if (options.script) + if (options.script instanceof bcoin.script) output.script = options.script.clone(); + else if (options.script) + output.script = bcoin.script(options.script); else output.script = Script.createOutputScript(options); }; diff --git a/lib/bcoin/output.js b/lib/bcoin/output.js index b174f1a4..de62379e 100644 --- a/lib/bcoin/output.js +++ b/lib/bcoin/output.js @@ -29,7 +29,7 @@ function Output(options, tx) { } this.value = utils.satoshi(value || new bn(0)); - this.script = options.script || new bcoin.script([]); + this.script = bcoin.script(options.script); this._mutable = !tx || (tx instanceof bcoin.mtx); // For safety: do not allow usage of @@ -114,7 +114,7 @@ Output.prototype.inspect = function inspect() { Output.prototype.toJSON = function toJSON() { return { value: utils.btc(this.value), - script: utils.toHex(this.script.encode()) + script: utils.toHex(bcoin.protocol.framer.script(this.script)) }; }; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 1ff08a4a..c7d1ea86 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -335,7 +335,7 @@ Framer.utxo = function _utxo(coin, writer) { p.writeU32(coin.version); p.writeU32(height); p.write64(coin.value); - p.writeVarBytes(coin.script.encode()); + Framer.script(coin.script, p); if (!writer) p = p.render(); @@ -355,7 +355,7 @@ Framer.coin = function _coin(coin, extended, writer) { p.writeU32(coin.version); p.writeU32(height); p.write64(coin.value); - p.writeVarBytes(coin.script.encode()); + Framer.script(coin.script, p); p.writeU8(coin.coinbase ? 1 : 0); if (extended) { @@ -398,7 +398,7 @@ Framer.input = function _input(input, writer) { p.writeHash(input.prevout.hash); p.writeU32(input.prevout.index); - p.writeVarBytes(input.script.encode()); + Framer.script(input.script, p); p.writeU32(input.sequence); if (!writer) @@ -413,7 +413,7 @@ Framer.output = function _output(output, writer) { assert(output.value.byteLength() <= 8); p.write64(output.value); - p.writeVarBytes(output.script.encode()); + Framer.script(output.script, p); if (!writer) p = p.render(); @@ -456,12 +456,28 @@ Framer.witnessTX = function _witnessTX(tx, writer) { return p; }; -Framer.witnessBlockSize = function witnessBlockSize(block) { - return Framer.witnessBlock(block, new BufferWriter())._witnessSize; -}; +// Scripts require extra magic since they're +// so goddamn bizarre. Normally in an "encoded" +// script we don't include the varint size +// because scripthashes don't include them. This +// is why script.encode/decode is separate +// from the framer and parser. +Framer.script = function _script(script, writer) { + var data; -Framer.witnessTXSize = function witnessTXSize(tx) { - return Framer.witnessTXSize(tx, new BufferWriter())._witnessSize; + p = new BufferWriter(writer); + + if (script.encode) + data = script.encode(); + else + data = script.raw || bcoin.script.encode(script.code); + + p.writeVarBytes(data); + + if (!writer) + p = p.render(); + + return p; }; Framer.witness = function _witness(witness, writer) { @@ -672,6 +688,54 @@ Framer.alert = function alert(data, writer) { return p; }; +// Witness size +Framer.blockSizes = function blockSizes(block) { + var sizes = Framer.witnessBlock(block, new BufferWriter()); + return { + size: sizes._size, + witnessSize: sizes._witnessSize + }; +}; + +Framer.txSizes = function txSizes(tx) { + var sizes = Framer.renderTX(tx, true, new BufferWriter()); + return { + size: sizes._size, + witnessSize: sizes._witnessSize + }; +}; + +// Size with witness (if present) +Framer.blockRealSize = function blockRealSize(block) { + return Framer.blockSizes(block).size; +}; + +Framer.txRealSize = function txRealSize(tx) { + return Framer.txSizes(tx).size; +}; + +// Size without witness +Framer.blockSize = function blockSize(block) { + return Framer.block(block, new BufferWriter())._size; +}; + +Framer.txSize = function txSize(tx) { + return Framer.renderTX(tx, false, new BufferWriter())._size; +}; + +// Virtual size +Framer.blockVsize = function blockVsize(block) { + var sizes = this.blockSizes(block); + var base = sizes.size - sizes.witnessSize; + return (base * 4 + sizes.witnessSize + 3) / 4 | 0; +}; + +Framer.txVsize = function txVsize(tx) { + var sizes = this.txSizes(tx); + var base = sizes.size - sizes.witnessSize; + return (base * 4 + sizes.witnessSize + 3) / 4 | 0; +}; + /** * Expose */ diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index cdb198c9..d0b6e880 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -405,7 +405,7 @@ Parser.parseInput = function parseInput(p) { hash = p.readHash('hex'); index = p.readU32(); - script = new bcoin.script(p.readVarBytes()); + script = Parser.parseScript(p); sequence = p.readU32(); return { @@ -413,6 +413,7 @@ Parser.parseInput = function parseInput(p) { hash: hash, index: index }, + coin: null, script: script, sequence: sequence, _size: p.end() @@ -426,7 +427,7 @@ Parser.parseOutput = function parseOutput(p) { p.start(); value = p.read64(); - script = new bcoin.script(p.readVarBytes()); + script = Parser.parseScript(p); return { value: value, @@ -444,7 +445,7 @@ Parser.parseUTXO = function parseUTXO(p) { version = p.readU32(); height = p.readU32(); value = p.read64(); - script = new bcoin.script(p.readVarBytes()); + script = Parser.parseScript(p); if (height === 0x7fffffff) height = -1; @@ -467,7 +468,7 @@ Parser.parseCoin = function parseCoin(p, extended) { version = p.readU32(); height = p.readU32(); value = p.read64(); - script = new bcoin.script(p.readVarBytes()); + script = Parser.parseScript(p); coinbase = p.readU8() === 1; if (extended) { @@ -510,7 +511,7 @@ Parser.parseTX = function parseTX(p) { tx = Parser.parseInput(p); txIn[i] = tx; - txIn[i].witness = new bcoin.script.witness([]); + txIn[i].witness = { items: [] }; } outCount = p.readVarint(); @@ -611,9 +612,23 @@ Parser.parseWitnessTX = function parseWitnessTX(p) { }; }; +Parser.parseScript = function parseScript(p) { + var data; + + p = new BufferReader(p); + p.start(); + data = p.readVarBytes(); + + return { + raw: data, + code: bcoin.script.decode(data), + _size: p.end() + }; +}; + Parser.parseWitness = function parseWitness(p) { var items = []; - var witness, chunkCount, i; + var chunkCount, i; p = new BufferReader(p); p.start(); @@ -623,10 +638,10 @@ Parser.parseWitness = function parseWitness(p) { for (i = 0; i < chunkCount; i++) items.push(p.readVarBytes()); - witness = new bcoin.script.witness(items); - witness._size = p.end(); - - return witness; + return { + items: items, + _size: p.end() + }; }; Parser.parseReject = function parseReject(p) { diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 95edb9e9..949d5922 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -16,13 +16,19 @@ var STACK_FALSE = new Buffer([]); var STACK_NEGATE = new Buffer([0xff]); function Witness(items) { + if (items instanceof Witness) + return items; + if (!(this instanceof Witness)) return new Witness(items); - if (Buffer.isBuffer(items)) - this.items = Witness.decode(items); - else if (items) - this.items = items || []; + if (!items) + items = []; + + if (items.items) + items = items.items; + + this.items = items || []; this.redeem = null; } @@ -31,18 +37,6 @@ Witness.prototype.inspect = function inspect() { return Script.format(this.items); }; -Witness.prototype.encode = function encode() { - return bcoin.protocol.framer.witness(this); -}; - -Witness.encode = function encode(witness) { - return bcoin.protocol.framer.witness(witness); -}; - -Witness.decode = function decode(buf) { - return bcoin.protocol.parser.parseWitness(buf).items; -}; - Witness.prototype.clone = function clone() { return new Witness(this.items.slice()); }; @@ -91,19 +85,11 @@ Witness.fromString = function fromString(items) { items = items.trim().split(/\s+/); - // Remove OP_ prefixes and lowercase for (i = 0; i < items.length; i++) { op = items[i].toLowerCase(); + if (op.indexOf('op_') === 0) op = op.slice(3); - items[i] = op; - } - - // Convert OP_FALSE to 0, convert OP_1-OP_16 - // to number literals, convert -1 to OP_1NEGATE. - // Convert hex strings to arrays. - for (i = 0; i < items.length; i++) { - op = items[i]; if (op === '-1' || op === '1negate') { op = STACK_NEGATE; @@ -115,13 +101,12 @@ Witness.fromString = function fromString(items) { op = new Buffer([+op]); } else { symbol = 'OP_' + op.toUpperCase(); - if (opcodes[symbol] == null) { if (op[0] === '[') op = op.slice(1, -1); if (op.indexOf('0x') === 0) op = op.substring(2); - assert(utils.isHex(op), 'Non hex-string.'); + assert(utils.isHex(op), 'Non-stack item in witness string.'); op = new Buffer(op, 'hex'); } else { assert(false, 'Non-stack item in witness string.'); @@ -445,6 +430,9 @@ Stack.isStack = function isStack(obj) { */ function Script(code) { + if (code instanceof Script) + return code; + if (!(this instanceof Script)) return new Script(code); @@ -454,9 +442,16 @@ function Script(code) { } else { if (!code) code = []; - assert(Array.isArray(code)); - this.raw = null; - this.code = code; + if (code.code) { + this.raw = code.raw || null; + this.code = code.code; + if (!this.code) + this.code = Script.decode(this.raw); + } else { + assert(Array.isArray(code)); + this.raw = null; + this.code = code; + } } this.redeem = null; @@ -2331,37 +2326,22 @@ Script.fromString = function fromString(code) { code = code.trim().split(/\s+/); - // Remove OP_ prefixes and lowercase - for (i = 0; i < code.length; i++) { - op = code[i].toLowerCase(); - if (op.indexOf('op_') === 0) - op = op.slice(3); - code[i] = op; - } - - // Convert OP_FALSE to 0, convert OP_1-OP_16 - // to number literals, convert -1 to OP_1NEGATE. - // Convert hex strings to arrays. for (i = 0; i < code.length; i++) { op = code[i]; if (op === '-1') op = '1negate'; - else if (op === '0' || op === 'false') - op = '0'; - else if (op === 'true') - op = '1'; - else if (+op >= 1 && +op <= 16) - op = +op + ''; - symbol = 'OP_' + op.toUpperCase(); + symbol = (op + '').toUpperCase(); + if (symbol.indexOf('OP_') !== 0) + symbol = 'OP_' + op; if (opcodes[symbol] == null) { if (op[0] === '[') op = op.slice(1, -1); if (op.indexOf('0x') === 0) op = op.substring(2); - assert(utils.isHex(op), 'Non hex-string.'); + assert(utils.isHex(op), 'Unknown opcode.'); op = new Buffer(op, 'hex'); code[i] = op; continue; @@ -2404,7 +2384,7 @@ Script.toSmall = function toSmall(op) { Script.fromSymbolic = function fromSymbolic(items) { var code = new Array(items.length); - var i, op, symbol; + var i, op; for (i = 0; i < items.length; i++) { op = items[i]; @@ -2414,24 +2394,16 @@ Script.fromSymbolic = function fromSymbolic(items) { continue; } - op = (op + '').toLowerCase(); - if (op.indexOf('op_') === 0) - op = op.slice(3); - if (+op === -1) op = '1negate'; - else if (+op === 0 || op === 'false') - op = '0'; - else if (+op === 1 || op === 'true') - op = '1'; - else if (+op >= 1 && +op <= 16) - op = +op + ''; - symbol = 'OP_' + op.toUpperCase(); + op = (op + '').toUpperCase(); + if (op.indexOf('OP_') !== 0) + op = 'OP_' + op; - code[i] = opcodes[symbol]; + assert(opcodes[op] != null, 'Unknown opcode.'); - assert(code[i] != null, 'Unknown opcode.'); + code[i] = opcodes[op]; } return new Script(code); diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index e5b62028..19621fdb 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -66,6 +66,52 @@ function TX(data, block, index) { } } +TX.prototype.clone = function clone() { + var copy, i; + + copy = { + version: this.version, + inputs: [], + outputs: [], + locktime: this.locktime, + flag: this.flag, + ts: this.ts, + block: this.block, + index: this.index, + height: this.height + }; + + for (i = 0; i < this.inputs.length; i++) { + copy.inputs.push({ + prevout: { + hash: this.inputs[i].prevout.hash, + index: this.inputs[i].prevout.index + }, + coin: null, + script: { + code: this.inputs[i].script.code.slice(), + raw: this.inputs[i].script.raw + }, + witness: { + items: this.inputs[i].witness.items.slice() + }, + sequence: this.inputs[i].sequence + }); + } + + for (i = 0; i < this.outputs.length; i++) { + copy.outputs.push({ + value: this.outputs[i].value.clone(), + script: { + code: this.inputs[i].script.code.slice(), + raw: this.inputs[i].script.raw + } + }); + } + + return new TX(copy); +}; + TX.prototype.setBlock = function setBlock(block, index) { this.ts = block.ts; this.block = block.hash('hex'); @@ -252,7 +298,7 @@ TX.prototype.signatureHashV0 = function signatureHashV0(index, prev, type) { for (i = 0; i < copy.outputs.length; i++) { if (i !== index) { copy.outputs[i].script = new Script([]); - copy.outputs[i].value = new bn('ffffffffffffffff', 'hex'); + copy.outputs[i].value = utils.U64; } }