'use strict';

var sdkCrypto = require('@virgilsecurity/sdk-crypto');
var initFoundationModules = require('@virgilsecurity/core-foundation/worker.asmjs.cjs.js');
var initUtils = require('@virgilsecurity/init-utils');
var dataUtils = require('@virgilsecurity/data-utils');

let random;
let keyProvider;
const ensureExist = () => {
    if (!random || !keyProvider) {
        throw new Error("Cannot use global instances if the 'resetGlobalInstances' function has been called or 'createGlobalInstances' function has not been called yet.");
    }
};
const getRandom = () => {
    ensureExist();
    return random;
};
const getKeyProvider = () => {
    ensureExist();
    return keyProvider;
};
const createGlobalInstances = (foundationModules) => {
    random = new foundationModules.CtrDrbg();
    try {
        random.setupDefaults();
    }
    catch (error) {
        random.delete();
        throw error;
    }
    keyProvider = new foundationModules.KeyProvider();
    keyProvider.random = random;
    try {
        keyProvider.setupDefaults();
    }
    catch (error) {
        random.delete();
        keyProvider.delete();
        throw error;
    }
};
const resetGlobalInstances = () => {
    if (!random && !keyProvider) {
        return;
    }
    ensureExist();
    random.delete();
    keyProvider.delete();
    random = undefined;
    keyProvider = undefined;
};

const moduleInitializer = new initUtils.ModuleInitializer();
const FOUNDATION_MODULE_KEY = 'foundation';
moduleInitializer.addModule(FOUNDATION_MODULE_KEY, initFoundationModules);
moduleInitializer.on('load', (name, modules) => {
    if (name === FOUNDATION_MODULE_KEY) {
        resetGlobalInstances();
        createGlobalInstances(modules);
    }
});
moduleInitializer.on('remove', name => {
    if (name === FOUNDATION_MODULE_KEY) {
        resetGlobalInstances();
    }
});
const hasFoundationModules = () => moduleInitializer.hasModule(FOUNDATION_MODULE_KEY);
const getFoundationModules = () => moduleInitializer.getModule(FOUNDATION_MODULE_KEY);
const setFoundationModules = (foundationModules) => {
    moduleInitializer.setModule(FOUNDATION_MODULE_KEY, foundationModules);
};
const initCrypto = moduleInitializer.loadModules;

exports.HashAlgorithm = void 0;
(function (HashAlgorithm) {
    HashAlgorithm["SHA224"] = "SHA224";
    HashAlgorithm["SHA256"] = "SHA256";
    HashAlgorithm["SHA384"] = "SHA384";
    HashAlgorithm["SHA512"] = "SHA512";
})(exports.HashAlgorithm || (exports.HashAlgorithm = {}));

exports.KeyPairType = void 0;
(function (KeyPairType) {
    KeyPairType["DEFAULT"] = "DEFAULT";
    KeyPairType["ED25519"] = "ED25519";
    KeyPairType["CURVE25519"] = "CURVE25519";
    KeyPairType["SECP256R1"] = "SECP256R1";
    KeyPairType["RSA_2048"] = "RSA_2048";
    KeyPairType["RSA_3072"] = "RSA_3072";
    KeyPairType["RSA_4096"] = "RSA_4096";
    KeyPairType["RSA_8192"] = "RSA_8192";
    KeyPairType["CURVE25519_ROUND5_ED25519_FALCON"] = "CURVE25519_ROUND5_ED25519_FALCON";
    KeyPairType["CURVE25519_ED25519"] = "CURVE25519_ED25519";
})(exports.KeyPairType || (exports.KeyPairType = {}));
const getKeyPairTypeConfig = (keyPairType) => {
    const { AlgId } = moduleInitializer.getModule('foundation');
    switch (keyPairType) {
        case exports.KeyPairType.DEFAULT:
            return {
                type: exports.KeyPairType.DEFAULT,
                algId: AlgId.ED25519,
            };
        case exports.KeyPairType.ED25519:
            return {
                type: exports.KeyPairType.ED25519,
                algId: AlgId.ED25519,
            };
        case exports.KeyPairType.CURVE25519:
            return {
                type: exports.KeyPairType.CURVE25519,
                algId: AlgId.CURVE25519,
            };
        case exports.KeyPairType.SECP256R1:
            return {
                type: exports.KeyPairType.SECP256R1,
                algId: AlgId.SECP256R1,
            };
        case exports.KeyPairType.RSA_2048:
            return {
                type: exports.KeyPairType.RSA_2048,
                algId: AlgId.RSA,
                bitlen: 2048,
            };
        case exports.KeyPairType.RSA_3072:
            return {
                type: exports.KeyPairType.RSA_3072,
                algId: AlgId.RSA,
                bitlen: 3072,
            };
        case exports.KeyPairType.RSA_4096:
            return {
                type: exports.KeyPairType.RSA_4096,
                algId: AlgId.RSA,
                bitlen: 4096,
            };
        case exports.KeyPairType.RSA_8192:
            return {
                type: exports.KeyPairType.RSA_8192,
                algId: AlgId.RSA,
                bitlen: 8192,
            };
        case exports.KeyPairType.CURVE25519_ROUND5_ED25519_FALCON:
            return {
                type: exports.KeyPairType.CURVE25519_ROUND5_ED25519_FALCON,
                cipherAlgIds: [AlgId.CURVE25519, AlgId.ROUND5_ND_1CCA_5D],
                signerAlgIds: [AlgId.ED25519, AlgId.FALCON],
            };
        case exports.KeyPairType.CURVE25519_ED25519:
            return {
                type: exports.KeyPairType.CURVE25519_ED25519,
                cipherAlgIds: [AlgId.CURVE25519, AlgId.NONE],
                signerAlgIds: [AlgId.ED25519, AlgId.NONE],
            };
        default:
            throw new TypeError(`Unknown key pair type '${keyPairType}'.`);
    }
};
const isRSAKeyPairType = (keyPairType) => keyPairType === exports.KeyPairType.RSA_2048 ||
    keyPairType === exports.KeyPairType.RSA_3072 ||
    keyPairType === exports.KeyPairType.RSA_4096 ||
    keyPairType === exports.KeyPairType.RSA_8192;
