"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