Skip to main content

Encrypted memory and search for AI apps, plus a planner that validates and costs encrypted computation before running it

Project description

Akita — computing on data no one is allowed to see

tests

The "100,000× overhead" of fully homomorphic encryption is a choice, not a constant. This repo demonstrates it four ways:

  1. Akita Private Memory (akita/memory.py) — a working SDK giving an AI app per-user memory that the operator provably cannot read, with sub-second semantic recall over encrypted embeddings.
  2. A planner (akita/planner.py) that validates and costs encrypted computation before it runs — and catches a class of silent-corruption bugs that today's tooling doesn't.
  3. A working private search engine (demos/private_rag/) — real embeddings, real client/server split, results exactly identical to plaintext, on a corpus the server ranks for queries it can never read.
  4. Measured micro-benchmarks (demos/, results/) showing the overhead spans five orders of magnitude on the same laptop depending only on workload shape.

All numbers measured on an Apple M1 Pro; every one traces to a script and a JSON file in results/. The technical reasoning and sources are in docs/why.md.

Status: v0.1 prototype. Real cryptography, honest measurements, not audited — don't protect production data with it yet (see SECURITY.md).

The planner: know the cost before you encrypt

from akita import PipelineSpec, plan

print(plan(PipelineSpec("matvec_ranking", dim=384, n_items=1_000_000)).explain())
Akita plan — matvec_ranking
  security   CKKS, N=8192, modulus 200 bits (<= 218 for 128-bit), depth 1
  layout     dim 384 -> padded 512 (validated: divides 4096 slots), 245 chunk(s) x 4096 items
  est compute  639.74 core-s (127.95 s wall on 10 cores)
  est traffic  326 KB up / 59,976 KB down (+ 33.8 MB one-time public context)
  est cost     $0.008885 per query at full utilization ($0.017771 on a dedicated 10-core box) @ $0.05/core-hr
  warning    CKKS results must be revealed only to the secret-key holder. ...

Why this matters — two facts we hit building this repo:

  • Silent corruption is one layout mistake away. A 384-dim encrypted matmul in TenSEAL returns wrong scores with no error because 384 doesn't divide the slot count (tests/test_planner.py::test_the_trap_is_real reproduces it). The planner makes that layout unrepresentable.
  • The cost model holds. The planner predicted 13.1 s for a 100k-doc encrypted search this laptop measured at 14.0 s — within 7% (test_cost_model_within_tolerance_of_scale_test).

v0 plans the two workload shapes measured here (matvec_ranking, column_scoring), refuses what shouldn't run under CKKS (comparisons → routed to TFHE or the client), and executes plans on TenSEAL via run_matvec / run_column_scoring. Cost constants are calibrated on this machine; expect ±30% elsewhere until re-calibrated.

Akita Private Memory: an AI's memory no operator can read

from akita import Memory, MemoryServer, MiniLMEmbedder

mem = Memory.open("user1", passphrase, server, MiniLMEmbedder())
mem.add(["dr chen increased the propranolol dose to 80mg", ...])
mem.search("what medication changes has my doctor made?", k=3)

The trust boundary: text is embedded on the key holder's side, sealed with AES-GCM, and its embeddings are CKKS-encrypted before anything leaves. The server (MemoryServer) stores blobs it cannot open and answers searches with one ciphertext×ciphertext multiply per packed segment — no rotations, so enrollment ships a 1.9 MB public context, not 34 MB. Keys derive from a passphrase (scrypt) and never leave the keystore; wipe() is crypto-shredding.

Measured (demo 6, 400 sensitive notes, real MiniLM embeddings, M1 Pro):

Metric Value
Store 400 notes (embed + encrypt + upload) 0.8–2.4 s (~2–6 ms/note)
Semantic search, end to end 0.24 s median
Server compute per search (encrypted corpus) 145 ms — planner predicted 145 ms
Storage at rest 45.5 KB/memory (18.6 MB total)
Delete tombstone effective immediately; compact() re-packs physically
Operator's view no secret key (asserted at enrollment + auditable), hex-opaque blobs and segments

