"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Signature_js_1 = __importDefault(require("./Signature.js"));
const BigNumber_js_1 = __importDefault(require("./BigNumber.js"));
const Hash = __importStar(require("./Hash.js"));
const utils_js_1 = require("./utils.js");
const Script_js_1 = __importDefault(require("../script/Script.js"));
const EMPTY_SCRIPT = new Uint8Array(0);
class TransactionSignature extends Signature_js_1.default {
    /**
     * Implements the original bitcoin transaction signature digest preimage algorithm (OTDA).
     * @param params
     * @returns preimage as a byte array
     */
    static formatOTDA(params) {
        const isAnyoneCanPay = (params.scope & TransactionSignature.SIGHASH_ANYONECANPAY) === TransactionSignature.SIGHASH_ANYONECANPAY;
        const isSingle = (params.scope & 31) === TransactionSignature.SIGHASH_SINGLE;
        const isNone = (params.scope & 31) === TransactionSignature.SIGHASH_NONE;
        const isAll = (params.scope & 31) === TransactionSignature.SIGHASH_ALL || (!isSingle && !isNone);
        const subscript = Script_js_1.default.fromBinary(params.subscript.toBinary());
        subscript.removeCodeseparators();
        const currentInput = {
            sourceTXID: params.sourceTXID,
            sourceOutputIndex: params.sourceOutputIndex,
            sequence: params.inputSequence,
            script: subscript.toBinary()
        };
        const writer = new utils_js_1.Writer();
        function writeInputs(inputs) {
            writer.writeVarIntNum(inputs.length);
            for (const input of inputs) {
                writer.writeReverse((0, utils_js_1.toArray)(input.sourceTXID, 'hex'));
                writer.writeUInt32LE(input.sourceOutputIndex);
                writer.writeVarIntNum(input.script.length);
                writer.write(input.script);
                writer.writeUInt32LE(input.sequence);
            }
        }
        function writeOutputs(outputs) {
            writer.writeVarIntNum(outputs.length);
            for (const output of outputs) {
                writer.writeUInt64LE(output.satoshis);
                writer.writeVarIntNum(output.script.length);
                writer.write(output.script);
            }
        }
        // Version
        writer.writeInt32LE(params.transactionVersion);
        const emptyScript = new Script_js_1.default().toBinary();
        if (!isAnyoneCanPay) {
            const inputs = params.otherInputs.map(input => ({
                sourceTXID: input.sourceTXID ?? input.sourceTransaction?.id('hex') ?? '',
                sourceOutputIndex: input.sourceOutputIndex,
                sequence: (isSingle || isNone) ? 0 : (input.sequence ?? 0xffffffff), // Default to max sequence number
                script: emptyScript
            }));
            inputs.splice(params.inputIndex, 0, currentInput);
            writeInputs(inputs);
        }
        else if (isAnyoneCanPay) {
            writeInputs([currentInput]);
        }
        if (isAll) {
            const outputs = params.outputs.map(output => ({
                satoshis: output.satoshis ?? 0, // Default to 0 if undefined
                script: output.lockingScript.toBinary()
            }));
            writeOutputs(outputs);
        }
        else if (isSingle) {
            const outputs = [];
            for (let i = 0; i < params.inputIndex; i++)
                outputs.push({ satoshis: -1, script: emptyScript });
            const o = params.outputs[params.inputIndex];
            if (o !== undefined) {
                outputs.push({ satoshis: o.satoshis ?? 0, script: o.lockingScript.toBinary() });
            }
            writeOutputs(outputs);
        }
        else if (isNone) {
            writeOutputs([]);
        }
        // Locktime
        writer.writeUInt32LE(params.lockTime);
        // sighashType
        writer.writeUInt32LE(params.scope >>> 0);
        const buf = writer.toUint8Array();
        // const preimage = toHex(buf)
        // const sighash = toHex(Hash.hash256(buf))
        return buf;
    }
    /**
     * Formats the same SIGHASH preimage bytes as `format`, supporting the optional cache for hash reuse.
     * @param params - Context for the signing operation.
     * @param params.cache - Optional `SignatureHashCache` that may already contain hashed prefixes and is populated during formatting.
     * @returns Bytes for signing.
     */
    static formatBip143(params) {
        const cache = params.cache;
        const currentInput = {
            sourceTXID: params.sourceTXID,
            sourceOutputIndex: params.sourceOutputIndex,
            sequence: params.inputSequence
        };
        const inputs = [...params.otherInputs];
        inputs.splice(params.inputIndex, 0, currentInput);
        const getPrevoutHash = () => {
            const writer = new utils_js_1.Writer();
            for (const input of inputs) {
                if (input.sourceTXID === undefined) {
                    if (input.sourceTransaction == null) {
                        throw new Error('Missing sourceTransaction for input');
                    }
                    writer.write(input.sourceTransaction.hash());
                }
                else {
                    writer.writeReverse((0, utils_js_1.toArray)(input.sourceTXID, 'hex'));
                }
                writer.writeUInt32LE(input.sourceOutputIndex);
            }
            const buf = writer.toUint8Array();
            const ret = Hash.hash256(buf);
            return ret;
        };
        const getSequenceHash = () => {
            const writer = new utils_js_1.Writer();
            for (const input of inputs) {
                const sequence = input.sequence ?? 0xffffffff; // Default to max sequence number
                writer.writeUInt32LE(sequence);
            }
            const buf = writer.toUint8Array();
            const ret = Hash.hash256(buf);
            return ret;
        };
        function getOutputsHash(outputIndex) {
            const writer = new utils_js_1.Writer();
            if (outputIndex === undefined) {
                for (const output of params.outputs) {
                    const satoshis = output.satoshis ?? 0; // Default to 0 if undefined
                    writer.writeUInt64LE(satoshis);
                    const script = output.lockingScript?.toUint8Array() ?? EMPTY_SCRIPT;
                    writer.writeVarIntNum(script.length);
                    writer.write(script);
                }
            }
            else {
                const output = params.outputs[outputIndex];
                if (output === undefined) { // ✅ Explicitly check for undefined
                    throw new Error(`Output at index ${outputIndex} does not exist`);
                }
                const satoshis = output.satoshis ?? 0; // Default to 0 if undefined
                writer.writeUInt64LE(satoshis);
                const script = output.lockingScript?.toUint8Array() ?? EMPTY_SCRIPT;
                writer.writeVarIntNum(script.length);
                writer.write(script);
            }
            const buf = writer.toUint8Array();
            const ret = Hash.hash256(buf);
            return ret;
        }
        let hashPrevouts = new Array(32).fill(0);
        let hashSequence = new Array(32).fill(0);
        let hashOutputs = new Array(32).fill(0);
        if ((params.scope & TransactionSignature.SIGHASH_ANYONECANPAY) === 0) {
            if (cache?.hashPrevouts == null) {
                hashPrevouts = getPrevoutHash();
                if (cache != null)
                    cache.hashPrevouts = hashPrevouts;
            }
            else {
                hashPrevouts = cache.hashPrevouts;
            }
        }
        if ((params.scope & TransactionSignature.SIGHASH_ANYONECANPAY) === 0 &&
            (params.scope & 31) !== TransactionSignature.SIGHASH_SINGLE &&
            (params.scope & 31) !== TransactionSignature.SIGHASH_NONE) {
            if (cache?.hashSequence == null) {
                hashSequence = getSequenceHash();
                if (cache != null)
                    cache.hashSequence = hashSequence;
            }
            else {
                hashSequence = cache.hashSequence;
            }
        }
        if ((params.scope & 31) !== TransactionSignature.SIGHASH_SINGLE &&
            (params.scope & 31) !== TransactionSignature.SIGHASH_NONE) {
            if (cache?.hashOutputsAll == null) {
                hashOutputs = getOutputsHash();
                if (cache != null)
                    cache.hashOutputsAll = hashOutputs;
            }
            else {
                hashOutputs = cache.hashOutputsAll;
            }
        }
        else if ((params.scope & 31) === TransactionSignature.SIGHASH_SINGLE &&
            params.inputIndex < params.outputs.length) {
            const key = params.inputIndex;
            const cachedSingle = cache?.hashOutputsSingle?.get(key);
            if (cachedSingle == null) {
                hashOutputs = getOutputsHash(key);
                if (cache != null) {
                    cache.hashOutputsSingle ?? (cache.hashOutputsSingle = new Map());
                    cache.hashOutputsSingle.set(key, hashOutputs);
                }
            }
            else {
                hashOutputs = cachedSingle;
            }
        }
        const writer = new utils_js_1.Writer();
        // Version
        writer.writeInt32LE(params.transactionVersion);
        // Input prevouts/nSequence (none/all, depending on flags)
        writer.write(hashPrevouts);
        writer.write(hashSequence);
        //  outpoint (32-byte hash + 4-byte little endian)
        writer.writeReverse((0, utils_js_1.toArray)(params.sourceTXID, 'hex'));
        writer.writeUInt32LE(params.sourceOutputIndex);
        // scriptCode of the input (serialized as scripts inside CTxOuts)
        const subscriptBin = params.subscript.toUint8Array();
        writer.writeVarIntNum(subscriptBin.length);
        writer.write(subscriptBin);
        // value of the output spent by this input (8-byte little endian)
        writer.writeUInt64LE(params.sourceSatoshis);
        // nSequence of the input (4-byte little endian)
        const sequenceNumber = currentInput.sequence;
        writer.writeUInt32LE(sequenceNumber);
        // Outputs (none/one/all, depending on flags)
        writer.write(hashOutputs);
        // Locktime
        writer.writeUInt32LE(params.lockTime);
        // sighashType
        writer.writeUInt32LE(params.scope >>> 0);
        const buf = writer.toUint8Array();
        // const preimage = toHex(buf)
        // const sighash = toHex(Hash.hash256(buf))
        return buf;
    }
    /**
     * Formats the SIGHASH preimage for the targeted input, optionally using a cache to skip recomputing shared hash prefixes.
     * @param params - Context for the signing input plus transaction metadata.
     * @param params.cache - Optional cache storing previously computed `hashPrevouts`, `hashSequence`, or `hashOutputs*` values; it will be populated if present.
     */
    static format(params) {
        return Array.from(this.formatBytes(params));
    }
    static formatBytes(params) {
        const hasForkId = (params.scope & TransactionSignature.SIGHASH_FORKID) !== 0;
        const hasChronicle = params.ignoreChronicle !== true && (params.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0;
        if (hasForkId && !hasChronicle) {
            return TransactionSignature.formatBip143(params);
        }
        if (!hasForkId || (hasForkId && hasChronicle)) {
            return TransactionSignature.formatOTDA(params);
        }
        return new Uint8Array(0);
    }
    static usesOtdaSingleBug(params) {
        const hasForkId = (params.scope & TransactionSignature.SIGHASH_FORKID) !== 0;
        const hasChronicle = params.ignoreChronicle !== true && (params.scope & TransactionSignature.SIGHASH_CHRONICLE) !== 0;
        const usesOtda = !hasForkId || (hasForkId && hasChronicle);
        return usesOtda &&
            (params.scope & 31) === TransactionSignature.SIGHASH_SINGLE &&
            params.inputIndex >= params.outputs.length;
    }
    // The format used in a tx
    static fromChecksigFormat(buf) {
        if (buf.length === 0) {
            // allow setting a "blank" signature
            const r = new BigNumber_js_1.default(1);
            const s = new BigNumber_js_1.default(1);
            const scope = 1;
            return new TransactionSignature(r, s, scope);
        }
        const scope = buf.at(-1);
        const derbuf = buf.slice(0, buf.length - 1);
        const tempSig = Signature_js_1.default.fromDER(derbuf);
        return new TransactionSignature(tempSig.r, tempSig.s, scope);
    }
    constructor(r, s, scope) {
        super(r, s);
        this.scope = scope;
    }
    /**
     * Compares to bitcoind's IsLowDERSignature
     * See also Ecdsa signature algorithm which enforces this.
     * See also Bip 62, "low S values in signatures"
     */
    hasLowS() {
        if (this.s.ltn(1) ||
            this.s.gt(new BigNumber_js_1.default('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex'))) {
            return false;
        }
        return true;
    }
    toChecksigFormat() {
        const derbuf = this.toDER();
        return [...derbuf, this.scope];
    }
}
TransactionSignature.SIGHASH_ALL = 0x00000001;
TransactionSignature.SIGHASH_NONE = 0x00000002;
TransactionSignature.SIGHASH_SINGLE = 0x00000003;
TransactionSignature.SIGHASH_CHRONICLE = 0x00000020;
TransactionSignature.SIGHASH_FORKID = 0x00000040;
TransactionSignature.SIGHASH_ANYONECANPAY = 0x00000080;
exports.default = TransactionSignature;
//# sourceMappingURL=TransactionSignature.js.map