const isCompoundKeyPairType = (keyPairType) => keyPairType === exports.KeyPairType.CURVE25519_ROUND5_ED25519_FALCON ||
    keyPairType === exports.KeyPairType.CURVE25519_ED25519;

const DATA_SIGNATURE_KEY = dataUtils.NodeBuffer.from('VIRGIL-DATA-SIGNATURE', 'utf8');
const DATA_SIGNER_ID_KEY = dataUtils.NodeBuffer.from('VIRGIL-DATA-SIGNER-ID', 'utf8');
const PADDING_LEN = 160;
const MIN_GROUP_ID_BYTE_LENGTH = 10;

class VirgilPrivateKey {
    get isDisposed() {
        return this._isDisposed;
    }
    constructor(indentifier, lowLevelPrivateKey) {
        this.identifier = dataUtils.toBuffer(indentifier);
        this.lowLevelPrivateKey = lowLevelPrivateKey;
        this._isDisposed = false;
    }
    dispose() {
        this.lowLevelPrivateKey.delete();
        this._isDisposed = true;
    }
}

class VirgilPublicKey {
    get isDisposed() {
        return this._isDisposed;
    }
    get key() {
        const foundationModules = getFoundationModules();
        const keyAsn1Serializer = new foundationModules.KeyAsn1Serializer();
        try {
            keyAsn1Serializer.setupDefaults();
            return keyAsn1Serializer.serializePublicKey(this.lowLevelPublicKey);
        }
        finally {
            keyAsn1Serializer.delete();
        }
    }
    constructor(identifier, lowLevelPublicKey) {
        this.identifier = dataUtils.toBuffer(identifier);
        this.lowLevelPublicKey = lowLevelPublicKey;
        this._isDisposed = false;
    }
    dispose() {
        this.lowLevelPublicKey.delete();
        this._isDisposed = true;
    }
}

function validatePrivateKey(privateKey) {
    if (!(privateKey instanceof VirgilPrivateKey)) {
        throw new TypeError("An argument is not an instance of 'VirgilPrivateKey' class.");
    }
    if (privateKey.isDisposed) {
        throw new TypeError("Cannot use an instance of 'VirgilPrivateKey' class after it was disposed.");
    }
}
function validatePublicKey(publicKey) {
    if (!(publicKey instanceof VirgilPublicKey)) {
        throw new TypeError("An argument is not a 'VirgilPublicKey'.");
    }
    if (publicKey.isDisposed) {
        throw new TypeError("Cannot use an instance of 'VirgilPublicKey' class after it was disposed.");
    }
}
function validatePublicKeysArray(publicKeys) {
    if (!Array.isArray(publicKeys)) {
        throw new TypeError('An argument is not an array.');
    }
    if (!publicKeys.length) {
        throw new TypeError("An array of 'VirgilPublicKey' instances should not be empty.");
    }
    publicKeys.forEach(validatePublicKey);
}
function validatePositiveNonZeroNumber(number) {
    if (typeof number !== 'number') {
        throw new TypeError('An argument is not a number.');
    }
    if (number <= 0) {
        throw new TypeError(`An argument should be greater that '0', but received '${number}'.`);
    }
}
function validateGroupId(groupId) {
    if (!(groupId instanceof Uint8Array)) {
        throw new TypeError("An argument is not an instance of 'Uint8Array' class.");
    }
    if (groupId.byteLength < MIN_GROUP_ID_BYTE_LENGTH) {
        throw new TypeError(`An argument byte length is too small. Expected to be at least '${MIN_GROUP_ID_BYTE_LENGTH}' bytes.`);
    }
}

function parseGroupSessionMessage(messageData) {
    const message = getFoundationModules().GroupSessionMessage.deserialize(messageData);
    const info = {
        epochNumber: message.getEpoch(),
        sessionId: dataUtils.toBuffer(message.getSessionId()).toString('hex'),
        data: dataUtils.toBuffer(messageData).toString('base64'),
    };
    message.delete();
    return info;
}
function getEpochNumberFromEpochMessage(epochMessageData) {
    const epoch = getFoundationModules().GroupSessionMessage.deserialize(epochMessageData);
    const epochNumber = epoch.getEpoch();
    epoch.delete();
    return epochNumber;
}
function createLowLevelSession(epochMessages) {
    const module = getFoundationModules();
    const session = new module.GroupSession();
    session.rng = getRandom();
    const deleteQueue = [];
    try {
        for (const epochMessageData of epochMessages) {
            const epoch = getFoundationModules().GroupSessionMessage.deserialize(epochMessageData);
            deleteQueue.push(epoch);
            session.addEpoch(epoch);
        }
        return session;
    }
    finally {
        while (deleteQueue.length) {
            const obj = deleteQueue.pop();
            obj && obj.delete();
        }
    }
}
function computeSessionId(groupId) {
    const foundation = getFoundationModules();
    const sha512 = new foundation.Sha512();
    try {
        return sha512.hash(groupId).subarray(0, 32);
    }
    finally {
        sha512.delete();
    }
}
function createInitialEpoch(sessionId) {
    const foundation = getFoundationModules();
    const ticket = new foundation.GroupSessionTicket();
    ticket.rng = getRandom();
    try {
        ticket.setupTicketAsNew(sessionId);
        return ticket.getTicketMessage();
    }
    finally {
        ticket.delete();
    }
}

