diff --git a/node_modules/bsv/lib/crypto/bn.js b/node_modules/bsv/lib/crypto/bn.js index 22a5d8f..88d809e 100644 --- a/node_modules/bsv/lib/crypto/bn.js +++ b/node_modules/bsv/lib/crypto/bn.js @@ -185,7 +185,8 @@ BN.prototype.toSM = function (opts) { * @param {number} size The maximum size. */ BN.fromScriptNumBuffer = function (buf, fRequireMinimal, size) { - var nMaxNumSize = size || 4 + // don't limit numSize default + var nMaxNumSize = size || Number.MAX_SAFE_INTEGER; $.checkArgument(buf.length <= nMaxNumSize, new Error('script number overflow')) if (fRequireMinimal && buf.length > 0) { // Check that the number is encoded with the minimum possible diff --git a/node_modules/bsv/lib/crypto/ecdsa.js b/node_modules/bsv/lib/crypto/ecdsa.js index 2fa7dfd..2c3f1e5 100644 --- a/node_modules/bsv/lib/crypto/ecdsa.js +++ b/node_modules/bsv/lib/crypto/ecdsa.js @@ -9,7 +9,7 @@ var Hash = require('./hash') var _ = require('../util/_') var $ = require('../util/preconditions') -var ECDSA = function ECDSA (obj) { +var ECDSA = function ECDSA(obj) { if (!(this instanceof ECDSA)) { return new ECDSA(obj) } @@ -205,7 +205,7 @@ ECDSA.prototype._findSignature = function (d, e) { badrs++ k = this.k Q = G.mul(k) - r = Q.x.umod(N) + r = new BN(1).mul(Q.x.umod(N)) s = k.invm(N).mul(e.add(d.mul(r))).umod(N) } while (r.cmp(BN.Zero) <= 0 || s.cmp(BN.Zero) <= 0) diff --git a/node_modules/bsv/lib/script/index.js b/node_modules/bsv/lib/script/index.js index 46683f1..b59a70c 100644 --- a/node_modules/bsv/lib/script/index.js +++ b/node_modules/bsv/lib/script/index.js @@ -1,3 +1,5 @@ module.exports = require('./script') module.exports.Interpreter = require('./interpreter') + +module.exports.Stack = require('./stack') diff --git a/node_modules/bsv/lib/script/interpreter.js b/node_modules/bsv/lib/script/interpreter.js index 4185994..0dc40f9 100644 --- a/node_modules/bsv/lib/script/interpreter.js +++ b/node_modules/bsv/lib/script/interpreter.js @@ -9,7 +9,7 @@ var Hash = require('../crypto/hash') var Signature = require('../crypto/signature') var PublicKey = require('../publickey') var cloneDeep = require('clone-deep') - +var Stack = require('./stack') /** * Bitcoin transactions contain scripts. Each input has a script called the * scriptSig, and each output has a script called the scriptPubkey. To validate @@ -46,7 +46,7 @@ var Interpreter = function Interpreter (obj) { * * Translated from bitcoind's VerifyScript */ -Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags, satoshisBN) { +Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags, satoshisBN, sighashScript) { var Transaction = require('../transaction') if (_.isUndefined(tx)) { @@ -74,7 +74,8 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags tx: tx, nin: nin, flags: flags, - satoshisBN: satoshisBN + satoshisBN: satoshisBN, + sighashScript: sighashScript }) var stackCopy @@ -84,12 +85,12 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags } // evaluate scriptSig - if (!this.evaluate()) { + if (!this.evaluate('scriptSig')) { return false } if (flags & Interpreter.SCRIPT_VERIFY_P2SH) { - stackCopy = this.stack.slice() + stackCopy = this.stack.copy() } var stack = this.stack @@ -100,11 +101,12 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags tx: tx, nin: nin, flags: flags, - satoshisBN: satoshisBN + satoshisBN: satoshisBN, + sighashScript: sighashScript }) // evaluate scriptPubkey - if (!this.evaluate()) { + if (!this.evaluate('scriptPubkey')) { return false } @@ -113,7 +115,7 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags return false } - var buf = this.stack[this.stack.length - 1] + var buf = this.stack.stacktop(-1) if (!Interpreter.castToBool(buf)) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK' return false @@ -134,7 +136,7 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags throw new Error('internal error - stack copy empty') } - var redeemScriptSerialized = stackCopy[stackCopy.length - 1] + var redeemScriptSerialized = stackCopy.stacktop(-1) var redeemScript = Script.fromBuffer(redeemScriptSerialized) stackCopy.pop() @@ -158,7 +160,7 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags return false } - if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { + if (!Interpreter.castToBool(stackCopy.stacktop(-1))) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK' return false } @@ -172,11 +174,11 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags // Disallow CLEANSTACK without P2SH, as otherwise a switch // CLEANSTACK->P2SH+CLEANSTACK would be possible, which is not a // softfork (and P2SH should be one). - if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) === 0) { - throw new Error('internal error - CLEANSTACK without P2SH') - } + // if ((flags & Interpreter.SCRIPT_VERIFY_P2SH) === 0) { + // throw new Error('internal error - CLEANSTACK without P2SH') + // } - if (stackCopy.length !== 1) { + if (this.stack.length !== 1) { this.errstr = 'SCRIPT_ERR_CLEANSTACK' return false } @@ -188,14 +190,17 @@ Interpreter.prototype.verify = function (scriptSig, scriptPubkey, tx, nin, flags module.exports = Interpreter Interpreter.prototype.initialize = function (obj) { - this.stack = [] - this.altstack = [] + this.stack = new Stack([]) + this.altstack = new Stack([]) this.pc = 0 this.pbegincodehash = 0 this.nOpCount = 0 this.vfExec = [] this.errstr = '' this.flags = 0 + // if OP_RETURN is found in executed branches after genesis is activated, + // we still have to check if the rest of the script is valid + this.nonTopLevelReturnAfterGenesis = false } Interpreter.prototype.set = function (obj) { @@ -211,10 +216,25 @@ Interpreter.prototype.set = function (obj) { this.vfExec = obj.vfExec || this.vfExec this.errstr = obj.errstr || this.errstr this.flags = typeof obj.flags !== 'undefined' ? obj.flags : this.flags + this.sighashScript = obj.sighashScript || this.sighashScript } -Interpreter.true = Buffer.from([1]) -Interpreter.false = Buffer.from([]) +Interpreter.prototype.subscript = function () { + if (this.sighashScript) { + return new Script().set({ + chunks: this.sighashScript.chunks + }) + } else { + // Subset of script starting at the most recent codeseparator + // CScript scriptCode(pbegincodehash, pend); + return new Script().set({ + chunks: this.script.chunks.slice(this.pbegincodehash) + }) + } +} + +Interpreter.getTrue = () => Buffer.from([1]) +Interpreter.getFalse = () => Buffer.from([]) Interpreter.MAX_SCRIPT_ELEMENT_SIZE = 520 Interpreter.MAXIMUM_ELEMENT_SIZE = 4 @@ -329,6 +349,10 @@ Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) */ Interpreter.SEQUENCE_LOCKTIME_MASK = 0x0000ffff +Interpreter.MAX_SCRIPT_SIZE = Number.MAX_SAFE_INTEGER + +Interpreter.MAX_OPCODE_COUNT = Number.MAX_SAFE_INTEGER + Interpreter.castToBool = function (buf) { for (var i = 0; i < buf.length; i++) { if (buf[i] !== 0) { @@ -371,13 +395,13 @@ Interpreter.prototype.checkSignatureEncoding = function (buf) { } if (!(this.flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) && - (sig.nhashtype & Signature.SIGHASH_FORKID)) { + (sig.nhashtype & Signature.SIGHASH_FORKID)) { this.errstr = 'SCRIPT_ERR_ILLEGAL_FORKID' return false } if ((this.flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) && - !(sig.nhashtype & Signature.SIGHASH_FORKID)) { + !(sig.nhashtype & Signature.SIGHASH_FORKID)) { this.errstr = 'SCRIPT_ERR_MUST_USE_FORKID' return false } @@ -485,21 +509,25 @@ Interpreter._minimallyEncode = function (buf) { * Interpreter.prototype.step() * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 */ -Interpreter.prototype.evaluate = function () { +Interpreter.prototype.evaluate = function (scriptType) { // TODO: script size should be configurable. no magic numbers - if (this.script.toBuffer().length > 10000) { + if (this.script.toBuffer().length > Interpreter.MAX_SCRIPT_SIZE) { this.errstr = 'SCRIPT_ERR_SCRIPT_SIZE' return false } try { while (this.pc < this.script.chunks.length) { - let thisStep = { pc: this.pc, opcode: Opcode.fromNumber(this.script.chunks[this.pc].opcodenum) } - var fSuccess = this.step() + // fExec: if the opcode will be executed, i.e., not in a false branch + let thisStep = { pc: this.pc, fExec: (this.vfExec.indexOf(false) === -1), opcode: Opcode.fromNumber(this.script.chunks[this.pc].opcodenum) } + + var fSuccess = this.step(scriptType) + + this._callbackStep(thisStep) + if (!fSuccess) { return false } - this._callbackStep(thisStep) } // Size limits @@ -523,13 +551,28 @@ Interpreter.prototype.evaluate = function () { Interpreter.prototype._callbackStep = function (thisStep) { if (typeof this.stepListener === 'function') { try { - this.stepListener(thisStep, cloneDeep(this.stack, true), cloneDeep(this.altstack, true)) + this.stepListener(thisStep, cloneDeep(this.stack.rawstack, true), cloneDeep(this.altstack.rawstack, true), cloneDeep(this.stack.varStack, true)) } catch (err) { console.log(`Error in Step callback:${err}`) } } } +/** + * call to update stackvar + * @param {*} stack + */ +Interpreter.prototype._callbackStack = function (stack, pc, scriptType) { + if (typeof this.stackListener === 'function') { + try { + this.stackListener(stack, pc, scriptType) + } catch (err) { + var chunk = this.script.chunks[pc] + console.error(`Error: ${err} in _updateStack pc: ${pc}, opcode ${Opcode.fromNumber(chunk.opcodenum).toSafeString()}`) + } + } +} + /** * Checks a locktime parameter with the transaction's locktime. * There are two times of nLockTime: lock-by-blockheight and lock-by-blocktime, @@ -604,7 +647,7 @@ Interpreter.prototype.checkSequence = function (nSequence) { // Mask off any bits that do not have consensus-enforced meaning before // doing the integer comparisons var nLockTimeMask = - Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG | Interpreter.SEQUENCE_LOCKTIME_MASK + Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG | Interpreter.SEQUENCE_LOCKTIME_MASK var txToSequenceMasked = new BN(txToSequence & nLockTimeMask) var nSequenceMasked = nSequence.and(nLockTimeMask) @@ -618,9 +661,9 @@ Interpreter.prototype.checkSequence = function (nSequence) { var SEQUENCE_LOCKTIME_TYPE_FLAG_BN = new BN(Interpreter.SEQUENCE_LOCKTIME_TYPE_FLAG) if (!((txToSequenceMasked.lt(SEQUENCE_LOCKTIME_TYPE_FLAG_BN) && - nSequenceMasked.lt(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)) || - (txToSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN) && - nSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)))) { + nSequenceMasked.lt(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)) || + (txToSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN) && + nSequenceMasked.gte(SEQUENCE_LOCKTIME_TYPE_FLAG_BN)))) { return false } @@ -644,11 +687,15 @@ function padBufferToSize (buf, len) { * Based on the inner loop of bitcoind's EvalScript function * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 */ -Interpreter.prototype.step = function () { +Interpreter.prototype.step = function (scriptType) { var self = this function stacktop (i) { - return self.stack[self.stack.length + i] + return self.stack.stacktop(i) + } + + function vartop (i) { + return self.stack.vartop(i) } function isOpcodeDisabled (opcode) { @@ -692,10 +739,11 @@ Interpreter.prototype.step = function () { var fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0 // bool fExec = !count(vfExec.begin(), vfExec.end(), false); - var fExec = (this.vfExec.indexOf(false) === -1) + var buf, buf1, buf2, spliced, n, x1, x2, bn, bn1, bn2, bufSig, bufPubkey, subscript var sig, pubkey var fValue, fSuccess + var var1, var2, var3 // Read instruction var chunk = this.script.chunks[this.pc] @@ -710,8 +758,11 @@ Interpreter.prototype.step = function () { return false } + // Do not execute instructions if Genesis OP_RETURN was found in executed branches. + var fExec = (this.vfExec.indexOf(false) === -1) && (!this.nonTopLevelReturnAfterGenesis || opcodenum === Opcode.OP_RETURN) + // Note how Opcode.OP_RESERVED does not count towards the opcode limit. - if (opcodenum > Opcode.OP_16 && ++(this.nOpCount) > 201) { + if (opcodenum > Opcode.OP_16 && ++(this.nOpCount) > Interpreter.MAX_OPCODE_COUNT) { this.errstr = 'SCRIPT_ERR_OP_COUNT' return false } @@ -727,7 +778,7 @@ Interpreter.prototype.step = function () { return false } if (!chunk.buf) { - this.stack.push(Interpreter.false) + this.stack.push(Interpreter.getFalse()) } else if (chunk.len !== chunk.buf.length) { throw new Error(`Length of push value not equal to length of data (${chunk.len},${chunk.buf.length})`) } else { @@ -762,9 +813,9 @@ Interpreter.prototype.step = function () { // they push, so no need for a CheckMinimalPush here. break - // - // Control - // + // + // Control + // case Opcode.OP_NOP: break @@ -799,7 +850,7 @@ Interpreter.prototype.step = function () { // Thus as a special case we tell CScriptNum to accept up // to 5-byte bignums, which are good until 2**39-1, well // beyond the 2**32-1 limit of the nLockTime field itself. - var nLockTime = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5) + var nLockTime = BN.fromScriptNumBuffer(this.stack.stacktop(-1), fRequireMinimal, 5) // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use @@ -941,19 +992,32 @@ Interpreter.prototype.step = function () { break case Opcode.OP_RETURN: - this.errstr = 'SCRIPT_ERR_OP_RETURN' - return false - // break // unreachable - // - // Stack ops - // + if ((this.flags & Interpreter.SCRIPT_VERIFY_P2SH) === 0) { // utxo_after_genesis + if (this.vfExec.length === 0) { + // Terminate the execution as successful. The remaining of the script does not affect the validity (even in + // presence of unbalanced IFs, invalid opcodes etc) + this.pc = this.script.chunks.length + return true + } + // op_return encountered inside if statement after genesis --> check for invalid grammar + this.nonTopLevelReturnAfterGenesis = true + } else { + return false + } + + break + + // + // Stack ops + // case Opcode.OP_TOALTSTACK: if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } - this.altstack.push(this.stack.pop()) + var1 = vartop(-1) + this.altstack.push(this.stack.pop(), var1) break case Opcode.OP_FROMALTSTACK: @@ -961,7 +1025,8 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_ALTSTACK_OPERATION' return false } - this.stack.push(this.altstack.pop()) + const varAlt = this.altstack.vartop(-1) + this.stack.push(this.altstack.pop(), varAlt) break case Opcode.OP_2DROP: @@ -982,8 +1047,10 @@ Interpreter.prototype.step = function () { } buf1 = stacktop(-2) buf2 = stacktop(-1) - this.stack.push(Buffer.from(buf1)) - this.stack.push(Buffer.from(buf2)) + var1 = vartop(-2) + var2 = vartop(-1) + this.stack.push(Buffer.from(buf1), `$${var1}`) + this.stack.push(Buffer.from(buf2), `$${var2}`) break case Opcode.OP_3DUP: @@ -995,9 +1062,12 @@ Interpreter.prototype.step = function () { buf1 = stacktop(-3) buf2 = stacktop(-2) var buf3 = stacktop(-1) - this.stack.push(Buffer.from(buf1)) - this.stack.push(Buffer.from(buf2)) - this.stack.push(Buffer.from(buf3)) + var1 = vartop(-3) + var2 = vartop(-2) + var3 = vartop(-1) + this.stack.push(Buffer.from(buf1), `$${var1}`) + this.stack.push(Buffer.from(buf2), `$${var2}`) + this.stack.push(Buffer.from(buf3), `$${var3}`) break case Opcode.OP_2OVER: @@ -1008,8 +1078,10 @@ Interpreter.prototype.step = function () { } buf1 = stacktop(-4) buf2 = stacktop(-3) - this.stack.push(Buffer.from(buf1)) - this.stack.push(Buffer.from(buf2)) + var1 = vartop(-4) + var2 = vartop(-3) + this.stack.push(Buffer.from(buf1), `$${var1}`) + this.stack.push(Buffer.from(buf2), `$${var2}`) break case Opcode.OP_2ROT: @@ -1018,9 +1090,13 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } + + var1 = vartop(-6) + var2 = vartop(-5) + spliced = this.stack.splice(this.stack.length - 6, 2) - this.stack.push(spliced[0]) - this.stack.push(spliced[1]) + this.stack.push(spliced[0], var1) + this.stack.push(spliced[1], var2) break case Opcode.OP_2SWAP: @@ -1029,9 +1105,11 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } + var1 = vartop(-4) + var2 = vartop(-3) spliced = this.stack.splice(this.stack.length - 4, 2) - this.stack.push(spliced[0]) - this.stack.push(spliced[1]) + this.stack.push(spliced[0], var1) + this.stack.push(spliced[1], var2) break case Opcode.OP_IFDUP: @@ -1043,14 +1121,15 @@ Interpreter.prototype.step = function () { buf = stacktop(-1) fValue = Interpreter.castToBool(buf) if (fValue) { - this.stack.push(Buffer.from(buf)) + var1 = vartop(-1) + this.stack.push(Buffer.from(buf), `$${var1}`) } break case Opcode.OP_DEPTH: // -- stacksize buf = new BN(this.stack.length).toScriptNumBuffer() - this.stack.push(buf) + this.stack.push(buf, '$depth') break case Opcode.OP_DROP: @@ -1068,7 +1147,8 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } - this.stack.push(Buffer.from(stacktop(-1))) + var1 = vartop(-1) + this.stack.push(Buffer.from(stacktop(-1)), `$${var1}`) break case Opcode.OP_NIP: @@ -1086,7 +1166,8 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } - this.stack.push(Buffer.from(stacktop(-2))) + var2 = vartop(-2) + this.stack.push(Buffer.from(stacktop(-2)), `$${var2}`) break case Opcode.OP_PICK: @@ -1098,7 +1179,7 @@ Interpreter.prototype.step = function () { return false } buf = stacktop(-1) - bn = BN.fromScriptNumBuffer(buf, fRequireMinimal) + bn = BN.fromScriptNumBuffer(buf, fRequireMinimal, 4) n = bn.toNumber() this.stack.pop() if (n < 0 || n >= this.stack.length) { @@ -1106,10 +1187,14 @@ Interpreter.prototype.step = function () { return false } buf = stacktop(-n - 1) + var1 = vartop(-n - 1) if (opcodenum === Opcode.OP_ROLL) { this.stack.splice(this.stack.length - n - 1, 1) + this.stack.push(Buffer.from(buf), var1) + } else { + this.stack.push(Buffer.from(buf), `$${var1}`) } - this.stack.push(Buffer.from(buf)) + break case Opcode.OP_ROT: @@ -1123,9 +1208,13 @@ Interpreter.prototype.step = function () { x1 = stacktop(-3) x2 = stacktop(-2) var x3 = stacktop(-1) - this.stack[this.stack.length - 3] = x2 - this.stack[this.stack.length - 2] = x3 - this.stack[this.stack.length - 1] = x1 + var1 = vartop(-3) + var2 = vartop(-2) + var3 = vartop(-1) + this.stack.write(-3, x2) + this.stack.write(-2, x3) + this.stack.write(-1, x1) + this.stack.updateTopVars([var1, var3, var2]) break case Opcode.OP_SWAP: @@ -1136,8 +1225,11 @@ Interpreter.prototype.step = function () { } x1 = stacktop(-2) x2 = stacktop(-1) - this.stack[this.stack.length - 2] = x2 - this.stack[this.stack.length - 1] = x1 + var1 = vartop(-2) + var2 = vartop(-1) + this.stack.write(-2, x2) + this.stack.write(-1, x1) + this.stack.updateTopVars([var1, var2]) break case Opcode.OP_TUCK: @@ -1146,7 +1238,12 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' return false } + + var1 = vartop(-2) + var2 = vartop(-1) + this.stack.splice(this.stack.length - 2, 0, Buffer.from(stacktop(-1))) + this.stack.updateTopVars([var2, var1, `$${var2}`]) break case Opcode.OP_SIZE: @@ -1156,7 +1253,7 @@ Interpreter.prototype.step = function () { return false } bn = new BN(stacktop(-1).length) - this.stack.push(bn.toScriptNumBuffer()) + this.stack.push(bn.toScriptNumBuffer(), `$size`) break // @@ -1227,7 +1324,7 @@ Interpreter.prototype.step = function () { this.stack.pop() } else { bn1 = new BN(buf1) - bn2 = BN.fromScriptNumBuffer(stacktop(-1), fRequireMinimal) + bn2 = BN.fromScriptNumBuffer(stacktop(-1), fRequireMinimal, 4) n = bn2.toNumber() if (n < 0) { this.errstr = 'SCRIPT_ERR_INVALID_NUMBER_RANGE' @@ -1264,7 +1361,7 @@ Interpreter.prototype.step = function () { var fEqual = buf1.toString('hex') === buf2.toString('hex') this.stack.pop() this.stack.pop() - this.stack.push(fEqual ? Interpreter.true : Interpreter.false) + this.stack.push(fEqual ? Interpreter.getTrue() : Interpreter.getFalse()) if (opcodenum === Opcode.OP_EQUALVERIFY) { if (fEqual) { this.stack.pop() @@ -1275,9 +1372,9 @@ Interpreter.prototype.step = function () { } break - // - // Numeric - // + // + // Numeric + // case Opcode.OP_1ADD: case Opcode.OP_1SUB: case Opcode.OP_NEGATE: @@ -1312,7 +1409,7 @@ Interpreter.prototype.step = function () { case Opcode.OP_0NOTEQUAL: bn = new BN((bn.cmp(BN.Zero) !== 0) + 0) break - // default: assert(!'invalid opcode'); break; // TODO: does this ever occur? + // default: assert(!'invalid opcode'); break; // TODO: does this ever occur? } this.stack.pop() this.stack.push(bn.toScriptNumBuffer()) @@ -1377,35 +1474,35 @@ Interpreter.prototype.step = function () { case Opcode.OP_BOOLAND: bn = new BN(((bn1.cmp(BN.Zero) !== 0) && (bn2.cmp(BN.Zero) !== 0)) + 0) break - // case Opcode.OP_BOOLOR: bn = (bn1 !== bnZero || bn2 !== bnZero); break; + // case Opcode.OP_BOOLOR: bn = (bn1 !== bnZero || bn2 !== bnZero); break; case Opcode.OP_BOOLOR: bn = new BN(((bn1.cmp(BN.Zero) !== 0) || (bn2.cmp(BN.Zero) !== 0)) + 0) break - // case Opcode.OP_NUMEQUAL: bn = (bn1 === bn2); break; + // case Opcode.OP_NUMEQUAL: bn = (bn1 === bn2); break; case Opcode.OP_NUMEQUAL: bn = new BN((bn1.cmp(bn2) === 0) + 0) break - // case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 === bn2); break; + // case Opcode.OP_NUMEQUALVERIFY: bn = (bn1 === bn2); break; case Opcode.OP_NUMEQUALVERIFY: bn = new BN((bn1.cmp(bn2) === 0) + 0) break - // case Opcode.OP_NUMNOTEQUAL: bn = (bn1 !== bn2); break; + // case Opcode.OP_NUMNOTEQUAL: bn = (bn1 !== bn2); break; case Opcode.OP_NUMNOTEQUAL: bn = new BN((bn1.cmp(bn2) !== 0) + 0) break - // case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break; + // case Opcode.OP_LESSTHAN: bn = (bn1 < bn2); break; case Opcode.OP_LESSTHAN: bn = new BN((bn1.cmp(bn2) < 0) + 0) break - // case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break; + // case Opcode.OP_GREATERTHAN: bn = (bn1 > bn2); break; case Opcode.OP_GREATERTHAN: bn = new BN((bn1.cmp(bn2) > 0) + 0) break - // case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break; + // case Opcode.OP_LESSTHANOREQUAL: bn = (bn1 <= bn2); break; case Opcode.OP_LESSTHANOREQUAL: bn = new BN((bn1.cmp(bn2) <= 0) + 0) break - // case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break; + // case Opcode.OP_GREATERTHANOREQUAL: bn = (bn1 >= bn2); break; case Opcode.OP_GREATERTHANOREQUAL: bn = new BN((bn1.cmp(bn2) >= 0) + 0) break @@ -1415,7 +1512,7 @@ Interpreter.prototype.step = function () { case Opcode.OP_MAX: bn = (bn1.cmp(bn2) > 0 ? bn1 : bn2) break - // default: assert(!'invalid opcode'); break; //TODO: does this ever occur? + // default: assert(!'invalid opcode'); break; //TODO: does this ever occur? } this.stack.pop() this.stack.pop() @@ -1446,12 +1543,12 @@ Interpreter.prototype.step = function () { this.stack.pop() this.stack.pop() this.stack.pop() - this.stack.push(fValue ? Interpreter.true : Interpreter.false) + this.stack.push(fValue ? Interpreter.getTrue() : Interpreter.getFalse()) break - // - // Crypto - // + // + // Crypto + // case Opcode.OP_RIPEMD160: case Opcode.OP_SHA1: case Opcode.OP_SHA256: @@ -1503,9 +1600,7 @@ Interpreter.prototype.step = function () { // Subset of script starting at the most recent codeseparator // CScript scriptCode(pbegincodehash, pend); - subscript = new Script().set({ - chunks: this.script.chunks.slice(this.pbegincodehash) - }) + subscript = this.subscript() // Drop the signature, since there's no way for a signature to sign itself var tmpScript = new Script().add(bufSig) @@ -1531,7 +1626,7 @@ Interpreter.prototype.step = function () { this.stack.pop() // stack.push_back(fSuccess ? vchTrue : vchFalse); - this.stack.push(fSuccess ? Interpreter.true : Interpreter.false) + this.stack.push(fSuccess ? Interpreter.getTrue() : Interpreter.getFalse()) if (opcodenum === Opcode.OP_CHECKSIGVERIFY) { if (fSuccess) { this.stack.pop() @@ -1559,7 +1654,7 @@ Interpreter.prototype.step = function () { return false } this.nOpCount += nKeysCount - if (this.nOpCount > 201) { + if (this.nOpCount > Interpreter.MAX_OPCODE_COUNT) { this.errstr = 'SCRIPT_ERR_OP_COUNT' return false } @@ -1592,9 +1687,7 @@ Interpreter.prototype.step = function () { } // Subset of script starting at the most recent codeseparator - subscript = new Script().set({ - chunks: this.script.chunks.slice(this.pbegincodehash) - }) + subscript = this.subscript() // Drop the signatures, since there's no way for a signature to sign itself for (var k = 0; k < nSigsCount; k++) { @@ -1668,7 +1761,7 @@ Interpreter.prototype.step = function () { } this.stack.pop() - this.stack.push(fSuccess ? Interpreter.true : Interpreter.false) + this.stack.push(fSuccess ? Interpreter.getTrue() : Interpreter.getFalse()) if (opcodenum === Opcode.OP_CHECKMULTISIGVERIFY) { if (fSuccess) { @@ -1680,9 +1773,9 @@ Interpreter.prototype.step = function () { } break - // - // Byte string operations - // + // + // Byte string operations + // case Opcode.OP_CAT: if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION' @@ -1695,7 +1788,7 @@ Interpreter.prototype.step = function () { this.errstr = 'SCRIPT_ERR_PUSH_SIZE' return false } - this.stack[this.stack.length - 2] = Buffer.concat([buf1, buf2]) + this.stack.write(-2, Buffer.concat([buf1, buf2])) this.stack.pop() break @@ -1719,13 +1812,13 @@ Interpreter.prototype.step = function () { var n1 = Buffer.from(buf1) // Replace existing stack values by the new values. - this.stack[this.stack.length - 2] = n1.slice(0, position) - this.stack[this.stack.length - 1] = n1.slice(position) + this.stack.write(-2, n1.slice(0, position)) + this.stack.write(-1, n1.slice(position)) break - // - // Conversion operations - // + // + // Conversion operations + // case Opcode.OP_NUM2BIN: // (in -- out) if (this.stack.length < 2) { @@ -1755,7 +1848,7 @@ Interpreter.prototype.step = function () { // We already have an element of the right size, we // don't need to do anything. if (rawnum.length === size) { - this.stack[this.stack.length - 1] = rawnum + this.stack.write(-1, rawnum) break } @@ -1775,7 +1868,7 @@ Interpreter.prototype.step = function () { num[l] = signbit - this.stack[this.stack.length - 1] = num + this.stack.write(-1, num) break case Opcode.OP_BIN2NUM: @@ -1788,7 +1881,7 @@ Interpreter.prototype.step = function () { buf1 = stacktop(-1) buf2 = Interpreter._minimallyEncode(buf1) - this.stack[this.stack.length - 1] = buf2 + this.stack.write(-1, buf2) // The resulting number must be a valid number. if (!Interpreter._isMinimallyEncoded(buf2)) { @@ -1803,5 +1896,10 @@ Interpreter.prototype.step = function () { } } + // only when next opcode is evaluate opcode, we update stack + if (this.vfExec.indexOf(false) === -1) { + this._callbackStack(this.stack, this.pc, scriptType) + } + return true } diff --git a/node_modules/bsv/lib/script/stack.js b/node_modules/bsv/lib/script/stack.js new file mode 100644 index 0000000..a439096 --- /dev/null +++ b/node_modules/bsv/lib/script/stack.js @@ -0,0 +1,92 @@ +'use strict' + +var Stack = function Stack (rawstack, varStack) { + this.stack = rawstack + this.varStack = varStack || [] +} + +module.exports = Stack + +Stack.prototype.pushVar = function (varName) { + this.varStack.push(varName || '$tmp') +} + +Stack.prototype.popVar = function () { + this.varStack.pop() +} + +Stack.prototype.push = function (n, varName) { + this.pushVar(varName) + this.stack.push(n) + this.checkConsistency() +} + +Stack.prototype.pop = function () { + this.popVar() + let top = this.stack.pop() + this.checkConsistency() + return top +} + +Stack.prototype.updateTopVars = function (vars) { + if (vars.length > this.varStack.length) { + throw new Error(`updateTopVars fail, stack: ${this.stack.length}, varStack: ${this.varStack.length}, vars:${vars.length}`) + } + vars = vars.reverse() + this.varStack.splice(this.varStack.length - vars.length, vars.length, ...vars) +} + +Stack.prototype.stacktop = function (i) { + return this.stack[this.stack.length + i] +} + +Stack.prototype.vartop = function (i) { + return this.varStack[this.varStack.length + i] +} + +Stack.prototype.slice = function (start, end) { + return this.stack.slice(start, end) +} + +Stack.prototype.splice = function (start, deleteCount, ...items) { + this.varStack.splice(start, deleteCount, ...items) + return this.stack.splice(start, deleteCount, ...items) +} + +Stack.prototype.write = function (i, value) { + this.stack[this.stack.length + i] = value +} + +Stack.prototype.copy = function () { + return new Stack(this.stack.slice() || [], this.varStack.slice() || []) +} + +Stack.prototype.printVarStack = function () { + console.log(this.varStack.join(',')) +} + +Stack.prototype.checkConsistency = function () { + if (this.stack.length !== this.varStack.length) { + this.printVarStack() + throw new Error(`checkConsistency fail, stack: ${this.stack.length}, varStack:${this.varStack.length}`) + } +} + +Stack.prototype.checkConsistencyWithVars = function (varStack) { + if (this.stack.length < varStack.length) { + this.printVarStack() + throw new Error(`checkConsistencyWithVars fail, stack: ${this.stack.length}, varStack:${varStack.length}`) + } +} + +Object.defineProperty(Stack.prototype, 'length', { + get: function () { + return this.stack.length + } +}) + +Object.defineProperty(Stack.prototype, 'rawstack', { + get: function () { + return this.stack + } +}) diff --git a/node_modules/bsv/lib/transaction/input/input.js b/node_modules/bsv/lib/transaction/input/input.js index 98df9b0..494efbb 100644 --- a/node_modules/bsv/lib/transaction/input/input.js +++ b/node_modules/bsv/lib/transaction/input/input.js @@ -15,7 +15,7 @@ var DEFAULT_RBF_SEQNUMBER = MAXINT - 2 var DEFAULT_SEQNUMBER = MAXINT var DEFAULT_LOCKTIME_SEQNUMBER = MAXINT - 1 -function Input (params) { +function Input(params) { if (!(this instanceof Input)) { return new Input(params) } @@ -72,7 +72,7 @@ Input.prototype._fromObject = function (params) { return this } -Input.prototype.toObject = Input.prototype.toJSON = function toObject () { +Input.prototype.toObject = Input.prototype.toJSON = function toObject() { var obj = { prevTxId: this.prevTxId.toString('hex'), outputIndex: this.outputIndex, @@ -167,11 +167,11 @@ Input.prototype.isFinal = function () { } Input.prototype.addSignature = function () { - throw new errors.AbstractMethodInvoked('Input#addSignature') + // throw new errors.AbstractMethodInvoked('Input#addSignature') } Input.prototype.clearSignatures = function () { - throw new errors.AbstractMethodInvoked('Input#clearSignatures') + // throw new errors.AbstractMethodInvoked('Input#clearSignatures') } Input.prototype.isValidSignature = function (transaction, signature) {