Honest v0 costs, stated plainly: the search download scales with corpus size (~230 KB per 8-memory segment — server-side aggregation is the roadmap fix); fetch patterns are padded with decoy reads pending a PIR stage; and key loss means memory loss — that's what "the operator cannot read it" costs. Run it: .venv/bin/python demos/demo6_private_memory.py.

See it: .venv/bin/python demos/demo_page/app.pyhttp://127.0.0.1:8642 — a live split-screen of both sides of the trust boundary: your device (ask anything, ~95 ms warm end-to-end) next to the operator's view (hex blobs, ciphertext segments, secret key: NO, and which fetches it saw — winners and decoys, indistinguishable). Real cryptography, no mocks.

The working prototype: private semantic search over HTTP

A real client/server system on real data: 12,288 documents (20 Newsgroups) embedded with MiniLM-L6-v2 (384-dim). The client owns the secret key, embeds and encrypts its query locally, and sends only a ciphertext; the server — holding a public context it verifiably cannot decrypt with — ranks the entire corpus and returns encrypted scores.

Measured across 8 natural-language queries:

Metric Value
Top-10 results vs plaintext search 80/80 exactly identical
Median end-to-end latency 2.26 s (1.68 s server compute, 10 threads)
Traffic per query 326 KB up / 690 KB down
One-time enrollment (public keys) 35.5 MB
Server overhead vs plaintext matvec ~1,459× — and the answer still costs ~$0.0002