function createVirgilGroupSession(epochMessages) {
    epochMessages = epochMessages
        .slice()
        .sort((a, b) => getEpochNumberFromEpochMessage(a) - getEpochNumberFromEpochMessage(b));
    return {
        getSessionId() {
            const session = createLowLevelSession(epochMessages);
            const id = session.getSessionId();
            session.delete();
            return dataUtils.toBuffer(id).toString('hex');
        },
        getCurrentEpochNumber() {
            return getEpochNumberFromEpochMessage(epochMessages[epochMessages.length - 1]);
        },
        encrypt(data, signingPrivateKey) {
            const dataBytes = dataUtils.dataToUint8Array(data, 'utf8');
            validatePrivateKey(signingPrivateKey);
            let session;
            try {
                session = createLowLevelSession(epochMessages);
                const message = session.encrypt(dataBytes, signingPrivateKey.lowLevelPrivateKey);
                const encrypted = message.serialize();
                message.delete();
                return dataUtils.toBuffer(encrypted);
            }
            finally {
                session && session.delete();
            }
        },
        decrypt(encryptedData, verifyingPublicKey) {
            const encryptedDataBytes = dataUtils.dataToUint8Array(encryptedData, 'base64');
            validatePublicKey(verifyingPublicKey);
            let session;
            let message;
            try {
                session = createLowLevelSession(epochMessages);
                message = getFoundationModules().GroupSessionMessage.deserialize(encryptedDataBytes);
                return dataUtils.toBuffer(session.decrypt(message, verifyingPublicKey.lowLevelPublicKey));
            }
            finally {
                message && message.delete();
                session && session.delete();
            }
        },
        addNewEpoch() {
            const session = createLowLevelSession(epochMessages);
            try {
                const newEpochTicket = session.createGroupTicket();
                const newEpoch = newEpochTicket.getTicketMessage();
                const newEpochMessage = newEpoch.serialize();
                epochMessages.push(newEpochMessage);
                newEpoch.delete();
                newEpochTicket.delete();
                return parseGroupSessionMessage(newEpochMessage);
            }
            finally {
                session.delete();
            }
        },
        export() {
            return epochMessages.map(dataUtils.toBuffer);
        },
        parseMessage(messageData) {
            const messageBytes = dataUtils.dataToUint8Array(messageData, 'base64');
            return parseGroupSessionMessage(messageBytes);
        },
    };
}

const toArray = (val) => {
    return val == null ? [] : Array.isArray(val) ? val : [val];
};

exports.VirgilCryptoErrorStatus = void 0;
(function (VirgilCryptoErrorStatus) {
    VirgilCryptoErrorStatus["STREAM_ILLEGAL_STATE"] = "STREAM_ILLEGAL_STATE";
    VirgilCryptoErrorStatus["DATA_NOT_SIGNED"] = "DATA_NOT_SIGNED";
    VirgilCryptoErrorStatus["SIGNER_NOT_FOUND"] = "SIGNER_NOT_FOUND";
    VirgilCryptoErrorStatus["INVALID_SIGNATURE"] = "INVALID_SIGNATURE";
})(exports.VirgilCryptoErrorStatus || (exports.VirgilCryptoErrorStatus = {}));
class VirgilCryptoError extends Error {
    constructor(errorStatus, message) {
        super(message || VirgilCryptoError.DEFAULT_MESSAGE);
        Object.setPrototypeOf(this, VirgilCryptoError.prototype);
        this.name = 'VirgilCryptoError';
        this.status = errorStatus;
    }
}
VirgilCryptoError.DEFAULT_MESSAGE = "Use the 'status' property and 'VirgilCryptoErrorStatus' enum to check for specific error.";

class VirgilStreamCipher {
    get isRunning() {
        return this._isRunning;
    }
    get isFinished() {
        return this._isFinished;
    }
    get isDisposed() {
        return this._isDisposed;
    }
    constructor(arg0, arg1) {
        const foundationModules = getFoundationModules();
        const publicKeys = toArray(arg0);
        validatePublicKeysArray(publicKeys);
        this.recipientCipher = new foundationModules.RecipientCipher();
        this.aes256Gcm = new foundationModules.Aes256Gcm();
        this.recipientCipher.encryptionCipher = this.aes256Gcm;
        this.recipientCipher.random = getRandom();
        publicKeys.forEach(publicKey => {
            this.recipientCipher.addKeyRecipient(publicKey.identifier, publicKey.lowLevelPublicKey);
        });
        if (arg1) {
            const mySignature = dataUtils.dataToUint8Array(arg1, 'base64');
            this.messageInfoCustomParams = this.recipientCipher.customParams();
            this.messageInfoCustomParams.addData(DATA_SIGNATURE_KEY, mySignature);
        }
        this._isFinished = false;
        this._isRunning = false;
        this._isDisposed = false;
    }
    start() {
        this.ensureLegalState();
        this.recipientCipher.startEncryption();
        this._isRunning = true;
        return dataUtils.toBuffer(this.recipientCipher.packMessageInfo());
    }
    update(data) {
        this.ensureLegalState();
        this.ensureIsRunning();
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        return dataUtils.toBuffer(this.recipientCipher.processEncryption(myData));
    }
    final(dispose = true) {
        this.ensureLegalState();
        this.ensureIsRunning();
        try {
            return dataUtils.toBuffer(this.recipientCipher.finishEncryption());
        }
        finally {
            this._isFinished = true;
            this._isRunning = false;
            if (dispose) {
                this.dispose();
            }
        }
    }
    dispose() {
        if (this.messageInfoCustomParams) {
            this.messageInfoCustomParams.delete();
        }
        this.aes256Gcm.delete();
        this.recipientCipher.delete();
        this._isDisposed = true;
    }
    ensureLegalState() {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'dispose' method has been called.");
        }
        if (this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'final' method has been called.");
        }
    }
    ensureIsRunning() {
        if (!this._isRunning) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher before the 'start' method.");
        }
    }
}

class VirgilStreamDecipher {
    get isFinished() {
        return this._isFinished;
    }
    get isDisposed() {
        return this._isDisposed;
    }
    constructor(privateKey) {
        this._isFinished = false;
        this._isDisposed = false;
        const foundationModules = getFoundationModules();
        validatePrivateKey(privateKey);
        this.recipientCipher = new foundationModules.RecipientCipher();
        try {
            this.recipientCipher.startDecryptionWithKey(privateKey.identifier, privateKey.lowLevelPrivateKey, new Uint8Array());
        }
        catch (error) {
            this.recipientCipher.delete();
            throw error;
        }
    }
    getSignature() {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot get signature after the 'dispose' method has been called.");
        }
        if (!this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot get signature before the 'final' method has been called.");
        }
        const messageInfoCustomParams = this.recipientCipher.customParams();
        try {
            return dataUtils.toBuffer(messageInfoCustomParams.findData(DATA_SIGNATURE_KEY));
        }
        finally {
            messageInfoCustomParams.delete();
        }
    }
    update(data) {
        this.ensureLegalState();
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        return dataUtils.toBuffer(this.recipientCipher.processDecryption(myData));
    }
    final(dispose = true) {
        this.ensureLegalState();
        try {
            return dataUtils.toBuffer(this.recipientCipher.finishDecryption());
        }
        finally {
            this._isFinished = true;
            if (dispose) {
                this.dispose();
            }
        }
    }
    dispose() {
        this.recipientCipher.delete();
        this._isDisposed = true;
    }
    ensureLegalState() {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'dispose' method has been called.");
        }
        if (this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'final' method has been called.");
        }
    }
}

