epir
Version:
EllipticPIR client library (Node.js / TypeScript bindings).
360 lines • 15.8 kB
JavaScript
"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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEpir = exports.Epir = exports.SelectorFactory = exports.saveDecryptionContextToIndexedDB = exports.loadDecryptionContextFromIndexedDB = exports.MGDatabase = exports.createDecryptionContext = exports.DecryptionContext = void 0;
const dexie_1 = __importDefault(require("dexie"));
const types_1 = require("./types");
const util_1 = require("./util");
const wasm_worker_ts_1 = __importDefault(require("./wasm.worker.ts"));
const wasm_libepir_1 = require("./wasm.libepir");
const wasm_SelectorFactory_1 = require("./wasm.SelectorFactory");
Object.defineProperty(exports, "SelectorFactory", { enumerable: true, get: function () { return wasm_SelectorFactory_1.SelectorFactory; } });
class DecryptionContext {
constructor(helper, mG, nThreads = navigator.hardwareConcurrency) {
this.helper = helper;
this.workers = [];
this.mG_ = helper.malloc(mG);
this.mmax = mG.byteLength / types_1.MG_SIZE;
for (let t = 0; t < nThreads; t++)
this.workers.push(new wasm_worker_ts_1.default());
}
//finalize() {
// this.helper.free(this.mG_);
//}
getMG() {
const ret = new ArrayBuffer(this.mmax * types_1.MG_SIZE);
new Uint8Array(ret).set(this.helper.subarray(this.mG_, this.mmax * types_1.MG_SIZE));
return ret;
}
decryptCipher(privkey, cipher) {
const decrypted = this.helper.call('ecelgamal_decrypt', privkey, cipher, this.mG_, this.mmax);
if (decrypted < 0)
throw new Error('Failed to decrypt.');
return decrypted;
}
async decryptReply(privkey, dimension, packing, reply) {
let midstate = reply;
for (let phase = 0; phase < dimension; phase++) {
const decrypted = await this.decryptMany(midstate, privkey, packing);
if (phase == dimension - 1) {
midstate = decrypted;
}
else {
midstate = decrypted.slice(0, decrypted.byteLength - (decrypted.byteLength % types_1.CIPHER_SIZE));
}
}
return midstate;
}
interpolationSearch(find) {
return this.helper.call('mG_interpolation_search', find, this.mG_, this.mmax);
}
async decryptMany(ciphers, privkey, packing) {
const nThreads = this.workers.length;
const ciphersCount = ciphers.byteLength / types_1.CIPHER_SIZE;
const mGs = await Promise.all(this.workers.map((worker, i) => {
return new Promise((resolve) => {
worker.onmessage = (ev) => {
switch (ev.data.method) {
case 'decrypt_mG_many':
resolve(ev.data.mG);
break;
}
};
const ciphersPerThread = Math.ceil(ciphersCount / nThreads);
const begin = i * ciphersPerThread;
const end = Math.min(ciphersCount + 1, (i + 1) * ciphersPerThread);
const ciphersMy = ciphers.slice(begin * types_1.CIPHER_SIZE, end * types_1.CIPHER_SIZE);
worker.postMessage({
method: 'decrypt_mG_many', ciphers: ciphersMy, privkey: privkey,
}, [ciphersMy]);
});
}));
const ms = [];
for (const mG of mGs) {
const mGView = new Uint8Array(mG);
for (let i = 0; types_1.POINT_SIZE * i < mGView.length; i++) {
ms.push(this.interpolationSearch(mGView.slice(i * types_1.POINT_SIZE, (i + 1) * types_1.POINT_SIZE).buffer));
}
}
const decrypted = new Uint8Array(packing * ciphersCount);
for (let i = 0; i < ms.length; i++) {
const m = ms[i];
if (m == -1)
throw 'Failed to decrypt.';
for (let p = 0; p < packing; p++) {
decrypted[i * packing + p] = (m >> (8 * p)) & 0xff;
}
}
return decrypted.buffer;
}
}
exports.DecryptionContext = DecryptionContext;
const mGGeneratePrepare = (helper, nThreads, mmax, cb) => {
const CTX_SIZE = 124;
const ctx_ = helper.malloc(CTX_SIZE);
helper.store32(ctx_, mmax);
const mG_ = helper.malloc(nThreads * types_1.MG_SIZE);
const mG_p3_ = helper.malloc(nThreads * types_1.GE25519_P3_SIZE);
if (cb) {
let pointsComputed = 0;
const cb_ = helper.addFunction(() => {
pointsComputed++;
if (pointsComputed % cb.interval != 0)
return;
cb.cb(pointsComputed);
}, 'vi');
helper.call('mG_generate_prepare', ctx_, mG_, mG_p3_, nThreads, cb_, null);
helper.removeFunction(cb_);
}
else {
helper.call('mG_generate_prepare', ctx_, mG_, mG_p3_, nThreads, null, null);
}
// Sort.
helper.call('mG_sort', mG_, nThreads);
const ctx = helper.slice(ctx_, CTX_SIZE);
const mG = helper.slice(mG_, nThreads * types_1.MG_SIZE);
const mG_p3 = helper.slice(mG_p3_, nThreads * types_1.GE25519_P3_SIZE);
helper.free(ctx_);
helper.free(mG_);
helper.free(mG_p3_);
return { ctx: ctx, mG: mG, mG_p3: mG_p3 };
};
const mGGenerate = async (helper, cb, mmax) => {
const nThreads = navigator.hardwareConcurrency;
const workers = [];
for (let i = 0; i < nThreads; i++) {
workers.push(new wasm_worker_ts_1.default());
}
const prepare = mGGeneratePrepare(helper, nThreads, mmax, cb);
const pointsComputed = [];
for (let t = 0; t < nThreads; t++) {
pointsComputed[t] = 0;
}
let pcLastReported = cb ? Math.floor(nThreads / cb.interval) : 0;
const promises = workers.map(async (worker, workerId) => {
return new Promise((resolve) => {
worker.onmessage = (ev) => {
switch (ev.data.method) {
case 'mg_generate_cb': {
if (!cb)
break;
pointsComputed[workerId] = ev.data.pointsComputed;
const pcAll = pointsComputed.reduce((acc, v) => acc + v, 0) + nThreads;
for (; pcLastReported + cb.interval <= pcAll; pcLastReported += cb.interval) {
cb.cb(pcLastReported + cb.interval);
}
if (pcAll === mmax && pcLastReported !== mmax) {
cb.cb(mmax);
}
break;
}
case 'mg_generate_compute': {
resolve(ev.data.mG);
break;
}
}
};
workers[workerId].postMessage({
method: 'mg_generate_compute', nThreads: nThreads, mmax: mmax,
ctx: prepare.ctx, mG_p3: prepare.mG_p3.slice(types_1.GE25519_P3_SIZE * workerId, types_1.GE25519_P3_SIZE * (workerId + 1)),
threadId: workerId, cbInterval: cb ? Math.max(1, Math.floor(cb.interval / nThreads)) : Number.MAX_SAFE_INTEGER,
});
});
});
const mGCounts = [];
const mGConcat = new Uint8Array(mmax * types_1.MG_SIZE);
mGConcat.set(new Uint8Array(prepare.mG));
let offset = prepare.mG.byteLength;
(await Promise.all(promises)).map((mGResult, i) => {
mGCounts[i] = mGResult.byteLength / types_1.MG_SIZE;
mGConcat.set(new Uint8Array(mGResult), offset);
offset += mGResult.byteLength;
});
const mGConcat_ = helper.malloc(mGConcat.buffer);
let aCount = nThreads;
const scratch_ = helper.malloc(mGConcat.length);
for (let i = 0; i < mGCounts.length; i++) {
helper.call('mG_merge', scratch_, mGConcat_, aCount, mGCounts[i]);
aCount += mGCounts[i];
}
helper.free(scratch_);
const ret = helper.slice(mGConcat_, mmax * types_1.MG_SIZE);
helper.free(mGConcat_);
return ret;
};
const getMG = async (helper, param, mmax) => {
if (typeof param == 'string') {
return new Uint8Array(await (await Promise.resolve().then(() => __importStar(require('fs')))).promises.readFile(param)).buffer;
}
else {
return mGGenerate(helper, param, mmax);
}
};
const createDecryptionContext = async (param, mmax = types_1.DEFAULT_MMAX) => {
const helper = await wasm_libepir_1.createLibEpirHelper();
const mG = (param instanceof ArrayBuffer ? param : await getMG(helper, param, mmax));
return new DecryptionContext(helper, mG);
};
exports.createDecryptionContext = createDecryptionContext;
class MGDatabase extends dexie_1.default {
constructor(dbName) {
super(dbName);
this.version(MGDatabase.VERSION).stores({
mG: 'key',
});
this.mG = this.table('mG');
}
}
exports.MGDatabase = MGDatabase;
MGDatabase.VERSION = 1;
const loadDecryptionContextFromIndexedDB = async (dbName = 'mG.bin') => {
const db = new MGDatabase(dbName);
const mGDB = await db.mG.get(0);
if (!mGDB)
return null;
return await exports.createDecryptionContext(mGDB.value);
};
exports.loadDecryptionContextFromIndexedDB = loadDecryptionContextFromIndexedDB;
const saveDecryptionContextToIndexedDB = async (decCtx, dbName = 'mG.bin') => {
const db = new MGDatabase(dbName);
await db.mG.put({ key: 0, value: decCtx.getMG() });
};
exports.saveDecryptionContextToIndexedDB = saveDecryptionContextToIndexedDB;
class Epir {
constructor(helper, nThreads = navigator.hardwareConcurrency) {
this.helper = helper;
this.workers = [];
this.helper = helper;
for (let t = 0; t < nThreads; t++)
this.workers.push(new wasm_worker_ts_1.default());
}
createPrivkey() {
return util_1.getRandomScalar();
}
createPubkey(privkey) {
const pubkey_ = this.helper.malloc(types_1.POINT_SIZE);
this.helper.call('pubkey_from_privkey', pubkey_, privkey);
const pubkey = this.helper.slice(pubkey_, types_1.POINT_SIZE);
this.helper.free(pubkey_);
return pubkey;
}
encrypt_(key, msg, r, encrypt) {
const cipher_ = this.helper.malloc(types_1.CIPHER_SIZE);
this.helper.call(encrypt, cipher_, key, msg & 0xffffffff, Math.floor(msg / 0x100000000), r ? r : util_1.getRandomScalar());
const cipher = this.helper.slice(cipher_, types_1.CIPHER_SIZE);
this.helper.free(cipher_);
return cipher;
}
encrypt(pubkey, msg, r) {
return this.encrypt_(pubkey, msg, r, 'ecelgamal_encrypt');
}
encryptFast(privkey, msg, r) {
return this.encrypt_(privkey, msg, r, 'ecelgamal_encrypt_fast');
}
ciphers_or_elements_count(index_counts, count) {
const ic_ = this.helper.malloc(8 * index_counts.length);
for (let i = 0; i < index_counts.length; i++) {
this.helper.store64(ic_ + 8 * i, index_counts[i]);
}
const c = this.helper.call(count, ic_, index_counts.length);
this.helper.free(ic_);
return c;
}
ciphersCount(index_counts) {
return this.ciphers_or_elements_count(index_counts, 'selector_ciphers_count');
}
elementsCount(index_counts) {
return this.ciphers_or_elements_count(index_counts, 'selector_elements_count');
}
create_choice(index_counts, idx) {
const ic_ = this.helper.malloc(8 * index_counts.length);
for (let i = 0; i < index_counts.length; i++) {
this.helper.store64(ic_ + 8 * i, index_counts[i]);
}
const ciphers = this.helper.call('selector_ciphers_count', ic_, index_counts.length);
const choices_ = this.helper.malloc(ciphers);
this.helper.call('selector_create_choice', choices_, 1, ic_, index_counts.length, idx & 0xffffffff, Math.floor(idx / 0xffffffff) & 0xffffffff);
const choices = this.helper.slice(choices_, ciphers);
this.helper.free(choices_);
this.helper.free(ic_);
return choices;
}
async selector_create_(key, index_counts, idx, r, isFast) {
const nThreads = this.workers.length;
const promises = [];
const random = new Uint8Array(r ? r : util_1.getRandomScalarsConcat(this.ciphersCount(index_counts)));
const choices = this.create_choice(index_counts, idx);
for (let t = 0; t < nThreads; t++) {
promises.push(new Promise((resolve) => {
this.workers[t].onmessage = (ev) => {
switch (ev.data.method) {
case 'selector_create':
resolve(ev.data.selector);
break;
}
};
}));
const ciphersPerThread = Math.ceil(choices.byteLength / nThreads);
const begin = t * ciphersPerThread;
const end = Math.min(choices.byteLength + 1, (t + 1) * ciphersPerThread);
const choices_t = choices.slice(begin, end);
const random_t = random.slice(begin * types_1.SCALAR_SIZE, end * types_1.SCALAR_SIZE).buffer;
this.workers[t].postMessage({
method: 'selector_create',
choices: choices_t, key: key, random: random_t, isFast: isFast
}, [choices_t, random_t]);
}
const selectors = await Promise.all(promises);
return util_1.arrayBufferConcat(selectors);
}
async createSelector(pubkey, index_counts, idx, r) {
return this.selector_create_(pubkey, index_counts, idx, r, false);
}
async createSelectorFast(privkey, index_counts, idx, r) {
return this.selector_create_(privkey, index_counts, idx, r, true);
}
// For testing.
computeReplySize(dimension, packing, elem_size) {
return this.helper.call('reply_size', dimension, packing, elem_size);
}
computeReplyRCount(dimension, packing, elem_size) {
return this.helper.call('reply_r_count', dimension, packing, elem_size);
}
computeReplyMock(pubkey, dimension, packing, elem, r) {
const rrc = this.computeReplyRCount(dimension, packing, elem.byteLength);
const rs = this.computeReplySize(dimension, packing, elem.byteLength);
const reply_ = this.helper.malloc(rs);
this.helper.call('reply_mock', reply_, pubkey, dimension, packing, elem, elem.byteLength, r ? r : util_1.getRandomScalarsConcat(rrc));
const reply = this.helper.slice(reply_, rs);
this.helper.free(reply_);
return reply;
}
}
exports.Epir = Epir;
const createEpir = async () => {
const helper = await wasm_libepir_1.createLibEpirHelper();
return new Epir(helper);
};
exports.createEpir = createEpir;
//# sourceMappingURL=wasm.js.map