"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validatePssword = exports.verifyProfileKeys = exports.preparePGPPublicKey = exports.encryptPGPKey = exports.decryptV2 = exports.encryptV2 = exports.stringToHex = exports.hexToBytes = exports.generateHash = exports.decryptPGPKey = exports.decryptWithWalletRPCMethod = exports.encryptV1 = exports.getPublicKey = void 0;
const tslib_1 = require("tslib");
const metamaskSigUtil = require("@metamask/eth-sig-util");
const eth_sig_util_1 = require("@metamask/eth-sig-util");
const CryptoJS = require("crypto-js");
const helpers_1 = require("../chat/helpers");
const constants_1 = require("../constants");
const address_1 = require("./address");
const signature_1 = require("../chat/helpers/signature");
const upgradeUser_1 = require("../user/upgradeUser");
const progressHook_1 = require("../progressHook");
const signer_1 = require("./signer");
const viem = require("viem");
const chains_1 = require("viem/chains");
const KDFSaltSize = 32; // bytes
const AESGCMNonceSize = 12; // property iv
let crypto;
if (typeof window !== 'undefined' && window.crypto) {
    crypto = window.crypto;
}
else if (typeof require !== 'undefined') {
    try {
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        crypto = require('crypto').webcrypto;
    }
    catch (e) {
        throw new Error('Unable To load crypto');
    }
}
/**
 * @deprecated
 */
const getPublicKey = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const { account, signer } = options || {};
    const pushSigner = signer ? new signer_1.Signer(signer) : undefined;
    const address = account || (yield (pushSigner === null || pushSigner === void 0 ? void 0 : pushSigner.getAddress())) || '';
    const metamaskProvider = viem.createWalletClient({
        chain: chains_1.mainnet,
        transport: viem.custom(window.ethereum),
    });
    const web3Provider = ((_a = signer === null || signer === void 0 ? void 0 : signer.provider) === null || _a === void 0 ? void 0 : _a.provider) || metamaskProvider;
    const keyB64 = yield web3Provider.request({
        method: 'eth_getEncryptionPublicKey',
        params: [address],
    });
    return keyB64;
});
exports.getPublicKey = getPublicKey;
/**
 * @deprecated
 * x25519-xsalsa20-poly1305 enryption
 */