class VirgilStreamDecryptAndVerify {
    constructor() {
        const foundation = getFoundationModules();
        this.paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
        this.recipientCipher = new foundation.RecipientCipher();
        this.recipientCipher.random = getRandom();
        this.recipientCipher.paddingParams = this.paddingParams;
        this._isDisposed = false;
        this._isFinished = false;
    }
    start(privateKey) {
        this.ensureLegalState();
        validatePrivateKey(privateKey);
        this.recipientCipher.startDecryptionWithKey(privateKey.identifier, privateKey.lowLevelPrivateKey, new Uint8Array());
    }
    update(data) {
        this.ensureLegalState();
        const myData = dataUtils.dataToUint8Array(data);
        const processEncryption = this.recipientCipher.processDecryption(myData);
        return dataUtils.toBuffer(processEncryption);
    }
    final() {
        this.ensureLegalState();
        const finishDecryption = this.recipientCipher.finishDecryption();
        try {
            return dataUtils.toBuffer(finishDecryption);
        }
        finally {
            this._isFinished = true;
        }
    }
    verify(arg0, arg1 = true) {
        const publicKeys = toArray(arg0);
        validatePublicKeysArray(publicKeys);
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot verify signature after the 'dispose' method has been called.");
        }
        if (!this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot verify signature before the 'final' method has been called.");
        }
        let signerInfoList;
        try {
            if (!this.recipientCipher.isDataSigned()) {
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.DATA_NOT_SIGNED);
            }
            signerInfoList = this.recipientCipher.signerInfos();
            if (!signerInfoList.hasItem()) {
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.DATA_NOT_SIGNED);
            }
            const signerInfo = signerInfoList.item();
            let signerPublicKey;
            for (let i = 0; i < publicKeys.length; i += 1) {
                if (dataUtils.NodeBuffer.compare(signerInfo.signerId(), publicKeys[i].identifier) === 0) {
                    signerPublicKey = publicKeys[i];
                    break;
                }
                if (i === publicKeys.length - 1) {
                    throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.SIGNER_NOT_FOUND);
                }
            }
            if (!this.recipientCipher.verifySignerInfo(signerInfo, signerPublicKey.lowLevelPublicKey)) {
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.INVALID_SIGNATURE);
            }
        }
        finally {
            if (signerInfoList) {
                signerInfoList.delete();
            }
            if (arg1) {
                this.dispose();
            }
        }
    }
    dispose() {
        this.paddingParams.delete();
        this.recipientCipher.delete();
        this._isDisposed = true;
    }
    ensureLegalState() {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'dispose' method has been called.");
        }
        if (this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'final' method has been called.");
        }
    }
}

class VirgilStreamSignAndEncrypt {
    get isRunning() {
        return this._isRunning;
    }
    get isFinished() {
        return this._isFinished;
    }
    get isDisposed() {
        return this._isDisposed;
    }
    constructor(arg0, arg1, arg2) {
        validatePrivateKey(arg0);
        const publicKeys = toArray(arg1);
        validatePublicKeysArray(publicKeys);
        const foundation = getFoundationModules();
        const random = getRandom();
        this.recipientCipher = new foundation.RecipientCipher();
        this.aes256Gcm = new foundation.Aes256Gcm();
        this.sha512 = new foundation.Sha512();
        this.recipientCipher.encryptionCipher = this.aes256Gcm;
        this.recipientCipher.random = random;
        this.recipientCipher.signerHash = this.sha512;
        if (arg2) {
            this.randomPadding = new foundation.RandomPadding();
            this.randomPadding.random = random;
            this.recipientCipher.encryptionPadding = this.randomPadding;
            this.paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
            this.recipientCipher.paddingParams = this.paddingParams;
        }
        publicKeys.forEach(publicKey => {
            this.recipientCipher.addKeyRecipient(publicKey.identifier, publicKey.lowLevelPublicKey);
        });
        try {
            this.recipientCipher.addSigner(arg0.identifier, arg0.lowLevelPrivateKey);
            this._isDisposed = false;
            this._isRunning = false;
            this._isFinished = false;
        }
        catch (error) {
            this.dispose();
            throw error;
        }
    }
    start(length) {
        this.ensureLegalState();
        this.recipientCipher.startSignedEncryption(length);
        const messageInfo = this.recipientCipher.packMessageInfo();
        this._isRunning = true;
        return dataUtils.toBuffer(messageInfo);
    }
    update(data) {
        this.ensureLegalState();
        this.ensureIsRunning();
        const myData = dataUtils.dataToUint8Array(data);
        const processEncryption = this.recipientCipher.processEncryption(myData);
        return dataUtils.toBuffer(processEncryption);
    }
    final(dispose = true) {
        this.ensureLegalState();
        this.ensureIsRunning();
        const finishEncryption = this.recipientCipher.finishEncryption();
        const messageInfoFooter = this.recipientCipher.packMessageInfoFooter();
        try {
            return dataUtils.NodeBuffer.concat([finishEncryption, messageInfoFooter]);
        }
        finally {
            this._isFinished = true;
            this._isRunning = false;
            if (dispose) {
                this.dispose();
            }
        }
    }
    dispose() {
        this.sha512.delete();
        this.aes256Gcm.delete();
        if (this.randomPadding) {
            this.randomPadding.delete();
        }
        if (this.paddingParams) {
            this.paddingParams.delete();
        }
        this.recipientCipher.delete();
        this._isDisposed = true;
    }
    ensureLegalState() {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'dispose' method has been called.");
        }
        if (this._isFinished) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher after the 'final' method has been called.");
        }
    }
    ensureIsRunning() {
        if (!this._isRunning) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use cipher before the 'start' method.");
        }
    }
}

