"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 });
exports.MasterCertificate = void 0;
const Certificate_js_1 = __importDefault(require("./Certificate.js"));
const Utils = __importStar(require("../../primitives/utils.js"));
const SymmetricKey_js_1 = __importDefault(require("../../primitives/SymmetricKey.js"));
const Random_js_1 = __importDefault(require("../../primitives/Random.js"));
/**
 * MasterCertificate extends the base Certificate class to manage a master keyring, enabling the creation of verifiable certificates.
 *
 * It allows for the selective disclosure of certificate fields by creating a `VerifiableCertificate` for a specific verifier.
 * The `MasterCertificate` can securely decrypt each master key and re-encrypt it for a verifier, creating a customized
 * keyring containing only the keys necessary for the verifier to access designated fields.
 *
 */
class MasterCertificate extends Certificate_js_1.default {
    constructor(type, serialNumber, subject, certifier, revocationOutpoint, fields, masterKeyring, signature) {
        super(type, serialNumber, subject, certifier, revocationOutpoint, fields, signature);
        // Ensure every field in `fields` is a string and has a corresponding key in `masterKeyring`
        for (const fieldName of Object.keys(fields)) {
            if (masterKeyring[fieldName] === undefined || masterKeyring[fieldName] === '') {
                throw new Error(`Master keyring must contain a value for every field. Missing or empty key for field: "${fieldName}".`);
            }
        }
        this.masterKeyring = masterKeyring;
    }
    /**
     * Encrypts certificate fields for a subject and generates a master keyring.
     * This method returns a master keyring tied to a specific certifier or subject who will validate
     * and sign off on the fields, along with the encrypted certificate fields.
     *
     * @param {ProtoWallet} creatorWallet - The wallet of the creator responsible for encrypting the fields.
     * @param {WalletCounterparty} certifierOrSubject - The certifier or subject who will validate the certificate fields.
     * @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - A record of certificate field names (under 50 bytes) mapped to their values.
     * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request.
     * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.   *
     * @returns {Promise<CreateCertificateFieldsResult>} A promise resolving to an object containing:
     *   - `certificateFields` {Record<CertificateFieldNameUnder50Bytes, Base64String>}:
     *     The encrypted certificate fields.
     *   - `masterKeyring` {Record<CertificateFieldNameUnder50Bytes, Base64String>}:
     *     The master keyring containing encrypted revelation keys for each field.
     */
    static async createCertificateFields(creatorWallet, certifierOrSubject, fields, privileged, privilegedReason) {
        const certificateFields = {};
        const masterKeyring = {};
        for (const [fieldName, fieldValue] of Object.entries(fields)) {
            const fieldSymmetricKey = SymmetricKey_js_1.default.fromRandom();
            const encryptedFieldValue = fieldSymmetricKey.encrypt(Utils.toArray(fieldValue, 'utf8'));
            certificateFields[fieldName] = Utils.toBase64(encryptedFieldValue);
            const { ciphertext: encryptedFieldRevelationKey } = await creatorWallet.encrypt({
                plaintext: fieldSymmetricKey.toArray(),
                ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName), // Only fieldName used on MasterCertificate
                counterparty: certifierOrSubject,
                privileged,
                privilegedReason
            });
            masterKeyring[fieldName] = Utils.toBase64(encryptedFieldRevelationKey);
        }
        return {
            certificateFields,
            masterKeyring
        };
    }
    /**
     * Creates a keyring for a verifier, enabling them to decrypt specific certificate fields.
     * This method decrypts the master field keys for the specified fields and re-encrypts them
     * for the verifier's identity key. The result is a keyring containing the keys necessary
     * for the verifier to access the designated fields.
     *
     * @param {ProtoWallet} subjectWallet - The wallet instance of the subject, used to decrypt and re-encrypt field keys.
     * @param {WalletCounterparty} verifier - The verifier who will receive access to the selectively revealed fields. Can be an identity key as hex, 'anyone', or 'self'.
     * @param {string[]} fieldsToReveal - An array of field names to be revealed to the verifier. Must be a subset of the certificate's fields.
     * @param {string} [originator] - Optional originator identifier, used if additional context is needed for decryption and encryption operations.
     * @returns {Promise<Record<CertificateFieldNameUnder50Bytes, string>>} - A keyring mapping field names to encrypted field revelation keys, allowing the verifier to decrypt specified fields.
     * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request.
     * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.   *
     * @throws {Error} Throws an error if:
     *   - fieldsToReveal is not an array of strings.
     *   - A field in `fieldsToReveal` does not exist in the certificate.
     *   - The decrypted master field key fails to decrypt the corresponding field (indicating an invalid key).
     */
    static async createKeyringForVerifier(subjectWallet, certifier, verifier, fields, fieldsToReveal, masterKeyring, serialNumber, privileged, privilegedReason) {
        if (!Array.isArray(fieldsToReveal)) {
            throw new TypeError('fieldsToReveal must be an array of strings');
        }
        const fieldRevelationKeyring = {};
        for (const fieldName of fieldsToReveal) {
            // Make sure that fields to reveal is a subset of the certificate fields
            if (fields[fieldName] === undefined || fields[fieldName] === null || fields[fieldName] === '') {
                throw new Error(`Fields to reveal must be a subset of the certificate fields. Missing the "${fieldName}" field.`);
            }
            // Decrypt the master field key and verify that derived key actually decrypts requested field
            const masterFieldKey = (await this.decryptField(subjectWallet, masterKeyring, fieldName, fields[fieldName], certifier, privileged, privilegedReason)).fieldRevelationKey;
            // Encrypt derived fieldRevelationKey for verifier
            const { ciphertext: encryptedFieldRevelationKey } = await subjectWallet.encrypt({
                plaintext: masterFieldKey,
                ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName, serialNumber),
                counterparty: verifier,
                privileged,
                privilegedReason
            });
            // Add encryptedFieldRevelationKey to fieldRevelationKeyring
            fieldRevelationKeyring[fieldName] = Utils.toBase64(encryptedFieldRevelationKey);
        }
        // Return the field revelation keyring which can be used to create a verifiable certificate for a verifier.
        return fieldRevelationKeyring;
    }
    /**
     * Issues a new MasterCertificate for a specified subject.
     *
     * This method generates a certificate containing encrypted fields and a keyring
     * for the subject to decrypt all fields. Each field is encrypted with a randomly
     * generated symmetric key, which is then encrypted for the subject. The certificate
     * can also includes a revocation outpoint to manage potential revocation.
     *
     * @param {ProtoWallet} certifierWallet - The wallet of the certifier, used to sign the certificate and encrypt field keys.
     * @param {WalletCounterparty} subject - The subject for whom the certificate is issued.
     * @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - Unencrypted certificate fields to include, with their names and values.
     * @param {string} certificateType - The type of certificate being issued.
     * @param {function(string, Record<CertificateFieldNameUnder50Bytes, string>?): Promise<string>} getRevocationOutpoint -
     *   Optional function to obtain a revocation outpoint for the certificate. Defaults to a placeholder.
     * @param {function(string): Promise<void>} updateProgress - Optional callback for reporting progress updates during the operation. Defaults to a no-op.
     * @returns {Promise<MasterCertificate>} - A signed MasterCertificate instance containing the encrypted fields and subject specific keyring.
     *
     * @throws {Error} Throws an error if any operation (e.g., encryption, signing) fails during certificate issuance.
     */
    static async issueCertificateForSubject(certifierWallet, subject, fields, certificateType, getRevocationOutpoint = async (_serial) => {
        void _serial; // Explicitly acknowledge unused parameter
        return '00'.repeat(32);
    }, serialNumber) {
        // 1. Generate a random serialNumber if not provided
        const finalSerialNumber = serialNumber ?? Utils.toBase64((0, Random_js_1.default)(32));
        // 2. Create encrypted certificate fields and associated master keyring
        const { certificateFields, masterKeyring } = await this.createCertificateFields(certifierWallet, subject, fields);
        // 3. Obtain a revocation outpoint
        const revocationOutpoint = await getRevocationOutpoint(finalSerialNumber);
        let subjectIdentityKey;
        if (subject === 'self') {
            subjectIdentityKey = (await certifierWallet.getPublicKey({ identityKey: true })).publicKey;
        }
        else {
            subjectIdentityKey = subject;
        }
        // 4. Create new MasterCertificate instance
        const certificate = new MasterCertificate(certificateType, finalSerialNumber, subjectIdentityKey, (await certifierWallet.getPublicKey({ identityKey: true })).publicKey, revocationOutpoint, certificateFields, masterKeyring);
        // 5. Sign and return the new MasterCertificate certifying the subject.
        await certificate.sign(certifierWallet);
        return certificate;
    }
    /**
     * Decrypts all fields in the MasterCertificate using the subject's or certifier's wallet.
     *
     * This method allows the subject or certifier to decrypt the `masterKeyring` and retrieve
     * the encryption keys for each field, which are then used to decrypt the corresponding field values.
     * The counterparty used for decryption depends on how the certificate fields were created:
     * - If the certificate is self-signed, the counterparty should be set to 'self'.
     * - Otherwise, the counterparty should always be the other party involved in the certificate issuance process (the subject or certifier).
     *
     * @param {ProtoWallet} subjectOrCertifierWallet - The wallet of the subject or certifier, used to decrypt the master keyring and field values.
     * @param {Record<CertificateFieldNameUnder50Bytes, Base64String>} masterKeyring - A record containing encrypted keys for each field.
     * @param {Record<CertificateFieldNameUnder50Bytes, Base64String>} fields - A record of encrypted field names and their values.
     * @param {WalletCounterparty} counterparty - The counterparty responsible for creating or signing the certificate. For self-signed certificates, use 'self'.
     * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request.
     * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
     * @returns {Promise<Record<CertificateFieldNameUnder50Bytes, string>>} A promise resolving to a record of field names and their decrypted values in plaintext.
     *
     * @throws {Error} Throws an error if the `masterKeyring` is invalid or if decryption fails for any field.
     */
    static async decryptFields(subjectOrCertifierWallet, masterKeyring, fields, counterparty, privileged, privilegedReason) {
        if (masterKeyring == null || Object.keys(masterKeyring).length === 0) {
            throw new Error('A MasterCertificate must have a valid masterKeyring!');
        }
        try {
            const decryptedFields = {};
            // Note: we want to iterate through all fields, not just masterKeyring keys/value pairs.
            for (const fieldName of Object.keys(fields)) {
                decryptedFields[fieldName] = (await this.decryptField(subjectOrCertifierWallet, masterKeyring, fieldName, fields[fieldName], counterparty, privileged, privilegedReason)).decryptedFieldValue;
            }
            return decryptedFields;
        }
        catch {
            throw new Error('Failed to decrypt all master certificate fields.');
        }
    }
    static async decryptField(subjectOrCertifierWallet, masterKeyring, fieldName, fieldValue, counterparty, privileged, privilegedReason) {
        if (masterKeyring == null || Object.keys(masterKeyring).length === 0) {
            throw new Error('A MasterCertificate must have a valid masterKeyring!');
        }
        try {
            const { plaintext: fieldRevelationKey } = await subjectOrCertifierWallet.decrypt({
                ciphertext: Utils.toArray(masterKeyring[fieldName], 'base64'),
                ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName), // Only fieldName used on MasterCertificate
                counterparty,
                privileged,
                privilegedReason
            });
            const decryptedFieldValue = new SymmetricKey_js_1.default(fieldRevelationKey).decrypt(Utils.toArray(fieldValue, 'base64'));
            return {
                fieldRevelationKey,
                decryptedFieldValue: Utils.toUTF8(decryptedFieldValue)
            };
        }
        catch {
            throw new Error('Failed to decrypt certificate field!');
        }
    }
}
exports.MasterCertificate = MasterCertificate;
//# sourceMappingURL=MasterCertificate.js.map