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
JavaScript
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