class VirgilStreamSigner {
    get isDisposed() {
        return this._isDisposed;
    }
    constructor() {
        this._isDisposed = false;
        const foundationModules = getFoundationModules();
        this.signer = new foundationModules.Signer();
        this.sha512 = new foundationModules.Sha512();
        this.signer.hash = this.sha512;
        this.signer.random = getRandom();
        this.signer.reset();
    }
    update(data) {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use signer after the 'dispose' method has been called.");
        }
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        this.signer.appendData(myData);
        return this;
    }
    sign(privateKey, final = true) {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. The VirgilStreamSigner has been disposed. Pass 'false' as the second argument to the 'sign' method if you need to generate more than one signature.");
        }
        validatePrivateKey(privateKey);
        const result = this.signer.sign(privateKey.lowLevelPrivateKey);
        if (final) {
            this.dispose();
        }
        return dataUtils.toBuffer(result);
    }
    dispose() {
        this.sha512.delete();
        this.signer.delete();
        this._isDisposed = true;
    }
}

class VirgilStreamVerifier {
    get isDisposed() {
        return this._isDisposed;
    }
    constructor(signature) {
        this._isDisposed = false;
        const foundationModules = getFoundationModules();
        const mySignature = dataUtils.dataToUint8Array(signature, 'base64');
        this.verifier = new foundationModules.Verifier();
        try {
            this.verifier.reset(mySignature);
        }
        catch (error) {
            this.verifier.delete();
            throw error;
        }
    }
    update(data) {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. Cannot use signer after the 'dispose' method has been called.");
        }
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        this.verifier.appendData(myData);
        return this;
    }
    verify(publicKey, final = true) {
        if (this._isDisposed) {
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.STREAM_ILLEGAL_STATE, "Illegal state. The VirgilStreamVerifier has been disposed. Pass 'false' as the second argument to the 'verify' method if you need to verify with more than one public key.");
        }
        validatePublicKey(publicKey);
        const result = this.verifier.verify(publicKey.lowLevelPublicKey);
        if (final) {
            this.dispose();
        }
        return result;
    }
    dispose() {
        this.verifier.delete();
        this._isDisposed = true;
    }
}

