@node-dlc/bitcoin
Version:
238 lines • 9.46 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TxBuilder = void 0;
const bufio_1 = require("@node-dlc/bufio");
const crypto_1 = require("@node-dlc/crypto");
const LockTime_1 = require("./LockTime");
const OutPoint_1 = require("./OutPoint");
const Script_1 = require("./Script");
const Tx_1 = require("./Tx");
const TxIn_1 = require("./TxIn");
const TxOut_1 = require("./TxOut");
const Value_1 = require("./Value");
class TxBuilder {
constructor() {
this._inputs = [];
this._outputs = [];
this._version = 2;
this._locktime = new LockTime_1.LockTime();
}
/**
* Gets or sets the transaction version. Valid transaction versions
* are > 1.
*/
get version() {
return this._version;
}
set version(val) {
this._version = val;
}
/**
* Gets or sets the absolute locktime for the transaction
*/
get locktime() {
return this._locktime;
}
set locktime(val) {
this._locktime = val;
}
/**
* Gets the inputs
*/
get inputs() {
return this._inputs;
}
/**
* Gets the outputs
*/
get outputs() {
return this._outputs;
}
/**
* Adds a new transaction input
* @param outpoint the previous output represented as an outpoint
*/
addInput(outpoint, sequence) {
if (outpoint instanceof TxIn_1.TxIn) {
this._inputs.push(outpoint.clone());
}
else {
outpoint =
outpoint instanceof OutPoint_1.OutPoint ? outpoint : OutPoint_1.OutPoint.fromString(outpoint);
this._inputs.push(new TxIn_1.TxIn(outpoint, undefined, sequence));
}
}
/**
* Adds a transaction output
* @param value value sent to the lock script. When represented as a
* number, the value is in Bitcoin.
* @param scriptPubKey the locking script encumbering the funds send
* to this output
*/
addOutput(value, scriptPubKey) {
if (value instanceof TxOut_1.TxOut) {
this._outputs.push(value.clone());
}
else {
value = value instanceof Value_1.Value ? value : Value_1.Value.fromBitcoin(value);
this._outputs.push(new TxOut_1.TxOut(value, scriptPubKey));
}
}
/**
* Creates a signature hash including all inputs and all outputs,
* which is referred to as SIGHASH_ALL. The scriptSig of all inputs
* is removed (as it is never signed), however we commit to the
* signatory input using the scriptPubKey from the prevOut or the
* redeemScript. The hash is constructed as the serialization of
* all information (with the input scriptSig replaced as just
* described) and then appending a 4-byte LE sighash type. We then
* take the hash256 of that serialized transaction.
*
* @param input signatory input index
* @param commitScript the scriptSig used for the signature input
*/
hashLegacy(input, commitScript) {
const writer = new bufio_1.BufferWriter();
// write the version
writer.writeUInt32LE(this.version);
// sign all inputs as sorted by the sorting function
const inputs = this._inputs;
writer.writeVarInt(inputs.length);
for (let i = 0; i < inputs.length; i++) {
// blank out scriptSig for non-signatory inputs
let scriptSig = new Script_1.Script();
// use the commit script for signatory input
if (i === input) {
scriptSig = commitScript;
}
// write the input
const vin = new TxIn_1.TxIn(inputs[i].outpoint, scriptSig, inputs[i].sequence);
writer.writeBytes(vin.serialize());
}
// sign all outputs as sorted by the sorting function
const outputs = this._outputs;
writer.writeVarInt(outputs.length);
for (const vout of outputs) {
writer.writeBytes(vout.serialize());
}
// write the sequence
writer.writeBytes(this.locktime.serialize());
// write the sighash type 0x01 as 4-bytes little endian
writer.writeUInt32LE(1);
// return hashed value
return (0, crypto_1.hash256)(writer.toBuffer());
}
/**
* Creates a signature hash using the new segregated witness digets
* alorithm defined in BIP143. The current version only supports
* SIGHASH_ALL and does not account for OP_CODESEPARATOR.
*
* This algorithm has side-effects in that it caches hashPrevOut,
* hashSequence, and hashOutput values used. This means transaction
* should not change after signing, though the code does not yet
* enforce this.
*
* @param index signatory input index
* @param commitScript the scriptSig used for the signature input
* @param value the value of the input
*/
hashSegwitv0(index, commitScript, value) {
const writer = new bufio_1.BufferWriter();
// Combines the previous outputs for all inputs in the
// transaction by serializing and hash256 the concated values:
// prevtx: 32-byte IBO
// prevIdx: 4-byte LE
if (this._hashPrevOuts === undefined) {
const hashWriter = new bufio_1.BufferWriter(Buffer.alloc(this._inputs.length * 36));
for (const input of this._inputs) {
hashWriter.writeBytes(input.outpoint.serialize());
}
this._hashPrevOuts = (0, crypto_1.hash256)(hashWriter.toBuffer());
}
// Combines the nSequence values for all inputs in the
// transaction and then hash256 the values
if (this._hashSequence === undefined) {
const hashWriter = new bufio_1.BufferWriter(Buffer.alloc(this._inputs.length * 4));
for (const input of this._inputs) {
hashWriter.writeBytes(input.sequence.serialize());
}
this._hashSequence = (0, crypto_1.hash256)(hashWriter.toBuffer());
}
// Combines the outputs for the transaction according by
// concatenating the serialization of the outputs into a single
// byte array and then hash256 the values.
if (this._hashOutputs === undefined) {
const hashWriter = new bufio_1.BufferWriter();
for (const vout of this._outputs) {
hashWriter.writeBytes(vout.serialize());
}
this._hashOutputs = (0, crypto_1.hash256)(hashWriter.toBuffer());
}
writer.writeUInt32LE(this.version);
writer.writeBytes(this._hashPrevOuts);
writer.writeBytes(this._hashSequence);
const vin = this._inputs[index];
writer.writeBytes(vin.outpoint.serialize());
writer.writeBytes(commitScript.serialize());
writer.writeUInt64LE(value.sats);
writer.writeBytes(vin.sequence.serialize());
writer.writeBytes(this._hashOutputs);
writer.writeBytes(this.locktime.serialize());
writer.writeUInt32LE(1); // SIGHASH_ALL
return (0, crypto_1.hash256)(writer.toBuffer());
}
/**
* Signs an input and returns the DER encoded signature. The
* script that is committed to will depend on the type of the
* signature. This is usually the locking script used in the prior
* output, but in the case of p2sh transactions, this is the
* redeem script, or the underlying script that is hashed in the
* prior output.
*
* @param input index of input that should be signed
* @param commitScript Script that is committed during signature
* @param privateKey 32-byte private key
*/
sign(input, commitScript, privateKey) {
// create the hash of the transaction for the input
const hash = this.hashLegacy(input, commitScript);
// sign DER encode signature
const sig = (0, crypto_1.sign)(hash, privateKey);
const der = (0, crypto_1.sigToDER)(sig);
// return signature with 1-byte sighash type
return Buffer.concat([der, Buffer.from([1])]);
}
/**
* Signs an SegWit v0 input and returns the DER encoded signature.
* The script that is committed to will depend on the type of the
* input. This is usually the locking script or redeem script.
*
* @param input index of input that should be signed
* @param commitScript Script that is committed during signature
* @param privateKey 32-byte private key
* @param value value of the prior input
*/
signSegWitv0(input, commitScript, privateKey, value) {
// create the hash of the transaction for the input
const hash = this.hashSegwitv0(input, commitScript, value);
// sign DER encode signature
const sig = (0, crypto_1.sign)(hash, privateKey);
const der = (0, crypto_1.sigToDER)(sig);
// return signature with 1-byte sighash type
return Buffer.concat([der, Buffer.from([1])]);
}
/**
* Returns an immutable transaction
*/
toTx() {
return new Tx_1.Tx(this.version, this._inputs.map((vin) => vin.clone()), this._outputs.map((vout) => vout.clone()), this.locktime.clone());
}
serialize() {
return this.toTx().serialize();
}
toHex(pretty = false) {
return this.toTx().toHex(pretty);
}
}
exports.TxBuilder = TxBuilder;
//# sourceMappingURL=TxBuilder.js.map