var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
import { PublicKey, SystemProgram, SYSVAR_INSTRUCTIONS_PUBKEY, SYSVAR_RENT_PUBKEY, } from "@solana/web3.js";
import { createSecp256k1Instruction } from "../../utils";
import { getGuardianSet, deriveGuardianSetKey, getWormholeBridgeData, } from "../accounts";
import { isBytes, parseVaa } from "../../../vaa";
import { createReadOnlyWormholeProgramInterface } from "../program";
var MAX_LEN_GUARDIAN_KEYS = 19;
/**
 * This is used in {@link createPostSignedVaaTransactions}'s initial transactions.
 *
 * Signatures are batched in groups of 7 due to instruction
 * data limits. These signatures are passed through to the Secp256k1
 * program to verify that the guardian public keys can be recovered.
 * This instruction is paired with `verify_signatures` to validate the
 * pubkey recovery.
 *
 * There are at most three pairs of instructions created.
 *
 * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/api/verify_signature.rs
 *
 *
 * @param {Connection} connection - Solana web3 connection
 * @param {PublicKeyInitData} wormholeProgramId - wormhole program address
 * @param {PublicKeyInitData} payer - transaction signer address
 * @param {SignedVaa | ParsedVaa} vaa - either signed VAA bytes or parsed VAA (use {@link parseVaa} on signed VAA)
 * @param {PublicKeyInitData} signatureSet - address to account of verified signatures
 * @param {web3.ConfirmOptions} [options] - Solana confirmation options
 */
export function createVerifySignaturesInstructions(connection, wormholeProgramId, payer, vaa, signatureSet, commitment) {
    return __awaiter(this, void 0, void 0, function () {
        var parsed, guardianSetIndex, info, guardianSetData, guardianSignatures, guardianKeys, batchSize, instructions, i, start, end, signatureStatus, signatures, keys, j, item, key;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    parsed = isBytes(vaa) ? parseVaa(vaa) : vaa;
                    guardianSetIndex = parsed.guardianSetIndex;
                    return [4 /*yield*/, getWormholeBridgeData(connection, wormholeProgramId)];
                case 1:
                    info = _a.sent();
                    if (guardianSetIndex != info.guardianSetIndex) {
                        throw new Error("guardianSetIndex != config.guardianSetIndex");
                    }
                    return [4 /*yield*/, getGuardianSet(connection, wormholeProgramId, guardianSetIndex, commitment)];
                case 2:
                    guardianSetData = _a.sent();
                    guardianSignatures = parsed.guardianSignatures;
                    guardianKeys = guardianSetData.keys;
                    batchSize = 7;
                    instructions = [];
                    for (i = 0; i < Math.ceil(guardianSignatures.length / batchSize); ++i) {
                        start = i * batchSize;
                        end = Math.min(guardianSignatures.length, (i + 1) * batchSize);
                        signatureStatus = Buffer.alloc(MAX_LEN_GUARDIAN_KEYS, -1);
                        signatures = [];
                        keys = [];
                        for (j = 0; j < end - start; ++j) {
                            item = guardianSignatures.at(j + start);
                            signatures.push(item.signature);
                            key = guardianKeys.at(item.index);
                            keys.push(key);
                            signatureStatus.writeInt8(j, item.index);
                        }
                        instructions.push(createSecp256k1Instruction(signatures, keys, parsed.hash));
                        instructions.push(createVerifySignaturesInstruction(wormholeProgramId, payer, parsed, signatureSet, signatureStatus));
                    }
                    return [2 /*return*/, instructions];
            }
        });
    });
}
/**
 * Make {@link TransactionInstruction} for `verify_signatures` instruction.
 *
 * This is used in {@link createVerifySignaturesInstructions} for each batch of signatures being verified.
 * `signatureSet` is a {@link web3.Keypair} generated outside of this method, used
 * for writing signatures and the message hash to.
 *
 * https://github.com/certusone/wormhole/blob/main/solana/bridge/program/src/api/verify_signature.rs
 *
 * @param {PublicKeyInitData} wormholeProgramId - wormhole program address
 * @param {PublicKeyInitData} payer - transaction signer address
 * @param {SignedVaa | ParsedVaa} vaa - either signed VAA (Buffer) or parsed VAA (use {@link parseVaa} on signed VAA)
 * @param {PublicKeyInitData} signatureSet - key for signature set account
 * @param {Buffer} signatureStatus - array of guardian indices
 *
 */
function createVerifySignaturesInstruction(wormholeProgramId, payer, vaa, signatureSet, signatureStatus) {
    var methods = createReadOnlyWormholeProgramInterface(wormholeProgramId).methods.verifySignatures(signatureStatus);
    // @ts-ignore
    return methods._ixFn.apply(methods, __spreadArray(__spreadArray([], __read(methods._args)), [{
            accounts: getVerifySignatureAccounts(wormholeProgramId, payer, signatureSet, vaa),
            signers: undefined,
            remainingAccounts: undefined,
            preInstructions: undefined,
            postInstructions: undefined,
        }]));
}
export function getVerifySignatureAccounts(wormholeProgramId, payer, signatureSet, vaa) {
    var parsed = isBytes(vaa) ? parseVaa(vaa) : vaa;
    return {
        payer: new PublicKey(payer),
        guardianSet: deriveGuardianSetKey(wormholeProgramId, parsed.guardianSetIndex),
        signatureSet: new PublicKey(signatureSet),
        instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
        rent: SYSVAR_RENT_PUBKEY,
        systemProgram: SystemProgram.programId,
    };
}
