@scrypt-inc/bitcoinjs-lib
Version:
Client-side Bitcoin JavaScript library
1,045 lines • 98.2 kB
JavaScript
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