@getanthill/datastore
Version:
Event-Sourced Datastore
238 lines • 8.37 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const vm_1 = __importDefault(require("vm"));
const utils_1 = require("../../utils");
const operator_overload_1 = require("./operator-overload");
let sealPromise = null;
class FullyHomomorphicEncryptionClient {
constructor(config) {
this._seal = null;
this._context = null;
this._keys = null;
this._evaluator = null;
/**
* @see https://github.com/s0l0ist/node-seal?tab=readme-ov-file#caveats
* Must have a `delete()` method
*/
this._instances = [];
/**
* Sample cipher instance to test the constructor equality
* @see computeFromAny()
*/
this._cipherSample = null;
this._encoder = null;
this._config = config;
}
/**
* @see https://github.com/s0l0ist/node-seal
*/
async connect() {
if (this._seal !== null) {
return this;
}
if (sealPromise === null) {
const SEAL = require('node-seal');
sealPromise = SEAL();
}
this._seal = await sealPromise;
this.init();
}
async init() {
await this.connect();
// Create a new EncryptionParameters
const schemeType = this.seal.SchemeType[this.scheme];
const securityLevel = this.seal.SecurityLevel.tc128;
const polyModulusDegree = this.polyModulusDegree;
const bitSizes = this.bitSizes;
const bitSize = this.bitSize;
const encParms = this.seal.EncryptionParameters(schemeType);
// Assign Poly Modulus Degree
encParms.setPolyModulusDegree(polyModulusDegree);
// Create a suitable set of CoeffModulus primes
encParms.setCoeffModulus(this.seal.CoeffModulus.Create(polyModulusDegree, Int32Array.from(bitSizes)));
// Assign a PlainModulus (only for bfv/bgv scheme type)
if (this.scheme !== 'ckks') {
encParms.setPlainModulus(this.seal.PlainModulus.Batching(polyModulusDegree, bitSize));
}
// Create a new Context
this._context = this.seal.Context(encParms, true, securityLevel);
// Helper to check if the Context was created successfully
/* istanbul ignore next */
if (!this._context.parametersSet()) {
throw new Error('Could not set the parameters in the given context. Please try different encryption parameters.');
}
this.createKeys();
this._evaluator = this.seal.Evaluator(this.context);
this.addInstance(this._evaluator);
this._cipherSample = this.seal.CipherText();
this.addInstance(this._cipherSample);
if (this.scheme === 'ckks') {
this._encoder = this.seal.CKKSEncoder(this._context);
}
else {
this._encoder = this.seal.BatchEncoder(this._context);
}
this.addInstance(this._encoder);
return this;
}
async disconnect() {
if (this._seal !== null) {
this.flushInstances();
this._seal = null;
}
return this;
}
async clone() {
const client = new FullyHomomorphicEncryptionClient(this._config);
await client.connect();
client._context = this.context;
client._keys = this.keys;
return client;
}
get seal() {
if (this._seal === null) {
throw new Error('Node seal must be initialized first with `.connect()`');
}
return this._seal;
}
get context() {
return this._context;
}
get keys() {
return this._keys;
}
get scheme() {
return this._config.scheme ?? 'bgv';
}
get bitSize() {
return this._config.bitSize ?? 20;
}
get bitSizes() {
return this._config.bitSizes ?? [36, 36, 37];
}
get polyModulusDegree() {
return this._config.polyModulusDegree ?? 4096;
}
get evaluator() {
return this._evaluator;
}
addInstance(instance) {
this._instances.push(instance);
}
flushInstances() {
for (const instance of this._instances) {
instance.delete();
}
this._instances = [];
}
createKeys(_keyGenerator) {
// Create a new KeyGenerator (use uploaded keys if applicable)
const keyGenerator = _keyGenerator ?? this.seal.KeyGenerator(this._context);
// Get the SecretKey from the keyGenerator
const Secret_key_A_ = keyGenerator.secretKey();
// Get the PublicKey from the keyGenerator
const Public_key_A_ = keyGenerator.createPublicKey();
// Create a new RelinKey
const Relin_key_A_ = keyGenerator.createRelinKeys();
// Create a new GaloisKey
const Galois_key_A_ = keyGenerator.createGaloisKeys();
this._keys = {
secret: Secret_key_A_,
public: Public_key_A_,
relin: Relin_key_A_,
galois: Galois_key_A_,
};
}
encode(arr) {
if (this.scheme === 'ckks') {
const float64 = Float64Array.from(arr);
return this._encoder.encode(float64, Math.pow(2, this.bitSize));
}
const int32 = Int32Array.from(arr);
return this._encoder.encode(int32);
}
createCypher(value) {
const plainText = this.encode(value);
const encryptor = this.seal.Encryptor(this._context, this.keys.public);
return encryptor.encrypt(plainText);
}
decode(plainText) {
if (this.scheme === 'ckks') {
const float64 = this._encoder.decode(plainText);
return Array.from(float64);
}
const int32 = this._encoder.decode(plainText);
return Array.from(int32);
}
fromCypher(cypher) {
const decryptor = this.seal.Decryptor(this._context, this._keys.secret);
const plainText = decryptor.decrypt(cypher);
return this.decode(plainText);
}
async computeFromAnyToCiphers(handler, ...args) {
return new Promise((resolve, reject) => {
/* istanbul ignore next */
const rawScript = (0, operator_overload_1.overloadAsString)(handler, args, this);
const script = new vm_1.default.Script(rawScript);
const context = {
client: this,
handler,
args,
resolve,
reject,
console,
};
script.runInNewContext(context, {
timeout: 120000,
breakOnSigint: true,
});
});
}
async computeFromAny(handler, ...args) {
return this.computeFromAnyToCiphers(handler, ...args).then((res) => {
if (res.constructor === this._cipherSample?.constructor) {
return res.save();
}
const result = (0, utils_1.mapValuesDeep)(res, (val) => {
if (!!val && typeof val.save === 'function') {
return val.save();
}
return val;
});
return result;
});
}
async compute(handler, ..._args) {
const client = await this.clone();
const value = await client.computeFromAny(handler, ..._args);
/**
* @see https://github.com/s0l0ist/node-seal?tab=readme-ov-file#caveats
* and specifically Garbage Collection
*/
client.flushInstances();
return value;
}
compile(script) {
const body = script.split('\n').slice(1, -1).join('\n');
const args = script
.split('\n', 1)[0]
.replace(/^(async )?function( [a-z]{1,30})?\(/, '')
.split(')', 1)[0]
.split(',')
.map((t) => t.trim());
const AsyncFunction = Object.getPrototypeOf(
/* istanbul ignore next */
async function () { }).constructor;
var retFn = AsyncFunction(...args, body);
return retFn;
}
// FHE functions
relinearize(val) {
return this.evaluator.relinearize(val, this.keys.relin);
}
}
exports.default = FullyHomomorphicEncryptionClient;
//# sourceMappingURL=index.js.map