"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 (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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto = __importStar(require("@shardeum-foundation/lib-crypto-utils"));
const child_process_1 = require("child_process");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("../logger");
const lib_types_1 = require("@shardeum-foundation/lib-types");
class Crypto {
    constructor(baseDir, config, logger, storage) {
        this.baseDir = baseDir;
        this.config = config;
        this.mainLogger = logger.getLogger('main');
        this.storage = storage;
        this.keypair = {};
        this.curveKeypair = {};
        this.powGenerators = {};
        this.sharedKeys = {};
    }
    async init() {
        crypto.init(this.config.crypto.hashKey);
        crypto.setCustomStringifier(lib_types_1.Utils.safeStringify, 'shardus_safeStringify');
        try {
            this.storage._checkInit();
            const keypair = await this.storage.getProperty('keypair');
            if (keypair) {
                this.mainLogger.info('Keypair loaded from database', this.getKeyPairFile());
                this.keypair = keypair;
                this.setCurveKeyPair(this.keypair);
                return;
            }
        }
        catch (e) {
            if (logger_1.logFlags.error)
                this.mainLogger.error(`error fetching keypair from database ${lib_types_1.Utils.safeStringify(e)}`);
        }
        if (this.config.crypto.keyPairConfig.useKeyPairFromFile) {
            const fsKeypair = this.readKeypairFromFile();
            if (fsKeypair && fsKeypair.secretKey && fsKeypair.publicKey) {
                this.keypair = fsKeypair;
                this.mainLogger.info('Keypair loaded from file', this.getKeyPairFile());
                this.setCurveKeyPair(this.keypair);
                return;
            }
        }
        try {
            this.mainLogger.info('Unable to load keypair. Generating new...');
            this.keypair = this._generateKeypair();
            if (this.config.crypto.keyPairConfig.useKeyPairFromFile)
                this.writeKeypairToFile(this.keypair);
            await this.storage.setProperty('keypair', this.keypair);
            this.mainLogger.info('New keypair successfully generated and saved to database.');
            this.setCurveKeyPair(this.keypair);
        }
        catch (e) {
            if (logger_1.logFlags.error)
                this.mainLogger.error(`error ${lib_types_1.Utils.safeStringify(e)}`);
        }
    }
    setCurveKeyPair(keypair) {
        if (keypair) {
            this.curveKeypair = {
                secretKey: crypto.convertSkToCurve(this.keypair.secretKey),
                publicKey: crypto.convertPkToCurve(this.keypair.publicKey),
            };
        }
    }
    getKeyPairFile() {
        return path_1.default.join(this.baseDir, this.config.crypto.keyPairConfig.keyPairJsonFile);
    }
    writeKeypairToFile(keypair) {
        // probably safe; accesses keypair defined by config
        // eslint-disable-next-line security/detect-non-literal-fs-filename
        fs_1.default.writeFileSync(this.getKeyPairFile(), lib_types_1.Utils.safeStringify(keypair));
    }
    readKeypairFromFile() {
        // probably safe; accesses keypair defined by config
        // eslint-disable-next-line security/detect-non-literal-fs-filename
        if (fs_1.default.existsSync(this.getKeyPairFile())) {
            // eslint-disable-next-line security/detect-non-literal-fs-filename
            const fileData = fs_1.default.readFileSync(this.getKeyPairFile());
            return lib_types_1.Utils.safeJsonParse(fileData.toString());
        }
        return null;
    }
    _generateKeypair() {
        const keypair = crypto.generateKeypair();
        this.mainLogger.info('New keypair generated.');
        return keypair;
    }
    convertPublicKeyToCurve(pk) {
        return crypto.convertPkToCurve(pk);
    }
    getPublicKey() {
        return this.keypair.publicKey;
    }
    getCurvePublicKey() {
        return this.curveKeypair.publicKey;
    }
    getSharedKey(curvePk) {
        // eslint-disable-next-line security/detect-object-injection
        let sharedKey = this.sharedKeys[curvePk];
        if (!sharedKey) {
            sharedKey = crypto.generateSharedKey(this.curveKeypair.secretKey, curvePk);
            // eslint-disable-next-line security/detect-object-injection
            this.sharedKeys[curvePk] = sharedKey;
        }
        return sharedKey;
    }
    tag(obj, recipientCurvePk) {
        const objCopy = lib_types_1.Utils.safeJsonParse(lib_types_1.Utils.safeStringify(obj));
        const sharedKey = this.getSharedKey(recipientCurvePk);
        crypto.tagObj(objCopy, sharedKey);
        return objCopy;
    }
    /**
     * Measure the message size when we are making the object copy (because it is pretty much free)
     * then add msgSize to the object
     * relies on node honesty.. better (but more work) to implement at shardus_net level
     *    to implement at shardus_net level we need to have it send us an extra argument to show
     *    the message size
     * @param obj
     * @param recipientCurvePk
     * @returns
     */
    tagWithSize(obj, recipientCurvePk) {
        const strEncoded = lib_types_1.Utils.safeStringify(obj);
        const msgSize = strEncoded.length; //get the message size
        const objCopy = lib_types_1.Utils.safeJsonParse(strEncoded);
        objCopy.msgSize = msgSize; // set the size
        const sharedKey = this.getSharedKey(recipientCurvePk);
        crypto.tagObj(objCopy, sharedKey);
        return objCopy;
    }
    signWithSize(obj) {
        const wrappedMsgStr = lib_types_1.Utils.safeStringify(obj);
        const msgLength = wrappedMsgStr.length;
        // What the linter wants:
        // const newObj = {
        //   ...obj,
        //   msgSize: msgLength
        // };
        // return this.sign(newObj)
        // Need to force this through TS compiler.
        // This code gets called a lot and we should not saturate the garbage collector to make
        // the compiler happy.
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        //@ts-ignore
        obj.msgSize = msgLength;
        return this.sign(obj);
    }
    authenticate(obj, senderCurvePk) {
        const sharedKey = this.getSharedKey(senderCurvePk);
        return crypto.authenticateObj(obj, sharedKey);
    }
    sign(obj) {
        const objCopy = lib_types_1.Utils.safeJsonParse(lib_types_1.Utils.safeStringify(obj));
        crypto.signObj(objCopy, this.keypair.secretKey, this.keypair.publicKey);
        return objCopy;
    }
    verify(obj, expectedPk) {
        try {
            if (expectedPk) {
                if (obj.sign.owner !== expectedPk)
                    return false;
            }
            return crypto.verifyObj(obj);
        }
        catch (e) {
            this.mainLogger.error(`Error in verifying object ${lib_types_1.Utils.safeStringify(obj)}`, e);
            return false;
        }
    }
    hash(obj) {
        if (typeof obj === 'string') {
            return crypto.hash(obj);
        }
        if (!obj.sign) {
            return crypto.hashObj(obj);
        }
        return crypto.hashObj(obj, true);
    }
    isGreaterHash(hash1, hash2) {
        return hash1 > hash2;
    }
    getComputeProofOfWork(seed, difficulty) {
        return this._runProofOfWorkGenerator('./computePowGenerator.js', seed, difficulty);
    }
    stopAllGenerators() {
        for (const generator in this.powGenerators) {
            // eslint-disable-next-line security/detect-object-injection
            this.powGenerators[generator].kill();
        }
        this.powGenerators = {};
    }
    /* eslint-disable security/detect-object-injection */
    _runProofOfWorkGenerator(generator, seed, difficulty) {
        // Fork a child process to compute the PoW, if it doesn't exist
        if (!this.powGenerators[generator]) {
            this.powGenerators[generator] = (0, child_process_1.fork)(generator, undefined, { cwd: __dirname });
        }
        const promise = new Promise((resolve) => {
            this.powGenerators[generator].on('message', (powObj) => {
                this._stopProofOfWorkGenerator(generator);
                resolve(powObj);
            });
        });
        // Tell child to compute PoW
        if (!this.powGenerators[generator].killed) {
            this.powGenerators[generator].send({ seed, difficulty });
        }
        // Return a promise the resolves to a valid { nonce, hash }
        return promise;
    }
    /* eslint-enable security/detect-object-injection */
    /* eslint-disable security/detect-object-injection */
    _stopProofOfWorkGenerator(generator) {
        if (!this.powGenerators[generator])
            return Promise.resolve('not running');
        const promise = new Promise((resolve) => {
            this.powGenerators[generator].on('close', (signal) => {
                delete this.powGenerators[generator];
                resolve(signal);
            });
        });
        if (!this.powGenerators[generator].killed) {
            this.powGenerators[generator].kill();
        }
        return promise;
    }
}
// tslint:disable-next-line: no-default-export
exports.default = Crypto;
//# sourceMappingURL=index.js.map