class VirgilCrypto {
    constructor(options = {}) {
        this.hashAlgorithm = exports.HashAlgorithm;
        this.keyPairType = exports.KeyPairType;
        this.defaultKeyPairType = options.defaultKeyPairType || exports.KeyPairType.DEFAULT;
        this.useSha256Identifiers = options.useSha256Identifiers || false;
    }
    generateKeys(type) {
        const keyPairType = type ? type : this.defaultKeyPairType;
        const keyPairTypeConfig = getKeyPairTypeConfig(keyPairType);
        return this.generateKeyPair(getKeyProvider(), keyPairTypeConfig);
    }
    generateKeysFromKeyMaterial(keyMaterial, type) {
        const keyPairType = type ? type : this.defaultKeyPairType;
        const keyPairTypeConfig = getKeyPairTypeConfig(keyPairType);
        const myKeyMaterial = dataUtils.dataToUint8Array(keyMaterial, 'base64');
        const foundation = getFoundationModules();
        const keyMaterialRng = new foundation.KeyMaterialRng();
        keyMaterialRng.resetKeyMaterial(myKeyMaterial);
        const keyProvider = new foundation.KeyProvider();
        keyProvider.random = keyMaterialRng;
        try {
            keyProvider.setupDefaults();
        }
        catch (error) {
            keyMaterialRng.delete();
            keyProvider.delete();
            throw error;
        }
        try {
            return this.generateKeyPair(keyProvider, keyPairTypeConfig);
        }
        finally {
            keyMaterialRng.delete();
            keyProvider.delete();
        }
    }
    importPrivateKey(rawPrivateKey) {
        const keyProvider = getKeyProvider();
        const serializedPrivateKey = dataUtils.dataToUint8Array(rawPrivateKey, 'base64');
        const lowLevelPrivateKey = keyProvider.importPrivateKey(serializedPrivateKey);
        const lowLevelPublicKey = lowLevelPrivateKey.extractPublicKey();
        try {
            const serializedPublicKey = keyProvider.exportPublicKey(lowLevelPublicKey);
            const identifier = this.calculateKeyPairIdentifier(serializedPublicKey);
            return new VirgilPrivateKey(identifier, lowLevelPrivateKey);
        }
        finally {
            lowLevelPublicKey.delete();
        }
    }
    exportPrivateKey(privateKey) {
        validatePrivateKey(privateKey);
        const keyProvider = getKeyProvider();
        const publicKeyData = keyProvider.exportPrivateKey(privateKey.lowLevelPrivateKey);
        return dataUtils.toBuffer(publicKeyData);
    }
    importPublicKey(rawPublicKey) {
        const serializedPublicKey = dataUtils.dataToUint8Array(rawPublicKey, 'base64');
        const keyProvider = getKeyProvider();
        const lowLevelPublicKey = keyProvider.importPublicKey(serializedPublicKey);
        const identifier = this.calculateKeyPairIdentifier(serializedPublicKey);
        return new VirgilPublicKey(identifier, lowLevelPublicKey);
    }
    exportPublicKey(publicKey) {
        validatePublicKey(publicKey);
        const keyProvider = getKeyProvider();
        const publicKeyData = keyProvider.exportPublicKey(publicKey.lowLevelPublicKey);
        return dataUtils.toBuffer(publicKeyData);
    }
    encrypt(arg0, arg1, arg2) {
        const data = dataUtils.dataToUint8Array(arg0, 'utf8');
        const publicKeys = toArray(arg1);
        validatePublicKeysArray(publicKeys);
        const foundation = getFoundationModules();
        const random = getRandom();
        const recipientCipher = new foundation.RecipientCipher();
        const aes256Gcm = new foundation.Aes256Gcm();
        recipientCipher.encryptionCipher = aes256Gcm;
        recipientCipher.random = random;
        let randomPadding;
        let paddingParams;
        if (arg2) {
            randomPadding = new foundation.RandomPadding();
            randomPadding.random = random;
            recipientCipher.encryptionPadding = randomPadding;
            paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
            recipientCipher.paddingParams = paddingParams;
        }
        publicKeys.forEach(({ identifier }, index) => {
            recipientCipher.addKeyRecipient(identifier, publicKeys[index].lowLevelPublicKey);
        });
        try {
            recipientCipher.startEncryption();
            const messageInfo = recipientCipher.packMessageInfo();
            const processEncryption = recipientCipher.processEncryption(data);
            const finishEncryption = recipientCipher.finishEncryption();
            return dataUtils.NodeBuffer.concat([messageInfo, processEncryption, finishEncryption]);
        }
        finally {
            aes256Gcm.delete();
            if (paddingParams) {
                paddingParams.delete();
            }
            if (randomPadding) {
                randomPadding.delete();
            }
            recipientCipher.delete();
        }
    }
    decrypt(encryptedData, privateKey) {
        const myData = dataUtils.dataToUint8Array(encryptedData, 'base64');
        validatePrivateKey(privateKey);
        const foundation = getFoundationModules();
        const recipientCipher = new foundation.RecipientCipher();
        recipientCipher.random = getRandom();
        const paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
        recipientCipher.paddingParams = paddingParams;
        try {
            recipientCipher.startDecryptionWithKey(privateKey.identifier, privateKey.lowLevelPrivateKey, new Uint8Array());
            const processDecryption = recipientCipher.processDecryption(myData);
            const finishDecryption = recipientCipher.finishDecryption();
            return dataUtils.NodeBuffer.concat([processDecryption, finishDecryption]);
        }
        finally {
            paddingParams.delete();
            recipientCipher.delete();
        }
    }
    calculateHash(data, algorithm = exports.HashAlgorithm.SHA512) {
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        let result;
        switch (algorithm) {
            case exports.HashAlgorithm.SHA224:
                result = this.createHash(myData, getFoundationModules().Sha224);
                break;
            case exports.HashAlgorithm.SHA256:
                result = this.createHash(myData, getFoundationModules().Sha256);
                break;
            case exports.HashAlgorithm.SHA384:
                result = this.createHash(myData, getFoundationModules().Sha384);
                break;
            case exports.HashAlgorithm.SHA512:
                result = this.createHash(myData, getFoundationModules().Sha512);
                break;
            default:
                throw new TypeError('Unknown hash algorithm');
        }
        return dataUtils.toBuffer(result);
    }
    extractPublicKey(privateKey) {
        validatePrivateKey(privateKey);
        const lowLevelPublicKey = privateKey.lowLevelPrivateKey.extractPublicKey();
        return new VirgilPublicKey(privateKey.identifier, lowLevelPublicKey);
    }
    calculateSignature(data, privateKey) {
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        validatePrivateKey(privateKey);
        const foundation = getFoundationModules();
        const signer = new foundation.Signer();
        const sha512 = new foundation.Sha512();
        signer.random = getRandom();
        signer.hash = sha512;
        signer.reset();
        signer.appendData(myData);
        try {
            const signature = signer.sign(privateKey.lowLevelPrivateKey);
            return dataUtils.toBuffer(signature);
        }
        finally {
            signer.delete();
            sha512.delete();
        }
    }
    verifySignature(data, signature, publicKey) {
        const myData = dataUtils.dataToUint8Array(data, 'utf8');
        const mySignature = dataUtils.dataToUint8Array(signature, 'base64');
        validatePublicKey(publicKey);
        const foundation = getFoundationModules();
        const verifier = new foundation.Verifier();
        try {
            verifier.reset(mySignature);
        }
        catch (error) {
            verifier.delete();
            throw error;
        }
        verifier.appendData(myData);
        const result = verifier.verify(publicKey.lowLevelPublicKey);
        verifier.delete();
        return result;
    }
    signAndEncrypt(arg0, arg1, arg2, arg3) {
        const data = dataUtils.dataToUint8Array(arg0, 'utf8');
        validatePrivateKey(arg1);
        const publicKeys = toArray(arg2);
        validatePublicKeysArray(publicKeys);
        const { messageInfo, processEncryption, finishEncryption, messageInfoFooter, } = this._signAndEncrypt(data, arg1, publicKeys, arg3);
        return dataUtils.NodeBuffer.concat([messageInfo, processEncryption, finishEncryption, messageInfoFooter]);
    }
    signThenEncrypt(arg0, arg1, arg2, arg3) {
        const data = dataUtils.dataToUint8Array(arg0, 'utf8');
        validatePrivateKey(arg1);
        const publicKeys = toArray(arg2);
        validatePublicKeysArray(publicKeys);
        const { messageInfo, processEncryption, finishEncryption } = this._signThenEncrypt(data, arg1, publicKeys, arg3);
        return dataUtils.NodeBuffer.concat([messageInfo, processEncryption, finishEncryption]);
    }
    decryptAndVerify(arg0, arg1, arg2) {
        const encryptedData = dataUtils.dataToUint8Array(arg0, 'base64');
        validatePrivateKey(arg1);
        const publicKeys = toArray(arg2);
        validatePublicKeysArray(publicKeys);
        return this._decryptAndVerify(encryptedData, new Uint8Array(), arg1, publicKeys);
    }
    decryptThenVerify(arg0, arg1, arg2) {
        const encryptedData = dataUtils.dataToUint8Array(arg0, 'base64');
        validatePrivateKey(arg1);
        const publicKeys = toArray(arg2);
        validatePublicKeysArray(publicKeys);
        return this._decryptThenVerify(encryptedData, new Uint8Array(), arg1, publicKeys);
    }
    getRandomBytes(length) {
        validatePositiveNonZeroNumber(length);
        const bytes = getRandom().random(length);
        return dataUtils.toBuffer(bytes);
    }
    signThenEncryptDetached(arg0, arg1, arg2, arg3) {
        const data = dataUtils.dataToUint8Array(arg0, 'utf8');
        validatePrivateKey(arg1);
        const publicKeys = toArray(arg2);
        validatePublicKeysArray(publicKeys);
        const { messageInfo, processEncryption, finishEncryption } = this._signThenEncrypt(data, arg1, publicKeys, arg3);
        return {
            encryptedData: dataUtils.NodeBuffer.concat([processEncryption, finishEncryption]),
            metadata: dataUtils.toBuffer(messageInfo),
        };
    }
    decryptThenVerifyDetached(arg0, arg1, arg2, arg3) {
        const encryptedData = dataUtils.dataToUint8Array(arg0, 'base64');
        const messageInfo = dataUtils.dataToUint8Array(arg1, 'base64');
        validatePrivateKey(arg2);
        const publicKeys = toArray(arg3);
        validatePublicKeysArray(publicKeys);
        return this._decryptThenVerify(encryptedData, messageInfo, arg2, publicKeys);
    }
    createStreamCipher(arg0, arg1) {
        return new VirgilStreamCipher(arg0, arg1);
    }
    createStreamDecipher(privateKey) {
        return new VirgilStreamDecipher(privateKey);
    }
    createStreamSignAndEncrypt(arg0, arg1, arg2) {
        return new VirgilStreamSignAndEncrypt(arg0, arg1, arg2);
    }
    createStreamDecryptAndVerify() {
        return new VirgilStreamDecryptAndVerify();
    }
    createStreamSigner() {
        return new VirgilStreamSigner();
    }
    createStreamVerifier(signature) {
        return new VirgilStreamVerifier(signature);
    }
    generateGroupSession(groupId) {
        const groupIdBytes = dataUtils.dataToUint8Array(groupId, 'utf8');
        validateGroupId(groupIdBytes);
        const sessionId = computeSessionId(groupIdBytes);
        const initialEpoch = createInitialEpoch(sessionId);
        const initialEpochMessage = initialEpoch.serialize();
        initialEpoch.delete();
        return createVirgilGroupSession([initialEpochMessage]);
    }
    importGroupSession(epochMessages) {
        if (!Array.isArray(epochMessages)) {
            throw new TypeError('Epoch messages must be an array.');
        }
        if (epochMessages.length === 0) {
            throw new TypeError('Epoch messages must not be empty.');
        }
        return createVirgilGroupSession(epochMessages.map(it => dataUtils.dataToUint8Array(it, 'base64')));
    }
    calculateGroupSessionId(groupId) {
        const groupIdBytes = dataUtils.dataToUint8Array(groupId, 'utf8');
        validateGroupId(groupIdBytes);
        return dataUtils.toBuffer(computeSessionId(groupIdBytes)).toString('hex');
    }
    createHash(data, HashClass) {
        const hashInstance = new HashClass();
        const hash = hashInstance.hash(data);
        hashInstance.delete();
        return hash;
    }
    calculateKeyPairIdentifier(serializedPublicKey) {
        if (this.useSha256Identifiers) {
            return this.createHash(serializedPublicKey, getFoundationModules().Sha256);
        }
        return this.createHash(serializedPublicKey, getFoundationModules().Sha512).slice(0, 8);
    }
    generateKeyPair(keyProvider, keyPairTypeConfig) {
        let lowLevelPrivateKey;
        if (isCompoundKeyPairType(keyPairTypeConfig.type)) {
            const [cipherFirstKeyAlgId, cipherSecondKeyAlgId] = keyPairTypeConfig.cipherAlgIds;
            const [signerFirstKeyAlgId, signerSecondKeyAlgId] = keyPairTypeConfig.signerAlgIds;
            lowLevelPrivateKey = keyProvider.generateCompoundHybridPrivateKey(cipherFirstKeyAlgId, cipherSecondKeyAlgId, signerFirstKeyAlgId, signerSecondKeyAlgId);
        }
        else {
            if (isRSAKeyPairType(keyPairTypeConfig.type)) {
                keyProvider.setRsaParams(keyPairTypeConfig.bitlen);
            }
            lowLevelPrivateKey = keyProvider.generatePrivateKey(keyPairTypeConfig.algId);
        }
        const lowLevelPublicKey = lowLevelPrivateKey.extractPublicKey();
        let serializedPublicKey;
        try {
            serializedPublicKey = keyProvider.exportPublicKey(lowLevelPublicKey);
        }
        catch (error) {
            lowLevelPrivateKey.delete();
            lowLevelPublicKey.delete();
            throw error;
        }
        const identifier = this.calculateKeyPairIdentifier(serializedPublicKey);
        return {
            privateKey: new VirgilPrivateKey(identifier, lowLevelPrivateKey),
            publicKey: new VirgilPublicKey(identifier, lowLevelPublicKey),
        };
    }
    _signAndEncrypt(data, privateKey, publicKeys, enablePadding) {
        const foundation = getFoundationModules();
        const random = getRandom();
        const recipientCipher = new foundation.RecipientCipher();
        const aes256Gcm = new foundation.Aes256Gcm();
        const sha512 = new foundation.Sha512();
        recipientCipher.encryptionCipher = aes256Gcm;
        recipientCipher.random = random;
        recipientCipher.signerHash = sha512;
        let randomPadding;
        let paddingParams;
        if (enablePadding) {
            randomPadding = new foundation.RandomPadding();
            randomPadding.random = random;
            recipientCipher.encryptionPadding = randomPadding;
            paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
            recipientCipher.paddingParams = paddingParams;
        }
        publicKeys.forEach(({ identifier }, index) => {
            recipientCipher.addKeyRecipient(identifier, publicKeys[index].lowLevelPublicKey);
        });
        try {
            recipientCipher.addSigner(privateKey.identifier, privateKey.lowLevelPrivateKey);
            recipientCipher.startSignedEncryption(data.length);
            const messageInfo = recipientCipher.packMessageInfo();
            const processEncryption = recipientCipher.processEncryption(data);
            const finishEncryption = recipientCipher.finishEncryption();
            const messageInfoFooter = recipientCipher.packMessageInfoFooter();
            return {
                messageInfo,
                processEncryption,
                finishEncryption,
                messageInfoFooter,
            };
        }
        finally {
            sha512.delete();
            aes256Gcm.delete();
            if (randomPadding) {
                randomPadding.delete();
            }
            if (paddingParams) {
                paddingParams.delete();
            }
            recipientCipher.delete();
        }
    }
    _signThenEncrypt(data, privateKey, publicKeys, enablePadding) {
        const foundation = getFoundationModules();
        const random = getRandom();
        const recipientCipher = new foundation.RecipientCipher();
        const aes256Gcm = new foundation.Aes256Gcm();
        recipientCipher.encryptionCipher = aes256Gcm;
        recipientCipher.random = random;
        let randomPadding;
        let paddingParams;
        if (enablePadding) {
            randomPadding = new foundation.RandomPadding();
            randomPadding.random = random;
            recipientCipher.encryptionPadding = randomPadding;
            paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
            recipientCipher.paddingParams = paddingParams;
        }
        publicKeys.forEach(({ identifier }, index) => {
            recipientCipher.addKeyRecipient(identifier, publicKeys[index].lowLevelPublicKey);
        });
        const messageInfoCustomParams = recipientCipher.customParams();
        try {
            const signature = this.calculateSignature(data, privateKey);
            messageInfoCustomParams.addData(DATA_SIGNATURE_KEY, signature);
            messageInfoCustomParams.addData(DATA_SIGNER_ID_KEY, privateKey.identifier);
            recipientCipher.startEncryption();
            const messageInfo = recipientCipher.packMessageInfo();
            const processEncryption = recipientCipher.processEncryption(data);
            const finishEncryption = recipientCipher.finishEncryption();
            return {
                messageInfo,
                processEncryption,
                finishEncryption,
            };
        }
        finally {
            messageInfoCustomParams.delete();
            aes256Gcm.delete();
            if (randomPadding) {
                randomPadding.delete();
            }
            if (paddingParams) {
                paddingParams.delete();
            }
            recipientCipher.delete();
        }
    }
    _decryptAndVerify(encryptedData, messageInfo, privateKey, publicKeys) {
        const foundation = getFoundationModules();
        const paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
        const recipientCipher = new foundation.RecipientCipher();
        recipientCipher.random = getRandom();
        recipientCipher.paddingParams = paddingParams;
        let decryptedData;
        try {
            recipientCipher.startDecryptionWithKey(privateKey.identifier, privateKey.lowLevelPrivateKey, messageInfo);
            const processDecryption = recipientCipher.processDecryption(encryptedData);
            const finishDecryption = recipientCipher.finishDecryption();
            decryptedData = dataUtils.NodeBuffer.concat([processDecryption, finishDecryption]);
        }
        catch (error) {
            paddingParams.delete();
            recipientCipher.delete();
            throw error;
        }
        if (!recipientCipher.isDataSigned()) {
            paddingParams.delete();
            recipientCipher.delete();
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.DATA_NOT_SIGNED);
        }
        const signerInfoList = recipientCipher.signerInfos();
        if (!signerInfoList.hasItem()) {
            paddingParams.delete();
            signerInfoList.delete();
            recipientCipher.delete();
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.DATA_NOT_SIGNED);
        }
        const signerInfo = signerInfoList.item();
        let signerPublicKey;
        for (let i = 0; i < publicKeys.length; i += 1) {
            if (dataUtils.NodeBuffer.compare(signerInfo.signerId(), publicKeys[i].identifier) === 0) {
                signerPublicKey = publicKeys[i];
                break;
            }
            if (i === publicKeys.length - 1) {
                paddingParams.delete();
                signerInfo.delete();
                signerInfoList.delete();
                recipientCipher.delete();
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.SIGNER_NOT_FOUND);
            }
        }
        if (!recipientCipher.verifySignerInfo(signerInfo, signerPublicKey.lowLevelPublicKey)) {
            paddingParams.delete();
            signerInfo.delete();
            signerInfoList.delete();
            recipientCipher.delete();
            throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.INVALID_SIGNATURE);
        }
        paddingParams.delete();
        signerInfo.delete();
        signerInfoList.delete();
        recipientCipher.delete();
        return decryptedData;
    }
    _decryptThenVerify(encryptedData, messageInfo, privateKey, publicKeys) {
        const foundation = getFoundationModules();
        const paddingParams = foundation.PaddingParams.newWithConstraints(PADDING_LEN, PADDING_LEN);
        const recipientCipher = new foundation.RecipientCipher();
        recipientCipher.random = getRandom();
        recipientCipher.paddingParams = paddingParams;
        let decryptedData;
        try {
            recipientCipher.startDecryptionWithKey(privateKey.identifier, privateKey.lowLevelPrivateKey, messageInfo);
            const processDecryption = recipientCipher.processDecryption(encryptedData);
            const finishDecryption = recipientCipher.finishDecryption();
            decryptedData = dataUtils.NodeBuffer.concat([processDecryption, finishDecryption]);
        }
        catch (error) {
            paddingParams.delete();
            recipientCipher.delete();
            throw error;
        }
        const messageInfoCustomParams = recipientCipher.customParams();
        let signerPublicKey;
        if (publicKeys.length === 1) {
            signerPublicKey = publicKeys[0];
        }
        else {
            let signerId;
            try {
                signerId = messageInfoCustomParams.findData(DATA_SIGNER_ID_KEY);
            }
            catch (error) {
                paddingParams.delete();
                recipientCipher.delete();
                messageInfoCustomParams.delete();
                throw error;
            }
            for (let i = 0; i < publicKeys.length; i += 1) {
                if (dataUtils.NodeBuffer.compare(signerId, publicKeys[i].identifier) === 0) {
                    signerPublicKey = publicKeys[i];
                    break;
                }
            }
            if (!signerPublicKey) {
                messageInfoCustomParams.delete();
                paddingParams.delete();
                recipientCipher.delete();
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.SIGNER_NOT_FOUND);
            }
        }
        try {
            const signature = messageInfoCustomParams.findData(DATA_SIGNATURE_KEY);
            const isValid = this.verifySignature(decryptedData, signature, signerPublicKey);
            if (!isValid) {
                throw new VirgilCryptoError(exports.VirgilCryptoErrorStatus.INVALID_SIGNATURE);
            }
            return decryptedData;
        }
        finally {
            messageInfoCustomParams.delete();
            paddingParams.delete();
            recipientCipher.delete();
        }
    }
}

exports.VirgilCrypto = VirgilCrypto;
exports.VirgilCryptoError = VirgilCryptoError;
exports.VirgilPrivateKey = VirgilPrivateKey;
exports.VirgilPublicKey = VirgilPublicKey;
exports.VirgilStreamCipher = VirgilStreamCipher;
exports.VirgilStreamDecipher = VirgilStreamDecipher;
exports.VirgilStreamSigner = VirgilStreamSigner;
exports.VirgilStreamVerifier = VirgilStreamVerifier;
exports.getFoundationModules = getFoundationModules;
exports.hasFoundationModules = hasFoundationModules;
exports.initCrypto = initCrypto;
exports.moduleInitializer = moduleInitializer;
exports.setFoundationModules = setFoundationModules;
Object.keys(sdkCrypto).forEach(function (k) {
    if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
        enumerable: true,
        get: function () { return sdkCrypto[k]; }
    });
});