const encryptV1 = (text, encryptionPublicKey, version) => {
    const encryptedSecret = metamaskSigUtil.encrypt({
        publicKey: encryptionPublicKey,
        data: text,
        version: version,
    });
    return encryptedSecret;
};
exports.encryptV1 = encryptV1;
/** DEPRECATED */
const decryptWithWalletRPCMethod = (encryptedPGPPrivateKey, account) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    console.warn('decryptWithWalletRPCMethod method is DEPRECATED. Use decryptPGPKey method with signer!');
    return yield (0, exports.decryptPGPKey)({
        encryptedPGPPrivateKey,
        account,
    });
});
exports.decryptWithWalletRPCMethod = decryptWithWalletRPCMethod;
const decryptPGPKey = (options) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    var _b, _c, _d;
    const { encryptedPGPPrivateKey, account = null, signer = null, env = constants_1.default.ENV.PROD, toUpgrade = false, additionalMeta = null, progressHook, } = options || {};
    try {
        if (account == null && signer == null) {
            throw new Error(`At least one from account or signer is necessary!`);
        }
        const wallet = (0, helpers_1.getWallet)({ account, signer });
        const address = yield (0, helpers_1.getAccountAddress)(wallet);
        if (!(0, address_1.isValidETHAddress)(address)) {
            throw new Error(`Invalid address!`);
        }
        const { version: encryptionType } = JSON.parse(encryptedPGPPrivateKey);
        let privateKey;
        // Report Progress
        progressHook === null || progressHook === void 0 ? void 0 : progressHook(progressHook_1.default['PUSH-DECRYPT-01']);
        switch (encryptionType) {
            case constants_1.default.ENC_TYPE_V1: {
                if ((_b = wallet === null || wallet === void 0 ? void 0 : wallet.signer) === null || _b === void 0 ? void 0 : _b.privateKey) {
                    privateKey = (0, eth_sig_util_1.decrypt)({
                        encryptedData: JSON.parse(encryptedPGPPrivateKey),
                        privateKey: (_c = wallet === null || wallet === void 0 ? void 0 : wallet.signer) === null || _c === void 0 ? void 0 : _c.privateKey.substring(2),
                    });
                }
                else {
                    const metamaskProvider = viem.createWalletClient({
                        chain: chains_1.mainnet,
                        transport: viem.custom(window.ethereum),
                    });
                    const web3Provider = ((_d = signer === null || signer === void 0 ? void 0 : signer.provider) === null || _d === void 0 ? void 0 : _d.provider) || metamaskProvider;
                    privateKey = yield web3Provider.request({
                        method: 'eth_decrypt',
                        params: [encryptedPGPPrivateKey, address],
                    });
                }
                break;
            }
            case constants_1.default.ENC_TYPE_V2: {
                if (!(wallet === null || wallet === void 0 ? void 0 : wallet.signer)) {
                    throw new Error('Cannot Decrypt this encryption version without signer!');
                }
                const { preKey: input } = JSON.parse(encryptedPGPPrivateKey);
                const enableProfileMessage = 'Enable Push Chat Profile \n' + input;
                let encodedPrivateKey;
                try {
                    const { verificationProof: secret } = yield (0, helpers_1.getEip712Signature)(wallet, enableProfileMessage, true);
                    encodedPrivateKey = yield (0, exports.decryptV2)(JSON.parse(encryptedPGPPrivateKey), (0, exports.hexToBytes)(secret || ''));
                }
                catch (err) {
                    const { verificationProof: secret } = yield (0, helpers_1.getEip712Signature)(wallet, enableProfileMessage, false);
                    encodedPrivateKey = yield (0, exports.decryptV2)(JSON.parse(encryptedPGPPrivateKey), (0, exports.hexToBytes)(secret || ''));
                }
                const dec = new TextDecoder();
                privateKey = dec.decode(encodedPrivateKey);
                break;
            }
            case constants_1.default.ENC_TYPE_V3: {
                if (!(wallet === null || wallet === void 0 ? void 0 : wallet.signer)) {
                    throw new Error('Cannot Decrypt this encryption version without signer!');
                }
                const { preKey: input } = JSON.parse(encryptedPGPPrivateKey);
                const enableProfileMessage = 'Enable Push Profile \n' + input;
                const { verificationProof: secret } = yield (0, helpers_1.getEip191Signature)(wallet, enableProfileMessage);
                const encodedPrivateKey = yield (0, exports.decryptV2)(JSON.parse(encryptedPGPPrivateKey), (0, exports.hexToBytes)(secret || ''));
                const dec = new TextDecoder();
                privateKey = dec.decode(encodedPrivateKey);
                break;
            }
            case constants_1.default.ENC_TYPE_V4: {
                let password = null;
                if (additionalMeta === null || additionalMeta === void 0 ? void 0 : additionalMeta.NFTPGP_V1) {
                    password = additionalMeta.NFTPGP_V1.password;
                }
                else {
                    if (!(wallet === null || wallet === void 0 ? void 0 : wallet.signer)) {
                        throw new Error('Cannot Decrypt this encryption version without signer!');
                    }
                    const { encryptedPassword } = JSON.parse(encryptedPGPPrivateKey);
                    password = yield (0, exports.decryptPGPKey)({
                        encryptedPGPPrivateKey: JSON.stringify(encryptedPassword),
                        signer,
                        env,
                    });
                }
                const encodedPrivateKey = yield (0, exports.decryptV2)(JSON.parse(encryptedPGPPrivateKey), (0, exports.hexToBytes)((0, exports.stringToHex)(password)));
                const dec = new TextDecoder();
                privateKey = dec.decode(encodedPrivateKey);
                break;
            }
            default:
                throw new Error('Invalid Encryption Type');
        }
        // try key upgradation
        if (signer && toUpgrade && encryptionType !== constants_1.default.ENC_TYPE_V4) {
            try {
                yield (0, upgradeUser_1.upgrade)({ env, account: address, signer, progressHook });
            }
            catch (err) {
                // Report Progress
                const errorProgressHook = progressHook_1.default['PUSH-ERROR-01'];
                progressHook === null || progressHook === void 0 ? void 0 : progressHook(errorProgressHook(err));
            }
        }
        // Report Progress
        progressHook === null || progressHook === void 0 ? void 0 : progressHook(progressHook_1.default['PUSH-DECRYPT-02']);
        return privateKey;
    }
    catch (err) {
        // Report Progress
        const errorProgressHook = progressHook_1.default['PUSH-ERROR-00'];
        progressHook === null || progressHook === void 0 ? void 0 : progressHook(errorProgressHook(exports.decryptPGPKey.name, err));
        throw Error(`[Push SDK] - API - Error - API ${exports.decryptPGPKey.name} -: ${err}`);
    }
});
exports.decryptPGPKey = decryptPGPKey;
const generateHash = (message) => {
    const hash = CryptoJS.SHA256(JSON.stringify(message)).toString(CryptoJS.enc.Hex);
    return hash;
};
exports.generateHash = generateHash;
const getRandomValues = (array) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    return crypto.getRandomValues(array);
});
const bytesToHex = (bytes) => {
    return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
};
const hexToBytes = (hex) => {
    const bytes = new Uint8Array(hex.length / 2);
    for (let i = 0; i < hex.length; i += 2) {
        bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
    }
    return bytes;
};
exports.hexToBytes = hexToBytes;
const stringToHex = (str) => {
    let hex = '';
    for (let i = 0; i < str.length; i++) {
        hex += str.charCodeAt(i).toString(16).padStart(2, '0');
    }
    return hex;
};
exports.stringToHex = stringToHex;
// Derive AES-256-GCM key from a shared secret and salt
const hkdf = (secret, salt) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    const key = yield crypto.subtle.importKey('raw', secret, 'HKDF', false, [
        'deriveKey',
    ]);
    return crypto.subtle.deriveKey({ name: 'HKDF', hash: 'SHA-256', salt, info: new ArrayBuffer(0) }, key, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
});
/** AES-GCM Encryption */
const encryptV2 = (data, secret, additionalData) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    const salt = crypto.getRandomValues(new Uint8Array(KDFSaltSize));
    const nonce = crypto.getRandomValues(new Uint8Array(AESGCMNonceSize));
    const key = yield hkdf(secret, salt);
    const aesGcmParams = {
        name: 'AES-GCM',
        iv: nonce,
    };
    if (additionalData) {
        aesGcmParams.additionalData = additionalData;
    }
    const encrypted = yield crypto.subtle.encrypt(aesGcmParams, key, data);
    return {
        ciphertext: bytesToHex(new Uint8Array(encrypted)),
        salt: bytesToHex(salt),
        nonce: bytesToHex(nonce),
    };
});
exports.encryptV2 = encryptV2;
/** AES-GCM Decryption */
const decryptV2 = (encryptedData, secret, additionalData) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    const key = yield hkdf(secret, (0, exports.hexToBytes)(encryptedData.salt));
    const aesGcmParams = {
        name: 'AES-GCM',
        iv: (0, exports.hexToBytes)(encryptedData.nonce),
    };
    if (additionalData) {
        aesGcmParams.additionalData = additionalData;
    }
    const decrypted = yield crypto.subtle.decrypt(aesGcmParams, key, (0, exports.hexToBytes)(encryptedData.ciphertext));
    return new Uint8Array(decrypted);
});
exports.decryptV2 = decryptV2;
const encryptPGPKey = (encryptionType, privateKey, wallet, additionalMeta) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    var _e, _f, _g;
    let encryptedPrivateKey;
    switch (encryptionType) {
        case constants_1.default.ENC_TYPE_V1: {
            let walletPublicKey;
            if ((_e = wallet === null || wallet === void 0 ? void 0 : wallet.signer) === null || _e === void 0 ? void 0 : _e.privateKey) {
                // get metamask specific encryption public key
                walletPublicKey = (0, eth_sig_util_1.getEncryptionPublicKey)((_f = wallet === null || wallet === void 0 ? void 0 : wallet.signer) === null || _f === void 0 ? void 0 : _f.privateKey.substring(2));
            }
            else {
                // wallet popup will happen to get encryption public key
                walletPublicKey = yield (0, exports.getPublicKey)(wallet);
            }
            encryptedPrivateKey = (0, exports.encryptV1)(privateKey, walletPublicKey, encryptionType);
            break;
        }
        case constants_1.default.ENC_TYPE_V2: {
            const input = bytesToHex(yield getRandomValues(new Uint8Array(32)));
            const enableProfileMessage = 'Enable Push Chat Profile \n' + input;
            const { verificationProof: secret } = yield (0, helpers_1.getEip712Signature)(wallet, enableProfileMessage, true);
            const enc = new TextEncoder();
            const encodedPrivateKey = enc.encode(privateKey);
            encryptedPrivateKey = yield (0, exports.encryptV2)(encodedPrivateKey, (0, exports.hexToBytes)(secret || ''));
            encryptedPrivateKey.version = constants_1.default.ENC_TYPE_V2;
            encryptedPrivateKey.preKey = input;
            break;
        }
        case constants_1.default.ENC_TYPE_V3: {
            const input = bytesToHex(yield getRandomValues(new Uint8Array(32)));
            const enableProfileMessage = 'Enable Push Profile \n' + input;
            const { verificationProof: secret } = yield (0, helpers_1.getEip191Signature)(wallet, enableProfileMessage);
            const enc = new TextEncoder();
            const encodedPrivateKey = enc.encode(privateKey);
            encryptedPrivateKey = yield (0, exports.encryptV2)(encodedPrivateKey, (0, exports.hexToBytes)(secret || ''));
            encryptedPrivateKey.version = constants_1.default.ENC_TYPE_V3;
            encryptedPrivateKey.preKey = input;
            break;
        }
        case constants_1.default.ENC_TYPE_V4: {
            if (!((_g = additionalMeta === null || additionalMeta === void 0 ? void 0 : additionalMeta.NFTPGP_V1) === null || _g === void 0 ? void 0 : _g.password)) {
                throw new Error('Password is required!');
            }
            const enc = new TextEncoder();
            const encodedPrivateKey = enc.encode(privateKey);
            encryptedPrivateKey = yield (0, exports.encryptV2)(encodedPrivateKey, (0, exports.hexToBytes)((0, exports.stringToHex)(additionalMeta.NFTPGP_V1.password)));
            encryptedPrivateKey.version = constants_1.default.ENC_TYPE_V4;
            encryptedPrivateKey.preKey = '';
            break;
        }
        default:
            throw new Error('Invalid Encryption Type');
    }
    return encryptedPrivateKey;
});
exports.encryptPGPKey = encryptPGPKey;
const preparePGPPublicKey = (encryptionType, publicKey, wallet) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    let chatPublicKey;
    switch (encryptionType) {
        case constants_1.default.ENC_TYPE_V1: {
            chatPublicKey = publicKey;
            break;
        }
        case constants_1.default.ENC_TYPE_V2:
        case constants_1.default.ENC_TYPE_V3:
        case constants_1.default.ENC_TYPE_V4: {
            const verificationProof = 'DEPRECATED';
            /**
             * @deprecated
             * PUSH CHAT PROFILE CREATION DOES NOT SIGN PGP PUBLIC KEY
             * VERIFICATION PROOF SIGNATURE SHOULD BE USED FOR VERIFICATION OF PUSH PROFILE KEYS
             */
            // const createProfileMessage =
            //   'Create Push Profile \n' + generateHash(publicKey);
            // const { verificationProof } = await getEip191Signature(
            //   wallet,
            //   createProfileMessage
            // );
            // TODO - Change JSON Structure to string ie equivalent to ENC_TYPE_V1 ( would be done after PUSH Node changes )
            chatPublicKey = JSON.stringify({
                key: publicKey,
                signature: verificationProof,
            });
            break;
        }
        default:
            throw new Error('Invalid Encryption Type');
    }
    return chatPublicKey;
});
exports.preparePGPPublicKey = preparePGPPublicKey;
/**
 * Checks the Push Profile keys using verificationProof
 * @param encryptedPrivateKey
 * @param publicKey
 * @param did
 * @param caip10
 * @param verificationProof
 * @returns PGP Public Key
 */
