UNPKG

@ecash/lib

Version:

Library for eCash transaction building

273 lines 10.8 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.UnsignedTxInput = exports.UnsignedTx = void 0; const hash_js_1 = require("./hash.js"); const writerbytes_js_1 = require("./io/writerbytes.js"); const writerlength_js_1 = require("./io/writerlength.js"); const varsize_js_1 = require("./io/varsize.js"); const op_js_1 = require("./op.js"); const opcode_js_1 = require("./opcode.js"); const script_js_1 = require("./script.js"); const sigHashType_js_1 = require("./sigHashType.js"); const tx_js_1 = require("./tx.js"); /** An unsigned tx, which helps us build the sighash preimage we need to sign */ class UnsignedTx { constructor(params) { this.tx = params.tx; this.prevoutsHash = params.prevoutsHash; this.sequencesHash = params.sequencesHash; this.outputsHash = params.outputsHash; } /** * Make an UnsignedTx from a Tx, will precompute the fields required to * sign the tx **/ static fromTx(tx) { return new UnsignedTx({ tx, prevoutsHash: txWriterHash(tx, writePrevouts), sequencesHash: txWriterHash(tx, writeSequences), outputsHash: txWriterHash(tx, writeOutputs), }); } /** * Make a dummy UnsignedTx from a Tx, will set dummy values for the fields * required to sign the tx. Useful for tx size estimation. **/ static dummyFromTx(tx) { return new UnsignedTx({ tx, prevoutsHash: new Uint8Array(32), sequencesHash: new Uint8Array(32), outputsHash: new Uint8Array(32), }); } /** Return the unsigned tx input at the given input index */ inputAt(inputIdx) { return new UnsignedTxInput({ inputIdx, unsignedTx: this }); } } exports.UnsignedTx = UnsignedTx; // Write the legacy preimage used pre-UAHF. // It's modeled closely after SignatureHash in interpreter.cpp. function writeLegacyPreimage(writer, tx, scriptCode, inputIdx, sigHashType) { const hasAnyoneCanPay = sigHashType.inputType === sigHashType_js_1.SigHashTypeInputs.ANYONECANPAY; const writeLegacyScriptCode = () => { const ops = scriptCode.ops(); let nextOp = undefined; const newOps = []; // Filter out all code separators while ((nextOp = ops.next()) !== undefined) { if ((0, op_js_1.isPushOp)(nextOp) || nextOp != opcode_js_1.OP_CODESEPARATOR) { newOps.push(nextOp); } } script_js_1.Script.fromOps(newOps).writeWithSize(writer); }; const writeLegacyInput = (idx) => { // In case of SIGHASH_ANYONECANPAY, only the input being signed is // serialized if (hasAnyoneCanPay) { idx = inputIdx; } const input = tx.inputs[idx]; // Serialize the prevout (0, tx_js_1.writeOutPoint)(input.prevOut, writer); // Serialize the script if (idx != inputIdx) { // Blank out other inputs' signatures new script_js_1.Script().writeWithSize(writer); } else { writeLegacyScriptCode(); } // Serialize the nSequence if (idx != inputIdx && (sigHashType.outputType === sigHashType_js_1.SigHashTypeOutputs.SINGLE || sigHashType.outputType === sigHashType_js_1.SigHashTypeOutputs.NONE)) { // let the others update at will writer.putU32(0); } else { writer.putU32(input.sequence ?? tx_js_1.DEFAULT_SEQUENCE); } }; const writeLegacyOutput = (idx) => { if (sigHashType.outputType === sigHashType_js_1.SigHashTypeOutputs.SINGLE && idx != inputIdx) { // Do not lock-in the txout payee at other indices as txin (0, tx_js_1.writeTxOutput)({ value: 0, script: new script_js_1.Script() }, writer); } else { (0, tx_js_1.writeTxOutput)(tx.outputs[idx], writer); } }; writer.putU32(tx.version); const numInputs = hasAnyoneCanPay ? 1 : tx.inputs.length; (0, varsize_js_1.writeVarSize)(numInputs, writer); for (let inputIdx = 0; inputIdx < numInputs; ++inputIdx) { writeLegacyInput(inputIdx); } // Serialize vout const numOutputs = (() => { switch (sigHashType.outputType) { case sigHashType_js_1.SigHashTypeOutputs.NONE: return 0; case sigHashType_js_1.SigHashTypeOutputs.SINGLE: return inputIdx + 1; default: return tx.outputs.length; } })(); (0, varsize_js_1.writeVarSize)(numOutputs, writer); for (let outputIdx = 0; outputIdx < numOutputs; outputIdx++) { writeLegacyOutput(outputIdx); } // Serialize nLockTime writer.putU32(tx.locktime); // Serialize sigHashType writer.putU32(sigHashType.toInt()); } /** * An unsigned tx input, can be used to build a sighash preimage ready to be * signed **/ class UnsignedTxInput { constructor(params) { this.inputIdx = params.inputIdx; this.unsignedTx = params.unsignedTx; } /** * Build the sigHashPreimage for this input, with the given sigHashType * and OP_CODESEPARATOR index **/ sigHashPreimage(sigHashType, nCodesep) { const tx = this.unsignedTx.tx; const input = tx.inputs[this.inputIdx]; if (input.signData === undefined) { throw new Error('Input must have signData set'); } const signData = input.signData; const redeemScript = signDataScriptCode(input.signData); const scriptCode = nCodesep === undefined ? redeemScript : redeemScript.cutOutCodesep(nCodesep); // Sign LEGACY signatures that don't use SIGHASH_FORKID if (sigHashType.variant === sigHashType_js_1.SigHashTypeVariant.LEGACY) { if (sigHashType.outputType == sigHashType_js_1.SigHashTypeOutputs.SINGLE && this.inputIdx >= tx.outputs.length) { throw new Error('Invalid usage of SINGLE, input has no corresponding output'); } const writePreimage = (writer) => { writeLegacyPreimage(writer, this.unsignedTx.tx, scriptCode, this.inputIdx, sigHashType); }; const preimageWriterLen = new writerlength_js_1.WriterLength(); writePreimage(preimageWriterLen); const preimageWriter = new writerbytes_js_1.WriterBytes(preimageWriterLen.length); writePreimage(preimageWriter); return { bytes: preimageWriter.data, scriptCode, redeemScript, }; } let hashOutputs; switch (sigHashType.outputType) { case sigHashType_js_1.SigHashTypeOutputs.ALL: hashOutputs = this.unsignedTx.outputsHash; break; case sigHashType_js_1.SigHashTypeOutputs.NONE: hashOutputs = new Uint8Array(32); break; case sigHashType_js_1.SigHashTypeOutputs.SINGLE: if (this.inputIdx < tx.outputs.length) { const output = tx.outputs[this.inputIdx]; const writerOutputLength = new writerlength_js_1.WriterLength(); (0, tx_js_1.writeTxOutput)(output, writerOutputLength); const writerOutput = new writerbytes_js_1.WriterBytes(writerOutputLength.length); (0, tx_js_1.writeTxOutput)(output, writerOutput); hashOutputs = (0, hash_js_1.sha256d)(writerOutput.data); } else { hashOutputs = new Uint8Array(32); } break; } const writePreimage = (writer) => { writer.putU32(tx.version); if (sigHashType.inputType == sigHashType_js_1.SigHashTypeInputs.FIXED) { writer.putBytes(this.unsignedTx.prevoutsHash); } else { writer.putBytes(new Uint8Array(32)); } if (sigHashType.inputType == sigHashType_js_1.SigHashTypeInputs.FIXED && sigHashType.outputType == sigHashType_js_1.SigHashTypeOutputs.ALL) { writer.putBytes(this.unsignedTx.sequencesHash); } else { writer.putBytes(new Uint8Array(32)); } (0, tx_js_1.writeOutPoint)(input.prevOut, writer); scriptCode.writeWithSize(writer); writer.putU64(signData.value); writer.putU32(input.sequence ?? tx_js_1.DEFAULT_SEQUENCE); writer.putBytes(hashOutputs); writer.putU32(tx.locktime); writer.putU32(sigHashType.toInt()); }; const preimageWriterLen = new writerlength_js_1.WriterLength(); writePreimage(preimageWriterLen); const preimageWriter = new writerbytes_js_1.WriterBytes(preimageWriterLen.length); writePreimage(preimageWriter); return { bytes: preimageWriter.data, scriptCode, redeemScript, }; } /** Return the TxInput of this UnsignedTxInput */ txInput() { return this.unsignedTx.tx.inputs[this.inputIdx]; } } exports.UnsignedTxInput = UnsignedTxInput; /** Find the scriptCode that should be signed */ function signDataScriptCode(signData) { if (signData.outputScript !== undefined) { if (signData.outputScript.isP2sh()) { throw new Error('P2SH requires redeemScript to be set, not outputScript'); } return signData.outputScript; } if (signData.redeemScript === undefined) { throw new Error('Must either set outputScript or redeemScript'); } return signData.redeemScript; } function txWriterHash(tx, fn) { const writerLength = new writerlength_js_1.WriterLength(); fn(tx, writerLength); const writer = new writerbytes_js_1.WriterBytes(writerLength.length); fn(tx, writer); return (0, hash_js_1.sha256d)(writer.data); } function writePrevouts(tx, writer) { for (const input of tx.inputs) { (0, tx_js_1.writeOutPoint)(input.prevOut, writer); } } function writeSequences(tx, writer) { for (const input of tx.inputs) { writer.putU32(input.sequence ?? tx_js_1.DEFAULT_SEQUENCE); } } function writeOutputs(tx, writer) { for (const output of tx.outputs) { (0, tx_js_1.writeTxOutput)(output, writer); } } //# sourceMappingURL=unsignedTx.js.map