At scale (measured, synthetic embeddings, same kernel): 102,400 docs in 14.0 s on this laptop ≈ $0.002/query; a derived 1M-doc corpus is ~$0.02/query but ~21 s on a 64-core server — honest evidence that linear scan stops being interactive around 10⁵–10⁶ docs. Production systems shard into clusters and search one shard (exactly Apple's Wally design), which is a planner problem, not a cryptography problem.

Costing conventions (so the numbers above can't be misread): scale-test and prototype figures price a dedicated machine for the query's wall time — the conservative upper bound. The planner also reports the full utilization basis (core-seconds priced directly), which is what batch serving achieves. The two bracket real deployments and differ by ~2× on this machine; the planner prints both.

.venv/bin/python demos/private_rag/build_index.py   # embeds the corpus (~2 min)
.venv/bin/python demos/private_rag/server.py &      # untrusted server
.venv/bin/python demos/private_rag/client.py        # data owner

The reframe in one paragraph

The famous "100,000× overhead" of homomorphic encryption is a category error twice over. First, it prices one encrypted op against one plaintext op — but modern lattice schemes are SIMD machines (one ciphertext operation processes thousands of values), so the amortized overhead is a function of workload shape, not a constant: we measure ~330× for packed rotation-free linear algebra, ~6,600× for rotation-heavy linear algebra, and ~10⁸× only at exact comparisons. Second, overhead multipliers are the wrong unit entirely — what decides viability is dollars per answer vs value per answer, and for retrieval, scoring, matching, and aggregation over sensitive data, the encrypted cost per answer is already far below the value of the answer.

Measured micro-benchmarks (single M1 Pro core, pinned to 1 thread)

Demo Workload Server compute Overhead vs plaintext Cost
1. Private search encrypted query ranked against 16,384 docs (dim 128) 2.6 s (0.54 s on 10 cores) ~6,600× $0.0022 / 1M docs
2. Encrypted scoring logistic model on 4,096 encrypted records (32 feat) 9.4 ms ~330× $0.00003 / 1M records
3. Exact rules (TFHE) AML rule (2 comparisons + AND/OR) on encrypted txn 11.3 s / decision ~10⁸× route around it

Correctness: demo 1 top-10 ranking identical to plaintext (max score error 5×10⁻⁸); demo 2 max probability error 4×10⁻⁸; demo 3 exact by construction. In every demo the server holds a public context only — the scripts assert it cannot decrypt.

Baseline caveat: overheads are measured against single-thread numpy. A production SIMD/ANN baseline (FAISS-class) is several times faster than naive numpy, so multipliers vs best plaintext are ~2–5× higher than the table shows. The dollars-per-answer conclusion is unchanged — that's the point of pricing answers instead of multipliers.

The five-order spread between the demos is the load-bearing observation: the overhead multiplier is set by workload shape — packing layout, rotation count, and where the nonlinearities go — not by the cryptography. Moving a stage from the expensive class to the cheap class is worth more than the last five years of hardware progress. That routing/packing layer is where the planner (and eventually a real compiler) lives.

Security notes & limitations — read before relying on this

  • Parameters: all CKKS contexts use N=8192 with ≤200-bit total coefficient modulus — within the 218-bit cap for 128-bit security in the homomorphicencryption.org standard. TFHE (Concrete) uses its defaults (p_fail ≤ 2⁻¹²⁸-class, post-2024 hardened).
  • IND-CPA-D / result disclosure: CKKS is approximate. Decrypted results are safe to use by the key holder; revealing them to other parties (including the server) enables key-recovery-style attacks unless noise flooding is applied (Li–Micciancio, and the 2024 CCS line of work). Every plan the planner emits carries this warning. The demos here never disclose results beyond the key holder.
  • Access patterns: in the prototype, fetching the top-k documents by ID reveals which documents matched (never the query or the scores). Closing this requires a PIR fetch stage — production-proven elsewhere (Apple), not yet implemented here.
  • Model extraction (scoring workloads): decrypted scores leak model information over many queries; rate-limit as you would any ML API.
  • Integrity: FHE guarantees privacy, not that the server ran the right function. Verifiable FHE is still research.
  • This is a prototype: TenSEAL/SEAL and Concrete are real libraries, but this repo's glue code is unaudited. Do not deploy as-is.

Layout

  • akita/ — the planner + TenSEAL runtime (pip install -e .)
  • tests/ — including the silent-corruption reproduction and the cost-model validation (python -m pytest tests/ -q)
  • demos/demo1_private_search.py — CKKS encrypted retrieval, measured
  • demos/demo2_encrypted_scoring.py — CKKS column-packed scoring, measured
  • demos/demo3_exact_rules.py — TFHE exact compliance logic, measured
  • demos/private_rag/ — the end-to-end private search engine (build index, server, client, scale test)
  • demos/demo_page/ — the Private Memory demo page (both sides of the trust boundary, live, in a browser)
  • demos/demo6_private_memory.py — Private Memory measured end to end
  • results/ — every number in this README, as JSON
  • docs/why.md — the technical reasoning, with sources

Setup

uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -e ".[demos,dev]"

or with plain pip: python3.12 -m venv .venv && .venv/bin/pip install -e ".[demos,dev]".

Exact versions every number in this README was produced with are pinned in requirements.lock.txt (Python 3.12.13, macOS 14.6, Apple M1 Pro). Requires Python 3.10–3.12 (TenSEAL has no 3.13+ wheels yet).

Contributing, security, roadmap

Contributions that don't require cryptography expertise are the most valuable right now — adapters, embedders, clients, and benchmarks on your hardware: see CONTRIBUTING.md. Vulnerabilities: privately, per SECURITY.md. Where this is going (and what it deliberately isn't): ROADMAP.md. License: Apache-2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

akita_fhe-0.1.0.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

akita_fhe-0.1.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file akita_fhe-0.1.0.tar.gz.

File metadata

  • Download URL: akita_fhe-0.1.0.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for akita_fhe-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d26931e648982bce36840936ebb8b69ee0286d370b64aac9830b9af4504bc8c7
MD5 e22cded4f5e245cf03ea174b90f32671
BLAKE2b-256 383ad4faa7580e5da6fe5299b66009ab7bb2883b5dd47e0a6d517b0f744e3d4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for akita_fhe-0.1.0.tar.gz:

Publisher: publish.yml on gauthierpiarrette/akita

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file akita_fhe-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: akita_fhe-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for akita_fhe-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9c929799e68882fb9d865cbcf0c9c1af942c04b82237e26b17f969839b6bdb89
MD5 641cb1ef18661d3a5e3fc5bbbbed6e07
BLAKE2b-256 3bad16360cbfc3507413a9b77e1e84e8010a02605826dfe6a85f4e687db8bb84

See more details on using hashes here.

Provenance

The following attestation bundles were made for akita_fhe-0.1.0-py3-none-any.whl:

Publisher: publish.yml on gauthierpiarrette/akita

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page