diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index f9eaa615..7d776579 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -846,7 +846,6 @@ Parser.parseBlockHeaders = function parseBlockHeaders(p) { Parser.parseBlock = function parseBlock(p) { var txs = []; - var witnessSize = 0; var version, prevBlock, merkleRoot, ts, bits, nonce; var i, totalTX, tx; @@ -863,10 +862,11 @@ Parser.parseBlock = function parseBlock(p) { for (i = 0; i < totalTX; i++) { tx = Parser.parseTX(p); - witnessSize += tx._witnessSize; txs.push(tx); } + p.end(); + return { version: version, prevBlock: prevBlock, @@ -874,9 +874,7 @@ Parser.parseBlock = function parseBlock(p) { ts: ts, bits: bits, nonce: nonce, - txs: txs, - _witnessSize: witnessSize, - _size: p.end() + txs: txs }; }; @@ -1105,7 +1103,6 @@ Parser.parseTX = function parseTX(p) { var inCount, inputs; var outCount, outputs; var version, locktime, i; - var raw; if (Parser.isWitnessTX(p)) return Parser.parseWitnessTX(p); @@ -1129,18 +1126,14 @@ Parser.parseTX = function parseTX(p) { locktime = p.readU32(); - raw = p.endData(); + p.end(); return { version: version, flag: 1, inputs: inputs, outputs: outputs, - locktime: locktime, - _witnessSize: 0, - _raw: raw, - _size: raw.length - // _size: p.end() + locktime: locktime }; }; @@ -1175,8 +1168,6 @@ Parser.parseWitnessTX = function parseWitnessTX(p) { var outCount, outputs; var marker, flag; var version, locktime, i; - var witnessSize = 0; - var raw; p = new BufferReader(p); p.start(); @@ -1207,23 +1198,18 @@ Parser.parseWitnessTX = function parseWitnessTX(p) { for (i = 0; i < inCount; i++) { witness = Parser.parseWitness(p); inputs[i].witness = witness; - witnessSize += witness._size; } locktime = p.readU32(); - raw = p.endData(); + p.end(); return { version: version, flag: flag, inputs: inputs, outputs: outputs, - locktime: locktime, - _raw: raw, - _size: raw.length, - // _size: p.end(), - _witnessSize: witnessSize + 2 + locktime: locktime }; }; diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 37469d50..24d7d465 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -2187,10 +2187,12 @@ Script.createWitnessProgram = function createWitnessProgram(version, data) { */ Script.createCommitment = function createCommitment(hash) { - assert(hash.length === 32); + var p = new BufferWriter(); + p.writeU32BE(0xaa21a9ed); + p.writeHash(hash); return new Script([ opcodes.OP_RETURN, - Buffer.concat([new Buffer([0xaa, 0x21, 0xa9, 0xed]), hash]) + p.render() ]); }; @@ -2203,8 +2205,11 @@ Script.prototype.getRedeem = function getRedeem() { if (this.mutable) return Script.getRedeem(this.code); - if (!this.redeem) + if (!this.redeem) { + if (!this.isPushOnly()) + return; this.redeem = Script.getRedeem(this.code); + } return this.redeem; }; @@ -2218,6 +2223,15 @@ Script.prototype.getRedeem = function getRedeem() { Script.getRedeem = function getRedeem(code) { var redeem = code[code.length - 1]; + if (typeof redeem === 'number') { + if (redeem > opcodes.OP_16) + return; + return new Script([redeem]); + } + + if (Script.isBadPush(redeem)) + return; + if (!Buffer.isBuffer(redeem)) return; @@ -3423,6 +3437,9 @@ Script.prototype.getSigops = function getSigops(accurate) { var lastOp = -1; var i, op; + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + for (i = 0; i < this.code.length; i++) { op = this.code[i]; @@ -3447,6 +3464,100 @@ Script.prototype.getSigops = function getSigops(accurate) { return total; }; +/** + * Count the sigops in the script, taking into account redeem scripts. + * @param {Script} input - Input script, needed for access to redeem script. + * @returns {Number} sigop count + */ + +Script.prototype.getScripthashSigops = function getScripthashSigops(input) { + var i, op, redeem; + + if (!this.isScripthash()) + return this.getSigops(true); + + for (i = 0; i < input.code.length; i++) { + op = input.code[i]; + if (Buffer.isBuffer(op)) + continue; + if (Script.isBadPush(op)) + return 0; + if (op > opcodes.OP_16) + return 0; + } + + if (typeof op === 'number') + return 0; + + redeem = new Script(op); + + return redeem.getSigops(true); +}; + +/** + * Count the sigops for a program. + * @param {Program} program + * @param {Witness} witness + * @param {VerifyFlags} flags + * @returns {Number} sigop count + */ + +Script.witnessSigops = function witnessSigops(program, witness, flags) { + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + if (program.version === 0) { + if (program.data.length === 20) + return 1; + + if (program.data.length === 32 && witness.items.length > 0) { + redeem = witness.getRedeem(); + return redeem.getSigops(true); + } + } + + return 0; +}; + +/** + * Count the sigops in a script, taking into account witness programs. + * @param {Script} input + * @param {Script} output + * @param {Witness} witness + * @param {VerifyFlags} flags + * @returns {Number} sigop count + */ + +Script.getWitnessSigops = function getWitnessSigops(input, output, witness, flags) { + var redeem; + + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + if ((flags & constants.flags.VERIFY_WITNESS) === 0) + return 0; + + assert((flags & constants.flags.VERIFY_P2SH) !== 0); + + if (output.isWitnessProgram()) + return Script.witnessSigops(output.getWitnessProgram(), witness, flags); + + // This is a unique situation in terms of consensus + // rules. We can just grab the redeem script without + // "parsing" (i.e. checking for pushdata parse errors) + // the script. This is because isPushOnly is called + // which checks for parse errors and will return + // false if one is found. Even the bitcoind code + // does not check the return value of GetOp. + if (output.isScripthash() && input.isPushOnly()) { + redeem = input.getRedeem(); + if (redeem.isWitnessProgram()) + return Script.witnessSigops(redeem.getWitnessProgram(), witness, flags); + } + + return 0; +}; + /** * Calculate the number of expected "arguments" (pushdata * ops in the input script) for an output script. Used for @@ -3728,7 +3839,7 @@ Script.verify = function verify(input, witness, output, tx, i, flags) { // If we had a witness but no witness program, fail. if (flags & constants.flags.VERIFY_WITNESS) { assert((flags & constants.flags.VERIFY_P2SH) !== 0); - if (!hadWitness && witness.length > 0) + if (!hadWitness && witness.items.length > 0) throw new ScriptError('WITNESS_UNEXPECTED'); } @@ -4086,10 +4197,7 @@ Script.encode = function encode(code, writer) { p.writeU8(opcodes.OP_0); } else if (op.length <= 0x4b) { if (op.length === 1) { - if (op[0] === 0) { - p.writeU8(opcodes.OP_0); - continue; - } else if (op[0] >= 1 && op[0] <= 16) { + if (op[0] >= 1 && op[0] <= 16) { p.writeU8(op[0] + 0x50); continue; } else if (op[0] === 0x81) { diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index e118aac2..922618a7 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -877,83 +877,87 @@ TX.prototype.isFinal = function isFinal(height, ts) { return true; }; -TX.prototype._getSigops = function _getSigops(scriptHash, accurate) { +/** + * Calculate legacy (inaccurate) sigop count. + * @returns {Number} sigop count + */ + +TX.prototype.getLegacySigops = function getLegacySigops() { var total = 0; - var i, input, output, prev; + var i; - for (i = 0; i < this.inputs.length; i++) { - input = this.inputs[i]; + for (i = 0; i < tx.inputs.length; i++) + total += tx.inputs[i].script.getSigops(false); - total += input.script.getSigops(accurate); + for (i = 0; i < tx.outputs.length; i++) + total += tx.outputs[i].script.getSigops(false); - if (!input.coin) - continue; + return total; +}; - prev = input.coin.script; +/** + * Calculate accurate sigop count, taking into account redeem scripts. + * @returns {Number} sigop count + */ - if (scriptHash && !this.isCoinbase()) { - if (!prev.isScripthash()) - continue; +TX.prototype.getScripthashSigops = function getScripthashSigops() { + var total = 0; + var i, input; - if (!input.script.isPushOnly()) - continue; + if (this.isCoinbase()) + return 0; - prev = input.script.getRedeem(); - - if (!prev) - continue; - - total += prev.getSigops(true); - } - } - - for (i = 0; i < this.outputs.length; i++) { - output = this.outputs[i]; - total += output.script.getSigops(accurate); + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + if (input.coin.script.isScripthash()) + total += input.coin.script.getSigops(input.script); } return total; }; /** - * Calculate virtual sigop count. First calculates - * the traditional amount of sigops as cost and takes - * into account sigops for witness programs. - * @param {Boolean?} scriptHash - Whether to count redeem script sigops. - * @param {Boolean?} accurate - Whether to count CHECKMULTISIG(VERIFY) - * ops accurately. - * @returns {Number} sigop count + * Calculate sigops cost, taking into account witness programs. + * @param {VerifyFlags?} flags + * @returns {Number} sigop cost */ -TX.prototype.getSigops = function getSigops(scriptHash, accurate) { - var cost = this._getSigops(scriptHash, accurate) * 4; - var i, input, output, prev; +TX.prototype.getSigopsCost = function getSigopsCost(flags) { + var cost = this.getLegacySigops() * 4; + var input, i; + + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + if (this.isCoinbase()) + return cost; + + if (flags & constants.flags.VERIFY_P2SH) + cost += this.getScripthashSigops() * 4; for (i = 0; i < this.inputs.length; i++) { input = this.inputs[i]; - - if (!input.coin) - continue; - - prev = input.coin.script; - - if (prev.isScripthash()) - prev = input.script.getRedeem(); - - if (prev && prev.isWitnessScripthash()) { - prev = input.witness.getRedeem(); - if (prev) - cost += prev.getSigops(true); - } + cost += Script.getWitnessSigops( + input.script, + input.coin.script, + input.witness, + flags); } - for (i = 0; i < this.outputs.length; i++) { - output = this.outputs[i]; - if (output.script.isWitnessPubkeyhash()) - cost += 1; - } + return cost; +}; - return (cost + 3) / 4 | 0; +/** + * Calculate virtual sigop count. + * @param {VerifyFlags?} flags + * @returns {Number} sigop count + */ + +TX.prototype.getSigops = function getSigops(flags) { + if (flags == null) + flags = constants.flags.STANDARD_VERIFY_FLAGS; + + return (this.getSigopsCost(flags) + 3) / 4 | 0; }; /**