@ecash/lib
Version:
Library for eCash transaction building
273 lines • 10.8 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.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