@ecash/lib
Version:
Library for eCash transaction building
166 lines • 6.29 kB
JavaScript
;
// 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