UNPKG

epir

Version:

EllipticPIR client library (Node.js / TypeScript bindings).

360 lines 15.8 kB
"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