UNPKG

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
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(); }