UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

1,167 lines 73.4 kB
import { Script } from '../script.js'; import { Opcode } from '../opcode.js'; import { BN } from '../crypto/bn.js'; import { Hash } from '../crypto/hash.js'; import { Signature } from '../crypto/signature.js'; import { PublicKey } from '../publickey.js'; import { ECDSA } from '../crypto/ecdsa.js'; import { Schnorr } from '../crypto/schnorr.js'; import { Preconditions } from '../util/preconditions.js'; import { TAPROOT_INTRO_SIZE, TAPROOT_SIZE_WITHOUT_STATE, TAPROOT_SCRIPTTYPE, verifyTaprootSpend, } from '../taproot.js'; export class Interpreter { static SCRIPT_VERIFY_NONE = 0; static SCRIPT_VERIFY_P2SH = 1 << 0; static SCRIPT_VERIFY_STRICTENC = 1 << 1; static SCRIPT_VERIFY_DERSIG = 1 << 2; static SCRIPT_VERIFY_LOW_S = 1 << 3; static SCRIPT_VERIFY_NULLDUMMY = 1 << 4; static SCRIPT_VERIFY_SIGPUSHONLY = 1 << 5; static SCRIPT_VERIFY_MINIMALDATA = 1 << 6; static SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = 1 << 7; static SCRIPT_VERIFY_CLEANSTACK = 1 << 8; static SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = 1 << 9; static SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = 1 << 10; static SCRIPT_VERIFY_MINIMALIF = 1 << 13; static SCRIPT_VERIFY_NULLFAIL = 1 << 14; static SCRIPT_VERIFY_COMPRESSED_PUBKEYTYPE = 1 << 15; static SCRIPT_ENABLE_SIGHASH_FORKID = 1 << 16; static SCRIPT_ENABLE_REPLAY_PROTECTION = 1 << 17; static SCRIPT_ENABLE_CHECKDATASIG = 1 << 18; static SCRIPT_DISALLOW_SEGWIT_RECOVERY = 1 << 20; static SCRIPT_ENABLE_SCHNORR_MULTISIG = 1 << 21; static SCRIPT_VERIFY_INPUT_SIGCHECKS = 1 << 22; static SCRIPT_TAPROOT_KEY_SPEND_PATH = 1 << 23; static SCRIPT_DISABLE_TAPROOT_SIGHASH_LOTUS = 1 << 24; static MAX_SCRIPT_ELEMENT_SIZE = 520; static MAX_SCRIPT_SIZE = 10000; static MAX_STACK_SIZE = 1000; static MAX_OPCODE_COUNT = 201; static false = Buffer.from([0]); static true = Buffer.from([1]); static MAXIMUM_ELEMENT_SIZE = 4; static LOCKTIME_THRESHOLD = 500000000; static LOCKTIME_THRESHOLD_BN = new BN(500000000); static SEQUENCE_LOCKTIME_DISABLE_FLAG = 1 << 31; static SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22; static SEQUENCE_LOCKTIME_MASK = 0x0000ffff; script; tx; nin; flags; satoshisBN; outputScript; stack = []; altstack = []; pc = 0; pbegincodehash = 0; nOpCount = 0; vfExec = []; errstr = ''; constructor(obj) { this.initialize(); if (obj) { this.set(obj); } } static create(obj) { return new Interpreter(obj); } initialize() { this.stack = []; this.altstack = []; this.pc = 0; this.pbegincodehash = 0; this.nOpCount = 0; this.vfExec = []; this.errstr = ''; this.flags = Interpreter.SCRIPT_VERIFY_NONE; } set(obj) { this.script = obj.script || this.script; this.tx = obj.tx || this.tx; this.nin = obj.nin !== undefined ? obj.nin : this.nin; this.flags = obj.flags !== undefined ? obj.flags : this.flags; this.satoshisBN = obj.satoshisBN || this.satoshisBN; this.outputScript = obj.outputScript || this.outputScript; this.stack = obj.stack || this.stack; return this; } verify(scriptSig, scriptPubkey, tx, nin, flags, satoshisBN) { Preconditions.checkArgument(scriptSig instanceof Script, 'scriptSig', 'Must be a Script'); Preconditions.checkArgument(scriptPubkey instanceof Script, 'scriptPubkey', 'Must be a Script'); if (flags && (flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) !== 0) { flags |= Interpreter.SCRIPT_VERIFY_STRICTENC; if (!satoshisBN) { throw new Error('internal error - need satoshisBN to verify FORKID transactions'); } } this.set({ script: scriptSig, tx: tx, nin: nin, flags: flags, satoshisBN: satoshisBN, }); let stackCopy = []; if ((this.flags & Interpreter.SCRIPT_VERIFY_SIGPUSHONLY) !== 0) { if (!scriptSig.isPushOnly()) { this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; return false; } } if (!this.evaluate()) { return false; } if (this.flags & Interpreter.SCRIPT_VERIFY_P2SH) { stackCopy = this.stack.slice(); } const stack = this.stack; this.initialize(); this.script = scriptPubkey; this.stack = stack; this.tx = tx; this.nin = nin; this.flags = flags || Interpreter.SCRIPT_VERIFY_NONE; this.satoshisBN = satoshisBN; const scriptPubkeyBuf = scriptPubkey.toBuffer(); if (scriptPubkeyBuf.length > 0 && scriptPubkeyBuf[0] === Opcode.OP_SCRIPTTYPE) { if (!this._verifyScriptType(scriptPubkey)) { return false; } return true; } if (!this.evaluate()) { return false; } if (this.stack.length === 0) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_RESULT'; return false; } const buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_STACK'; return false; } if (this.flags & Interpreter.SCRIPT_VERIFY_P2SH && scriptPubkey.isScriptHashOut()) { if (!scriptSig.isPushOnly()) { this.errstr = 'SCRIPT_ERR_SIG_PUSHONLY'; return false; } if (stackCopy.length === 0) { throw new Error('internal error - stack copy empty'); } const redeemScriptSerialized = stackCopy[stackCopy.length - 1]; const redeemScript = Script.fromBuffer(redeemScriptSerialized); stackCopy.pop(); this.initialize(); this.script = redeemScript; this.stack = stackCopy; this.tx = tx; this.nin = nin; this.flags = flags || Interpreter.SCRIPT_VERIFY_NONE; this.satoshisBN = satoshisBN; if (!this.evaluate()) { return false; } if (stackCopy.length === 0) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK'; return false; } if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { this.errstr = 'SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK'; return false; } } if ((this.flags & Interpreter.SCRIPT_VERIFY_CLEANSTACK) != 0) { if ((this.flags & Interpreter.SCRIPT_VERIFY_P2SH) == 0) { throw new Error('internal error - CLEANSTACK without P2SH'); } if (stackCopy.length != 1) { this.errstr = 'SCRIPT_ERR_CLEANSTACK'; return false; } } return true; } checkRawSignatureEncoding(buf) { if (buf.length === 0) { return true; } if (Interpreter.isSchnorrSig(buf)) { return true; } if ((this.flags & (Interpreter.SCRIPT_VERIFY_DERSIG | Interpreter.SCRIPT_VERIFY_LOW_S | Interpreter.SCRIPT_VERIFY_STRICTENC)) !== 0 && !Signature.isDER(buf)) { this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT'; return false; } else if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) { const sig = Signature.fromDER(buf, false); if (!sig.hasLowS()) { this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S'; return false; } } return true; } checkSignatureEncoding(buf) { if (buf.length === 0) { return true; } try { const sig = Signature.fromDER(buf); if ((this.flags & Interpreter.SCRIPT_VERIFY_LOW_S) !== 0) { if (sig.s > new BN('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 16)) { this.errstr = 'SCRIPT_ERR_SIG_DER_HIGH_S'; return false; } } const hashType = buf[buf.length - 1]; if (hashType < 0x80 || hashType > 0x84) { this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; return false; } if ((this.flags & Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID) !== 0) { if ((hashType & 0x40) === 0) { this.errstr = 'SCRIPT_ERR_ILLEGAL_FORKID'; return false; } } else { if ((hashType & 0x40) !== 0) { this.errstr = 'SCRIPT_ERR_MUST_USE_FORKID'; return false; } } return true; } catch (e) { this.errstr = 'SCRIPT_ERR_SIG_DER_INVALID_FORMAT'; return false; } } checkTxSignatureEncoding(buf) { if (buf.length === 0) { return true; } if (!this.checkRawSignatureEncoding(buf.subarray(0, buf.length - 1))) { return false; } if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { const sig = Signature.fromTxFormat(buf); if (!sig.hasDefinedHashtype()) { this.errstr = 'SCRIPT_ERR_SIG_HASHTYPE'; return false; } const isTaprootKeyPath = (this.flags & Interpreter.SCRIPT_TAPROOT_KEY_SPEND_PATH) !== 0; if (!isTaprootKeyPath) { if (!(this.flags & Interpreter.SCRIPT_ENABLE_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)) { this.errstr = 'SCRIPT_ERR_MUST_USE_FORKID'; return false; } } } return true; } checkDataSignatureEncoding(buf) { if (buf.length === 0) { return true; } return this.checkRawSignatureEncoding(buf); } checkPubkeyEncoding(buf) { if ((this.flags & Interpreter.SCRIPT_VERIFY_STRICTENC) !== 0) { if (!PublicKey.isValid(buf) && !this.isCompressedOrUncompressedPubkey(buf)) { this.errstr = 'SCRIPT_ERR_PUBKEYTYPE'; return false; } } return true; } isCompressedOrUncompressedPubkey(buf) { if (buf.length === 33) { return buf[0] === 0x02 || buf[0] === 0x03; } if (buf.length === 65) { return buf[0] === 0x04; } return false; } evaluate() { 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) { if (this.stack.length > Interpreter.MAX_STACK_SIZE) { this.errstr = 'SCRIPT_ERR_STACK_SIZE'; return false; } if (!this.step()) { return false; } } if (this.stack.length + this.altstack.length > Interpreter.MAX_STACK_SIZE) { this.errstr = 'SCRIPT_ERR_STACK_SIZE'; return false; } if (this.vfExec.length > 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } return true; } catch (e) { this.errstr = 'SCRIPT_ERR_UNKNOWN_ERROR: ' + e.message; return false; } } toScriptNumBuffer(value) { const num = typeof value === 'bigint' ? value : BigInt(value); if (num === 0n) { return Buffer.alloc(0); } const isNegative = num < 0n; const absNum = isNegative ? -num : num; const bytes = []; let temp = absNum; while (temp > 0n) { bytes.push(Number(temp & 0xffn)); temp >>= 8n; } if (isNegative) { if (bytes.length > 0 && (bytes[bytes.length - 1] & 0x80) !== 0) { bytes.push(0x80); } else if (bytes.length > 0) { bytes[bytes.length - 1] |= 0x80; } else { bytes.push(0x80); } } return Buffer.from(bytes); } fromScriptNumBuffer(buf) { if (buf.length === 0) { return 0n; } let result = 0n; for (let i = 0; i < buf.length; i++) { result |= BigInt(buf[i]) << BigInt(i * 8); } if (buf.length > 0 && (buf[buf.length - 1] & 0x80) !== 0) { const lastByte = buf[buf.length - 1] & 0x7f; result = (result & ~(0xffn << BigInt((buf.length - 1) * 8))) | (BigInt(lastByte) << BigInt((buf.length - 1) * 8)); result = -result; } return result; } castToBool(buf) { for (let i = 0; i < buf.length; i++) { if (buf[i] !== 0) { if (i === buf.length - 1 && buf[i] === 0x80) { return false; } return true; } } return false; } step() { if (this.pc >= this.script.chunks.length) { return true; } const chunk = this.script.chunks[this.pc]; this.pc++; const opcodenum = chunk.opcodenum; if (opcodenum === undefined) { this.errstr = 'SCRIPT_ERR_UNDEFINED_OPCODE'; return false; } if (chunk.buf && chunk.buf.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) { this.errstr = 'SCRIPT_ERR_PUSH_SIZE'; return false; } if (opcodenum > Opcode.OP_16 && ++this.nOpCount > Interpreter.MAX_OPCODE_COUNT) { this.errstr = 'SCRIPT_ERR_OP_COUNT'; return false; } if (this.isOpcodeDisabled(opcodenum)) { this.errstr = 'SCRIPT_ERR_DISABLED_OPCODE'; return false; } const fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; const fExec = this.vfExec.indexOf(false) === -1; if (fExec && opcodenum >= 0 && opcodenum <= Opcode.OP_PUSHDATA4) { if (fRequireMinimal && !this.script.checkMinimalPush(this.pc - 1)) { this.errstr = 'SCRIPT_ERR_MINIMALDATA'; return false; } if (!chunk.buf) { this.stack.push(Interpreter.false); } else if (chunk.len !== chunk.buf.length) { throw new Error('Length of push value not equal to length of data'); } else { this.stack.push(chunk.buf); } } else if (fExec || (Opcode.OP_IF <= opcodenum && opcodenum <= Opcode.OP_ENDIF)) { switch (opcodenum) { case Opcode.OP_1NEGATE: { this.stack.push(this.toScriptNumBuffer(-1)); break; } case Opcode.OP_1: case Opcode.OP_2: case Opcode.OP_3: case Opcode.OP_4: case Opcode.OP_5: case Opcode.OP_6: case Opcode.OP_7: case Opcode.OP_8: case Opcode.OP_9: case Opcode.OP_10: case Opcode.OP_11: case Opcode.OP_12: case Opcode.OP_13: case Opcode.OP_14: case Opcode.OP_15: case Opcode.OP_16: { const value = opcodenum - Opcode.OP_1 + 1; this.stack.push(this.toScriptNumBuffer(value)); break; } case Opcode.OP_NOP: case Opcode.OP_NOP1: case Opcode.OP_NOP4: case Opcode.OP_NOP5: case Opcode.OP_NOP6: case Opcode.OP_NOP7: case Opcode.OP_NOP8: case Opcode.OP_NOP9: case Opcode.OP_NOP10: { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } break; } case Opcode.OP_NOP2: case Opcode.OP_CHECKLOCKTIMEVERIFY: { if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } break; } if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; const nLockTime = BN.fromScriptNumBuffer(this.stack[this.stack.length - 1], fRequireMinimal, 5); if (nLockTime.lt(new BN(0))) { this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME'; return false; } if (!this.checkLockTime(nLockTime)) { this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME'; return false; } break; } case Opcode.OP_NOP3: case Opcode.OP_CHECKSEQUENCEVERIFY: { if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)) { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = 'SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS'; return false; } break; } if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; const nSequence = BN.fromScriptNumBuffer(this.stacktop(-1), fRequireMinimal, 5); if (nSequence.lt(new BN(0))) { this.errstr = 'SCRIPT_ERR_NEGATIVE_LOCKTIME'; return false; } if (!nSequence.and(Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG).isZero()) { break; } if (!this.checkSequence(nSequence)) { this.errstr = 'SCRIPT_ERR_UNSATISFIED_LOCKTIME'; return false; } break; } case Opcode.OP_IF: case Opcode.OP_NOTIF: { let fValue = false; if (fExec) { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } const buf = this.stacktop(-1); if (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALIF) { if (buf.length > 1) { this.errstr = 'SCRIPT_ERR_MINIMALIF'; return false; } if (buf.length == 1 && buf[0] != 1) { this.errstr = 'SCRIPT_ERR_MINIMALIF'; return false; } } fValue = Interpreter.castToBool(buf); if (opcodenum === Opcode.OP_NOTIF) { fValue = !fValue; } this.stack.pop(); } this.vfExec.push(fValue); break; } case Opcode.OP_ELSE: { if (this.vfExec.length === 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } this.vfExec[this.vfExec.length - 1] = !this.vfExec[this.vfExec.length - 1]; break; } case Opcode.OP_ENDIF: { if (this.vfExec.length === 0) { this.errstr = 'SCRIPT_ERR_UNBALANCED_CONDITIONAL'; return false; } this.vfExec.pop(); break; } case Opcode.OP_VERIFY: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stacktop(-1); const fValue = Interpreter.castToBool(buf); if (fValue) { this.stack.pop(); } else { this.errstr = 'SCRIPT_ERR_VERIFY'; return false; } break; } case Opcode.OP_RETURN: this.errstr = 'SCRIPT_ERR_OP_RETURN'; return false; case Opcode.OP_TOALTSTACK: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.altstack.push(this.stack.pop()); break; } case Opcode.OP_FROMALTSTACK: { if (this.altstack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_ALTSTACK_OPERATION'; return false; } this.stack.push(this.altstack.pop()); break; } case Opcode.OP_2DROP: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.pop(); this.stack.pop(); break; } case Opcode.OP_2DUP: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 2]; const x2 = this.stack[this.stack.length - 1]; this.stack.push(x1); this.stack.push(x2); break; } case Opcode.OP_3DUP: { if (this.stack.length < 3) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 3]; const x2 = this.stack[this.stack.length - 2]; const x3 = this.stack[this.stack.length - 1]; this.stack.push(x1); this.stack.push(x2); this.stack.push(x3); break; } case Opcode.OP_2OVER: { if (this.stack.length < 4) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 4]; const x2 = this.stack[this.stack.length - 3]; this.stack.push(x1); this.stack.push(x2); break; } case Opcode.OP_2ROT: { if (this.stack.length < 6) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack.splice(this.stack.length - 6, 1)[0]; const x2 = this.stack.splice(this.stack.length - 5, 1)[0]; this.stack.push(x1); this.stack.push(x2); break; } case Opcode.OP_2SWAP: { if (this.stack.length < 4) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 4]; const x2 = this.stack[this.stack.length - 3]; const x3 = this.stack[this.stack.length - 2]; const x4 = this.stack[this.stack.length - 1]; this.stack[this.stack.length - 4] = x3; this.stack[this.stack.length - 3] = x4; this.stack[this.stack.length - 2] = x1; this.stack[this.stack.length - 1] = x2; break; } case Opcode.OP_DEPTH: { this.stack.push(this.toScriptNumBuffer(this.stack.length)); break; } case Opcode.OP_DROP: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.pop(); break; } case Opcode.OP_DUP: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.push(this.stack[this.stack.length - 1]); break; } case Opcode.OP_NIP: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.splice(this.stack.length - 2, 1); break; } case Opcode.OP_OVER: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } this.stack.push(this.stack[this.stack.length - 2]); break; } case Opcode.OP_PICK: case Opcode.OP_ROLL: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const n = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack.pop(); if (n < 0n || n >= BigInt(this.stack.length)) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const val = this.stack[this.stack.length - 1 - Number(n)]; if (opcodenum === Opcode.OP_ROLL) { this.stack.splice(this.stack.length - 1 - Number(n), 1); } this.stack.push(val); break; } case Opcode.OP_ROT: { if (this.stack.length < 3) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 3]; const x2 = this.stack[this.stack.length - 2]; const x3 = this.stack[this.stack.length - 1]; this.stack[this.stack.length - 3] = x2; this.stack[this.stack.length - 2] = x3; this.stack[this.stack.length - 1] = x1; break; } case Opcode.OP_SWAP: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 2]; const x2 = this.stack[this.stack.length - 1]; this.stack[this.stack.length - 2] = x2; this.stack[this.stack.length - 1] = x1; break; } case Opcode.OP_TUCK: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const x1 = this.stack[this.stack.length - 2]; const x2 = this.stack[this.stack.length - 1]; this.stack.splice(this.stack.length - 2, 0, x2); break; } case Opcode.OP_SIZE: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const size = this.stack[this.stack.length - 1].length; this.stack.push(this.toScriptNumBuffer(size)); break; } case Opcode.OP_AND: case Opcode.OP_OR: case Opcode.OP_XOR: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf1 = this.stack.pop(); const buf2 = this.stack.pop(); if (buf1.length !== buf2.length) { this.errstr = 'SCRIPT_ERR_INVALID_OPERAND_SIZE'; return false; } const result = Buffer.alloc(buf1.length); for (let i = 0; i < buf1.length; i++) { switch (opcodenum) { case Opcode.OP_AND: result[i] = buf1[i] & buf2[i]; break; case Opcode.OP_OR: result[i] = buf1[i] | buf2[i]; break; case Opcode.OP_XOR: result[i] = buf1[i] ^ buf2[i]; break; } } this.stack.push(result); break; } case Opcode.OP_EQUAL: case Opcode.OP_EQUALVERIFY: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf1 = this.stacktop(-2); const buf2 = this.stacktop(-1); const fEqual = buf1.toString('hex') === buf2.toString('hex'); this.stack.pop(); this.stack.pop(); this.stack.push(fEqual ? Interpreter.true : Interpreter.false); if (opcodenum === Opcode.OP_EQUALVERIFY) { if (fEqual) { this.stack.pop(); } else { this.errstr = 'SCRIPT_ERR_EQUALVERIFY'; return false; } } break; } case Opcode.OP_1ADD: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(bn + 1n); break; } case Opcode.OP_1SUB: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(bn - 1n); break; } case Opcode.OP_NEGATE: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(-bn); break; } case Opcode.OP_ABS: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(bn < 0n ? -bn : bn); break; } case Opcode.OP_NOT: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(bn === 0n ? 1n : 0n); break; } case Opcode.OP_0NOTEQUAL: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn = this.fromScriptNumBuffer(this.stack[this.stack.length - 1]); this.stack[this.stack.length - 1] = this.toScriptNumBuffer(bn !== 0n ? 1n : 0n); break; } case Opcode.OP_ADD: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); this.stack.push(this.toScriptNumBuffer(bn1 + bn2)); break; } case Opcode.OP_SUB: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); this.stack.push(this.toScriptNumBuffer(bn2 - bn1)); break; } case Opcode.OP_BOOLAND: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn1 !== 0n && bn2 !== 0n ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_BOOLOR: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn1 !== 0n || bn2 !== 0n ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_LESSTHAN: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn2 < bn1 ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_GREATERTHAN: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn2 > bn1 ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_LESSTHANOREQUAL: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn2 <= bn1 ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_GREATERTHANOREQUAL: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn2 >= bn1 ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_MIN: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn1 < bn2 ? bn1 : bn2; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_MAX: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn1 > bn2 ? bn1 : bn2; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_WITHIN: { if (this.stack.length < 3) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bn1 = this.fromScriptNumBuffer(this.stack.pop()); const bn2 = this.fromScriptNumBuffer(this.stack.pop()); const bn3 = this.fromScriptNumBuffer(this.stack.pop()); const result = bn3 >= bn2 && bn3 < bn1 ? 1n : 0n; this.stack.push(this.toScriptNumBuffer(result)); break; } case Opcode.OP_RIPEMD160: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stack.pop(); this.stack.push(Hash.ripemd160(buf)); break; } case Opcode.OP_SHA256: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stack.pop(); this.stack.push(Hash.sha256(buf)); break; } case Opcode.OP_HASH160: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stack.pop(); this.stack.push(Hash.sha256ripemd160(buf)); break; } case Opcode.OP_HASH256: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stack.pop(); this.stack.push(Hash.sha256sha256(buf)); break; } case Opcode.OP_CODESEPARATOR: { this.pbegincodehash = this.pc; break; } case Opcode.OP_CHECKDATASIG: case Opcode.OP_CHECKDATASIGVERIFY: { if (this.stack.length < 3) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const bufSig = this.stacktop(-3); const bufMessage = this.stacktop(-2); const bufPubkey = this.stacktop(-1); if (!this.checkDataSignatureEncoding(bufSig) || !this.checkPubkeyEncoding(bufPubkey)) { return false; } let fSuccess = false; try { const sig = Signature.fromDataFormat(bufSig); const pubkey = new PublicKey(bufPubkey); const bufHash = Hash.sha256(bufMessage); if (!sig.isSchnorr) { fSuccess = ECDSA.verify(bufHash, sig, pubkey, 'big'); } else { fSuccess = Schnorr.verify(bufHash, sig, pubkey, 'big'); } } catch (e) { fSuccess = false; } if (!fSuccess && this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL && bufSig.length) { this.errstr = 'SCRIPT_ERR_NULLFAIL'; return false; } this.stack.pop(); this.stack.pop(); this.stack.pop(); this.stack.push(fSuccess ? Interpreter.true : Interpreter.false); if (opcodenum === Opcode.OP_CHECKDATASIGVERIFY) { if (fSuccess) { this.stack.pop(); } else { this.errstr = 'SCRIPT_ERR_CHECKDATASIGVERIFY'; return false; } } break; } case Opcode.OP_REVERSEBYTES: { if (this.stack.length < 1) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const buf = this.stacktop(-1); const reversedBuf = Buffer.from(buf).reverse(); this.stack.pop(); this.stack.push(reversedBuf); break; } case Opcode.OP_CHECKSIG: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const sigBuf = this.stack[this.stack.length - 2]; const pubkeyBuf = this.stack[this.stack.length - 1]; if (!this.checkTxSignatureEncoding(sigBuf) || !this.checkPubkeyEncoding(pubkeyBuf)) { return false; } const subscript = new Script(); subscript.chunks = this.script.chunks.slice(this.pbegincodehash); const tmpScript = new Script().add(sigBuf); subscript.findAndDelete(tmpScript); let fSuccess = false; try { const signature = Signature.fromTxFormat(sigBuf); const pubkey = new PublicKey(pubkeyBuf); if (this.tx && this.nin !== undefined && this.satoshisBN !== undefined) { if (!signature.isSchnorr) { fSuccess = this.tx.verifySignature(signature, pubkey, this.nin, subscript, new BN(this.satoshisBN.toString()), this.flags); } else { fSuccess = this.tx.verifySignature(signature, pubkey, this.nin, subscript, new BN(this.satoshisBN.toString()), this.flags, 'schnorr'); } } } catch (e) { fSuccess = false; } if (!fSuccess && this.flags & Interpreter.SCRIPT_VERIFY_NULLFAIL && sigBuf.length) { this.errstr = 'SCRIPT_ERR_NULLFAIL'; return false; } this.stack.pop(); this.stack.pop(); this.stack.push(fSuccess ? Interpreter.true : Interpreter.false); break; } case Opcode.OP_CHECKSIGVERIFY: { if (this.stack.length < 2) { this.errstr = 'SCRIPT_ERR_INVALID_STACK_OPERATION'; return false; } const sigBuf2 = this.stack[this.stack.length - 2]; const pubkeyBuf2 = this.stack[this.stack.length - 1]; if (!this.checkTxSignatureEncoding(sigBuf2) || !this.checkPubkeyEncoding(pubkeyBuf2)) { return false; } const subscript2 = new Script(); subscript2.chunks = this.script.chunks.slice(this.pbegincodehash); const tmpScript2 = new Script().add(sigBuf2); subscript2.findAndDelete(tmpScript2); let fSuccess2 = false; try { const signature = Signature.fromTxFormat(sigBuf2); const pubkey = new PublicKey(pubkeyBuf2); if (this.tx && this.nin !== undefined && this.satoshisBN !== undefined) { if (!signature.isSchnorr) { fSuccess