Utils functions for decoding tx client side

This commit is contained in:
natsoni 2024-11-27 17:54:07 +01:00
parent 4c66bf61f0
commit 2de16322ae
No known key found for this signature in database
GPG key ID: C65917583181743B

View file

@ -1,8 +1,9 @@
import { TransactionFlags } from '@app/shared/filters.utils';
import { getVarIntLength, opcodes, parseMultisigScript, isPoint } from '@app/shared/script.utils';
import { Transaction } from '@interfaces/electrs.interface';
import { getVarIntLength, parseMultisigScript, isPoint } from '@app/shared/script.utils';
import { Transaction, Vin } from '@interfaces/electrs.interface';
import { CpfpInfo, RbfInfo, TransactionStripped } from '@interfaces/node-api.interface';
import { StateService } from '@app/services/state.service';
import { Hash } from './sha256';
// Bitcoin Core default policy settings
const MAX_STANDARD_TX_WEIGHT = 400_000;
@ -588,3 +589,744 @@ export function identifyPrioritizedTransactions(transactions: TransactionStrippe
return { prioritized, deprioritized };
}
function convertScriptSigAsm(hex: string): string {
const buf = new Uint8Array(hex.length / 2);
for (let i = 0; i < buf.length; i++) {
buf[i] = parseInt(hex.substr(i * 2, 2), 16);
}
const b = [];
let i = 0;
while (i < buf.length) {
const op = buf[i];
if (op >= 0x01 && op <= 0x4e) {
i++;
let push;
if (op === 0x4c) {
push = buf[i];
b.push('OP_PUSHDATA1');
i += 1;
} else if (op === 0x4d) {
push = buf[i] | (buf[i + 1] << 8);
b.push('OP_PUSHDATA2');
i += 2;
} else if (op === 0x4e) {
push = buf[i] | (buf[i + 1] << 8) | (buf[i + 2] << 16) | (buf[i + 3] << 24);
b.push('OP_PUSHDATA4');
i += 4;
} else {
push = op;
b.push('OP_PUSHBYTES_' + push);
}
const data = buf.slice(i, i + push);
if (data.length !== push) {
break;
}
b.push(uint8ArrayToHexString(data));
i += data.length;
} else {
if (op === 0x00) {
b.push('OP_0');
} else if (op === 0x4f) {
b.push('OP_PUSHNUM_NEG1');
} else if (op === 0xb1) {
b.push('OP_CLTV');
} else if (op === 0xb2) {
b.push('OP_CSV');
} else if (op === 0xba) {
b.push('OP_CHECKSIGADD');
} else {
const opcode = opcodes[op];
if (opcode) {
b.push(opcode);
} else {
b.push('OP_RETURN_' + op);
}
}
i += 1;
}
}
return b.join(' ');
}
/**
* This function must only be called when we know the witness we are parsing
* is a taproot witness.
* @param witness An array of hex strings that represents the witness stack of
* the input.
* @returns null if the witness is not a script spend, and the hex string of
* the script item if it is a script spend.
*/
function witnessToP2TRScript(witness: string[]): string | null {
if (witness.length < 2) return null;
// Note: see BIP341 for parsing details of witness stack
// If there are at least two witness elements, and the first byte of the
// last element is 0x50, this last element is called annex a and
// is removed from the witness stack.
const hasAnnex = witness[witness.length - 1].substring(0, 2) === '50';
// If there are at least two witness elements left, script path spending is used.
// Call the second-to-last stack element s, the script.
// (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack)
if (hasAnnex && witness.length < 3) return null;
const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2;
return witness[positionOfScript];
}
export function addInnerScriptsToVin(vin: Vin): void {
if (!vin.prevout) {
return;
}
if (vin.prevout.scriptpubkey_type === 'p2sh') {
const redeemScript = vin.scriptsig_asm.split(' ').reverse()[0];
vin.inner_redeemscript_asm = convertScriptSigAsm(redeemScript);
if (vin.witness && vin.witness.length > 2) {
const witnessScript = vin.witness[vin.witness.length - 1];
vin.inner_witnessscript_asm = convertScriptSigAsm(witnessScript);
}
}
if (vin.prevout.scriptpubkey_type === 'v0_p2wsh' && vin.witness) {
const witnessScript = vin.witness[vin.witness.length - 1];
vin.inner_witnessscript_asm = convertScriptSigAsm(witnessScript);
}
if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness) {
const witnessScript = witnessToP2TRScript(vin.witness);
if (witnessScript !== null) {
vin.inner_witnessscript_asm = convertScriptSigAsm(witnessScript);
}
}
}
function fromBuffer(buffer: Uint8Array, network: string): Transaction {
let offset = 0;
function readInt8(): number {
if (offset + 1 > buffer.length) {
throw new Error('Buffer out of bounds');
}
return buffer[offset++];
}
function readInt16() {
if (offset + 2 > buffer.length) {
throw new Error('Buffer out of bounds');
}
const value = buffer[offset] | (buffer[offset + 1] << 8);
offset += 2;
return value;
}
function readInt32(unsigned = false): number {
if (offset + 4 > buffer.length) {
throw new Error('Buffer out of bounds');
}
const value = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24);
offset += 4;
if (unsigned) {
return value >>> 0;
}
return value;
}
function readInt64(): bigint {
if (offset + 8 > buffer.length) {
throw new Error('Buffer out of bounds');
}
const low = BigInt(buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24));
const high = BigInt(buffer[offset + 4] | (buffer[offset + 5] << 8) | (buffer[offset + 6] << 16) | (buffer[offset + 7] << 24));
offset += 8;
return (high << 32n) | (low & 0xffffffffn);
}
function readVarInt(): bigint {
const first = readInt8();
if (first < 0xfd) {
return BigInt(first);
} else if (first === 0xfd) {
return BigInt(readInt16());
} else if (first === 0xfe) {
return BigInt(readInt32(true));
} else if (first === 0xff) {
return readInt64();
} else {
throw new Error("Invalid VarInt prefix");
}
}
function readSlice(n: number | bigint): Uint8Array {
const length = Number(n);
if (offset + length > buffer.length) {
throw new Error('Cannot read slice out of bounds');
}
const slice = buffer.slice(offset, offset + length);
offset += length;
return slice;
}
function readVarSlice(): Uint8Array {
return readSlice(readVarInt());
}
function readVector(): Uint8Array[] {
const count = readVarInt();
const vector = [];
for (let i = 0; i < count; i++) {
vector.push(readVarSlice());
}
return vector;
}
// Parse raw transaction
const tx = {
status: {
confirmed: null,
block_height: null,
block_hash: null,
block_time: null,
}
} as Transaction;
tx.version = readInt32();
const marker = readInt8();
const flag = readInt8();
let hasWitnesses = false;
if (
marker === 0x00 &&
flag === 0x01
) {
hasWitnesses = true;
} else {
offset -= 2;
}
const vinLen = readVarInt();
tx.vin = [];
for (let i = 0; i < vinLen; ++i) {
const txid = uint8ArrayToHexString(readSlice(32).reverse());
const vout = readInt32(true);
const scriptsig = uint8ArrayToHexString(readVarSlice());
const sequence = readInt32(true);
const is_coinbase = txid === '0'.repeat(64);
const scriptsig_asm = convertScriptSigAsm(scriptsig);
tx.vin.push({ txid, vout, scriptsig, sequence, is_coinbase, scriptsig_asm, prevout: null });
}
const voutLen = readVarInt();
tx.vout = [];
for (let i = 0; i < voutLen; ++i) {
const value = Number(readInt64());
const scriptpubkeyArray = readVarSlice();
const scriptpubkey = uint8ArrayToHexString(scriptpubkeyArray)
const scriptpubkey_asm = convertScriptSigAsm(scriptpubkey);
const toAddress = scriptPubKeyToAddress(scriptpubkey, network);
const scriptpubkey_type = toAddress.type;
const scriptpubkey_address = toAddress?.address;
tx.vout.push({ value, scriptpubkey, scriptpubkey_asm, scriptpubkey_type, scriptpubkey_address });
}
let witnessSize = 0;
if (hasWitnesses) {
const startOffset = offset;
for (let i = 0; i < vinLen; ++i) {
tx.vin[i].witness = readVector().map(uint8ArrayToHexString);
}
witnessSize = offset - startOffset + 2;
}
tx.locktime = readInt32(true);
if (offset !== buffer.length) {
throw new Error('Transaction has unexpected data');
}
tx.size = buffer.length;
tx.weight = (tx.size - witnessSize) * 3 + tx.size;
tx.txid = txid(tx);
return tx;
}
export function decodeRawTransaction(rawtx: string, network: string): Transaction {
if (!rawtx.length || rawtx.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(rawtx)) {
throw new Error('Invalid hex string');
}
const buffer = new Uint8Array(rawtx.length / 2);
for (let i = 0; i < rawtx.length; i += 2) {
buffer[i / 2] = parseInt(rawtx.substring(i, i + 2), 16);
}
return fromBuffer(buffer, network);
}
function serializeTransaction(tx: Transaction): Uint8Array {
const result: number[] = [];
// Add version
result.push(...intToBytes(tx.version, 4));
// Add input count and inputs
result.push(...varIntToBytes(tx.vin.length));
for (const input of tx.vin) {
result.push(...hexStringToUint8Array(input.txid).reverse());
result.push(...intToBytes(input.vout, 4));
const scriptSig = hexStringToUint8Array(input.scriptsig);
result.push(...varIntToBytes(scriptSig.length));
result.push(...scriptSig);
result.push(...intToBytes(input.sequence, 4));
}
// Add output count and outputs
result.push(...varIntToBytes(tx.vout.length));
for (const output of tx.vout) {
result.push(...bigIntToBytes(BigInt(output.value), 8));
const scriptPubKey = hexStringToUint8Array(output.scriptpubkey);
result.push(...varIntToBytes(scriptPubKey.length));
result.push(...scriptPubKey);
}
// Add locktime
result.push(...intToBytes(tx.locktime, 4));
return new Uint8Array(result);
}
function txid(tx: Transaction): string {
const serializedTx = serializeTransaction(tx);
const hash1 = new Hash().update(serializedTx).digest();
const hash2 = new Hash().update(hash1).digest();
return uint8ArrayToHexString(hash2.reverse());
}
export function countSigops(transaction: Transaction): number {
let sigops = 0;
for (const input of transaction.vin) {
if (input.scriptsig_asm) {
sigops += countScriptSigops(input.scriptsig_asm, true);
}
if (input.prevout) {
switch (true) {
case input.prevout.scriptpubkey_type === 'p2sh' && input.witness?.length === 2 && input.scriptsig && input.scriptsig.startsWith('160014'):
case input.prevout.scriptpubkey_type === 'v0_p2wpkh':
sigops += 1;
break;
case input.prevout?.scriptpubkey_type === 'p2sh' && input.witness?.length && input.scriptsig && input.scriptsig.startsWith('220020'):
case input.prevout.scriptpubkey_type === 'v0_p2wsh':
if (input.witness?.length) {
sigops += countScriptSigops(convertScriptSigAsm(input.witness[input.witness.length - 1]), false, true);
}
break;
case input.prevout.scriptpubkey_type === 'p2sh':
if (input.inner_redeemscript_asm) {
sigops += countScriptSigops(input.inner_redeemscript_asm);
}
break;
}
}
}
for (const output of transaction.vout) {
if (output.scriptpubkey_asm) {
sigops += countScriptSigops(output.scriptpubkey_asm, true);
}
}
return sigops;
}
function scriptPubKeyToAddress(scriptPubKey: string, network: string): { address: string, type: string } {
// P2PKH
if (/^76a914[0-9a-f]{40}88ac$/.test(scriptPubKey)) {
return { address: p2pkh(scriptPubKey.substring(6, 6 + 40), network), type: 'p2pkh' };
}
// P2PK
if (/^21[0-9a-f]{66}ac$/.test(scriptPubKey) || /^41[0-9a-f]{130}ac$/.test(scriptPubKey)) {
return { address: null, type: 'p2pk' };
}
// P2SH
if (/^a914[0-9a-f]{40}87$/.test(scriptPubKey)) {
return { address: p2sh(scriptPubKey.substring(4, 4 + 40), network), type: 'p2sh' };
}
// P2WPKH
if (/^0014[0-9a-f]{40}$/.test(scriptPubKey)) {
return { address: p2wpkh(scriptPubKey.substring(4, 4 + 40), network), type: 'v0_p2wpkh' };
}
// P2WSH
if (/^0020[0-9a-f]{64}$/.test(scriptPubKey)) {
return { address: p2wsh(scriptPubKey.substring(4, 4 + 64), network), type: 'v0_p2wsh' };
}
// P2TR
if (/^5120[0-9a-f]{64}$/.test(scriptPubKey)) {
return { address: p2tr(scriptPubKey.substring(4, 4 + 64), network), type: 'v1_p2tr' };
}
// multisig
if (/^[0-9a-f]+ae$/.test(scriptPubKey)) {
return { address: null, type: 'multisig' };
}
// anchor
if (scriptPubKey === '51024e73') {
return { address: 'bc1pfeessrawgf', type: 'anchor' };
}
// op_return
if (/^6a/.test(scriptPubKey)) {
return { address: null, type: 'op_return' };
}
return { address: null, type: 'unknown' };
}
function p2pkh(pubKeyHash: string, network: string): string {
const pubkeyHashArray = hexStringToUint8Array(pubKeyHash);
const version = ['testnet', 'testnet4', 'signet'].includes(network) ? 0x6f : 0x00;
const versionedPayload = Uint8Array.from([version, ...pubkeyHashArray]);
const hash1 = new Hash().update(versionedPayload).digest();
const hash2 = new Hash().update(hash1).digest();
const checksum = hash2.slice(0, 4);
const finalPayload = Uint8Array.from([...versionedPayload, ...checksum]);
const bitcoinAddress = base58Encode(finalPayload);
return bitcoinAddress;
}
function p2sh(scriptHash: string, network: string): string {
const scriptHashArray = hexStringToUint8Array(scriptHash);
const version = ['testnet', 'testnet4', 'signet'].includes(network) ? 0xc4 : 0x05;
const versionedPayload = Uint8Array.from([version, ...scriptHashArray]);
const hash1 = new Hash().update(versionedPayload).digest();
const hash2 = new Hash().update(hash1).digest();
const checksum = hash2.slice(0, 4);
const finalPayload = Uint8Array.from([...versionedPayload, ...checksum]);
const bitcoinAddress = base58Encode(finalPayload);
return bitcoinAddress;
}
function p2wpkh(pubKeyHash: string, network: string): string {
const pubkeyHashArray = hexStringToUint8Array(pubKeyHash);
const hrp = ['testnet', 'testnet4', 'signet'].includes(network) ? 'tb' : 'bc';
const version = 0;
const words = [version].concat(toWords(pubkeyHashArray));
const bech32Address = bech32Encode(hrp, words);
return bech32Address;
}
function p2wsh(scriptHash: string, network: string): string {
const scriptHashArray = hexStringToUint8Array(scriptHash);
const hrp = ['testnet', 'testnet4', 'signet'].includes(network) ? 'tb' : 'bc';
const version = 0;
const words = [version].concat(toWords(scriptHashArray));
const bech32Address = bech32Encode(hrp, words);
return bech32Address;
}
function p2tr(pubKeyHash: string, network: string): string {
const pubkeyHashArray = hexStringToUint8Array(pubKeyHash);
const hrp = ['testnet', 'testnet4', 'signet'].includes(network) ? 'tb' : 'bc';
const version = 1;
const words = [version].concat(toWords(pubkeyHashArray));
const bech32Address = bech32Encode(hrp, words, 0x2bc830a3);
return bech32Address;
}
// base58 encoding
function base58Encode(data: Uint8Array): string {
const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
let hexString = Array.from(data)
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
let num = BigInt("0x" + hexString);
let encoded = "";
while (num > 0) {
const remainder = Number(num % 58n);
num = num / 58n;
encoded = BASE58_ALPHABET[remainder] + encoded;
}
for (let byte of data) {
if (byte === 0) {
encoded = "1" + encoded;
} else {
break;
}
}
return encoded;
}
// bech32 encoding
// Adapted from https://github.com/bitcoinjs/bech32/blob/5ceb0e3d4625561a459c85643ca6947739b2d83c/src/index.ts
function bech32Encode(prefix: string, words: number[], constant: number = 1) {
const BECH32_ALPHABET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
const checksum = createChecksum(prefix, words, constant);
const combined = words.concat(checksum);
let result = prefix + '1';
for (let i = 0; i < combined.length; ++i) {
result += BECH32_ALPHABET.charAt(combined[i]);
}
return result;
}
function polymodStep(pre) {
const GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
const b = pre >> 25;
return (
((pre & 0x1ffffff) << 5) ^
((b & 1 ? GENERATORS[0] : 0) ^
(b & 2 ? GENERATORS[1] : 0) ^
(b & 4 ? GENERATORS[2] : 0) ^
(b & 8 ? GENERATORS[3] : 0) ^
(b & 16 ? GENERATORS[4] : 0))
);
}
function prefixChk(prefix) {
let chk = 1;
for (let i = 0; i < prefix.length; ++i) {
const c = prefix.charCodeAt(i);
chk = polymodStep(chk) ^ (c >> 5);
}
chk = polymodStep(chk);
for (let i = 0; i < prefix.length; ++i) {
const c = prefix.charCodeAt(i);
chk = polymodStep(chk) ^ (c & 0x1f);
}
return chk;
}
function createChecksum(prefix: string, words: number[], constant: number) {
const POLYMOD_CONST = constant;
let chk = prefixChk(prefix);
for (let i = 0; i < words.length; ++i) {
const x = words[i];
chk = polymodStep(chk) ^ x;
}
for (let i = 0; i < 6; ++i) {
chk = polymodStep(chk);
}
chk ^= POLYMOD_CONST;
const checksum = [];
for (let i = 0; i < 6; ++i) {
checksum.push((chk >> (5 * (5 - i))) & 31);
}
return checksum;
}
function convertBits(data, fromBits, toBits, pad) {
let acc = 0;
let bits = 0;
const ret = [];
const maxV = (1 << toBits) - 1;
for (let i = 0; i < data.length; ++i) {
const value = data[i];
if (value < 0 || value >> fromBits) throw new Error('Invalid value');
acc = (acc << fromBits) | value;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
ret.push((acc >> bits) & maxV);
}
}
if (pad) {
if (bits > 0) {
ret.push((acc << (toBits - bits)) & maxV);
}
} else if (bits >= fromBits || ((acc << (toBits - bits)) & maxV)) {
throw new Error('Invalid data');
}
return ret;
}
function toWords(bytes) {
return convertBits(bytes, 8, 5, true);
}
// Helper functions
function uint8ArrayToHexString(uint8Array: Uint8Array): string {
return Array.from(uint8Array).map(byte => byte.toString(16).padStart(2, '0')).join('');
}
function hexStringToUint8Array(hex: string): Uint8Array {
const buf = new Uint8Array(hex.length / 2);
for (let i = 0; i < buf.length; i++) {
buf[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return buf;
}
function intToBytes(value: number, byteLength: number): number[] {
const bytes = [];
for (let i = 0; i < byteLength; i++) {
bytes.push((value >> (8 * i)) & 0xff);
}
return bytes;
}
function bigIntToBytes(value: bigint, byteLength: number): number[] {
const bytes = [];
for (let i = 0; i < byteLength; i++) {
bytes.push(Number((value >> BigInt(8 * i)) & 0xffn));
}
return bytes;
}
function varIntToBytes(value: number | bigint): number[] {
const bytes = [];
if (typeof value === 'number') {
if (value < 0xfd) {
bytes.push(value);
} else if (value <= 0xffff) {
bytes.push(0xfd, value & 0xff, (value >> 8) & 0xff);
} else if (value <= 0xffffffff) {
bytes.push(0xfe, ...intToBytes(value, 4));
}
} else {
if (value < 0xfdn) {
bytes.push(Number(value));
} else if (value <= 0xffffn) {
bytes.push(0xfd, Number(value & 0xffn), Number((value >> 8n) & 0xffn));
} else if (value <= 0xffffffffn) {
bytes.push(0xfe, ...intToBytes(Number(value), 4));
} else {
bytes.push(0xff, ...bigIntToBytes(value, 8));
}
}
return bytes;
}
const opcodes = {
0: 'OP_0',
76: 'OP_PUSHDATA1',
77: 'OP_PUSHDATA2',
78: 'OP_PUSHDATA4',
79: 'OP_PUSHNUM_NEG1',
80: 'OP_RESERVED',
81: 'OP_PUSHNUM_1',
82: 'OP_PUSHNUM_2',
83: 'OP_PUSHNUM_3',
84: 'OP_PUSHNUM_4',
85: 'OP_PUSHNUM_5',
86: 'OP_PUSHNUM_6',
87: 'OP_PUSHNUM_7',
88: 'OP_PUSHNUM_8',
89: 'OP_PUSHNUM_9',
90: 'OP_PUSHNUM_10',
91: 'OP_PUSHNUM_11',
92: 'OP_PUSHNUM_12',
93: 'OP_PUSHNUM_13',
94: 'OP_PUSHNUM_14',
95: 'OP_PUSHNUM_15',
96: 'OP_PUSHNUM_16',
97: 'OP_NOP',
98: 'OP_VER',
99: 'OP_IF',
100: 'OP_NOTIF',
101: 'OP_VERIF',
102: 'OP_VERNOTIF',
103: 'OP_ELSE',
104: 'OP_ENDIF',
105: 'OP_VERIFY',
106: 'OP_RETURN',
107: 'OP_TOALTSTACK',
108: 'OP_FROMALTSTACK',
109: 'OP_2DROP',
110: 'OP_2DUP',
111: 'OP_3DUP',
112: 'OP_2OVER',
113: 'OP_2ROT',
114: 'OP_2SWAP',
115: 'OP_IFDUP',
116: 'OP_DEPTH',
117: 'OP_DROP',
118: 'OP_DUP',
119: 'OP_NIP',
120: 'OP_OVER',
121: 'OP_PICK',
122: 'OP_ROLL',
123: 'OP_ROT',
124: 'OP_SWAP',
125: 'OP_TUCK',
126: 'OP_CAT',
127: 'OP_SUBSTR',
128: 'OP_LEFT',
129: 'OP_RIGHT',
130: 'OP_SIZE',
131: 'OP_INVERT',
132: 'OP_AND',
133: 'OP_OR',
134: 'OP_XOR',
135: 'OP_EQUAL',
136: 'OP_EQUALVERIFY',
137: 'OP_RESERVED1',
138: 'OP_RESERVED2',
139: 'OP_1ADD',
140: 'OP_1SUB',
141: 'OP_2MUL',
142: 'OP_2DIV',
143: 'OP_NEGATE',
144: 'OP_ABS',
145: 'OP_NOT',
146: 'OP_0NOTEQUAL',
147: 'OP_ADD',
148: 'OP_SUB',
149: 'OP_MUL',
150: 'OP_DIV',
151: 'OP_MOD',
152: 'OP_LSHIFT',
153: 'OP_RSHIFT',
154: 'OP_BOOLAND',
155: 'OP_BOOLOR',
156: 'OP_NUMEQUAL',
157: 'OP_NUMEQUALVERIFY',
158: 'OP_NUMNOTEQUAL',
159: 'OP_LESSTHAN',
160: 'OP_GREATERTHAN',
161: 'OP_LESSTHANOREQUAL',
162: 'OP_GREATERTHANOREQUAL',
163: 'OP_MIN',
164: 'OP_MAX',
165: 'OP_WITHIN',
166: 'OP_RIPEMD160',
167: 'OP_SHA1',
168: 'OP_SHA256',
169: 'OP_HASH160',
170: 'OP_HASH256',
171: 'OP_CODESEPARATOR',
172: 'OP_CHECKSIG',
173: 'OP_CHECKSIGVERIFY',
174: 'OP_CHECKMULTISIG',
175: 'OP_CHECKMULTISIGVERIFY',
176: 'OP_NOP1',
177: 'OP_CHECKLOCKTIMEVERIFY',
178: 'OP_CHECKSEQUENCEVERIFY',
179: 'OP_NOP4',
180: 'OP_NOP5',
181: 'OP_NOP6',
182: 'OP_NOP7',
183: 'OP_NOP8',
184: 'OP_NOP9',
185: 'OP_NOP10',
186: 'OP_CHECKSIGADD',
253: 'OP_PUBKEYHASH',
254: 'OP_PUBKEY',
255: 'OP_INVALIDOPCODE',
};