From a6b2c23a41c4a65eb4087df37d5c807c4742484d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 17 Aug 2017 11:00:58 -0700 Subject: [PATCH] script: start using scriptnum implementation. --- docs/Scripting.md | 14 +-- lib/bcoin-browser.js | 1 + lib/bcoin.js | 1 + lib/mining/template.js | 5 +- lib/script/common.js | 126 +++++-------------------- lib/script/index.js | 2 +- lib/script/opcode.js | 24 ++++- lib/script/script.js | 207 +++++++++++++++-------------------------- lib/script/stack.js | 127 +++++++++++++++++++++++++ lib/script/witness.js | 31 +++--- scripts/fuzz.js | 2 +- scripts/gen.js | 4 +- test/chain-test.js | 18 ++-- test/node-test.js | 10 +- test/script-test.js | 2 +- 15 files changed, 294 insertions(+), 280 deletions(-) diff --git a/docs/Scripting.md b/docs/Scripting.md index d6c7682aa..0e70ef613 100644 --- a/docs/Scripting.md +++ b/docs/Scripting.md @@ -3,13 +3,13 @@ Scripts are array-like objects with some helper functions. ``` js var bcoin = require('bcoin'); var assert = require('assert'); -var BN = bcoin.bn; +var ScriptNum = bcoin.scriptnum; var opcodes = bcoin.script.opcodes; var output = new bcoin.script(); output.push(opcodes.OP_DROP); output.push(opcodes.OP_ADD); -output.push(new BN(7)); +output.push(new ScriptNum(7)); output.push(opcodes.OP_NUMEQUAL); // Compile the script to its binary representation // (you must do this if you change something!). @@ -18,8 +18,8 @@ assert(output.getSmall(2) === 7); // compiled as OP_7 var input = new bcoin.script(); input.set(0, 'hello world'); // add some metadata -input.push(new BN(2)); -input.push(new BN(5)); +input.push(new ScriptNum(2)); +input.push(new ScriptNum(5)); input.push(input.shift()); assert(input.getString(2) === 'hello world'); input.compile(); @@ -40,10 +40,10 @@ Stack object (an array-like object containing Buffers). ``` js var witness = new bcoin.witness(); -witness.push(new BN(2)); -witness.push(new BN(5)); +witness.push(new ScriptNum(2)); +witness.push(new ScriptNum(5)); witness.push('hello world'); var stack = witness.toStack(); output.execute(stack); -``` \ No newline at end of file +``` diff --git a/lib/bcoin-browser.js b/lib/bcoin-browser.js index 17374c9b9..1bd05f0f8 100644 --- a/lib/bcoin-browser.js +++ b/lib/bcoin-browser.js @@ -240,6 +240,7 @@ bcoin.txscript = require('./script'); bcoin.opcode = require('./script/opcode'); bcoin.program = require('./script/program'); bcoin.script = require('./script/script'); +bcoin.scriptnum = require('./script/scriptnum'); bcoin.sigcache = require('./script/sigcache'); bcoin.stack = require('./script/stack'); bcoin.witness = require('./script/witness'); diff --git a/lib/bcoin.js b/lib/bcoin.js index 6f1573f40..92dc153f5 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -277,6 +277,7 @@ bcoin.define('txscript', './script'); bcoin.define('opcode', './script/opcode'); bcoin.define('program', './script/program'); bcoin.define('script', './script/script'); +bcoin.define('scriptnum', './script/scriptnum'); bcoin.define('sigcache', './script/sigcache'); bcoin.define('stack', './script/stack'); bcoin.define('witness', './script/witness'); diff --git a/lib/mining/template.js b/lib/mining/template.js index 510cb3eeb..744abd749 100644 --- a/lib/mining/template.js +++ b/lib/mining/template.js @@ -11,7 +11,6 @@ const assert = require('assert'); const util = require('../utils/util'); const digest = require('../crypto/digest'); const merkle = require('../crypto/merkle'); -const BN = require('../crypto/bn'); const StaticWriter = require('../utils/staticwriter'); const Address = require('../primitives/address'); const TX = require('../primitives/tx'); @@ -23,6 +22,7 @@ const policy = require('../protocol/policy'); const encoding = require('../utils/encoding'); const CoinView = require('../coins/coinview'); const Script = require('../script/script'); +const ScriptNum = require('../script/scriptnum'); const common = require('./common'); const DUMMY = Buffer.alloc(0); @@ -241,7 +241,8 @@ BlockTemplate.prototype.createCoinbase = function createCoinbase(hash) { const input = new Input(); // Height (required in v2+ blocks) - input.script.push(new BN(this.height)); + const height = ScriptNum.fromNumber(this.height); + input.script.push(height); // Coinbase flags. input.script.push(encoding.ZERO_HASH160); diff --git a/lib/script/common.js b/lib/script/common.js index 33e305629..7fa84a4ad 100644 --- a/lib/script/common.js +++ b/lib/script/common.js @@ -12,7 +12,6 @@ */ const assert = require('assert'); -const BN = require('../crypto/bn'); const util = require('../utils/util'); const secp256k1 = require('../crypto/secp256k1'); @@ -532,6 +531,28 @@ exports.isSignatureEncoding = function isSignatureEncoding(sig) { return true; }; +/** + * Cast a big number or Buffer to a bool. + * @see CastToBool + * @param {Buffer} value + * @returns {Boolean} + */ + +exports.toBool = function toBool(value) { + assert(Buffer.isBuffer(value)); + + for (let i = 0; i < value.length; i++) { + if (value[i] !== 0) { + // Cannot be negative zero + if (i === value.length - 1 && value[i] === 0x80) + return false; + return true; + } + } + + return false; +}; + /** * Format script code into a human readable-string. * @param {Array} code @@ -692,109 +713,6 @@ exports.formatStackASM = function formatStackASM(items, decode) { return out.join(' '); }; -/** - * Create a CScriptNum. - * @param {Buffer} value - * @param {Boolean?} minimal - * @param {Number?} size - Max size in bytes. - * @returns {BN} - * @throws {ScriptError} - */ - -exports.num = function num(value, minimal, size) { - assert(Buffer.isBuffer(value)); - - if (size == null) - size = 4; - - if (value.length > size) - throw new exports.ScriptError('UNKNOWN_ERROR', 'Script number overflow.'); - - if (minimal && value.length > 0) { - // If the low bits on the last byte are unset, - // fail if the value's second to last byte does - // not have the high bit set. A number can't - // justify having the last byte's low bits unset - // unless they ran out of space for the sign bit - // in the second to last bit. We also fail on [0] - // to avoid negative zero (also avoids positive - // zero). - if (!(value[value.length - 1] & 0x7f)) { - if (value.length === 1 || !(value[value.length - 2] & 0x80)) { - throw new exports.ScriptError( - 'UNKNOWN_ERROR', - 'Non-minimally encoded Script number.'); - } - } - } - - if (value.length === 0) - return new BN(0); - - const result = new BN(value, 'le'); - - // If the input vector's most significant byte is - // 0x80, remove it from the result's msb and return - // a negative. - // Equivalent to: - // -(result & ~(0x80 << (8 * (value.length - 1)))) - if (value[value.length - 1] & 0x80) - result.setn((value.length * 8) - 1, 0).ineg(); - - return result; -}; - -/** - * Create a script array. Will convert Numbers and big - * numbers to a little-endian buffer while taking into - * account negative zero, minimaldata, etc. - * @example - * assert.deepEqual(Script.array(0), Buffer.alloc(0)); - * assert.deepEqual(Script.array(0xffee), Buffer.from('eeff00', 'hex')); - * assert.deepEqual(Script.array(new BN(0xffee)), Buffer.from('eeff00', 'hex')); - * assert.deepEqual(Script.array(new BN(0x1e).neg()), Buffer.from('9e', 'hex')); - * @param {Number|BN} value - * @returns {Buffer} - */ - -exports.array = function array(value) { - if (typeof value === 'number') { - assert(util.isInt(value)); - value = new BN(value); - } - - assert(BN.isBN(value)); - - if (value.cmpn(0) === 0) - return exports.STACK_FALSE; - - // If the most significant byte is >= 0x80 - // and the value is positive, push a new - // zero-byte to make the significant - // byte < 0x80 again. - - // If the most significant byte is >= 0x80 - // and the value is negative, push a new - // 0x80 byte that will be popped off when - // converting to an integral. - - // If the most significant byte is < 0x80 - // and the value is negative, add 0x80 to - // it, since it will be subtracted and - // interpreted as a negative when - // converting to an integral. - - const neg = value.cmpn(0) < 0; - const result = value.toArray('le'); - - if (result[result.length - 1] & 0x80) - result.push(neg ? 0x80 : 0); - else if (neg) - result[result.length - 1] |= 0x80; - - return Buffer.from(result); -}; - /** * An error thrown from the scripting system, * potentially pertaining to Script execution. diff --git a/lib/script/index.js b/lib/script/index.js index 4d83dc6e8..351b9d836 100644 --- a/lib/script/index.js +++ b/lib/script/index.js @@ -14,7 +14,7 @@ exports.common = require('./common'); exports.Opcode = require('./opcode'); exports.Program = require('./program'); exports.Script = require('./script'); -// exports.ScriptNum = require('./scriptnum'); +exports.ScriptNum = require('./scriptnum'); exports.sigcache = require('./sigcache'); exports.Stack = require('./stack'); exports.Witness = require('./witness'); diff --git a/lib/script/opcode.js b/lib/script/opcode.js index 1f7a1652e..dcf76b888 100644 --- a/lib/script/opcode.js +++ b/lib/script/opcode.js @@ -8,7 +8,7 @@ 'use strict'; const assert = require('assert'); -const BN = require('../crypto/bn'); +const ScriptNum = require('./scriptnum'); const util = require('../utils/util'); const common = require('./common'); const BufferReader = require('../utils/reader'); @@ -334,12 +334,23 @@ Opcode.fromPush = function fromPush(data) { /** * Instantiate an opcode from a Number. - * @param {Number|BN} num + * @param {Number|ScriptNum|BN} num * @returns {Opcode} */ Opcode.fromNumber = function fromNumber(num) { - return Opcode.fromData(common.array(num)); + return Opcode.fromData(ScriptNum.encode(num)); +}; + +/** + * Instantiate an opcode from a Number. + * @param {Boolean} value + * @returns {Opcode} + */ + +Opcode.fromBool = function fromBool(value) { + assert(typeof value === 'boolean'); + return Opcode.fromSmall(value ? 1 : 0); }; /** @@ -368,7 +379,7 @@ Opcode.fromString = function fromString(data, enc) { /** * Instantiate a pushdata opcode from anything. - * @param {String|Buffer|Number|BN|Opcode} data + * @param {String|Buffer|Number|ScriptNum|Opcode} data * @returns {Opcode} */ @@ -385,7 +396,10 @@ Opcode.from = function from(data) { if (typeof data === 'string') return Opcode.fromString(data, 'utf8'); - if (BN.isBN(data)) + if (typeof data === 'boolean') + return Opcode.fromBool(data); + + if (ScriptNum.isEncodable(data)) return Opcode.fromNumber(data); throw new Error('Bad data for opcode.'); diff --git a/lib/script/script.js b/lib/script/script.js index 833a7deb8..443ae915d 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -8,7 +8,6 @@ 'use strict'; const assert = require('assert'); -const BN = require('../crypto/bn'); const consensus = require('../protocol/consensus'); const policy = require('../protocol/policy'); const util = require('../utils/util'); @@ -20,6 +19,7 @@ const StaticWriter = require('../utils/staticwriter'); const Program = require('./program'); const Opcode = require('./opcode'); const Stack = require('./stack'); +const ScriptNum = require('./scriptnum'); const common = require('./common'); const encoding = require('../utils/encoding'); const secp256k1 = require('../crypto/secp256k1'); @@ -27,9 +27,7 @@ const Address = require('../primitives/address'); const opcodes = common.opcodes; const scriptTypes = common.types; const ScriptError = common.ScriptError; -const STACK_TRUE = common.STACK_TRUE; const STACK_FALSE = common.STACK_FALSE; -const STACK_NEGATE = common.STACK_NEGATE; /** * Represents a input or output script. @@ -521,17 +519,14 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers switch (op.value) { case opcodes.OP_0: { - stack.push(STACK_FALSE); + stack.pushInt(0); break; } case opcodes.OP_1NEGATE: { - stack.push(STACK_NEGATE); - break; - } - case opcodes.OP_1: { - stack.push(STACK_TRUE); + stack.pushInt(-1); break; } + case opcodes.OP_1: case opcodes.OP_2: case opcodes.OP_3: case opcodes.OP_4: @@ -547,7 +542,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers case opcodes.OP_14: case opcodes.OP_15: case opcodes.OP_16: { - stack.push(Buffer.from([op.value - 0x50])); + stack.pushInt(op.value - 0x50); break; } case opcodes.OP_NOP: { @@ -567,9 +562,9 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let locktime = Script.num(stack.top(-1), minimal, 5); + let locktime = stack.num(-1, minimal, 5); - if (locktime.cmpn(0) < 0) + if (locktime.isNeg()) throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); locktime = locktime.toNumber(); @@ -593,9 +588,9 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let locktime = Script.num(stack.top(-1), minimal, 5); + let locktime = stack.num(-1, minimal, 5); - if (locktime.cmpn(0) < 0) + if (locktime.isNeg()) throw new ScriptError('NEGATIVE_LOCKTIME', op, ip); locktime = locktime.toNumber(); @@ -625,17 +620,17 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 1) throw new ScriptError('UNBALANCED_CONDITIONAL', op, ip); - val = stack.top(-1); - if (version === 1 && (flags & Script.flags.VERIFY_MINIMALIF)) { - if (val.length > 1) + const item = stack.top(-1); + + if (item.length > 1) throw new ScriptError('MINIMALIF'); - if (val.length === 1 && val[0] !== 1) + if (item.length === 1 && item[0] !== 1) throw new ScriptError('MINIMALIF'); } - val = Script.bool(val); + val = stack.bool(-1); if (op.value === opcodes.OP_NOTIF) val = !val; @@ -676,7 +671,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - if (!Script.bool(stack.top(-1))) + if (!stack.bool(-1)) throw new ScriptError('VERIFY', op, ip); stack.pop(); @@ -767,14 +762,15 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length === 0) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const val = stack.top(-1); - - if (Script.bool(val)) + if (stack.bool(-1)) { + const val = stack.top(-1); stack.push(val); + } + break; } case opcodes.OP_DEPTH: { - stack.push(Script.array(stack.length)); + stack.pushInt(stack.length); break; } case opcodes.OP_DROP: { @@ -810,7 +806,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const num = Script.num(stack.top(-1), minimal).toNumber(); + const num = stack.int(-1, minimal); stack.pop(); if (num < 0 || num >= stack.length) @@ -850,7 +846,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 1) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - stack.push(Script.array(stack.top(-1).length)); + stack.pushInt(stack.top(-1).length); break; } case opcodes.OP_EQUAL: @@ -866,7 +862,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers stack.pop(); stack.pop(); - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pushBool(res); if (op.value === opcodes.OP_EQUALVERIFY) { if (!res) @@ -885,7 +881,8 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 1) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let num = Script.num(stack.top(-1), minimal); + let num = stack.num(-1, minimal); + let cmp; switch (op.value) { case opcodes.OP_1ADD: @@ -901,12 +898,12 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers num.iabs(); break; case opcodes.OP_NOT: - num = num.cmpn(0) === 0; - num = new BN(num ? 1 : 0); + cmp = num.isZero(); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_0NOTEQUAL: - num = num.cmpn(0) !== 0; - num = new BN(num ? 1 : 0); + cmp = !num.isZero(); + num = ScriptNum.fromBool(cmp); break; default: assert(false, 'Fatal script error.'); @@ -914,7 +911,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers } stack.pop(); - stack.push(Script.array(num)); + stack.pushNum(num); break; } @@ -934,9 +931,9 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 2) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const n1 = Script.num(stack.top(-2), minimal); - const n2 = Script.num(stack.top(-1), minimal); - let num; + const n1 = stack.num(-2, minimal); + const n2 = stack.num(-1, minimal); + let num, cmp; switch (op.value) { case opcodes.OP_ADD: @@ -946,46 +943,46 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers num = n1.isub(n2); break; case opcodes.OP_BOOLAND: - num = n1.cmpn(0) !== 0 && n2.cmpn(0) !== 0; - num = new BN(num ? 1 : 0); + cmp = n1.toBool() && n2.toBool(); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_BOOLOR: - num = n1.cmpn(0) !== 0 || n2.cmpn(0) !== 0; - num = new BN(num ? 1 : 0); + cmp = n1.toBool() || n2.toBool(); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_NUMEQUAL: - num = n1.cmp(n2) === 0; - num = new BN(num ? 1 : 0); + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_NUMEQUALVERIFY: - num = n1.cmp(n2) === 0; - num = new BN(num ? 1 : 0); + cmp = n1.eq(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_NUMNOTEQUAL: - num = n1.cmp(n2) !== 0; - num = new BN(num ? 1 : 0); + cmp = !n1.eq(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_LESSTHAN: - num = n1.cmp(n2) < 0; - num = new BN(num ? 1 : 0); + cmp = n1.lt(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_GREATERTHAN: - num = n1.cmp(n2) > 0; - num = new BN(num ? 1 : 0); + cmp = n1.gt(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_LESSTHANOREQUAL: - num = n1.cmp(n2) <= 0; - num = new BN(num ? 1 : 0); + cmp = n1.lte(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_GREATERTHANOREQUAL: - num = n1.cmp(n2) >= 0; - num = new BN(num ? 1 : 0); + cmp = n1.gte(n2); + num = ScriptNum.fromBool(cmp); break; case opcodes.OP_MIN: - num = n1.cmp(n2) < 0 ? n1 : n2; + num = ScriptNum.min(n1, n2); break; case opcodes.OP_MAX: - num = n1.cmp(n2) > 0 ? n1 : n2; + num = ScriptNum.max(n1, n2); break; default: assert(false, 'Fatal script error.'); @@ -994,10 +991,10 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers stack.pop(); stack.pop(); - stack.push(Script.array(num)); + stack.pushNum(num); if (op.value === opcodes.OP_NUMEQUALVERIFY) { - if (!Script.bool(stack.top(-1))) + if (!stack.bool(-1)) throw new ScriptError('NUMEQUALVERIFY', op, ip); stack.pop(); } @@ -1008,17 +1005,17 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < 3) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - const n1 = Script.num(stack.top(-3), minimal); - const n2 = Script.num(stack.top(-2), minimal); - const n3 = Script.num(stack.top(-1), minimal); + const n1 = stack.num(-3, minimal); + const n2 = stack.num(-2, minimal); + const n3 = stack.num(-1, minimal); - const val = n2.cmp(n1) <= 0 && n1.cmp(n3) < 0; + const val = n2.lte(n1) && n1.lt(n3); stack.pop(); stack.pop(); stack.pop(); - stack.push(val ? STACK_TRUE : STACK_FALSE); + stack.pushBool(val); break; } case opcodes.OP_RIPEMD160: { @@ -1095,7 +1092,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers stack.pop(); stack.pop(); - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pushBool(res); if (op.value === opcodes.OP_CHECKSIGVERIFY) { if (!res) @@ -1114,7 +1111,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < i) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let n = Script.num(stack.top(-i), minimal).toNumber(); + let n = stack.int(-i, minimal); let ikey2 = n + 2; if (!(n >= 0 && n <= consensus.MAX_MULTISIG_PUBKEYS)) @@ -1132,7 +1129,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers if (stack.length < i) throw new ScriptError('INVALID_STACK_OPERATION', op, ip); - let m = Script.num(stack.top(-i), minimal).toNumber(); + let m = stack.int(-i, minimal); if (!(m >= 0 && m <= n)) throw new ScriptError('SIG_COUNT', op, ip); @@ -1203,7 +1200,7 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers stack.pop(); - stack.push(res ? STACK_TRUE : STACK_FALSE); + stack.pushBool(res); if (op.value === opcodes.OP_CHECKMULTISIGVERIFY) { if (!res) @@ -1226,58 +1223,6 @@ Script.prototype.execute = function execute(stack, flags, tx, index, value, vers throw new ScriptError('UNBALANCED_CONDITIONAL'); }; -/** - * Cast a big number or Buffer to a bool. - * @see CastToBool - * @param {BN|Buffer} value - * @returns {Boolean} - */ - -Script.bool = function bool(value) { - assert(Buffer.isBuffer(value)); - - for (let i = 0; i < value.length; i++) { - if (value[i] !== 0) { - // Cannot be negative zero - if (i === value.length - 1 && value[i] === 0x80) - return false; - return true; - } - } - - return false; -}; - -/** - * Create a CScriptNum. - * @param {Buffer} value - * @param {Boolean?} minimal - * @param {Number?} size - Max size in bytes. - * @returns {BN} - * @throws {ScriptError} - */ - -Script.num = function num(value, minimal, size) { - return common.num(value, minimal, size); -}; - -/** - * Create a script array. Will convert Numbers and big - * numbers to a little-endian buffer while taking into - * account negative zero, minimaldata, etc. - * @example - * assert.deepEqual(Script.array(0), Buffer.alloc(0)); - * assert.deepEqual(Script.array(0xffee), Buffer.from('eeff00', 'hex')); - * assert.deepEqual(Script.array(new BN(0xffee)), Buffer.from('eeff00', 'hex')); - * assert.deepEqual(Script.array(new BN(0x1e).neg()), Buffer.from('9e', 'hex')); - * @param {Number|BN} value - * @returns {Buffer} - */ - -Script.array = function array(value) { - return common.array(value); -}; - /** * Remove all matched data elements from * a script's code (used to remove signatures @@ -2288,7 +2233,7 @@ Script.getCoinbaseHeight = function getCoinbaseHeight(raw) { // Deserialize the height. let height; try { - height = Script.num(data, true, 6); + height = ScriptNum.decode(data, true, 6); } catch (e) { return -1; } @@ -2331,7 +2276,7 @@ Script.prototype.test = function test(filter) { /** * Unshift an item onto the `code` array. - * @param {Number|String|BN|Buffer} data + * @param {Number|String|ScriptNum|Buffer} data * @returns {Number} Length. */ @@ -2341,7 +2286,7 @@ Script.prototype.unshift = function unshift(data) { /** * Push an item onto the `code` array. - * @param {Number|String|BN|Buffer} data + * @param {Number|String|ScriptNum|Buffer} data * @returns {Number} Length. */ @@ -2395,7 +2340,7 @@ Script.prototype.remove = function remove(i) { /** * Insert an item into the `code` array. * @param {Number} index - * @param {Number|String|BN|Buffer} data + * @param {Number|String|ScriptNum|Buffer} data */ Script.prototype.insert = function insert(i, data) { @@ -2436,7 +2381,7 @@ Script.prototype.getSmall = function getSmall(i) { /** * Get a number from the `code` array (5-byte limit). * @params {Number} index - * @returns {BN} + * @returns {ScriptNum} */ Script.prototype.getNumber = function getNumber(i) { @@ -2444,12 +2389,12 @@ Script.prototype.getNumber = function getNumber(i) { const op = this.code[i]; if (small !== -1) - return new BN(small); + return ScriptNum.fromInt(small); if (!op || !op.data || op.data.length > 5) return null; - return Script.num(op.data, false, 5); + return ScriptNum.decode(op.data, false, 5); }; /** @@ -2478,7 +2423,7 @@ Script.prototype.clear = function clear() { /** * Set an item in the `code` array. * @param {Number} index - * @param {Buffer|Number|String|BN} data + * @param {Buffer|Number|String|ScriptNum} data */ Script.prototype.set = function set(i, data) { @@ -2688,7 +2633,7 @@ Script.prototype.fromString = function fromString(code) { } if (/^-?\d+$/.test(item)) { - const num = new BN(item, 10); + const num = ScriptNum.fromString(item, 10); const op = Opcode.fromNumber(num); bw.writeBytes(op.toRaw()); continue; @@ -2760,7 +2705,7 @@ Script.verify = function verify(input, witness, output, tx, index, value, flags) output.execute(stack, flags, tx, index, value, 0); // Verify the stack values. - if (stack.length === 0 || !Script.bool(stack.top(-1))) + if (stack.length === 0 || !stack.bool(-1)) throw new ScriptError('EVAL_FALSE'); let hadWitness = false; @@ -2800,7 +2745,7 @@ Script.verify = function verify(input, witness, output, tx, index, value, flags) redeem.execute(stack, flags, tx, index, value, 0); // Verify the the stack values. - if (stack.length === 0 || !Script.bool(stack.top(-1))) + if (stack.length === 0 || !stack.bool(-1)) throw new ScriptError('EVAL_FALSE'); if ((flags & Script.flags.VERIFY_WITNESS) && redeem.isProgram()) { @@ -2901,7 +2846,7 @@ Script.verifyProgram = function verifyProgram(witness, output, flags, tx, index, redeem.execute(stack, flags, tx, index, value, 1); // Verify the stack values. - if (stack.length !== 1 || !Script.bool(stack.top(-1))) + if (stack.length !== 1 || !stack.bool(-1)) throw new ScriptError('EVAL_FALSE'); }; diff --git a/lib/script/stack.js b/lib/script/stack.js index 74f59f915..4a66bf126 100644 --- a/lib/script/stack.js +++ b/lib/script/stack.js @@ -7,7 +7,13 @@ 'use strict'; +const assert = require('assert'); const common = require('./common'); +const ScriptNum = require('./scriptnum'); +const ScriptError = common.ScriptError; +const STACK_FALSE = common.STACK_FALSE; +const STACK_TRUE = common.STACK_TRUE; +const STACK_NEGATE = common.STACK_NEGATE; /** * Represents the stack of a Script during execution. @@ -84,9 +90,63 @@ Stack.prototype.clone = function clone() { */ Stack.prototype.push = function push(item) { + assert(Buffer.isBuffer(item)); return this.items.push(item); }; +/** + * Push boolean onto stack. + * @see Array#push + * @param {Boolean} value + * @returns {Number} Stack size. + */ + +Stack.prototype.pushBool = function pushBool(value) { + assert(typeof value === 'boolean'); + return this.items.push(value ? STACK_TRUE : STACK_FALSE); +}; + +/** + * Push script number onto stack. + * @see Array#push + * @param {ScriptNum} num + * @returns {Number} Stack size. + */ + +Stack.prototype.pushNum = function pushNum(num) { + assert(ScriptNum.isScriptNum(num)); + return this.items.push(num.encode()); +}; + +/** + * Push integer onto stack. + * @see Array#push + * @param {Number} value + * @returns {Number} Stack size. + */ + +Stack.prototype.pushInt = function pushInt(value) { + assert(typeof value === 'number'); + + if (value >= -1 && value <= 16) { + switch (value) { + case -1: + return this.items.push(STACK_NEGATE); + case 0: + return this.items.push(STACK_FALSE); + case 1: + return this.items.push(STACK_TRUE); + } + const item = Buffer.allocUnsafe(1); + item[0] = value; + return this.items.push(item); + } + + const num = ScriptNum.fromNumber(value); + + return this.items.push(num.encode()); +}; + /** * Unshift item from stack. * @see Array#unshift @@ -95,6 +155,7 @@ Stack.prototype.push = function push(item) { */ Stack.prototype.unshift = function unshift(item) { + assert(Buffer.isBuffer(item)); return this.items.unshift(item); }; @@ -127,6 +188,8 @@ Stack.prototype.splice = function splice(i, remove, insert) { if (insert === undefined) return this.items.splice(i, remove); + assert(Buffer.isBuffer(insert)); + return this.items.splice(i, remove, insert); }; @@ -158,6 +221,8 @@ Stack.prototype.insert = function insert(i, item) { if (i < 0) i = this.items.length + i; + assert(Buffer.isBuffer(item)); + this.items.splice(i, 0, item); }; @@ -204,9 +269,69 @@ Stack.prototype.shift = function shift() { */ Stack.prototype.get = function get(i) { + if (i < 0) + i = this.items.length + i; + return this.items[i]; }; +/** + * Get a stack item by index + * and decode as a boolean. + * @param {Number} index + * @returns {Boolean} + * @throws on invalid stack operation + */ + +Stack.prototype.bool = function bool(i) { + if (i < 0) + i = this.items.length + i; + + if (i < 0 || i >= this.items.length) + throw new ScriptError('INVALID_STACK_OPERATION', -1, -1); + + return common.toBool(this.items[i]); +}; + +/** + * Get a stack item by index + * and decode as a scriptnum. + * @param {Number} index + * @param {Boolean?} minimal + * @param {Number?} limit + * @returns {ScriptNum} + * @throws on invalid stack operation + */ + +Stack.prototype.num = function num(i, minimal, limit) { + if (i < 0) + i = this.items.length + i; + + if (i < 0 || i >= this.items.length) + throw new ScriptError('INVALID_STACK_OPERATION', -1, -1); + + return ScriptNum.decode(this.items[i], minimal, limit); +}; + +/** + * Get a stack item by index + * and decode as an integer. + * @param {Number} index + * @param {Boolean?} minimal + * @returns {Number} + * @throws on invalid stack operation + */ + +Stack.prototype.int = function int(i, minimal) { + if (i < 0) + i = this.items.length + i; + + if (i < 0 || i >= this.items.length) + throw new ScriptError('INVALID_STACK_OPERATION', -1, -1); + + return ScriptNum.decode(this.items[i], minimal).getInt(); +}; + /** * Get a stack item relative to * the top of the stack. @@ -239,6 +364,8 @@ Stack.prototype.set = function set(i, value) { if (i < 0) i = this.items.length + i; + assert(Buffer.isBuffer(value)); + this.items[i] = value; return value; diff --git a/lib/script/witness.js b/lib/script/witness.js index 4ed7d19a9..073e61fc5 100644 --- a/lib/script/witness.js +++ b/lib/script/witness.js @@ -8,7 +8,7 @@ 'use strict'; const assert = require('assert'); -const BN = require('../crypto/bn'); +const ScriptNum = require('./scriptnum'); const util = require('../utils/util'); const Script = require('./script'); const common = require('./common'); @@ -20,6 +20,7 @@ const Address = require('../primitives/address'); const Stack = require('./stack'); const opcodes = common.opcodes; const scriptTypes = common.types; +const STACK_TRUE = common.STACK_TRUE; const STACK_FALSE = common.STACK_FALSE; const STACK_NEGATE = common.STACK_NEGATE; @@ -391,7 +392,7 @@ Witness.fromJSON = function fromJSON(json) { /** * Unshift an item onto the witness vector. - * @param {Number|String|Buffer|BN} data + * @param {Number|String|Buffer|ScriptNum} data * @returns {Number} */ @@ -401,7 +402,7 @@ Witness.prototype.unshift = function unshift(data) { /** * Push an item onto the witness vector. - * @param {Number|String|Buffer|BN} data + * @param {Number|String|Buffer|ScriptNum} data * @returns {Number} */ @@ -440,7 +441,7 @@ Witness.prototype.remove = function remove(i) { /** * Insert an item into the witness vector. * @param {Number} index - * @param {Number|String|Buffer|BN} data + * @param {Number|String|Buffer|ScriptNum} data */ Witness.prototype.insert = function insert(i, data) { @@ -478,7 +479,7 @@ Witness.prototype.getSmall = function getSmall(i) { /** * Get a number from the witness vector. * @param {Number} index - * @returns {BN} + * @returns {ScriptNum} */ Witness.prototype.getNumber = function getNumber(i) { @@ -487,7 +488,7 @@ Witness.prototype.getNumber = function getNumber(i) { if (!item || item.length > 5) return null; - return common.num(item, false, 5); + return ScriptNum.decode(item, false, 5); }; /** @@ -516,7 +517,7 @@ Witness.prototype.clear = function clear() { /** * Set an item in the witness vector. * @param {Number} index - * @param {Number|String|Buffer|BN} data + * @param {Number|String|Buffer|ScriptNum} data */ Witness.prototype.set = function set(i, data) { @@ -526,7 +527,7 @@ Witness.prototype.set = function set(i, data) { /** * Encode a witness item. - * @param {Number|String|Buffer|BN} data + * @param {Number|String|Buffer|ScriptNum} data * @returns {Buffer} */ @@ -534,6 +535,9 @@ Witness.encodeItem = function encodeItem(data) { if (data instanceof Opcode) data = data.data || data.value; + if (Buffer.isBuffer(data)) + return data; + if (typeof data === 'number') { if (data === opcodes.OP_1NEGATE) return STACK_NEGATE; @@ -547,13 +551,16 @@ Witness.encodeItem = function encodeItem(data) { throw new Error('Non-push opcode in witness.'); } - if (BN.isBN(data)) - return common.array(data); - if (typeof data === 'string') return Buffer.from(data, 'utf8'); - return data; + if (typeof data === 'boolean') + return data ? STACK_TRUE : STACK_FALSE; + + if (ScriptNum.isEncodable(data)) + return ScriptNum.encode(data); + + throw new Error('Not a witness item.'); }; /** diff --git a/scripts/fuzz.js b/scripts/fuzz.js index a31e3363e..03c72717b 100644 --- a/scripts/fuzz.js +++ b/scripts/fuzz.js @@ -308,7 +308,7 @@ function fuzzSimple(flags) { if (stack.length === 0) continue; - if (!Script.bool(stack.top(-1))) + if (!stack.bool(-1)) continue; if (isPushOnly(output)) diff --git a/scripts/gen.js b/scripts/gen.js index 2489d591a..0d35288f1 100644 --- a/scripts/gen.js +++ b/scripts/gen.js @@ -1,6 +1,5 @@ 'use strict'; -const BN = require('../lib/crypto/bn'); const util = require('../lib/utils/util'); const consensus = require('../lib/protocol/consensus'); const encoding = require('../lib/utils/encoding'); @@ -8,6 +7,7 @@ const TX = require('../lib/primitives/tx'); const Block = require('../lib/primitives/block'); const Script = require('../lib/script/script'); const Opcode = require('../lib/script/opcode'); +const ScriptNum = require('../lib/script/scriptnum'); const opcodes = Script.opcodes; function createGenesisBlock(options) { @@ -41,7 +41,7 @@ function createGenesisBlock(options) { index: 0xffffffff }, script: [ - Opcode.fromNumber(new BN(486604799)), + Opcode.fromNumber(new ScriptNum(486604799)), Opcode.fromPush(Buffer.from([4])), Opcode.fromData(flags) ], diff --git a/test/chain-test.js b/test/chain-test.js index ee47adcb2..693c0e86c 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -4,7 +4,7 @@ 'use strict'; const assert = require('./util/assert'); -const BN = require('../lib/crypto/bn'); +const ScriptNum = require('../lib/script/scriptnum'); const consensus = require('../lib/protocol/consensus'); const encoding = require('../lib/utils/encoding'); const Coin = require('../lib/primitives/coin'); @@ -79,7 +79,7 @@ async function mineCSV(fund) { spend.addOutput({ script: [ - Script.array(new BN(1)), + ScriptNum.encode(1), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10000 @@ -419,7 +419,7 @@ describe('Chain', function() { spend.addOutput({ script: [ - Script.array(new BN(2)), + ScriptNum.encode(2), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10000 @@ -444,7 +444,7 @@ describe('Chain', function() { spend.addOutput({ script: [ - Script.array(new BN(1)), + ScriptNum.encode(1), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 1 * 1e8 @@ -479,7 +479,7 @@ describe('Chain', function() { spend.addOutput({ script: [ - Script.array(new BN(2)), + ScriptNum.encode(2), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 1 * 1e8 @@ -787,12 +787,12 @@ describe('Chain', function() { const flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; const redeem = new Script(); - redeem.push(new BN(20)); + redeem.push(new ScriptNum(20)); for (let i = 0; i < 20; i++) redeem.push(encoding.ZERO_KEY); - redeem.push(new BN(20)); + redeem.push(new ScriptNum(20)); redeem.push(opcodes.OP_CHECKMULTISIG); redeem.compile(); @@ -828,12 +828,12 @@ describe('Chain', function() { const job = await cpu.createJob(); const script = new Script(); - script.push(new BN(20)); + script.push(new ScriptNum(20)); for (let i = 0; i < 20; i++) script.push(encoding.ZERO_KEY); - script.push(new BN(20)); + script.push(new ScriptNum(20)); script.push(opcodes.OP_CHECKMULTISIG); script.compile(); diff --git a/test/node-test.js b/test/node-test.js index 37bbb8f62..6e1eb42e1 100644 --- a/test/node-test.js +++ b/test/node-test.js @@ -4,7 +4,7 @@ 'use strict'; const assert = require('./util/assert'); -const BN = require('../lib/crypto/bn'); +const ScriptNum = require('../lib/script/scriptnum'); const consensus = require('../lib/protocol/consensus'); const co = require('../lib/utils/co'); const Coin = require('../lib/primitives/coin'); @@ -63,7 +63,7 @@ async function mineCSV(fund) { spend.addOutput({ script: [ - Script.array(new BN(1)), + ScriptNum.encode(1), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10 * 1e8 @@ -348,7 +348,7 @@ describe('Node', function() { spend.addOutput({ script: [ - Script.array(new BN(2)), + ScriptNum.encode(2), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10 * 1e8 @@ -373,7 +373,7 @@ describe('Node', function() { spend.addOutput({ script: [ - Script.array(new BN(1)), + ScriptNum.encode(1), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10 * 1e8 @@ -418,7 +418,7 @@ describe('Node', function() { spend.addOutput({ script: [ - Script.array(new BN(2)), + ScriptNum.encode(2), Script.opcodes.OP_CHECKSEQUENCEVERIFY ], value: 10 * 1e8 diff --git a/test/script-test.js b/test/script-test.js index f954e67c2..9885ad029 100644 --- a/test/script-test.js +++ b/test/script-test.js @@ -18,7 +18,7 @@ function isSuccess(stack) { if (stack.length === 0) return false; - if (!Script.bool(stack.top(-1))) + if (!stack.bool(-1)) return false; return true;