lotus-sdk
Version:
Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem
1,113 lines (1,112 loc) • 38.2 kB
JavaScript
import { Preconditions } from './util/preconditions.js';
import { BufferReader } from './encoding/bufferreader.js';
import { BufferWriter } from './encoding/bufferwriter.js';
import { Hash } from './crypto/hash.js';
import { Opcode } from './opcode.js';
import { PublicKey } from './publickey.js';
import { Address } from './address.js';
import { BitcoreError } from './errors.js';
import { BufferUtil } from './util/buffer.js';
import { Signature } from './crypto/signature.js';
import { Chunk } from './chunk.js';
import { TAPROOT_SIZE_WITH_STATE, TAPROOT_SIZE_WITHOUT_STATE, } from './taproot.js';
export class Script {
chunks;
_network;
constructor(from) {
if (Buffer.isBuffer(from)) {
return Script.fromBuffer(from);
}
else if (from instanceof Address) {
return Script.fromAddress(from);
}
else if (from instanceof Script) {
return Script.fromBuffer(from.toBuffer());
}
else if (typeof from === 'string') {
return Script.fromString(from);
}
else if (typeof from === 'object' &&
from !== null &&
Array.isArray(from.chunks)) {
this.set(from);
}
else {
this.chunks = [];
}
}
set(obj) {
Preconditions.checkArgument(typeof obj === 'object', 'obj', 'Must be an object');
Preconditions.checkArgument(Array.isArray(obj.chunks), 'obj.chunks', 'Must be an array');
this.chunks = obj.chunks;
this._network = obj._network;
return this;
}
static fromBuffer(buffer) {
const script = new Script();
script.chunks = [];
const br = new BufferReader(buffer);
while (!br.finished()) {
try {
const opcodenum = br.readUInt8();
let len, buf;
if (opcodenum > 0 && opcodenum < Opcode.OP_PUSHDATA1) {
len = opcodenum;
script.chunks.push(new Chunk({
buf: br.read(len),
len: len,
opcodenum: opcodenum,
}));
}
else if (opcodenum === Opcode.OP_PUSHDATA1) {
len = br.readUInt8();
buf = br.read(len);
script.chunks.push(new Chunk({
buf: buf,
len: len,
opcodenum: opcodenum,
}));
}
else if (opcodenum === Opcode.OP_PUSHDATA2) {
len = br.readUInt16LE();
buf = br.read(len);
script.chunks.push(new Chunk({
buf: buf,
len: len,
opcodenum: opcodenum,
}));
}
else if (opcodenum === Opcode.OP_PUSHDATA4) {
len = br.readUInt32LE();
buf = br.read(len);
script.chunks.push(new Chunk({
buf: buf,
len: len,
opcodenum: opcodenum,
}));
}
else {
script.chunks.push(new Chunk({
opcodenum: opcodenum,
}));
}
}
catch (e) {
if (e instanceof RangeError) {
throw new BitcoreError.Script.InvalidBuffer(buffer.toString('hex'));
}
throw e;
}
}
return script;
}
static fromString(str) {
Preconditions.checkArgument(typeof str === 'string', 'str', 'Must be a string');
const cleanStr = str.replace(/\s/g, '').toLowerCase();
if (!/^[0-9a-f]*$/.test(cleanStr)) {
throw new BitcoreError.Script.InvalidScriptString(str);
}
const buffer = Buffer.from(cleanStr, 'hex');
return Script.fromBuffer(buffer);
}
static fromASM(str) {
const script = new Script();
script.chunks = [];
const tokens = str.split(' ');
let i = 0;
while (i < tokens.length) {
const token = tokens[i];
const opcode = new Opcode(token);
const opcodenum = opcode.num;
if (opcodenum === undefined) {
const buf = Buffer.from(tokens[i], 'hex');
let opcodenum;
const len = buf.length;
if (len >= 0 && len < Opcode.OP_PUSHDATA1) {
opcodenum = len;
}
else if (len < Math.pow(2, 8)) {
opcodenum = Opcode.OP_PUSHDATA1;
}
else if (len < Math.pow(2, 16)) {
opcodenum = Opcode.OP_PUSHDATA2;
}
else if (len < Math.pow(2, 32)) {
opcodenum = Opcode.OP_PUSHDATA4;
}
else {
throw new Error('Invalid push data length');
}
script.chunks.push(new Chunk({
buf: buf,
len: buf.length,
opcodenum: opcodenum,
}));
i = i + 1;
}
else {
script.chunks.push(new Chunk({
opcodenum: opcodenum,
}));
i = i + 1;
}
}
return script;
}
static fromHex(str) {
return new Script(Buffer.from(str, 'hex'));
}
static fromAddress(address) {
if (typeof address === 'string') {
address = Address.fromString(address);
}
if (address.isPayToTaproot()) {
return Script.buildPayToTaproot(address.hashBuffer);
}
else if (address.isPayToScriptHash()) {
return Script.buildScriptHashOut(address);
}
else if (address.isPayToPublicKeyHash()) {
return Script.buildPublicKeyHashOut(address);
}
throw new BitcoreError.Script.UnrecognizedAddress(address);
}
static buildMultisigOut(publicKeys, threshold, opts = {}) {
Preconditions.checkArgument(threshold <= publicKeys.length, 'threshold', 'Number of required signatures must be less than or equal to the number of public keys');
const script = new Script();
script.add(Opcode.OP_1 + threshold - 1);
const sorted = opts.noSorting
? publicKeys
: publicKeys.sort((a, b) => a.toString().localeCompare(b.toString()));
for (const pubkey of sorted) {
script.add(pubkey.toBuffer());
}
script.add(Opcode.OP_1 + publicKeys.length - 1);
script.add(Opcode.OP_CHECKMULTISIG);
return script;
}
static buildPublicKeyHashOut(to) {
Preconditions.checkArgument(to !== undefined, 'to', 'Must be defined');
Preconditions.checkArgument(to instanceof PublicKey ||
to instanceof Address ||
typeof to === 'string', 'to', 'Must be PublicKey, Address, or string');
let address;
if (to instanceof PublicKey) {
address = to.toAddress();
}
else if (typeof to === 'string') {
address = Address.fromString(to);
}
else {
address = to;
}
const script = new Script();
script.chunks = [];
script
.add(Opcode.OP_DUP)
.add(Opcode.OP_HASH160)
.add(address.hashBuffer)
.add(Opcode.OP_EQUALVERIFY)
.add(Opcode.OP_CHECKSIG);
script._network = address.network;
return script;
}
static buildScriptHashOut(script) {
Preconditions.checkArgument(script instanceof Script ||
(script instanceof Address && script.isPayToScriptHash()), 'script', 'Must be Script or P2SH Address');
const s = new Script();
s.add(Opcode.OP_HASH160)
.add(script instanceof Address
? script.hashBuffer
: Hash.sha256ripemd160(script.toBuffer()))
.add(Opcode.OP_EQUAL);
s._network =
script._network ||
script.network;
return s;
}
static buildMultisigIn(pubkeys, threshold, signatures, opts = {}) {
const script = new Script();
if (opts.signingMethod === 'schnorr' && opts.checkBits) {
const N = pubkeys.length;
let checkBitsValue = 0;
for (let i = 0; i < opts.checkBits.length; i++) {
checkBitsValue |= opts.checkBits[i] << (8 * i);
}
if (N >= 1 && N <= 4) {
script.add(Opcode.OP_1 + checkBitsValue - 1);
}
else if (N >= 5 && N <= 8) {
if (checkBitsValue === 0x81 && N === 8 && threshold === 2) {
script.add(Opcode.OP_1NEGATE);
}
else if (checkBitsValue >= 0x01 && checkBitsValue <= 0x10) {
script.add(Opcode.OP_1 + checkBitsValue - 1);
}
else {
script.add(0x01);
script.add(checkBitsValue);
}
}
else if (N >= 9 && N <= 16) {
script.add(0x02);
script.add(checkBitsValue & 0xff);
script.add((checkBitsValue >> 8) & 0xff);
}
else if (N >= 17 && N <= 20) {
script.add(0x03);
script.add(checkBitsValue & 0xff);
script.add((checkBitsValue >> 8) & 0xff);
script.add((checkBitsValue >> 16) & 0xff);
}
}
else {
script.add(Opcode.OP_0);
}
for (const sig of signatures) {
script.add(sig);
}
script.add((opts.cachedMultisig || Script.buildMultisigOut(pubkeys, threshold, opts)).toBuffer());
return script;
}
static buildP2SHMultisigIn(pubkeys, threshold, signatures, opts = {}) {
const script = new Script();
if (opts.signingMethod === 'schnorr' && opts.checkBits) {
const N = pubkeys.length;
let checkBitsValue = 0;
for (let i = 0; i < opts.checkBits.length; i++) {
checkBitsValue |= opts.checkBits[i] << (8 * i);
}
if (N >= 1 && N <= 4) {
script.add(Opcode.OP_1 + checkBitsValue - 1);
}
else if (N >= 5 && N <= 8) {
if (checkBitsValue === 0x81 && N === 8 && threshold === 2) {
script.add(Opcode.OP_1NEGATE);
}
else if (checkBitsValue >= 0x01 && checkBitsValue <= 0x10) {
script.add(Opcode.OP_1 + checkBitsValue - 1);
}
else {
script.add(0x01);
script.add(checkBitsValue);
}
}
else if (N >= 9 && N <= 16) {
script.add(0x02);
script.add(checkBitsValue & 0xff);
script.add((checkBitsValue >> 8) & 0xff);
}
else if (N >= 17 && N <= 20) {
script.add(0x03);
script.add(checkBitsValue & 0xff);
script.add((checkBitsValue >> 8) & 0xff);
script.add((checkBitsValue >> 16) & 0xff);
}
}
else {
script.add(Opcode.OP_0);
}
for (const sig of signatures) {
script.add(sig);
}
script.add((opts.cachedMultisig || Script.buildMultisigOut(pubkeys, threshold, opts)).toBuffer());
return script;
}
static buildWitnessMultisigOutFromScript(script) {
const scriptHash = Hash.sha256(script.toBuffer());
const witnessScript = new Script();
witnessScript.add(Opcode.OP_0);
witnessScript.add(scriptHash);
return witnessScript;
}
static buildPublicKeyOut(pubkey) {
const script = new Script();
script.add(pubkey.toBuffer());
script.add(Opcode.OP_CHECKSIG);
return script;
}
static buildDataOut(data, encoding = 'utf8') {
let buffer;
if (typeof data === 'string') {
if (encoding === 'hex') {
buffer = Buffer.from(data, 'hex');
}
else {
buffer = Buffer.from(data, 'utf8');
}
}
else {
buffer = data;
}
const script = new Script();
script.add(Opcode.OP_RETURN);
script.add(buffer);
return script;
}
static buildPublicKeyIn(signature, sigtype) {
const script = new Script();
if (signature instanceof Signature) {
if (signature.isSchnorr) {
script.add(signature.toTxFormat('schnorr'));
}
else {
signature = signature.toTxFormat();
script.add(BufferUtil.concat([
signature,
BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL),
]));
}
}
else {
script.add(BufferUtil.concat([
signature,
BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL),
]));
}
return script;
}
static buildPublicKeyHashIn(publicKey, signature, sigtype) {
const script = new Script();
if (signature instanceof Signature) {
if (signature.isSchnorr) {
script.add(signature.toTxFormat('schnorr'));
}
else {
signature = signature.toTxFormat();
script.add(BufferUtil.concat([
signature,
BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL),
]));
}
}
else {
script.add(BufferUtil.concat([
signature,
BufferUtil.integerAsSingleByteBuffer(sigtype || Signature.SIGHASH_ALL),
]));
}
script.add(publicKey.toBuffer());
return script;
}
static buildPayToTaproot(commitment, state) {
Preconditions.checkArgument(commitment !== undefined, 'commitment', 'Must be defined');
const commitmentBuf = commitment instanceof PublicKey ? commitment.toBuffer() : commitment;
if (commitmentBuf.length !== 33) {
throw new Error('Taproot commitment must be 33-byte compressed public key');
}
if (state && state.length !== 32) {
throw new Error('Taproot state must be exactly 32 bytes');
}
const script = new Script();
script.add(Opcode.OP_SCRIPTTYPE);
script.add(Opcode.OP_1);
script.add(commitmentBuf);
if (state) {
script.add(state);
}
return script;
}
add(chunk) {
if (chunk instanceof Opcode) {
this.chunks.push(new Chunk({
opcodenum: chunk.num,
}));
}
else if (Buffer.isBuffer(chunk)) {
const chunkObj = {
buf: chunk,
len: chunk.length,
opcodenum: chunk.length,
};
if (chunk.length < Opcode.OP_PUSHDATA1) {
chunkObj.opcodenum = chunk.length;
}
else if (chunk.length <= 0xff) {
chunkObj.opcodenum = Opcode.OP_PUSHDATA1;
}
else if (chunk.length <= 0xffff) {
chunkObj.opcodenum = Opcode.OP_PUSHDATA2;
}
else {
chunkObj.opcodenum = Opcode.OP_PUSHDATA4;
}
this.chunks.push(new Chunk(chunkObj));
}
else if (typeof chunk === 'number') {
this.chunks.push(new Chunk({
opcodenum: chunk,
}));
}
else {
throw new TypeError('Invalid chunk type');
}
return this;
}
toBuffer() {
const bw = new BufferWriter();
for (const chunk of this.chunks) {
bw.writeUInt8(chunk.opcodenum);
if (chunk.buf) {
if (chunk.opcodenum === Opcode.OP_PUSHDATA1) {
bw.writeUInt8(chunk.len);
}
else if (chunk.opcodenum === Opcode.OP_PUSHDATA2) {
bw.writeUInt16LE(chunk.len);
}
else if (chunk.opcodenum === Opcode.OP_PUSHDATA4) {
bw.writeUInt32LE(chunk.len);
}
bw.write(chunk.buf);
}
}
return bw.toBuffer();
}
toString() {
return this.toBuffer().toString('hex');
}
toP2PKH() {
if (!this.isPayToPublicKeyHash()) {
throw new Error('Script is not a P2PKH address');
}
return this.chunks[2].buf.toString('hex');
}
toP2SH() {
if (!this.isPayToScriptHash()) {
throw new Error('Script is not a P2SH address');
}
return this.chunks[1].buf.toString('hex');
}
toASM() {
let str = '';
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
str += this._chunkToString(chunk, 'asm');
}
return str.substring(1);
}
toHex() {
return this.toBuffer().toString('hex');
}
inspect() {
return '<Script: ' + this.toString() + '>';
}
_chunkToString(chunk, type) {
const opcodenum = chunk.opcodenum;
const asm = type === 'asm';
let str = '';
if (!chunk.buf) {
const opcodeNames = {};
for (const [name, value] of Object.entries(Opcode.map)) {
if (name === 'OP_FALSE' ||
name === 'OP_TRUE' ||
name === 'OP_NOP2' ||
name === 'OP_NOP3') {
continue;
}
if (!opcodeNames[value]) {
opcodeNames[value] = name;
}
}
if (opcodeNames[opcodenum]) {
if (asm) {
if (opcodenum === 0) {
str = str + ' 0';
}
else if (opcodenum === 79) {
str = str + ' -1';
}
else {
str = str + ' ' + opcodeNames[opcodenum];
}
}
else {
str = str + ' ' + opcodeNames[opcodenum];
}
}
else {
let numstr = opcodenum.toString(16);
if (numstr.length % 2 !== 0) {
numstr = '0' + numstr;
}
if (asm) {
str = str + ' ' + numstr;
}
else {
str = str + ' ' + '0x' + numstr;
}
}
}
else {
if (!asm &&
(opcodenum === Opcode.OP_PUSHDATA1 ||
opcodenum === Opcode.OP_PUSHDATA2 ||
opcodenum === Opcode.OP_PUSHDATA4)) {
str = str + ' ' + new Opcode(opcodenum).toString();
}
if (chunk.len > 0) {
if (asm) {
str = str + ' ' + chunk.buf.toString('hex');
}
else {
str = str + ' ' + chunk.len + ' ' + '0x' + chunk.buf.toString('hex');
}
}
}
return str;
}
isPayToPublicKeyHash() {
return (this.chunks.length === 5 &&
this.chunks[0].opcodenum === Opcode.OP_DUP &&
this.chunks[1].opcodenum === Opcode.OP_HASH160 &&
this.chunks[2].opcodenum === 20 &&
this.chunks[2].buf.length === 20 &&
this.chunks[3].opcodenum === Opcode.OP_EQUALVERIFY &&
this.chunks[4].opcodenum === Opcode.OP_CHECKSIG);
}
isPublicKeyHashOut() {
return !!(this.chunks.length === 5 &&
this.chunks[0].opcodenum === Opcode.OP_DUP &&
this.chunks[1].opcodenum === Opcode.OP_HASH160 &&
this.chunks[2].buf &&
this.chunks[2].buf.length === 20 &&
this.chunks[3].opcodenum === Opcode.OP_EQUALVERIFY &&
this.chunks[4].opcodenum === Opcode.OP_CHECKSIG);
}
isPayToScriptHash() {
return (this.chunks.length === 3 &&
this.chunks[0].opcodenum === Opcode.OP_HASH160 &&
this.chunks[1].opcodenum === 20 &&
this.chunks[1].buf.length === 20 &&
this.chunks[2].opcodenum === Opcode.OP_EQUAL);
}
isScriptHashOut() {
const buf = this.toBuffer();
return (buf.length === 23 &&
buf[0] === Opcode.OP_HASH160 &&
buf[1] === 0x14 &&
buf[buf.length - 1] === Opcode.OP_EQUAL);
}
isPayToTaproot() {
const buf = this.toBuffer();
if (buf.length < TAPROOT_SIZE_WITHOUT_STATE) {
return false;
}
if (buf[0] !== Opcode.OP_SCRIPTTYPE || buf[1] !== Opcode.OP_1) {
return false;
}
if (buf[2] !== 33) {
return false;
}
if (buf.length === TAPROOT_SIZE_WITHOUT_STATE) {
return true;
}
return buf.length === TAPROOT_SIZE_WITH_STATE && buf[36] === 32;
}
getData() {
if (this.isScriptHashOut()) {
if (this.chunks[1] === undefined) {
return Buffer.alloc(0);
}
else {
return Buffer.from(this.chunks[1].buf);
}
}
if (this.isPublicKeyHashOut()) {
return Buffer.from(this.chunks[2].buf);
}
throw new Error('Unrecognized script type to get data from');
}
getAddressInfo() {
if (this._isInput) {
return this._getInputAddressInfo();
}
else if (this._isOutput) {
return this._getOutputAddressInfo();
}
else {
const info = this._getOutputAddressInfo();
if (!info) {
return this._getInputAddressInfo();
}
return info;
}
}
_getOutputAddressInfo() {
const info = {};
if (this.isPayToTaproot()) {
const buf = this.toBuffer();
info.hashBuffer = buf.slice(3, 36);
info.type = Address.PayToTaproot;
}
else if (this.isScriptHashOut()) {
info.hashBuffer = this.getData();
info.type = Address.PayToScriptHash;
}
else if (this.isPublicKeyHashOut()) {
info.hashBuffer = this.getData();
info.type = Address.PayToPublicKeyHash;
}
else {
return null;
}
return new Address(info);
}
_getInputAddressInfo() {
const info = {};
if (this.isPublicKeyHashIn()) {
info.hashBuffer = Hash.sha256ripemd160(this.chunks[1].buf);
info.type = Address.PayToPublicKeyHash;
}
else if (this.isScriptHashIn()) {
info.hashBuffer = Hash.sha256ripemd160(this.chunks[this.chunks.length - 1].buf);
info.type = Address.PayToScriptHash;
}
else {
return null;
}
return new Address(info);
}
toAddress(network) {
const info = this.getAddressInfo();
if (!info) {
return null;
}
if (info instanceof Address) {
if (network) {
if (this.isPayToTaproot()) {
const buf = this.toBuffer();
const commitment = buf.slice(3, 36);
return Address.fromTaprootCommitment(commitment, network);
}
else if (this.isPublicKeyHashOut()) {
const hashBuffer = this.getData();
return Address.fromPublicKeyHash(hashBuffer, network);
}
else if (this.isScriptHashOut()) {
const hashBuffer = this.getData();
return Address.fromScriptHash(hashBuffer, network);
}
}
return info;
}
return null;
}
checkMinimalPush(index) {
if (index >= this.chunks.length) {
return false;
}
const chunk = this.chunks[index];
const opcodenum = chunk.opcodenum;
if (opcodenum === undefined) {
return false;
}
if (opcodenum >= 0 && opcodenum <= Opcode.OP_PUSHDATA4) {
if (!chunk.buf) {
return opcodenum === Opcode.OP_0;
}
const dataLength = chunk.buf.length;
if (dataLength === 0) {
return opcodenum === Opcode.OP_0;
}
else if (dataLength === 1) {
return opcodenum === Opcode.OP_1 || opcodenum === Opcode.OP_PUSHDATA1;
}
else if (dataLength <= 75) {
return opcodenum === dataLength;
}
else if (dataLength <= 255) {
return opcodenum === Opcode.OP_PUSHDATA1;
}
else if (dataLength <= 65535) {
return opcodenum === Opcode.OP_PUSHDATA2;
}
else {
return opcodenum === Opcode.OP_PUSHDATA4;
}
}
return true;
}
isValid() {
try {
return this.chunks.length > 0;
}
catch (e) {
return false;
}
}
clone() {
const cloned = new Script();
cloned.chunks = this.chunks.map(chunk => new Chunk({
opcodenum: chunk.opcodenum,
buf: chunk.buf ? Buffer.from(chunk.buf) : undefined,
len: chunk.len,
}));
return cloned;
}
isPublicKeyHashIn() {
if (this.chunks.length === 2) {
const signatureBuf = this.chunks[0].buf;
const pubkeyBuf = this.chunks[1].buf;
if (signatureBuf &&
signatureBuf.length &&
pubkeyBuf &&
pubkeyBuf.length) {
const version = pubkeyBuf[0];
if ((version === 0x04 || version === 0x06 || version === 0x07) &&
pubkeyBuf.length === 65) {
return true;
}
else if ((version === 0x03 || version === 0x02) &&
pubkeyBuf.length === 33) {
return true;
}
}
}
return false;
}
getPublicKey() {
Preconditions.checkState(this.isPublicKeyOut(), "Can't retrieve PublicKey from a non-PK output");
return this.chunks[0].buf;
}
getPublicKeyHash() {
Preconditions.checkState(this.isPublicKeyHashOut(), "Can't retrieve PublicKeyHash from a non-PKH output");
return this.chunks[2].buf;
}
isPublicKeyOut() {
if (this.chunks.length === 2 &&
this.chunks[0].buf &&
this.chunks[0].buf.length &&
this.chunks[1].opcodenum === Opcode.OP_CHECKSIG) {
const pubkeyBuf = this.chunks[0].buf;
const version = pubkeyBuf[0];
let isVersion = false;
if ((version === 0x04 || version === 0x06 || version === 0x07) &&
pubkeyBuf.length === 65) {
isVersion = true;
}
else if ((version === 0x03 || version === 0x02) &&
pubkeyBuf.length === 33) {
isVersion = true;
}
if (isVersion) {
return PublicKey.isValid(pubkeyBuf);
}
}
return false;
}
isPublicKeyIn() {
if (this.chunks.length === 1) {
const signatureBuf = this.chunks[0].buf;
if (signatureBuf && signatureBuf.length && signatureBuf[0] === 0x30) {
return true;
}
}
return false;
}
isScriptHashIn() {
if (this.chunks.length <= 1) {
return false;
}
const redeemChunk = this.chunks[this.chunks.length - 1];
const redeemBuf = redeemChunk.buf;
if (!redeemBuf) {
return false;
}
let redeemScript;
try {
redeemScript = Script.fromBuffer(redeemBuf);
}
catch (e) {
if (e instanceof BitcoreError.Script.InvalidBuffer) {
return false;
}
throw e;
}
const type = redeemScript.classify();
return type !== ScriptTypes.UNKNOWN;
}
isMultisigOut() {
return (this.chunks.length > 3 &&
this._isSmallIntOp(this.chunks[0].opcodenum) &&
this.chunks.slice(1, this.chunks.length - 2).every(function (obj) {
return obj.buf && Buffer.isBuffer(obj.buf);
}) &&
this._isSmallIntOp(this.chunks[this.chunks.length - 2].opcodenum) &&
this.chunks[this.chunks.length - 1].opcodenum === Opcode.OP_CHECKMULTISIG);
}
_isSmallIntOp(opcode) {
return opcode >= Opcode.OP_1 && opcode <= Opcode.OP_16;
}
isMultisigIn() {
return (this.chunks.length >= 2 &&
this.chunks[0].opcodenum === 0 &&
this.chunks.slice(1, this.chunks.length).every(function (obj) {
return obj.buf && Buffer.isBuffer(obj.buf) && Signature.isTxDER(obj.buf);
}));
}
isDataOut() {
const step1 = this.chunks.length >= 1 &&
this.chunks[0].opcodenum === Opcode.OP_RETURN &&
this.toBuffer().length <= 223;
if (!step1)
return false;
const chunks = this.chunks.slice(1);
const script2 = new Script({ chunks: chunks });
return script2.isPushOnly();
}
isPushOnly() {
return this.chunks.every(function (chunk) {
return (chunk.opcodenum <= Opcode.OP_16 ||
chunk.opcodenum === Opcode.OP_PUSHDATA1 ||
chunk.opcodenum === Opcode.OP_PUSHDATA2 ||
chunk.opcodenum === Opcode.OP_PUSHDATA4);
});
}
classify() {
if (this._isInput) {
return this.classifyInput();
}
else if (this._isOutput) {
return this.classifyOutput();
}
else {
const outputType = this.classifyOutput();
return outputType !== ScriptTypes.UNKNOWN
? outputType
: this.classifyInput();
}
}
classifyOutput() {
const outputIdentifiers = {
PUBKEY_OUT: this.isPublicKeyOut.bind(this),
PUBKEYHASH_OUT: this.isPublicKeyHashOut.bind(this),
MULTISIG_OUT: this.isMultisigOut.bind(this),
SCRIPTHASH_OUT: this.isScriptHashOut.bind(this),
DATA_OUT: this.isDataOut.bind(this),
};
for (const type in outputIdentifiers) {
if (outputIdentifiers[type]()) {
return ScriptTypes[type];
}
}
return ScriptTypes.UNKNOWN;
}
classifyInput() {
const inputIdentifiers = {
PUBKEY_IN: this.isPublicKeyIn.bind(this),
PUBKEYHASH_IN: this.isPublicKeyHashIn.bind(this),
MULTISIG_IN: this.isMultisigIn.bind(this),
SCRIPTHASH_IN: this.isScriptHashIn.bind(this),
};
for (const type in inputIdentifiers) {
if (inputIdentifiers[type]()) {
return ScriptTypes[type];
}
}
return ScriptTypes.UNKNOWN;
}
isStandard() {
return this.classify() !== ScriptTypes.UNKNOWN;
}
getType() {
if (this.isPayToTaproot()) {
const buf = this.toBuffer();
return buf.length === TAPROOT_SIZE_WITH_STATE
? 'p2tr-state'
: 'p2tr-commitment';
}
else if (this.isPublicKeyOut()) {
return 'p2pk';
}
else if (this.isPublicKeyHashOut()) {
return 'p2pkh';
}
else if (this.isScriptHashOut()) {
return 'p2sh';
}
return 'other';
}
prepend(obj) {
this._addByType(obj, true);
return this;
}
equals(script) {
Preconditions.checkState(script instanceof Script, 'Must provide another script');
if (this.chunks.length !== script.chunks.length) {
return false;
}
for (let i = 0; i < this.chunks.length; i++) {
if (Buffer.isBuffer(this.chunks[i].buf) &&
!Buffer.isBuffer(script.chunks[i].buf)) {
return false;
}
if (Buffer.isBuffer(this.chunks[i].buf) &&
!BufferUtil.equals(this.chunks[i].buf, script.chunks[i].buf)) {
return false;
}
else if (this.chunks[i].opcodenum !== script.chunks[i].opcodenum) {
return false;
}
}
return true;
}
_addByType(obj, prepend) {
if (typeof obj === 'string') {
this._addOpcode(obj, prepend);
}
else if (typeof obj === 'number') {
this._addOpcode(obj, prepend);
}
else if (obj instanceof Opcode) {
this._addOpcode(obj, prepend);
}
else if (Buffer.isBuffer(obj)) {
this._addBuffer(obj, prepend);
}
else if (obj instanceof Script) {
this.chunks = this.chunks.concat(obj.chunks);
}
else if (typeof obj === 'object' && obj !== null) {
this._insertAtPosition(obj, prepend);
}
else {
throw new Error('Invalid script chunk');
}
}
_insertAtPosition(op, prepend) {
if (prepend) {
this.chunks.unshift(op);
}
else {
this.chunks.push(op);
}
}
_addOpcode(opcode, prepend) {
let op;
if (typeof opcode === 'number') {
op = opcode;
}
else if (opcode instanceof Opcode) {
op = opcode.num;
}
else {
op = new Opcode(opcode).num;
}
this._insertAtPosition({
opcodenum: op,
}, prepend);
}
_addBuffer(buf, prepend) {
let opcodenum;
const len = buf.length;
if (len >= 0 && len < Opcode.OP_PUSHDATA1) {
opcodenum = len;
}
else if (len < Math.pow(2, 8)) {
opcodenum = Opcode.OP_PUSHDATA1;
}
else if (len < Math.pow(2, 16)) {
opcodenum = Opcode.OP_PUSHDATA2;
}
else if (len < Math.pow(2, 32)) {
opcodenum = Opcode.OP_PUSHDATA4;
}
else {
throw new Error("You can't push that much data");
}
this._insertAtPosition({
buf: buf,
len: len,
opcodenum: opcodenum,
}, prepend);
}
hasCodeseparators() {
return this.chunks.some(chunk => chunk.opcodenum === Opcode.OP_CODESEPARATOR);
}
removeCodeseparators() {
const chunks = [];
for (let i = 0; i < this.chunks.length; i++) {
if (this.chunks[i].opcodenum !== Opcode.OP_CODESEPARATOR) {
chunks.push(this.chunks[i]);
}
}
this.chunks = chunks;
return this;
}
findAndDelete(script) {
const buf = script.toBuffer();
const hex = buf.toString('hex');
for (let i = 0; i < this.chunks.length; i++) {
const script2 = new Script({
chunks: [this.chunks[i]],
});
const buf2 = script2.toBuffer();
const hex2 = buf2.toString('hex');
if (hex === hex2) {
this.chunks.splice(i, 1);
}
}
return this;
}
getSignatureOperationsCount(accurate = true) {
let n = 0;
let lastOpcode = 0xffff;
for (const chunk of this.chunks) {
const opcode = chunk.opcodenum;
if (opcode === Opcode.OP_CHECKSIG ||
opcode === Opcode.OP_CHECKSIGVERIFY) {
n++;
}
else if (opcode === Opcode.OP_CHECKMULTISIG ||
opcode === Opcode.OP_CHECKMULTISIGVERIFY) {
if (accurate &&
lastOpcode >= Opcode.OP_1 &&
lastOpcode <= Opcode.OP_16) {
n += this._decodeOP_N(lastOpcode);
}
else {
n += 20;
}
}
lastOpcode = opcode;
}
return n;
}
_decodeOP_N(opcode) {
if (opcode === Opcode.OP_0) {
return 0;
}
else if (opcode >= Opcode.OP_1 && opcode <= Opcode.OP_16) {
return opcode - (Opcode.OP_1 - 1);
}
else {
throw new Error('Invalid opcode: ' + JSON.stringify(opcode));
}
}
toScriptHashOut() {
return Script.buildScriptHashOut(this);
}
}
export const ScriptTypes = {
UNKNOWN: 'Unknown',
PUBKEY_OUT: 'Pay to public key',
PUBKEY_IN: 'Spend from public key',
PUBKEYHASH_OUT: 'Pay to public key hash',
PUBKEYHASH_IN: 'Spend from public key hash',
SCRIPTHASH_OUT: 'Pay to script hash',
SCRIPTHASH_IN: 'Spend from script hash',
MULTISIG_OUT: 'Pay to multisig',
MULTISIG_IN: 'Spend from multisig',
DATA_OUT: 'Data push',
};
export function toAddress(script, network) {
const addr = script.toAddress(network);
if (!addr || typeof addr === 'boolean') {
throw new Error('Cannot convert script to address');
}
return addr;
}
export function empty() {
return new Script();
}