bitcoin-tx-lib
Version:
A Typescript library for building and signing Bitcoin transactions
171 lines • 7.39 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HDTransactionBase = void 0;
const utils_1 = require("../utils");
const buffer_1 = require("../utils/buffer");
const txutils_1 = require("../utils/txutils");
const txbuilder_1 = require("./txbuilder");
/**
* Abstract base class for building HD (Hierarchical Deterministic) Bitcoin transactions,
* supporting per-input signing using separate key pairs.
*/
class HDTransactionBase extends txbuilder_1.TransactionBuilder {
/**
* Constructs an HDTransactionBase instance with optional transaction settings.
* @param options Optional transaction configuration: version, locktime, fee, who pays the fee.
*/
constructor(options) {
var _a, _b;
super();
/** Transaction version (default is 2) */
this.version = 2;
/** Transaction locktime (default is 0) */
this.locktime = 0;
/** List of inputs included in the transaction */
this.inputs = [];
/** List of outputs included in the transaction */
this.outputs = [];
this.signingKeys = new Map();
this.version = (_a = options === null || options === void 0 ? void 0 : options.version) !== null && _a !== void 0 ? _a : 2;
this.locktime = (_b = options === null || options === void 0 ? void 0 : options.locktime) !== null && _b !== void 0 ? _b : 0;
this.whoPayTheFee = options === null || options === void 0 ? void 0 : options.whoPayTheFee;
this.fee = options === null || options === void 0 ? void 0 : options.fee;
this.cachedata = new Map();
}
/**
* Adds a transaction input and associates a signing key to it.
* @param input The transaction input to be added.
* @param pairkey The key pair used to sign this specific input.
* @throws If the input is invalid or already exists.
*/
addInput(input, pairkey) {
this.validateInput(input, this.inputs);
if (!input.scriptPubKey)
input.scriptPubKey = (0, utils_1.bytesToHex)((0, txutils_1.addressToScriptPubKey)(pairkey.getAddress()));
// 0xfffffffd Replace By Fee (RBF) enabled BIP 125
if (!input.sequence)
input.sequence = "fffffffd";
this.signingKeys.set(this.getkey(input), pairkey);
this.inputs.push(input);
}
/**
* Adds an output to the transaction.
* @param output The output (address and amount) to be added.
* @throws If the output is invalid or duplicated.
*/
addOutput(output) {
this.validateOutput(output, this.outputs);
this.outputs.push(output);
}
/**
* Checks if the transaction contains at least one SegWit input.
* @returns True if any input is SegWit, false otherwise.
*/
isSegwit() {
return super.isSegwit(this.inputs);
}
/**
* Builds the raw transaction (optionally for txid calculation).
* Handles both SegWit and legacy inputs.
* @param format Output format, either "raw" (default) or "txid".
* @returns Serialized transaction as Uint8Array.
* @throws If any input lacks its associated signing key.
*/
build(format = "raw") {
this.validateSigning();
let witnessData = new buffer_1.ByteBuffer();
let hexTransaction = new buffer_1.ByteBuffer((0, utils_1.numberToHexLE)(this.version, 32)); // version
if (this.isSegwit() && format != "txid") // Marker and Flag for SegWit transactions
hexTransaction.append(new Uint8Array([0x00, 0x01])); //"00" + "01";
// number of inputs
hexTransaction.append((0, utils_1.numberToVarint)(this.inputs.length));
this.inputs.forEach(input => {
var _a;
hexTransaction.append((0, utils_1.hexToBytes)(input.txid).reverse()); // txid
hexTransaction.append((0, utils_1.numberToHexLE)(input.vout, 32)); // index output (vout)
if (this.isSegwitInput(input)) {
witnessData.append(this.buildWitness(input));
hexTransaction.append(new Uint8Array([0])); // script sig in witness area // P2WPKH
}
else {
let scriptSig = this.buildScriptSig(input);
hexTransaction.append((0, utils_1.numberToHexLE)(scriptSig.length, 8));
hexTransaction.append(scriptSig);
witnessData.append(new Uint8Array([0])); // no witness, only scriptSig
}
// 0xfffffffd Replace By Fee (RBF) enabled BIP 125
hexTransaction.append((0, utils_1.hexToBytes)((_a = input.sequence) !== null && _a !== void 0 ? _a : "fffffffd").reverse()); // 0xfffffffd
});
hexTransaction.append((0, utils_1.numberToVarint)(this.outputs.length)); // number of outputs
hexTransaction.append(this.outputsRaw(this.outputs)); // amount+scriptpubkey
if (this.isSegwit() && format != "txid")
hexTransaction.append(witnessData.raw());
hexTransaction.append((0, utils_1.numberToHexLE)(this.locktime, 32)); // locktime
return hexTransaction.raw();
}
/**
* Generates the witness data for a SegWit input.
* @param input The input to generate witness for.
* @returns Serialized witness field.
* @throws If the input has no associated signing key.
*/
buildWitness(input) {
const pairkey = this.signingKeys.get(this.getkey(input));
if (!pairkey)
throw new Error("Transaction not signed, please sign the transaction");
return super.generateWitness(input, {
version: this.version,
locktime: this.locktime,
inputs: this.inputs,
outputs: this.outputs,
pairkey: pairkey
});
}
/**
* Generates the legacy scriptSig for a non-SegWit input.
* @param input The input to generate the scriptSig for.
* @returns Serialized scriptSig.
* @throws If the input has no associated signing key.
*/
buildScriptSig(input) {
const pairkey = this.signingKeys.get(this.getkey(input));
if (!pairkey)
throw new Error("Transaction not signed, please sign the transaction");
return super.generateScriptSig(input, {
version: this.version,
locktime: this.locktime,
inputs: this.inputs,
outputs: this.outputs,
pairkey
});
}
/**
* Clears all inputs, outputs, cached data, and signing keys.
*/
clear() {
this.inputs = [];
this.outputs = [];
this.signingKeys.clear();
this.cachedata.clear();
}
/**
* Generates a unique key for the signingKeys map based on txid and vout.
* @param input The input to derive the key from.
* @returns A string in the format "txid:vout".
*/
getkey(input) {
return `${input.txid}:${input.vout}`;
}
/**
* Validates that all inputs have an associated signing key.
* @throws If any input is missing its corresponding key.
*/
validateSigning() {
for (const input of this.inputs) {
if (!this.signingKeys.has(this.getkey(input)))
throw new Error(`Missing signing key for input ${JSON.stringify(input)}`);
}
}
}
exports.HDTransactionBase = HDTransactionBase;
//# sourceMappingURL=hdtxbase.js.map