"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 });
// import { AESWrappercbc } from './aescbc'
const Random_js_1 = __importDefault(require("../primitives/Random.js"));
const PrivateKey_js_1 = __importDefault(require("../primitives/PrivateKey.js"));
const PublicKey_js_1 = __importDefault(require("../primitives/PublicKey.js"));
const Point_js_1 = __importDefault(require("../primitives/Point.js"));
const Hash = __importStar(require("../primitives/Hash.js"));
const utils_js_1 = require("../primitives/utils.js");
function AES(key) {
    if (this._tables[0][0][0] === 0)
        this._precompute();
    let tmp;
    const sbox = this._tables[0][4];
    const decTable = this._tables[1];
    const keyLen = key.length;
    let rcon = 1;
    if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
        throw new Error('invalid aes key size');
    }
    const encKey = key.slice(0);
    const decKey = [];
    this._key = [encKey, decKey];
    // schedule encryption keys
    let i;
    for (i = keyLen; i < 4 * keyLen + 28; i++) {
        tmp = encKey[i - 1];
        // apply sbox
        if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
            tmp =
                (sbox[tmp >>> 24] << 24) ^
                    (sbox[(tmp >> 16) & 255] << 16) ^
                    (sbox[(tmp >> 8) & 255] << 8) ^
                    sbox[tmp & 255];
            // shift rows and add rcon
            if (i % keyLen === 0) {
                tmp = (tmp << 8) ^ (tmp >>> 24) ^ (rcon << 24);
                rcon = (rcon << 1) ^ ((rcon >> 7) * 283);
            }
        }
        encKey[i] = encKey[i - keyLen] ^ tmp;
    }
    // schedule decryption keys
    for (let j = 0; i > 0; j++, i--) {
        tmp = encKey[(j & 3) === 0 ? i - 4 : i];
        if (i <= 4 || j < 4) {
            decKey[j] = tmp;
        }
        else {
            decKey[j] =
                decTable[0][sbox[tmp >>> 24]] ^
                    decTable[1][sbox[(tmp >> 16) & 255]] ^
                    decTable[2][sbox[(tmp >> 8) & 255]] ^
                    decTable[3][sbox[tmp & 255]];
        }
    }
}
AES.prototype = {
    /**
     * Encrypt an array of 4 big-endian words.
     * @param {Array} data The plaintext.
     * @return {Array} The ciphertext.
     */
    encrypt: function (data) {
        return this._crypt(data, 0);
    },
    /**
     * Decrypt an array of 4 big-endian words.
     * @param {Array} data The ciphertext.
     * @return {Array} The plaintext.
     */
    decrypt: function (data) {
        return this._crypt(data, 1);
    },
    /**
     * The expanded S-box and inverse S-box tables.  These will be computed
     * on the client so that we don't have to send them down the wire.
     *
     * There are two tables, _tables[0] is for encryption and
     * _tables[1] is for decryption.
     *
     * The first 4 sub-tables are the expanded S-box with MixColumns.  The
     * last (_tables[01][4]) is the S-box itself.
     *
     * @private
     */
    _tables: [
        [
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256)
        ],
        [
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256),
            new Uint32Array(256)
        ]
    ],
    // Expand the S-box tables.
    _precompute: function () {
        const encTable = this._tables[0];
        const decTable = this._tables[1];
        const sbox = encTable[4];
        const sboxInv = decTable[4];
        let i;
        let x;
        let xInv;
        const d = new Uint8Array(256);
        const th = new Uint8Array(256);
        let x2;
        let x4;
        let x8;
        let s;
        let tEnc;
        let tDec;
        // Compute double and third tables
        for (i = 0; i < 256; i++) {
            d[i] = (i << 1) ^ ((i >> 7) * 283);
            th[d[i] ^ i] = i;
        }
        for (x = xInv = 0; sbox[x] === 0; x ^= (x2 === 0 ? 1 : x2), xInv = th[xInv] === 0 ? 1 : th[xInv]) {
            // Compute sbox
            s = xInv ^ (xInv << 1) ^ (xInv << 2) ^ (xInv << 3) ^ (xInv << 4);
            s = (s >> 8) ^ (s & 255) ^ 99;
            sbox[x] = s;
            sboxInv[s] = x;
            // Compute MixColumns
            x2 = d[x];
            x4 = d[x2];
            x8 = d[x4];
            tDec = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
            tEnc = (d[s] * 0x101) ^ (s * 0x1010100);
            for (i = 0; i < 4; i++) {
                encTable[i][x] = tEnc = (tEnc << 24) ^ (tEnc >>> 8);
                decTable[i][s] = tDec = (tDec << 24) ^ (tDec >>> 8);
            }
        }
    },
    /**
     * Encryption and decryption core.
     * @param {Array} input Four words to be encrypted or decrypted.
     * @param dir The direction, 0 for encrypt and 1 for decrypt.
     * @return {Array} The four encrypted or decrypted words.
     * @private
     */
    _crypt: function (input, dir) {
        if (input.length !== 4) {
            throw new Error('invalid aes block size');
        }
        const key = this._key[dir];
        // state variables a,b,c,d are loaded with pre-whitened data
        let a = input[0] ^ key[0];
        let b = input[dir === 1 ? 3 : 1] ^ key[1];
        let c = input[2] ^ key[2];
        let d = input[dir === 1 ? 1 : 3] ^ key[3];
        let a2;
        let b2;
        let c2;
        const nInnerRounds = key.length / 4 - 2;
        let i;
        let kIndex = 4;
        const out = new Uint32Array(4);
        const // <--- this is slower in Node, about the same in Chrome */
        table = this._tables[dir];
        // load up the tables
        const t0 = table[0];
        const t1 = table[1];
        const t2 = table[2];
        const t3 = table[3];
        const sbox = table[4];
        // Inner rounds.  Cribbed from OpenSSL.
        for (i = 0; i < nInnerRounds; i++) {
            a2 =
                t0[a >>> 24] ^
                    t1[(b >> 16) & 255] ^
                    t2[(c >> 8) & 255] ^
                    t3[d & 255] ^
                    key[kIndex];
            b2 =
                t0[b >>> 24] ^
                    t1[(c >> 16) & 255] ^
                    t2[(d >> 8) & 255] ^
                    t3[a & 255] ^
                    key[kIndex + 1];
            c2 =
                t0[c >>> 24] ^
                    t1[(d >> 16) & 255] ^
                    t2[(a >> 8) & 255] ^
                    t3[b & 255] ^
                    key[kIndex + 2];
            d =
                t0[d >>> 24] ^
                    t1[(a >> 16) & 255] ^
                    t2[(b >> 8) & 255] ^
                    t3[c & 255] ^
                    key[kIndex + 3];
            kIndex += 4;
            a = a2;
            b = b2;
            c = c2;
        }
        // Last round.
        for (i = 0; i < 4; i++) {
            out[dir === 1 ? 3 & -i : i] =
                (sbox[a >>> 24] << 24) ^
                    (sbox[(b >> 16) & 255] << 16) ^
                    (sbox[(c >> 8) & 255] << 8) ^
                    sbox[d & 255] ^
                    key[kIndex++];
            a2 = a;
            a = b;
            b = c;
            c = d;
            d = a2;
        }
        return out;
    }
};
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class AESWrapper {
    static encrypt(messageBuf, keyBuf) {
        const key = AESWrapper.buf2Words(keyBuf);
        const message = AESWrapper.buf2Words(messageBuf);
        const a = new AES(key);
        const enc = a.encrypt(message);
        const encBuf = AESWrapper.words2Buf(enc);
        return encBuf;
    }
    static decrypt(encBuf, keyBuf) {
        const enc = AESWrapper.buf2Words(encBuf);
        const key = AESWrapper.buf2Words(keyBuf);
        const a = new AES(key);
        const message = a.decrypt(enc);
        const messageBuf = AESWrapper.words2Buf(message);
        return messageBuf;
    }
    static buf2Words(buf) {
        if (buf.length % 4 !== 0) {
            throw new Error('buf length must be a multiple of 4');
        }
        const words = [];
        for (let i = 0; i < buf.length / 4; i++) {
            const val = buf[i * 4] * 0x1000000 + // Shift the first byte by 24 bits
                ((buf[i * 4 + 1] << 16) | // Shift the second byte by 16 bits
                    (buf[i * 4 + 2] << 8) | // Shift the third byte by 8 bits
                    buf[i * 4 + 3]); // The fourth byte
            words.push(val);
        }
        return words;
    }
    static words2Buf(words) {
        const buf = new Array(words.length * 4);
        for (let i = 0; i < words.length; i++) {
            const word = words[i];
            buf[i * 4] = (word >>> 24) & 0xff;
            buf[i * 4 + 1] = (word >>> 16) & 0xff;
            buf[i * 4 + 2] = (word >>> 8) & 0xff;
            buf[i * 4 + 3] = word & 0xff;
        }
        return buf;
    }
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class CBC {
    static buf2BlocksBuf(buf, blockSize) {
        const bytesize = blockSize / 8;
        const blockBufs = [];
        for (let i = 0; i <= buf.length / bytesize; i++) {
            let blockBuf = buf.slice(i * bytesize, i * bytesize + bytesize);
            if (blockBuf.length < blockSize) {
                blockBuf = CBC.pkcs7Pad(blockBuf, blockSize);
            }
            blockBufs.push(blockBuf);
        }
        return blockBufs;
    }
    static blockBufs2Buf(blockBufs) {
        let last = blockBufs.at(-1);
        last = CBC.pkcs7Unpad(last);
        blockBufs[blockBufs.length - 1] = last;
        const buf = blockBufs.flat();
        return buf;
    }
    static encrypt(messageBuf, ivBuf, blockCipher, cipherKeyBuf) {
        const blockSize = ivBuf.length * 8;
        const blockBufs = CBC.buf2BlocksBuf(messageBuf, blockSize);
        const encBufs = CBC.encryptBlocks(blockBufs, ivBuf, blockCipher, cipherKeyBuf);
        const encBuf = encBufs.flat();
        return encBuf;
    }
    static decrypt(encBuf, ivBuf, blockCipher, cipherKeyBuf) {
        const bytesize = ivBuf.length;
        const encBufs = [];
        for (let i = 0; i < encBuf.length / bytesize; i++) {
            encBufs.push(encBuf.slice(i * bytesize, i * bytesize + bytesize));
        }
        const blockBufs = CBC.decryptBlocks(encBufs, ivBuf, blockCipher, cipherKeyBuf);
        const buf = CBC.blockBufs2Buf(blockBufs);
        return buf;
    }
    static encryptBlock(blockBuf, ivBuf, blockCipher, cipherKeyBuf) {
        const xorbuf = CBC.xorBufs(blockBuf, ivBuf);
        const encBuf = blockCipher.encrypt(xorbuf, cipherKeyBuf);
        return encBuf;
    }
    static decryptBlock(encBuf, ivBuf, blockCipher, cipherKeyBuf) {
        const xorbuf = blockCipher.decrypt(encBuf, cipherKeyBuf);
        const blockBuf = CBC.xorBufs(xorbuf, ivBuf);
        return blockBuf;
    }
    static encryptBlocks(blockBufs, ivBuf, blockCipher, cipherKeyBuf) {
        const encBufs = [];
        for (const blockBuf of blockBufs) {
            const encBuf = CBC.encryptBlock(blockBuf, ivBuf, blockCipher, cipherKeyBuf);
            encBufs.push(encBuf);
            ivBuf = encBuf;
        }
        return encBufs;
    }
    static decryptBlocks(encBufs, ivBuf, blockCipher, cipherKeyBuf) {
        const blockBufs = [];
        for (const encBuf of encBufs) {
            const blockBuf = CBC.decryptBlock(encBuf, ivBuf, blockCipher, cipherKeyBuf);
            blockBufs.push(blockBuf);
            ivBuf = encBuf;
        }
        return blockBufs;
    }
    static pkcs7Pad(buf, blockSize) {
        const bytesize = blockSize / 8;
        const padbytesize = bytesize - buf.length;
        const pad = new Array(padbytesize);
        pad.fill(padbytesize);
        const paddedbuf = [...buf, ...pad];
        return paddedbuf;
    }
    static pkcs7Unpad(paddedbuf) {
        const padlength = paddedbuf.at(-1);
        const padbuf = paddedbuf.slice(paddedbuf.length - padlength, paddedbuf.length);
        const padbuf2 = new Array(padlength);
        padbuf2.fill(padlength);
        if ((0, utils_js_1.toHex)(padbuf) !== (0, utils_js_1.toHex)(padbuf2)) {
            throw new Error('invalid padding');
        }
        return paddedbuf.slice(0, paddedbuf.length - padlength);
    }
    static xorBufs(buf1, buf2) {
        if (buf1.length !== buf2.length) {
            throw new Error('bufs must have the same length');
        }
        const buf = new Array(buf1.length);
        for (let i = 0; i < buf1.length; i++) {
            buf[i] = buf1[i] ^ buf2[i];
        }
        return buf;
    }
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class AESCBC {
    static encrypt(messageBuf, cipherKeyBuf, ivBuf, concatIvBuf = true) {
        ivBuf = ivBuf ?? (0, Random_js_1.default)(128 / 8);
        const ctBuf = CBC.encrypt(messageBuf, ivBuf, AESWrapper, cipherKeyBuf);
        if (concatIvBuf) {
            return [...ivBuf, ...ctBuf];
        }
        else {
            return [...ctBuf];
        }
    }
    static decrypt(encBuf, cipherKeyBuf, ivBuf) {
        if (ivBuf == null) {
            ivBuf = encBuf.slice(0, 128 / 8);
            const ctBuf = encBuf.slice(128 / 8);
            return CBC.decrypt(ctBuf, ivBuf, AESWrapper, cipherKeyBuf);
        }
        else {
            const ctBuf = encBuf;
            return CBC.decrypt(ctBuf, ivBuf, AESWrapper, cipherKeyBuf);
        }
    }
}
/**
 * @class ECIES
 * Implements the Electrum ECIES protocol for encrypted communication.
 *
 * @prprecated This class is deprecated in favor of the BRC-78 standard for portable encrypted messages,
 * which provides a more comprehensive and secure solution by integrating with BRC-42 and BRC-43 standards.
 */
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class ECIES {
    /**
     * Generates the initialization vector (iv), encryption key (kE), and MAC key (kM)
     * using the sender's private key and receiver's public key.
     *
     * @param {PrivateKey} privKey - The sender's private key.
     * @param {PublicKey} pubKey - The receiver's public key.
     * @returns {Object} An object containing the iv, kE, and kM as number arrays.
     */
    static ivkEkM(privKey, pubKey) {
        const r = privKey;
        const KB = pubKey;
        const P = KB.mul(r);
        const S = new PublicKey_js_1.default(P.x, P.y);
        const Sbuf = S.encode(true);
        const hash = Hash.sha512(Sbuf);
        return {
            iv: hash.slice(0, 16),
            kE: hash.slice(16, 32),
            kM: hash.slice(32, 64)
        };
    }
    /**
     * Encrypts a given message using the Electrum ECIES method.
     *
     * @param {number[]} messageBuf - The message to be encrypted, in number array format.
     * @param {PublicKey} toPublicKey - The public key of the recipient.
     * @param {PrivateKey} [fromPrivateKey] - The private key of the sender. If not provided, a random private key is used.
     * @param {boolean} [noKey=false] - If true, does not include the sender's public key in the encrypted message.
     * @returns {number[]} The encrypted message as a number array.
     */
    static electrumEncrypt(messageBuf, toPublicKey, fromPrivateKey, noKey = false) {
        let Rbuf = null;
        fromPrivateKey ?? (fromPrivateKey = PrivateKey_js_1.default.fromRandom());
        if (!noKey) {
            Rbuf = fromPrivateKey.toPublicKey().encode(true);
        }
        const { iv, kE, kM } = ECIES.ivkEkM(fromPrivateKey, toPublicKey);
        const ciphertext = AESCBC.encrypt(messageBuf, kE, iv, false);
        const BIE1 = (0, utils_js_1.toArray)('BIE1', 'utf8');
        let encBuf;
        if (Rbuf !== undefined && Rbuf !== null && Rbuf.length > 0) {
            encBuf = [...BIE1, ...Rbuf, ...ciphertext];
        }
        else {
            encBuf = [...BIE1, ...ciphertext];
        }
        const hmac = Hash.sha256hmac(kM, encBuf);
        return [...encBuf, ...hmac];
    }
    /**
     * Decrypts a message encrypted using the Electrum ECIES method.
     *
     * @param {number[]} encBuf - The encrypted message buffer.
     * @param {PrivateKey} toPrivateKey - The private key of the recipient.
     * @param {PublicKey} [fromPublicKey=null] - The public key of the sender. If not provided, it is extracted from the message.
     * @returns {number[]} The decrypted message as a number array.
     */
    static electrumDecrypt(encBuf, toPrivateKey, fromPublicKey) {
        const tagLength = 32;
        const magic = encBuf.slice(0, 4);
        if ((0, utils_js_1.encode)(magic, 'utf8') !== 'BIE1') {
            throw new Error('Invalid Magic');
        }
        let offset = 4;
        // Determine if the sender's public key is included in encBuf
        let Rbuf = null;
        if (encBuf.length - offset - tagLength >= 33) {
            const firstByte = encBuf[offset];
            if (firstByte === 0x02 || firstByte === 0x03) {
                // Compressed public key
                Rbuf = encBuf.slice(offset, offset + 33);
                offset += 33;
            }
            else if (firstByte === 0x04) {
                // Uncompressed public key
                Rbuf = encBuf.slice(offset, offset + 65);
                offset += 65;
            }
        }
        if (Rbuf === null) {
            if (fromPublicKey == null) {
                throw new Error('Sender public key is required');
            }
        }
        else {
            fromPublicKey ?? (fromPublicKey = PublicKey_js_1.default.fromString((0, utils_js_1.toHex)(Rbuf)));
        }
        const { iv, kE, kM } = ECIES.ivkEkM(toPrivateKey, fromPublicKey);
        const ciphertext = encBuf.slice(offset, encBuf.length - tagLength);
        const hmac = encBuf.slice(encBuf.length - tagLength, encBuf.length);
        const hmac2 = Hash.sha256hmac(kM, encBuf.slice(0, encBuf.length - tagLength));
        if ((0, utils_js_1.toHex)(hmac) !== (0, utils_js_1.toHex)(hmac2)) {
            throw new Error('Invalid checksum');
        }
        return AESCBC.decrypt(ciphertext, kE, iv);
    }
    /**
     * Encrypts a given message using the Bitcore variant of ECIES.
     *
     * @param {number[]} messageBuf - The message to be encrypted, in number array format.
     * @param {PublicKey} toPublicKey - The public key of the recipient.
     * @param {PrivateKey} [fromPrivateKey] - The private key of the sender. If not provided, a random private key is used.
     * @param {number[]} [ivBuf] - The initialization vector for encryption. If not provided, a random IV is used.
     * @returns {number[]} The encrypted message as a number array.
     */
    static bitcoreEncrypt(messageBuf, toPublicKey, fromPrivateKey, ivBuf) {
        fromPrivateKey ?? (fromPrivateKey = PrivateKey_js_1.default.fromRandom());
        ivBuf ?? (ivBuf = (0, Random_js_1.default)(16));
        const r = fromPrivateKey;
        const RPublicKey = fromPrivateKey.toPublicKey();
        const RBuf = RPublicKey.encode(true);
        const KB = toPublicKey;
        const P = KB.mul(r);
        const S = P.getX();
        const Sbuf = S.toArray('be', 32);
        const kEkM = Hash.sha512(Sbuf);
        const kE = kEkM.slice(0, 32);
        const kM = kEkM.slice(32, 64);
        const c = AESCBC.encrypt(messageBuf, kE, ivBuf);
        const d = Hash.sha256hmac(kM, [...c]);
        const encBuf = [...RBuf, ...c, ...d];
        return encBuf;
    }
    /**
     * Decrypts a message encrypted using the Bitcore variant of ECIES.
     *
     * @param {number[]} encBuf - The encrypted message buffer.
     * @param {PrivateKey} toPrivateKey - The private key of the recipient.
     * @returns {number[]} The decrypted message as a number array.
     */
    static bitcoreDecrypt(encBuf, toPrivateKey) {
        const kB = toPrivateKey;
        const fromPublicKey = PublicKey_js_1.default.fromString((0, utils_js_1.toHex)(encBuf.slice(0, 33)));
        const R = fromPublicKey;
        const P = R.mul(kB);
        if (P.eq(new Point_js_1.default(0, 0))) {
            throw new Error('P equals 0');
        }
        const S = P.getX();
        const Sbuf = S.toArray('be', 32);
        const kEkM = Hash.sha512(Sbuf);
        const kE = kEkM.slice(0, 32);
        const kM = kEkM.slice(32, 64);
        const c = encBuf.slice(33, -32);
        const d = encBuf.slice(-32);
        const d2 = Hash.sha256hmac(kM, c);
        if ((0, utils_js_1.toHex)(d) !== (0, utils_js_1.toHex)(d2)) {
            throw new Error('Invalid checksum');
        }
        const messageBuf = AESCBC.decrypt(c, kE);
        return [...messageBuf];
    }
}
exports.default = ECIES;
//# sourceMappingURL=ECIES.js.map