UNPKG

@ecash/lib

Version:

Library for eCash transaction building

166 lines 6.29 kB
"use strict"; // Copyright (c) 2024 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. Object.defineProperty(exports, "__esModule", { value: true }); exports.ScriptOpIter = exports.Script = void 0; const varsize_js_1 = require("./io/varsize.js"); const writerlength_js_1 = require("./io/writerlength.js"); const writerbytes_js_1 = require("./io/writerbytes.js"); const hex_js_1 = require("./io/hex.js"); const op_js_1 = require("./op.js"); const opcode_js_1 = require("./opcode.js"); const bytes_js_1 = require("./io/bytes.js"); const address_1 = require("./address/address"); /** A Bitcoin Script locking/unlocking a UTXO */ class Script { /** Create a new Script with the given bytecode or empty */ constructor(bytecode) { this.bytecode = bytecode ?? new Uint8Array(); } /** * Write the script to the writer with the script size as VARINT * prepended. **/ writeWithSize(writer) { (0, varsize_js_1.writeVarSize)(this.bytecode.length, writer); writer.putBytes(this.bytecode); } static readWithSize(bytes) { const size = (0, varsize_js_1.readVarSize)(bytes); return new Script(bytes.readBytes(Number(size))); } /** Build a Script from the given Script Ops */ static fromOps(ops) { let scriptSize = 0; for (const op of ops) { const writerLength = new writerlength_js_1.WriterLength(); (0, op_js_1.writeOp)(op, writerLength); scriptSize += writerLength.length; } const bytecodeWriter = new writerbytes_js_1.WriterBytes(scriptSize); for (const op of ops) { (0, op_js_1.writeOp)(op, bytecodeWriter); } return new Script(bytecodeWriter.data); } static fromAddress(address) { // make Address from address const thisAddress = address_1.Address.fromCashAddress(address); switch (thisAddress.type) { case 'p2pkh': { return Script.p2pkh((0, hex_js_1.fromHex)(thisAddress.hash)); } case 'p2sh': { return Script.p2sh((0, hex_js_1.fromHex)(thisAddress.hash)); } default: { // Note we should never get here, as Address constructor // only supports p2pkh and p2sh throw new Error(`Unsupported address type: ${thisAddress.type}`); } } } /** Iterate over the Ops of this Script */ ops() { return new ScriptOpIter(new bytes_js_1.Bytes(this.bytecode)); } /** Create a deep copy of this Script */ copy() { return new Script(new Uint8Array(this.bytecode)); } /** * Find the n-th OP_CODESEPARATOR (0-based) and cut out the bytecode * following it. Required for signing BIP143 scripts that have an * OP_CODESEPARATOR. * * Throw an error if the n-th OP_CODESEPARATOR doesn't exist. * * Historically this opcode has been seen as obscure and useless, but in * BIP143 sighash-based covenants, basically every covenant benefits from * its usage, by trimming down the sighash preimage size and thus tx size. * * Really long Scripts will have a big BIP143 preimage, which costs precious * bytes (and the preimage might even go over the 520 pushdata limit). * This can be trimmed down to just one single byte by ending the covenant * in `... OP_CODESEPARATOR OP_CHECKSIG`, in which case the BIP143 signature * algo will cut out everything after the OP_CODESEPARATOR, so only the * OP_CHECKSIG remains. * If the covenant bytecode is 520 or so, this would save 519 bytes. */ cutOutCodesep(nCodesep) { const ops = this.ops(); let op; let nCodesepsFound = 0; while ((op = ops.next()) !== undefined) { if (op == opcode_js_1.OP_CODESEPARATOR) { if (nCodesepsFound == nCodesep) { return new Script(this.bytecode.slice(ops.bytes.idx)); } nCodesepsFound++; } } throw new Error('OP_CODESEPARATOR not found'); } /** * Whether the Script is a P2SH Script. * Matches CScript::IsPayToScriptHash in /src/script/script.h. **/ isP2sh() { if (this.bytecode.length != 23) { return false; } return (this.bytecode[0] == opcode_js_1.OP_HASH160 && this.bytecode[1] == 20 && this.bytecode[22] == opcode_js_1.OP_EQUAL); } /** * Return hex string of this Script's bytecode */ toHex() { return (0, hex_js_1.toHex)(this.bytecode); } /** Build a P2SH script for the given script hash */ static p2sh(scriptHash) { if (scriptHash.length !== 20) { throw new Error(`scriptHash length must be 20, got ${scriptHash.length}`); } return Script.fromOps([opcode_js_1.OP_HASH160, (0, op_js_1.pushBytesOp)(scriptHash), opcode_js_1.OP_EQUAL]); } /** Build a P2PKH script for the given public key hash */ static p2pkh(pkh) { if (pkh.length !== 20) { throw new Error(`pkh length must be 20, got ${pkh.length}`); } return Script.fromOps([ opcode_js_1.OP_DUP, opcode_js_1.OP_HASH160, (0, op_js_1.pushBytesOp)(pkh), opcode_js_1.OP_EQUALVERIFY, opcode_js_1.OP_CHECKSIG, ]); } /** Build a scriptSig for spending a P2PKH output */ static p2pkhSpend(pk, sig) { return Script.fromOps([(0, op_js_1.pushBytesOp)(sig), (0, op_js_1.pushBytesOp)(pk)]); } } exports.Script = Script; /** Iterator over the Ops of a Script. */ class ScriptOpIter { constructor(bytes) { this.bytes = bytes; } /** * Read the next Op and return it, or `undefined` if there are no more Ops. * Throws an error if reading the next op failed. */ next() { if (this.bytes.idx >= this.bytes.data.length) { return undefined; } return (0, op_js_1.readOp)(this.bytes); } } exports.ScriptOpIter = ScriptOpIter; //# sourceMappingURL=script.js.map