"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ShamirKeysManager = void 0;
const utils_1 = require("../utils");
const CryptoActorStub_1 = require("../../icc-api/model/CryptoActorStub");
const utils_2 = require("./utils");
/**
 * Allows to create or update shamir split keys.
 */
class ShamirKeysManager {
    constructor(primitives, dataOwnerApi, encryptionKeysManager, exchangeDataManager) {
        this.primitives = primitives;
        this.dataOwnerApi = dataOwnerApi;
        this.encryptionKeysManager = encryptionKeysManager;
        this.exchangeDataManager = exchangeDataManager;
    }
    /**
     * Get information on existing private keys splits for the provided data owner. For each private key of the provided data owner which has been
     * split using the Shamir sharing algorithm gives the list of the notaries (other data owners) which hold a copy of the key part.
     * @param dataOwner a data owner
     * @return the existing splits for the current data owner as a publicKeyFingerprint -> notariesIds object
     */
    getExistingSplitsInfo(dataOwner) {
        var _a;
        const legacyPartitionDelegates = Object.keys((_a = dataOwner.privateKeyShamirPartitions) !== null && _a !== void 0 ? _a : {});
        if (legacyPartitionDelegates.length > 0) {
            const fp = dataOwner.publicKey && (0, utils_2.fingerprintV1)(dataOwner.publicKey);
            if (!fp) {
                console.error('Invalid data owner: the owner has legacy key partitions but no legacy key.');
            }
            else
                return { [fp]: legacyPartitionDelegates };
        }
        return {};
    }
    /**
     * Creates, updates or deletes shamir splits for keys of the current data owner. Any request to update the splits for a specific key will completely
     * replace any existing data on that split (all previous notaries will be ignored).
     * Note: currently you can only split the legacy key pair.
     * @param keySplitsToUpdate the fingerprints of key pairs which will have updated/new splits and the details on how to create the updated splits.
     * @param keySplitsToDelete the fingerprints or hex-encoded spki public keys which will not be shared anymore.
     * @throws if the parameters refer to non-existing or unavailable keys, have split creation details, or if they request to delete a non-existing
     * split.
     */
    updateSelfSplits(keySplitsToUpdate, keySplitsToDelete) {
        return __awaiter(this, void 0, void 0, function* () {
            const self = CryptoActorStub_1.CryptoActorStubWithType.fromDataOwner(yield this.dataOwnerApi.getCurrentDataOwner());
            const toUpdateSet = new Set(Object.keys(keySplitsToUpdate).map((x) => (0, utils_2.fingerprintV1)(x)));
            const toDeleteSet = new Set(Object.keys(keySplitsToDelete).map((x) => (0, utils_2.fingerprintV1)(x)));
            const intersection = Array.from(toDeleteSet).filter((x) => toUpdateSet.has(x));
            const existingSplits = new Set(Object.keys(this.getExistingSplitsInfo(self.stub)));
            const allKeys = this.encryptionKeysManager.getDecryptionKeys();
            if (toDeleteSet.size !== keySplitsToDelete.length || toUpdateSet.size !== Object.keys(keySplitsToUpdate).length || intersection.length > 0)
                throw new Error(`Duplicate keys in input:\nkeySplitsToUpdate: ${keySplitsToUpdate}\nkeySplitsToDelete: ${keySplitsToDelete}`);
            Object.entries(keySplitsToUpdate).forEach(([key, params]) => this.validateShamirParams(key, params));
            if (!Array.from(existingSplits).every((x) => existingSplits.has(x))) {
                throw new Error(`Requested to delete non-existing split.\nexistingSplits: ${Array.from(existingSplits)}\ntoDelete:${Array.from(toDeleteSet)}`);
            }
            if (Array.from(toUpdateSet).some((x) => !allKeys[x])) {
                throw new Error(`Private key is not available for some of the requested key split updates. ${keySplitsToUpdate}`);
            }
            const delegatesKeys = {};
            let updatedSelf = self;
            for (const delegateId of new Set(Object.values(keySplitsToUpdate).flatMap((x) => x.notariesIds))) {
                const res = yield this.exchangeDataManager.getOrCreateEncryptionDataTo(delegateId);
                delegatesKeys[delegateId] = res.exchangeKey;
            }
            for (const [key, params] of Object.entries(keySplitsToUpdate)) {
                updatedSelf = yield this.updateKeySplit(updatedSelf, (0, utils_2.fingerprintV1)(key), params.notariesIds, params.minShares, delegatesKeys, allKeys);
            }
            for (const keyFp of toDeleteSet) {
                updatedSelf = this.deleteKeySplit(updatedSelf, keyFp);
            }
            return yield this.dataOwnerApi.modifyCryptoActorStub(updatedSelf);
        });
    }
    /*TODO
     * Get suggested recovery keys: analyse transfer keys and shamir to get keys which should be shared with shamir for optimal recovery.
     */
    validateShamirParams(key, params) {
        if (params.notariesIds.length > 0) {
            if (params.minShares > params.notariesIds.length) {
                throw new Error(`Invalid parameters for key ${key}: min shares can't be greater than the number of delegates. ${params}`);
            }
        }
        else
            throw new Error(`Invalid parameters for key ${key}: must have at least one delegate. ${params}`);
    }
    deleteKeySplit(dataOwner, keyFp) {
        if (!dataOwner.stub.publicKey || keyFp !== (0, utils_2.fingerprintV1)(dataOwner.stub.publicKey)) {
            throw new Error('Currently it is possible to use shamir splits only for the legacy public key');
        }
        return {
            type: dataOwner.type,
            stub: Object.assign(Object.assign({}, dataOwner.stub), { privateKeyShamirPartitions: {} }),
        };
    }
    updateKeySplit(dataOwner, keyFp, delegateIds, minShares, delegatesKeys, allKeys) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!dataOwner.stub.publicKey || keyFp !== (0, utils_2.fingerprintV1)(dataOwner.stub.publicKey)) {
                throw new Error('Currently it is possible to use shamir splits only for the legacy public key');
            }
            return {
                type: dataOwner.type,
                stub: Object.assign(Object.assign({}, dataOwner.stub), { privateKeyShamirPartitions: yield this.createPartitionsFor(allKeys[keyFp], delegateIds, minShares, delegatesKeys) }),
            };
        });
    }
    createPartitionsFor(keyPair, delegateIds, minShares, delegatesKeys) {
        return __awaiter(this, void 0, void 0, function* () {
            const exportedKey = yield this.primitives.RSA.exportKey(keyPair.privateKey, 'pkcs8');
            if (delegateIds.length == 1) {
                return yield this.encryptShares([exportedKey], delegateIds, delegatesKeys);
            }
            else {
                const exportedKeysHex = (0, utils_1.ua2hex)(exportedKey);
                const stringShares = this.primitives.shamir.share(exportedKeysHex, delegateIds.length, minShares);
                const paddedStringShares = stringShares.map((x) => `f${x}`); // Shares are uneven length, apart from that they are perfect hexes
                for (const share of paddedStringShares) {
                    if (!/^(?:[0-9a-f][0-9a-f])+$/g.test(share))
                        throw new Error('Unexpected result of shamir split: padded shares should be a valid hex value');
                    if (share !== (0, utils_1.ua2hex)((0, utils_1.hex2ua)(share)))
                        throw new Error('Unexpected result with encoding-decoding share');
                }
                return yield this.encryptShares(paddedStringShares.map((x) => (0, utils_1.ua2ab)((0, utils_1.hex2ua)(x))), delegateIds, delegatesKeys);
            }
        });
    }
    encryptShares(shares, delegateIds, delegatesKeys) {
        return __awaiter(this, void 0, void 0, function* () {
            return delegateIds.reduce((acc, delegateId, index) => __awaiter(this, void 0, void 0, function* () {
                return (Object.assign(Object.assign({}, (yield acc)), { [delegateId]: (0, utils_1.ua2hex)(yield this.primitives.AES.encrypt(delegatesKeys[delegateId], shares[index])) }));
            }), Promise.resolve({}));
        });
    }
}
exports.ShamirKeysManager = ShamirKeysManager;
//# sourceMappingURL=ShamirKeysManager.js.map