UNPKG

@scrypt-inc/bitcoinjs-lib

Version:
1,045 lines 98.2 kB
import * as bscript from './script.js'; import { decode, isTxDER, SignatureVersion } from './script_signature.js'; import { isFinal, Transaction, varSliceSize, vectorSize, } from './transaction.js'; import { toPushdataCode } from './push_data.js'; import { bn2Buf, buf2BN } from './bn.js'; import { sha1 } from '@noble/hashes/sha1'; import { ripemd160 } from '@noble/hashes/ripemd160'; import { sha256 } from '@noble/hashes/sha256'; import * as tools from 'uint8array-tools'; import { hash160, hash256 } from './crypto.js'; import { isOpSuccess } from './ops.js'; import { BufferWriter } from './bufferutils.js'; import { rootHashFromPath, tapleafHash, tweakKey } from './payments/bip341.js'; import { ECPairFactory } from '@scrypt-inc/ecpair'; import { decodeSchnorrSignature } from './psbt/bip371.js'; import { getEccLib } from './ecc_lib.js'; function requireTrue(res, message) { if (!res) { throw new Error(message); } } export var InterpreterErr; (function (InterpreterErr) { InterpreterErr["NONE"] = ""; InterpreterErr["SCRIPT_ERR_SCRIPT_SIZE"] = "SCRIPT_ERR_SCRIPT_SIZE"; InterpreterErr["SCRIPT_ERR_DECOMPILE"] = "SCRIPT_ERR_DECOMPILE"; InterpreterErr["SCRIPT_ERR_SIG_PUSHONLY"] = "SCRIPT_ERR_SIG_PUSHONLY"; InterpreterErr["SCRIPT_ERR_EVAL_FALSE_NO_RESULT"] = "SCRIPT_ERR_EVAL_FALSE_NO_RESULT"; InterpreterErr["SCRIPT_ERR_UNBALANCED_CONDITIONAL"] = "SCRIPT_ERR_UNBALANCED_CONDITIONAL"; InterpreterErr["SCRIPT_ERR_CLEANSTACK"] = "SCRIPT_ERR_CLEANSTACK"; InterpreterErr["SCRIPT_ERR_WITNESS_UNEXPECTED"] = "SCRIPT_ERR_WITNESS_UNEXPECTED"; InterpreterErr["SCRIPT_ERR_UNDEFINED_OPCODE"] = "SCRIPT_ERR_UNDEFINED_OPCODE"; InterpreterErr["SCRIPT_ERR_PUSH_SIZE"] = "SCRIPT_ERR_PUSH_SIZE"; InterpreterErr["SCRIPT_ERR_OP_COUNT"] = "SCRIPT_ERR_OP_COUNT"; InterpreterErr["SCRIPT_ERR_DISABLED_OPCODE"] = "SCRIPT_ERR_DISABLED_OPCODE"; InterpreterErr["SCRIPT_ERR_OP_CODESEPARATOR"] = "SCRIPT_ERR_OP_CODESEPARATOR"; InterpreterErr["SCRIPT_ERR_MINIMALDATA"] = "SCRIPT_ERR_MINIMALDATA"; InterpreterErr["SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS"] = "SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS"; InterpreterErr["SCRIPT_ERR_INVALID_STACK_OPERATION"] = "SCRIPT_ERR_INVALID_STACK_OPERATION"; InterpreterErr["SCRIPT_ERR_NEGATIVE_LOCKTIME"] = "SCRIPT_ERR_NEGATIVE_LOCKTIME"; InterpreterErr["SCRIPT_ERR_UNSATISFIED_LOCKTIME"] = "SCRIPT_ERR_UNSATISFIED_LOCKTIME"; InterpreterErr["SCRIPT_ERR_TAPSCRIPT_MINIMALIF"] = "SCRIPT_ERR_TAPSCRIPT_MINIMALIF"; InterpreterErr["SCRIPT_ERR_MINIMALIF"] = "SCRIPT_ERR_MINIMALIF"; InterpreterErr["SCRIPT_ERR_VERIFY"] = "SCRIPT_ERR_VERIFY"; InterpreterErr["SCRIPT_ERR_OP_RETURN"] = "SCRIPT_ERR_OP_RETURN"; InterpreterErr["SCRIPT_ERR_EQUALVERIFY"] = "SCRIPT_ERR_EQUALVERIFY"; InterpreterErr["SCRIPT_ERR_NUMEQUALVERIFY"] = "SCRIPT_ERR_NUMEQUALVERIFY"; InterpreterErr["SCRIPT_ERR_INVALID_ALTSTACK_OPERATION"] = "SCRIPT_ERR_INVALID_ALTSTACK_OPERATION"; InterpreterErr["SCRIPT_ERR_CHECKSIGVERIFY"] = "SCRIPT_ERR_CHECKSIGVERIFY"; InterpreterErr["SCRIPT_ERR_BAD_OPCODE"] = "SCRIPT_ERR_BAD_OPCODE"; InterpreterErr["SCRIPT_ERR_PUBKEY_COUNT"] = "SCRIPT_ERR_PUBKEY_COUNT"; InterpreterErr["SCRIPT_ERR_SIG_COUNT"] = "SCRIPT_ERR_SIG_COUNT"; InterpreterErr["SCRIPT_ERR_NULLFAIL"] = "SCRIPT_ERR_NULLFAIL"; InterpreterErr["SCRIPT_ERR_SIG_NULLDUMMY"] = "SCRIPT_ERR_SIG_NULLDUMMY"; InterpreterErr["SCRIPT_ERR_CHECKMULTISIGVERIFY"] = "SCRIPT_ERR_CHECKMULTISIGVERIFY"; InterpreterErr["SCRIPT_ERR_SIG_NULLFAIL"] = "SCRIPT_ERR_SIG_NULLFAIL"; InterpreterErr["SCRIPT_ERR_SIG_FINDANDDELETE"] = "SCRIPT_ERR_SIG_FINDANDDELETE"; InterpreterErr["SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT"] = "SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT"; InterpreterErr["SCRIPT_ERR_PUBKEYTYPE"] = "SCRIPT_ERR_PUBKEYTYPE"; InterpreterErr["SCRIPT_ERR_SCHNORR_SIG"] = "SCRIPT_ERR_SCHNORR_SIG"; InterpreterErr["SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE"] = "SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE"; InterpreterErr["SCRIPT_ERR_WITNESS_PUBKEYTYPE"] = "SCRIPT_ERR_WITNESS_PUBKEYTYPE"; InterpreterErr["SCRIPT_ERR_SIG_HASHTYPE"] = "SCRIPT_ERR_SIG_HASHTYPE"; InterpreterErr["SCRIPT_ERR_SIG_DER_HIGH_S"] = "SCRIPT_ERR_SIG_DER_HIGH_S"; InterpreterErr["SCRIPT_ERR_SIG_DER_INVALID_FORMAT"] = "SCRIPT_ERR_SIG_DER_INVALID_FORMAT"; InterpreterErr["SCRIPT_ERR_WITNESS_MALLEATED"] = "SCRIPT_ERR_WITNESS_MALLEATED"; InterpreterErr["SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY"] = "SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY"; InterpreterErr["SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH"] = "SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH"; InterpreterErr["SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH"] = "SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH"; InterpreterErr["SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE"] = "SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE"; InterpreterErr["SCRIPT_ERR_DISCOURAGE_OP_SUCCESS"] = "SCRIPT_ERR_DISCOURAGE_OP_SUCCESS"; InterpreterErr["SCRIPT_ERR_STACK_SIZE"] = "SCRIPT_ERR_STACK_SIZE"; InterpreterErr["SCRIPT_ERR_EVAL_FALSE"] = "SCRIPT_ERR_EVAL_FALSE"; InterpreterErr["SCRIPT_ERR_EVAL_FALSE_IN_STACK"] = "SCRIPT_ERR_EVAL_FALSE_IN_STACK"; InterpreterErr["SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"] = "SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"; InterpreterErr["SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"] = "SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"; InterpreterErr["SCRIPT_ERR_SCHNORR_SIG_SIZE"] = "SCRIPT_ERR_SCHNORR_SIG_SIZE"; InterpreterErr["SCRIPT_ERR_SCHNORR_SIG_HASHTYPE"] = "SCRIPT_ERR_SCHNORR_SIG_HASHTYPE"; InterpreterErr["SCRIPT_ERR_SCHNORR_SIG_NO_PREVOUTS"] = "SCRIPT_ERR_SCHNORR_SIG_NO_PREVOUTS"; InterpreterErr["SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK"] = "SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK"; InterpreterErr["SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK"] = "SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK"; InterpreterErr["SCRIPT_ERR_WITNESS_MALLEATED_P2SH"] = "SCRIPT_ERR_WITNESS_MALLEATED_P2SH"; InterpreterErr["SCRIPT_ERR_UNKNOWN_ERROR"] = "SCRIPT_ERR_UNKNOWN_ERROR"; })(InterpreterErr || (InterpreterErr = {})); export class Interpreter { static MAX_SCRIPT_SIZE = 10000; static MAX_STACK_SIZE = 1000; static MAX_SCRIPT_ELEMENT_SIZE = 520; static LOCKTIME_THRESHOLD = 500000000; // flags taken from bitcoind // bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 static SCRIPT_VERIFY_NONE = 0; // Evaluate P2SH subscripts (softfork safe, BIP16). static SCRIPT_VERIFY_P2SH = 1 << 0; // Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure. // Passing a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) to checksig causes that pubkey to be // skipped (not softfork safe: this flag can widen the validity of OP_CHECKSIG OP_NOT). static SCRIPT_VERIFY_STRICTENC = 1 << 1; // Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP62 rule 1) static SCRIPT_VERIFY_DERSIG = 1 << 2; // Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure // (softfork safe, BIP62 rule 5). static SCRIPT_VERIFY_LOW_S = 1 << 3; // verify dummy stack item consumed by CHECKMULTISIG is of zero-length (softfork safe, BIP62 rule 7). static SCRIPT_VERIFY_NULLDUMMY = 1 << 4; // Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2). static SCRIPT_VERIFY_SIGPUSHONLY = 1 << 5; // Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE where possible, direct // pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, OP_PUSHDATA2 for anything larger). Evaluating // any other push causes the script to fail (BIP62 rule 3). // In addition, whenever a stack element is interpreted as a number, it must be of minimal length (BIP62 rule 4). // (softfork safe) static SCRIPT_VERIFY_MINIMALDATA = 1 << 6; // Discourage use of NOPs reserved for upgrades (NOP1-10) // // Provided so that nodes can avoid accepting or mining transactions // containing executed NOP's whose meaning may change after a soft-fork, // thus rendering the script invalid; with this flag set executing // discouraged NOPs fails the script. This verification flag will never be // a mandatory flag applied to scripts in a block. NOPs that are not // executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. static SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = 1 << 7; // Require that only a single stack element remains after evaluation. This // changes the success criterion from "At least one stack element must // remain, and when interpreted as a boolean, it must be true" to "Exactly // one stack element must remain, and when interpreted as a boolean, it must // be true". // (softfork safe, BIP62 rule 6) // Note: CLEANSTACK should never be used without P2SH or WITNESS. static SCRIPT_VERIFY_CLEANSTACK = 1 << 8; // Verify CHECKLOCKTIMEVERIFY // // See BIP65 for details. static SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = 1 << 9; // support CHECKSEQUENCEVERIFY opcode // // See BIP112 for details static SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = 1 << 10; // Support segregated witness // static SCRIPT_VERIFY_WITNESS = 1 << 11; // Making v1-v16 witness program non-standard // static SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM = 1 << 12; // // Segwit script only: Require the argument of OP_IF/NOTIF to be exactly // 0x01 or empty vector // static SCRIPT_VERIFY_MINIMALIF = 1 << 13; // Signature(s) must be empty vector if an CHECK(MULTI)SIG operation failed // static SCRIPT_VERIFY_NULLFAIL = 1 << 14; // Public keys in scripts must be compressed // static SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = 1 << 15; // Do we accept signature using SIGHASH_FORKID // static SCRIPT_ENABLE_SIGHASH_FORKID = 1 << 16; // Do we accept activate replay protection using a different fork id. // static SCRIPT_ENABLE_REPLAY_PROTECTION = 1 << 17; // Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts // static SCRIPT_VERIFY_CONST_SCRIPTCODE = 1 << 16; // Verify taproot script // static SCRIPT_VERIFY_TAPROOT = 1 << 17; // Making unknown Taproot leaf versions non-standard // static SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = 1 << 18; // Making unknown OP_SUCCESS non-standard static SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS = 1 << 19; // Making unknown public key versions (in BIP 342 scripts) non-standard static SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = 1 << 20; /* Below flags apply in the context of BIP 68*/ /** * If this flag set, CTxIn::nSequence is NOT interpreted as a relative * lock-time. */ static SEQUENCE_LOCKTIME_DISABLE_FLAG = 1 << 31; /** * If CTxIn::nSequence encodes a relative lock-time and this flag is set, * the relative lock-time has units of 512 seconds, otherwise it specifies * blocks with a granularity of 1. */ static SEQUENCE_LOCKTIME_TYPE_FLAG = 1 << 22; /** * If CTxIn::nSequence encodes a relative lock-time, this mask is applied to * extract that lock-time from the sequence field. */ static SEQUENCE_LOCKTIME_MASK = 0x0000ffff; /** Signature hash sizes */ static WITNESS_V0_SCRIPTHASH_SIZE = 32; static WITNESS_V0_KEYHASH_SIZE = 20; static WITNESS_V1_TAPROOT_SIZE = 32; static TAPROOT_LEAF_MASK = 0xfe; static TAPROOT_LEAF_TAPSCRIPT = 0xc0; static TAPROOT_CONTROL_BASE_SIZE = 33; static TAPROOT_CONTROL_NODE_SIZE = 32; static TAPROOT_CONTROL_MAX_NODE_COUNT = 128; // Validation weight per passing signature (Tapscript only, see BIP 342). static VALIDATION_WEIGHT_PER_SIGOP_PASSED = 50; // How much weight budget is added to the witness size (Tapscript only, see BIP 342). static VALIDATION_WEIGHT_OFFSET = 50; // Tag for input annex. If there are at least two witness elements for a transaction input, // and the first byte of the last element is 0x50, this last element is called annex, and // has meanings independent of the script static ANNEX_TAG = 0x50; static TRUE = Uint8Array.from([1]); static FALSE = Uint8Array.from([]); static TAPROOT_CONTROL_MAX_SIZE = Interpreter.TAPROOT_CONTROL_BASE_SIZE + Interpreter.TAPROOT_CONTROL_NODE_SIZE * Interpreter.TAPROOT_CONTROL_MAX_NODE_COUNT; // Conceptually, this doesn't really belong with the Interpreter, but I haven't found a better place for it. static PROTOCOL_VERSION = 70016; sigversion = SignatureVersion.BASE; errstr = InterpreterErr.NONE; stack = []; altstack = []; vfExec = []; pc = 0; pbegincodehash = 0; satoshis = 0; nOpCount = 0; flags = 0; execdata = {}; script = []; tx = new Transaction(); prevOuts = undefined; nin = 0; constructor() { this.initialize(); } getErr() { return this.errstr; } initialize() { this.stack = []; this.altstack = []; this.pc = 0; this.sigversion = SignatureVersion.BASE; this.satoshis = 0; this.pbegincodehash = 0; this.nOpCount = 0; this.vfExec = []; this.errstr = InterpreterErr.NONE; this.flags = 0; this.execdata = {}; } setScript(script) { if (script instanceof Uint8Array) { const fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; const res = bscript.decompile(script, fRequireMinimal); if (res === null) { return false; } this.script = res; } else { this.script = script; } return true; } setTx(tx) { this.tx = tx; } setPrevOuts(prevOuts) { if (Array.isArray(prevOuts)) { this.prevOuts = prevOuts; } else { this.prevOuts = undefined; } } setNin(nin) { this.nin = nin; } setFlags(flags) { this.flags = flags; } setStack(stack) { this.stack = stack; } setSigversion(sigversion) { this.sigversion = sigversion; } setSatoshis(satoshis) { this.satoshis = satoshis; } setExecdata(execdata) { this.execdata = execdata; } verify(scriptSig, scriptPubkey, tx = new Transaction(), nin = 0, flags = 0, witness = [], satoshis = 0, prevOuts) { this.setTx(tx); this.setNin(nin); this.setFlags(flags); this.setPrevOuts(prevOuts); if (!this.setScript(scriptSig)) { return false; } let stackCopy = []; if ((flags & Interpreter.SCRIPT_VERIFY_SIGPUSHONLY) !== 0 && !bscript.isPushOnly(this.script)) { this.errstr = InterpreterErr.SCRIPT_ERR_SIG_PUSHONLY; return false; } // evaluate scriptSig if (!this.evaluate()) { return false; } if (flags & Interpreter.SCRIPT_VERIFY_P2SH) { stackCopy = this.stack.slice(); } let stack = this.stack; this.initialize(); this.setStack(stack); this.setTx(tx); this.setPrevOuts(prevOuts); this.setNin(nin); this.setFlags(flags); if (!this.setScript(scriptPubkey)) { return false; } // evaluate scriptPubkey if (!this.evaluate()) { return false; } if (this.stack.length === 0) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE_NO_RESULT; return false; } const buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE_NO_RESULT; return false; } // SCRIPT_VERIFY_WITNESS let hadWitness = false; if (flags & Interpreter.SCRIPT_VERIFY_WITNESS) { const witnessProgram = bscript.createWitnessProgram(scriptPubkey); if (witnessProgram) { hadWitness = true; if (scriptSig.length !== 0) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_MALLEATED; return false; } if (!this.verifyWitnessProgram(witnessProgram.version, witnessProgram.program, witness, satoshis, this.flags, /* isP2SH */ false)) { return false; } } } // Additional validation for spend-to-script-hash transactions: if (flags & Interpreter.SCRIPT_VERIFY_P2SH && bscript.isScriptHashOut(scriptPubkey)) { // scriptSig must be literals-only or validation fails if (!bscript.isPushOnly(scriptSig)) { this.errstr = InterpreterErr.SCRIPT_ERR_SIG_PUSHONLY; return false; } // stackCopy cannot be empty here, because if it was the // P2SH HASH <> EQUAL scriptPubKey would be evaluated with // an empty stack and the EvalScript above would return false. if (stackCopy.length === 0) { throw new Error('internal error - stack copy empty'); } const redeemScript = stackCopy[stackCopy.length - 1]; stackCopy.pop(); this.initialize(); this.setStack(stackCopy); this.setTx(tx); this.setPrevOuts(prevOuts); this.setNin(nin); this.setFlags(flags); this.setScript(redeemScript); // evaluate redeemScript if (!this.evaluate()) { return false; } if (stackCopy.length === 0) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE_NO_P2SH_STACK; return false; } if (!Interpreter.castToBool(stackCopy[stackCopy.length - 1])) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE_IN_P2SH_STACK; return false; } if (flags & Interpreter.SCRIPT_VERIFY_WITNESS) { const p2shWitnessValues = bscript.createWitnessProgram(redeemScript); if (p2shWitnessValues) { hadWitness = true; const bw = BufferWriter.withCapacity(varSliceSize(redeemScript)); bw.writeVarSlice(redeemScript); if (tools.toHex(scriptSig) !== tools.toHex(bw.end())) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_MALLEATED_P2SH; return false; } if (!this.verifyWitnessProgram(p2shWitnessValues.version, p2shWitnessValues.program, witness, satoshis, this.flags, /* isP2SH */ true)) { return false; } // Bypass the cleanstack check at the end. The actual stack is obviously not clean // for witness programs. stack = [stack[0]]; } } } // The CLEANSTACK check is only performed after potential P2SH evaluation, // as the non-P2SH evaluation of a P2SH script will obviously not result in // a clean stack (the P2SH inputs remain). The same holds for witness // evaluation. if ((this.flags & Interpreter.SCRIPT_VERIFY_CLEANSTACK) != 0) { // 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 ((this.flags & Interpreter.SCRIPT_VERIFY_P2SH) == 0 || (this.flags & Interpreter.SCRIPT_VERIFY_WITNESS) == 0) { throw 'flags & SCRIPT_VERIFY_P2SH'; } if (stackCopy.length != 1) { this.errstr = InterpreterErr.SCRIPT_ERR_CLEANSTACK; return false; } } if (this.flags & Interpreter.SCRIPT_VERIFY_WITNESS) { if (!hadWitness && witness.length > 0) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_UNEXPECTED; return false; } } return true; } verifyWitnessProgram(version, program, witness, satoshis, flags, isP2SH = false) { let scriptPubKey = []; let stack = []; if (version === 0) { if (program.length === Interpreter.WITNESS_V0_SCRIPTHASH_SIZE) { if (witness.length === 0) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY; return false; } const scriptPubKeyBuffer = witness[witness.length - 1]; const res = bscript.decompile(scriptPubKeyBuffer); if (res === null) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY; return false; } scriptPubKey = res; const hash = sha256(scriptPubKeyBuffer); if (tools.toHex(hash) !== tools.toHex(program)) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH; return false; } stack = witness.slice(0, -1); return this.executeWitnessScript(scriptPubKey, stack, SignatureVersion.WITNESS_V0, satoshis, flags, {}); } else if (program.length === Interpreter.WITNESS_V0_KEYHASH_SIZE) { if (witness.length !== 2) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH; return false; } scriptPubKey.push(bscript.OPS.OP_DUP); scriptPubKey.push(bscript.OPS.OP_HASH160); scriptPubKey.push(program); scriptPubKey.push(bscript.OPS.OP_EQUALVERIFY); scriptPubKey.push(bscript.OPS.OP_CHECKSIG); stack = witness; return this.executeWitnessScript(scriptPubKey, stack, SignatureVersion.WITNESS_V0, satoshis, flags, {}); } else { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH; return false; } } else if (version === 1 && program.length == Interpreter.WITNESS_V1_TAPROOT_SIZE && !isP2SH) { const execdata = { annexPresent: false, annex: Uint8Array.from([]), annexInit: false, tapleafHash: Uint8Array.from([]), tapleafHashInit: false, validationWeightLeft: 0, validationWeightLeftInit: false, }; // BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey) if (!(flags & Interpreter.SCRIPT_VERIFY_TAPROOT)) { return true; } stack = Array.from(witness); if (stack.length == 0) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY; return false; } if (stack.length >= 2 && stack[stack.length - 1].length && stack[stack.length - 1][0] === Interpreter.ANNEX_TAG) { // Drop annex (this is non-standard; see IsWitnessStandard) const annex = stack.pop(); execdata.annex = annex; execdata.annexPresent = true; } execdata.annexInit = true; if (stack.length === 1) { // Key path spending (stack size is 1 after removing optional annex) return this.checkSchnorrSignature(stack[0], program, SignatureVersion.TAPROOT, execdata); } else { // Script path spending (stack size is >1 after removing optional annex) const control = stack.pop(); const scriptPubKeyBuf = stack.pop(); if (control.length < Interpreter.TAPROOT_CONTROL_BASE_SIZE || control.length > Interpreter.TAPROOT_CONTROL_MAX_SIZE || (control.length - Interpreter.TAPROOT_CONTROL_BASE_SIZE) % Interpreter.TAPROOT_CONTROL_NODE_SIZE != 0) { this.errstr = InterpreterErr.SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE; return false; } execdata.tapleafHash = Interpreter.computeTapleafHash(control[0] & Interpreter.TAPROOT_LEAF_MASK, scriptPubKeyBuf); if (!Interpreter.verifyTaprootCommitment(control, program, execdata.tapleafHash)) { this.errstr = InterpreterErr.SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH; return false; } execdata.tapleafHashInit = true; if ((control[0] & Interpreter.TAPROOT_LEAF_MASK) === Interpreter.TAPROOT_LEAF_TAPSCRIPT) { // Tapscript (leaf version 0xc0) let witnessSize; { const bw = BufferWriter.withCapacity(vectorSize(witness)); bw.writeVarInt(witness.length); for (const element of witness) { bw.writeVarSlice(element); } witnessSize = bw.end().length; } const res = bscript.decompile(scriptPubKeyBuf); if (res === null) { // Note how this condition would not be reached if an unknown OP_SUCCESSx was found this.errstr = InterpreterErr.SCRIPT_ERR_BAD_OPCODE; return false; } scriptPubKey = res; execdata.validationWeightLeft = witnessSize + Interpreter.VALIDATION_WEIGHT_OFFSET; execdata.validationWeightLeftInit = true; return this.executeWitnessScript(scriptPubKey, stack, SignatureVersion.TAPSCRIPT, satoshis, flags, execdata); } // If none of the above conditions are met then this must be an upgraded taproot version. if (flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION; return false; } // Future softfork compatibility return true; } } else if (flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM; return false; } // Other version/size/p2sh combinations return true for future softfork compatibility return true; } executeWitnessScript(scriptPubKey, stack, sigversion, satoshis, flags, execdata) { if (sigversion === SignatureVersion.TAPSCRIPT) { for (const chunk of scriptPubKey) { // New opcodes will be listed here. May use a different sigversion to modify existing opcodes. if (typeof chunk === 'number' && isOpSuccess(chunk)) { if (flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_OP_SUCCESS; return false; } return true; } } // Tapscript enforces initial stack size limits (altstack is empty here) if (stack.length > Interpreter.MAX_STACK_SIZE) { this.errstr = InterpreterErr.SCRIPT_ERR_STACK_SIZE; return false; } } // Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack if (stack.length && stack.some(elem => elem.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE)) { this.errstr = InterpreterErr.SCRIPT_ERR_PUSH_SIZE; return false; } this.initialize(); this.setStack(stack); this.setSigversion(sigversion); this.setSatoshis(satoshis); this.setFlags(flags); this.setExecdata(execdata); this.setScript(scriptPubKey); if (!this.evaluate()) { return false; } if (this.stack.length !== 1) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE; return false; } const buf = this.stack[this.stack.length - 1]; if (!Interpreter.castToBool(buf)) { this.errstr = InterpreterErr.SCRIPT_ERR_EVAL_FALSE_IN_STACK; return false; } return true; } /** * Based on bitcoind's EvalScript function, with the inner loop moved to * static readonly prototype.step() * bitcoind commit: b5d1b1092998bc95313856d535c632ea5a8f9104 */ evaluate() { // sigversion cannot be TAPROOT here, as it admits no script execution. requireTrue(this.sigversion == SignatureVersion.BASE || this.sigversion == SignatureVersion.WITNESS_V0 || this.sigversion == SignatureVersion.TAPSCRIPT, 'invalid sigversion'); if ((this.sigversion == SignatureVersion.BASE || this.sigversion == SignatureVersion.WITNESS_V0) && this.script.length > Interpreter.MAX_SCRIPT_SIZE) { this.errstr = InterpreterErr.SCRIPT_ERR_SCRIPT_SIZE; return false; } try { while (this.pc < this.script.length) { const fSuccess = this.step(); if (!fSuccess) { return false; } } } catch (e) { this.errstr = InterpreterErr.SCRIPT_ERR_UNKNOWN_ERROR; return false; } if (this.vfExec.length > 0) { this.errstr = InterpreterErr.SCRIPT_ERR_UNBALANCED_CONDITIONAL; return false; } return true; } static castToBool(buf) { for (let i = 0; i < buf.length; i++) { if (buf[i] !== 0) { // can be negative zero if (i === buf.length - 1 && buf[i] === 0x80) { return false; } return true; } } return false; } step() { const fRequireMinimal = (this.flags & Interpreter.SCRIPT_VERIFY_MINIMALDATA) !== 0; //bool fExec = !count(vfExec.begin(), vfExec.end(), false); const fExec = this.vfExec.indexOf(false) === -1; let buf, buf1, buf2, spliced, n, x1, x2, bn, bn1, bn2, bufSig, bufPubkey; //let sig, pubkey; let fValue, fSuccess; this.execdata = this.execdata || {}; if (!this.execdata.codeseparatorPosInit) { this.execdata.codeseparatorPos = 0xffffffff; this.execdata.codeseparatorPosInit = true; } // Read instruction const chunk = this.script[this.pc]; this.pc++; if (!(chunk instanceof Uint8Array || typeof chunk === 'number')) { this.errstr = InterpreterErr.SCRIPT_ERR_UNDEFINED_OPCODE; return false; } if (chunk instanceof Uint8Array && chunk.length > Interpreter.MAX_SCRIPT_ELEMENT_SIZE) { this.errstr = InterpreterErr.SCRIPT_ERR_PUSH_SIZE; return false; } const opcodenum = chunk instanceof Uint8Array ? toPushdataCode(chunk.length) : chunk; if (this.sigversion === SignatureVersion.BASE || this.sigversion === SignatureVersion.WITNESS_V0) { // Note how Opcode.OP_RESERVED does not count towards the opcode limit. if (opcodenum > bscript.OPS.OP_16 && ++this.nOpCount > 201) { this.errstr = InterpreterErr.SCRIPT_ERR_OP_COUNT; return false; } } if (opcodenum === bscript.OPS.OP_SUBSTR || opcodenum === bscript.OPS.OP_LEFT || opcodenum === bscript.OPS.OP_RIGHT || opcodenum === bscript.OPS.OP_INVERT || opcodenum === bscript.OPS.OP_AND || opcodenum === bscript.OPS.OP_OR || opcodenum === bscript.OPS.OP_XOR || opcodenum === bscript.OPS.OP_2MUL || opcodenum === bscript.OPS.OP_2DIV || opcodenum === bscript.OPS.OP_MUL || opcodenum === bscript.OPS.OP_DIV || opcodenum === bscript.OPS.OP_MOD || opcodenum === bscript.OPS.OP_LSHIFT || opcodenum === bscript.OPS.OP_RSHIFT) { this.errstr = InterpreterErr.SCRIPT_ERR_DISABLED_OPCODE; return false; } // With SCRIPT_VERIFY_CONST_SCRIPTCODE, OP_CODESEPARATOR in non-segwit script is rejected even in an unexecuted branch if (opcodenum == bscript.OPS.OP_CODESEPARATOR && this.sigversion === SignatureVersion.BASE && this.flags & Interpreter.SCRIPT_VERIFY_CONST_SCRIPTCODE) { this.errstr = InterpreterErr.SCRIPT_ERR_OP_CODESEPARATOR; return false; } if (fExec && 0 <= opcodenum && opcodenum <= bscript.OPS.OP_PUSHDATA4) { if (chunk instanceof Uint8Array) { this.stack.push(chunk); } else if (opcodenum === 0) { this.stack.push(Interpreter.FALSE); } else { this.errstr = InterpreterErr.SCRIPT_ERR_MINIMALDATA; return false; } } else if (fExec || (bscript.OPS.OP_IF <= opcodenum && opcodenum <= bscript.OPS.OP_ENDIF)) { switch (opcodenum) { // Push value case bscript.OPS.OP_1NEGATE: case bscript.OPS.OP_1: case bscript.OPS.OP_2: case bscript.OPS.OP_3: case bscript.OPS.OP_4: case bscript.OPS.OP_5: case bscript.OPS.OP_6: case bscript.OPS.OP_7: case bscript.OPS.OP_8: case bscript.OPS.OP_9: case bscript.OPS.OP_10: case bscript.OPS.OP_11: case bscript.OPS.OP_12: case bscript.OPS.OP_13: case bscript.OPS.OP_14: case bscript.OPS.OP_15: case bscript.OPS.OP_16: { // ( -- value) // ScriptNum bn((int)opcode - (int)(Opcode.OP_1 - 1)); n = opcodenum - (bscript.OPS.OP_1 - 1); buf = bn2Buf(BigInt(n)); this.stack.push(buf); // The result of these opcodes should always be the minimal way to push the data // they push, so no need for a CheckMinimalPush here. } break; // // Control // case bscript.OPS.OP_NOP: break; case bscript.OPS.OP_NOP2: case bscript.OPS.OP_CHECKLOCKTIMEVERIFY: if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { // not enabled; treat as a NOP2 if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS; return false; } break; } if (this.stack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } // Note that elsewhere numeric opcodes are limited to // operands in the range -2**31+1 to 2**31-1, however it is // legal for opcodes to produce results exceeding that // range. This limitation is implemented by CScriptNum's // default 4-byte limit. // // If we kept to that limit we'd have a year 2038 problem, // even though the nLockTime field in transactions // themselves is uint32 which only becomes meaningless // after the year 2106. // // 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. const nLockTime = buf2BN(this.stack[this.stack.length - 1], fRequireMinimal, 5); // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use // 0 MAX CHECKLOCKTIMEVERIFY. if (nLockTime < BigInt(0)) { this.errstr = InterpreterErr.SCRIPT_ERR_NEGATIVE_LOCKTIME; return false; } // Actually compare the specified lock time with the transaction. if (!this.checkLockTime(nLockTime)) { this.errstr = InterpreterErr.SCRIPT_ERR_UNSATISFIED_LOCKTIME; return false; } break; case bscript.OPS.OP_NOP3: case bscript.OPS.OP_CHECKSEQUENCEVERIFY: if (!(this.flags & Interpreter.SCRIPT_VERIFY_CHECKSEQUENCEVERIFY)) { // not enabled; treat as a NOP3 if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS; return false; } break; } if (this.stack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } // nSequence, like nLockTime, is a 32-bit unsigned // integer field. See the comment in CHECKLOCKTIMEVERIFY // regarding 5-byte numeric operands. const nSequence = buf2BN(this.stack[this.stack.length - 1], fRequireMinimal, 5); // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use // 0 MAX CHECKSEQUENCEVERIFY. if (nSequence < BigInt(0)) { this.errstr = InterpreterErr.SCRIPT_ERR_NEGATIVE_LOCKTIME; return false; } // To provide for future soft-fork extensibility, if the // operand has the disabled lock-time flag set, // CHECKSEQUENCEVERIFY behaves as a NOP. if ((Number(nSequence) & Interpreter.SEQUENCE_LOCKTIME_DISABLE_FLAG) != 0) { break; } // Actually compare the specified lock time with the transaction. if (!this.checkSequence(nSequence)) { this.errstr = InterpreterErr.SCRIPT_ERR_UNSATISFIED_LOCKTIME; return false; } break; case bscript.OPS.OP_NOP1: case bscript.OPS.OP_NOP4: case bscript.OPS.OP_NOP5: case bscript.OPS.OP_NOP6: case bscript.OPS.OP_NOP7: case bscript.OPS.OP_NOP8: case bscript.OPS.OP_NOP9: case bscript.OPS.OP_NOP10: { if (this.flags & Interpreter.SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { this.errstr = InterpreterErr.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS; return false; } } break; case bscript.OPS.OP_IF: case bscript.OPS.OP_NOTIF: { // <expression> if [statements] [else [statements]] endif // bool fValue = false; fValue = false; if (fExec) { if (this.stack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_UNBALANCED_CONDITIONAL; return false; } buf = this.stack[this.stack.length - 1]; // Tapscript requires minimal IF/NOTIF inputs as a consensus rule. if (this.sigversion === SignatureVersion.TAPSCRIPT) { // The input argument to the OP_IF and OP_NOTIF opcodes must be either // exactly 0 (the empty vector) or exactly 1 (the one-byte vector with value 1). if (buf.length > 1 || (buf.length === 1 && buf[0] !== 1)) { this.errstr = InterpreterErr.SCRIPT_ERR_TAPSCRIPT_MINIMALIF; return false; } } // Under witness v0 rules it is only a policy rule, enabled through SCRIPT_VERIFY_MINIMALIF. if (this.sigversion === SignatureVersion.WITNESS_V0 && this.flags & Interpreter.SCRIPT_VERIFY_MINIMALIF) { buf = this.stack[this.stack.length - 1]; if (buf.length > 1) { this.errstr = InterpreterErr.SCRIPT_ERR_MINIMALIF; return false; } if (buf.length == 1 && buf[0] != 1) { this.errstr = InterpreterErr.SCRIPT_ERR_MINIMALIF; return false; } } fValue = Interpreter.castToBool(buf); if (opcodenum === bscript.OPS.OP_NOTIF) { fValue = !fValue; } this.stack.pop(); } this.vfExec.push(fValue); } break; case bscript.OPS.OP_ELSE: { if (this.vfExec.length === 0) { this.errstr = InterpreterErr.SCRIPT_ERR_UNBALANCED_CONDITIONAL; return false; } this.vfExec[this.vfExec.length - 1] = !this.vfExec[this.vfExec.length - 1]; } break; case bscript.OPS.OP_ENDIF: { if (this.vfExec.length === 0) { this.errstr = InterpreterErr.SCRIPT_ERR_UNBALANCED_CONDITIONAL; return false; } this.vfExec.pop(); } break; case bscript.OPS.OP_VERIFY: { // (true -- ) or // (false -- false) and return if (this.stack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } buf = this.stack[this.stack.length - 1]; fValue = Interpreter.castToBool(buf); if (fValue) { this.stack.pop(); } else { this.errstr = InterpreterErr.SCRIPT_ERR_VERIFY; return false; } } break; case bscript.OPS.OP_RETURN: { this.errstr = InterpreterErr.SCRIPT_ERR_OP_RETURN; return false; } break; // // Stack ops // case bscript.OPS.OP_TOALTSTACK: { if (this.stack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } this.altstack.push(this.stack.pop()); } break; case bscript.OPS.OP_FROMALTSTACK: { if (this.altstack.length < 1) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_ALTSTACK_OPERATION; return false; } this.stack.push(this.altstack.pop()); } break; case bscript.OPS.OP_2DROP: { // (x1 x2 -- ) if (this.stack.length < 2) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } this.stack.pop(); this.stack.pop(); } break; case bscript.OPS.OP_2DUP: { // (x1 x2 -- x1 x2 x1 x2) if (this.stack.length < 2) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } buf1 = this.stack[this.stack.length - 2]; buf2 = this.stack[this.stack.length - 1]; this.stack.push(buf1); this.stack.push(buf2); } break; case bscript.OPS.OP_3DUP: { // (x1 x2 x3 -- x1 x2 x3 x1 x2 x3) if (this.stack.length < 3) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } buf1 = this.stack[this.stack.length - 3]; buf2 = this.stack[this.stack.length - 2]; const buf3 = this.stack[this.stack.length - 1]; this.stack.push(buf1); this.stack.push(buf2); this.stack.push(buf3); } break; case bscript.OPS.OP_2OVER: { // (x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2) if (this.stack.length < 4) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } buf1 = this.stack[this.stack.length - 4]; buf2 = this.stack[this.stack.length - 3]; this.stack.push(buf1); this.stack.push(buf2); } break; case bscript.OPS.OP_2ROT: { // (x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2) if (this.stack.length < 6) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } spliced = this.stack.splice(this.stack.length - 6, 2); this.stack.push(spliced[0]); this.stack.push(spliced[1]); } break; case bscript.OPS.OP_2SWAP: { // (x1 x2 x3 x4 -- x3 x4 x1 x2) if (this.stack.length < 4) { this.errstr = InterpreterErr.SCRIPT_ERR_INVALID_STACK_OPERATION; return false; } spliced = this.stack.splice(this.stack.length - 4, 2); this.stack.push(splice