"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractConsensusIoPubkey = exports.EncryptionUtilsImpl = void 0;
const encoding_1 = require("@cosmjs/encoding");
const hkdf_1 = require("@noble/hashes/hkdf");
const sha256_1 = require("@noble/hashes/sha256");
const curve25519_js_1 = require("curve25519-js");
const miscreant = __importStar(require("miscreant"));
const secure_random_1 = __importDefault(require("secure-random"));
const cryptoProvider = new miscreant.PolyfillCryptoProvider();
const hkdfSalt = (0, encoding_1.fromHex)("000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d");
const mainnetConsensusIoPubKey = (0, encoding_1.fromHex)("083b1a03661211d5a4cc8d39a77795795862f7730645573b2bcc2c1920c53c04");
const mainnetChainIds = new Set(["secret-2", "secret-3", "secret-4"]);
class EncryptionUtilsImpl {
    constructor(registrationQuerier, seed, chainId) {
        this.consensusIoPubKey = new Uint8Array(); // cache
        this.registrationQuerier = registrationQuerier;
        if (!seed) {
            this.seed = EncryptionUtilsImpl.GenerateNewSeed();
        }
        else {
            if (seed.length !== 32) {
                throw new Error("encryptionSeed must be a Uint8Array of length 32");
            }
            this.seed = seed;
        }
        const { privkey, pubkey } = EncryptionUtilsImpl.GenerateNewKeyPairFromSeed(this.seed);
        this.privkey = privkey;
        this.pubkey = pubkey;
        if (chainId && mainnetChainIds.has(chainId)) {
            // Major speedup
            // TODO: not sure if this is the best approach for detecting mainnet
            this.consensusIoPubKey = mainnetConsensusIoPubKey;
        }
    }
    static GenerateNewKeyPair() {
        return EncryptionUtilsImpl.GenerateNewKeyPairFromSeed(EncryptionUtilsImpl.GenerateNewSeed());
    }
    static GenerateNewSeed() {
        return (0, secure_random_1.default)(32, { type: "Uint8Array" });
    }
    static GenerateNewKeyPairFromSeed(seed) {
        const { private: privkey, public: pubkey } = (0, curve25519_js_1.generateKeyPair)(seed);
        return { privkey, pubkey };
    }
    getConsensusIoPubKey() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.consensusIoPubKey.length === 32) {
                return this.consensusIoPubKey;
            }
            const { key } = yield this.registrationQuerier.txKey({});
            this.consensusIoPubKey = key;
            return this.consensusIoPubKey;
        });
    }
    getTxEncryptionKey(nonce) {
        return __awaiter(this, void 0, void 0, function* () {
            const consensusIoPubKey = yield this.getConsensusIoPubKey();
            const txEncryptionIkm = (0, curve25519_js_1.sharedKey)(this.privkey, consensusIoPubKey);
            const txEncryptionKey = (0, hkdf_1.hkdf)(sha256_1.sha256, Uint8Array.from([...txEncryptionIkm, ...nonce]), hkdfSalt, "", 32);
            return txEncryptionKey;
        });
    }
    encrypt(contractCodeHash, msg) {
        return __awaiter(this, void 0, void 0, function* () {
            const nonce = (0, secure_random_1.default)(32, { type: "Uint8Array" });
            const txEncryptionKey = yield this.getTxEncryptionKey(nonce);
            const siv = yield miscreant.SIV.importKey(txEncryptionKey, "AES-SIV", cryptoProvider);
            const plaintext = (0, encoding_1.toUtf8)(contractCodeHash + JSON.stringify(msg));
            const ciphertext = yield siv.seal(plaintext, [new Uint8Array()]);
            // ciphertext = nonce(32) || wallet_pubkey(32) || ciphertext
            return Uint8Array.from([...nonce, ...this.pubkey, ...ciphertext]);
        });
    }
    decrypt(ciphertext, nonce) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(ciphertext === null || ciphertext === void 0 ? void 0 : ciphertext.length)) {
                return new Uint8Array();
            }
            const txEncryptionKey = yield this.getTxEncryptionKey(nonce);
            const siv = yield miscreant.SIV.importKey(txEncryptionKey, "AES-SIV", cryptoProvider);
            const plaintext = yield siv.open(ciphertext, [new Uint8Array()]);
            return plaintext;
        });
    }
    getPubkey() {
        return Promise.resolve(this.pubkey);
    }
}
exports.EncryptionUtilsImpl = EncryptionUtilsImpl;
// extractPubkey ported from https://github.com/enigmampc/SecretNetwork/blob/8ab20a273570bfb3d55d67e0300ecbdc67e0e739/x/registration/remote_attestation/remote_attestation.go#L25
function extractConsensusIoPubkey(cert) {
    const nsCmtOid = new Uint8Array([
        0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d,
    ]); // Netscape Comment OID
    const payload = extractAsn1Value(cert, nsCmtOid);
    try {
        // Try SW mode
        const pubkey = (0, encoding_1.fromBase64)((0, encoding_1.fromUtf8)(payload));
        if (pubkey.length === 32) {
            return pubkey;
        }
    }
    catch (e) {
        // Not SW mode
    }
    try {
        // Try HW mode
        // Ported from https://github.com/scrtlabs/SecretNetwork/blob/8ab20a273570bfb3d55d67e0300ecbdc67e0e739/x/registration/remote_attestation/remote_attestation.go#L110
        const quoteHex = (0, encoding_1.fromBase64)(JSON.parse((0, encoding_1.fromUtf8)((0, encoding_1.fromBase64)(JSON.parse((0, encoding_1.fromUtf8)(payload)).report)))
            .isvEnclaveQuoteBody);
        const reportData = quoteHex.slice(368, 400);
        return reportData;
    }
    catch (e) {
        throw new Error("Cannot extract tx io pubkey: error parsing certificate - malformed certificate");
    }
}
exports.extractConsensusIoPubkey = extractConsensusIoPubkey;
function extractAsn1Value(cert, oid) {
    let offset = (0, encoding_1.toHex)(cert).indexOf((0, encoding_1.toHex)(oid)) / 2;
    if (!Number.isInteger(offset)) {
        throw new Error("Error parsing certificate - malformed certificate");
    }
    offset += 12; // 11 + TAG (0x04)
    // we will be accessing offset + 2, so make sure it's not out-of-bounds
    if (offset + 2 >= cert.length) {
        throw new Error("Error parsing certificate - malformed certificate");
    }
    let length = cert[offset];
    if (length > 0x80) {
        length = cert[offset + 1] * 0x100 + cert[offset + 2];
        offset += 2;
    }
    if (offset + length + 1 >= cert.length) {
        throw new Error("Error parsing certificate - malformed certificate");
    }
    // Obtain Netscape Comment
    offset += 1;
    const payload = cert.slice(offset, offset + length);
    return payload;
}
//# sourceMappingURL=encryption.js.map