const verifyProfileKeys = (encryptedPrivateKey, publicKey, did, caip10, verificationProof) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
    let parsedPublicKey;
    try {
        parsedPublicKey = JSON.parse(publicKey).key;
        if (parsedPublicKey === undefined) {
            throw new Error('Invalid Public Key');
        }
    }
    catch (err) {
        parsedPublicKey = publicKey;
    }
    try {
        if (publicKey &&
            publicKey.length > 0 &&
            verificationProof &&
            // Allow pgp sig validation after eip191v2 only
            verificationProof.split(':')[0] === 'eip191v2') {
            const data = {
                caip10,
                did,
                publicKey,
                encryptedPrivateKey,
            };
            if ((0, address_1.isValidCAIP10NFTAddress)(did)) {
                const keyToRemove = 'owner';
                const parsedEncryptedPrivateKey = JSON.parse(encryptedPrivateKey);
                if (keyToRemove in parsedEncryptedPrivateKey) {
                    delete parsedEncryptedPrivateKey[keyToRemove];
                }
                data.encryptedPrivateKey = JSON.stringify(parsedEncryptedPrivateKey);
            }
            const signedData = (0, exports.generateHash)(data);
            const isValidSig = yield (0, signature_1.verifyProfileSignature)(verificationProof, signedData, (0, address_1.isValidCAIP10NFTAddress)(did)
                ? (0, address_1.pCAIP10ToWallet)(JSON.parse(encryptedPrivateKey).owner)
                : (0, address_1.pCAIP10ToWallet)(did));
            if (isValidSig) {
                return parsedPublicKey;
            }
            else {
                throw new Error('Invalid Signature');
            }
        }
        return parsedPublicKey;
    }
    catch (err) {
        console.warn(`Cannot Verify keys for DID : ${did} !!!`);
        return parsedPublicKey;
    }
});
exports.verifyProfileKeys = verifyProfileKeys;
const validatePssword = (password) => {
    if (password.length < 8) {
        throw new Error('Password must be at least 8 characters long!');
    }
    if (!/[A-Z]/.test(password)) {
        throw new Error('Password must contain at least one uppercase letter!');
    }
    if (!/[a-z]/.test(password)) {
        throw new Error('Password must contain at least one lowercase letter!');
    }
    if (!/\d/.test(password)) {
        throw new Error('Password must contain at least one digit!');
    }
    if (!/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
        throw new Error('Password must contain at least one special character!');
    }
};
exports.validatePssword = validatePssword;
//# sourceMappingURL=crypto.js.map