RemoteRAG
— alternative

A second storage strategy explored during the prototype: keep the index plaintext on the server, give the query formal (n, ε)-DistanceDP via planar-Laplace noise, recover exact ranking with a Paillier homomorphic dot-product. Implemented and benchmarked — not the chosen production path.

status  parked · not in runner
protocol  Cheng et al. · ACL Findings 2025
code  crates/remote-rag stays as parallel service
tests  passing · validated to 10k docs
documented for context
§01

What was built

RemoteRAG (Cheng et al., ACL Findings 2025, arXiv 2412.12775) is a two-stage retrieval protocol that gives the query formal (n, ε)-DistanceDP — the server cannot distinguish the true query from any other within radius n/ε in embedding space. n is the embedding dimension, not a cluster count (a famously common misreading).

Stage 1 (planar-Laplace). Client samples r ~ Γ(n, 1/ε) and v ~ U(S^{n−1}), ships q′ = q + r·v to the server. Server runs HNSW DistCosine over the plaintext doc embeddings against q′ and returns the top-k′ candidates. The DP guarantee comes from the noise on q′, not from any encryption.

Stage 2 (Paillier PHE rerank). Client also ships Paillier ciphertexts Enc(qᵢ) for each dim. Server computes ∏ᵢ Enc(qᵢ)^{e_d[i]_int} = Enc(⟨q, e_d⟩) per Stage-1 candidate. Server returns Paillier ciphertexts of the exact dot products. Client decrypts and re-sorts to recover the top-k under the clean query — bringing recall back to ~100% despite the Stage-1 noise.

The crate crates/remote-rag implements both stages in pure Rust: planar_laplace (Gamma radius + uniform direction), paillier (keygen with CRT factor precompute, Enc/Dec, homomorphic add, scalar-mul, dot-product via multi-exponentiation), and RemoteRagService (orchestrator with HNSW Stage-1 + Paillier-rerank, over-fetch factor knob). The tests/remote_rag_scale.rs benchmark validates the protocol at 10k docs: recall vs linear-cosine ground truth = 96.0%, mean end-to-end latency = 23.5 ms / query.

§02

Definitions & glossary

TermDefinition
RemoteRAGCheng et al., ACL Findings 2025 (arXiv 2412.12775). Two-stage retrieval: planar-Laplace DP on the query for Stage-1 ANN, Paillier homomorphic dot-product for Stage-2 rerank.
DistanceDP(n, ε)-Distance Differential Privacy. Any two queries within radius n/ε in embedding space are formally indistinguishable to the server. n is the embedding dimension, not a cluster count.
Planar-Laplace mechanismStage-1 noise: sample radius r ~ Γ(n, 1/ε) and direction v ~ U(Sⁿ⁻¹); ship q′ = q + r·v. Implements DistanceDP on the embedding.
Paillier PHEPaillier partially homomorphic encryption (additive). Lets the server compute Enc(⟨q, d⟩) from Enc(qᵢ) and plaintext dᵢ via ∏ᵢ Enc(qᵢ)^{dᵢ}.
HNSWHierarchical Navigable Small World (Malkov & Yashunin) — the graph-based ANN index the Stage-1 search runs over.
CAPRISEDistance-preserving encryption (Ye et al. 2026) — the production path on storage. Mentioned here as the contrast: CAPRISE keeps doc embeddings encrypted at rest, RemoteRAG keeps them plaintext.
Vec2TextMorris et al., EMNLP 2023. Inverts embedding vectors back to near-exact source text — the inversion attack that motivates encrypting doc embeddings at rest.
§03

Why this isn't the production path

ConcernRemoteRAG behaviourCAPRISE behaviour (chosen)
Server holds plaintext doc embeddings Yes — required by the Paillier protocol. Embeddings are unit-norm but readable. No — server holds CAPRISE ciphertext only. Embeddings unreadable without the key.
At-rest confidentiality of the corpus None — operator with DB access can read embeddings (and run Vec2Text-style inversion against them). Strong — operator sees only ciphertext + access patterns.
Per-query latency budget Dominated by Paillier: ~750 ms CRT-encrypt of the 1024-dim query (once per query) + ~47 ms × k′ rerank. Sub-second end-to-end including the GELO-masked embedding step.
Client key custody Client holds the Paillier private key. Compromised client → server-side Paillier ciphertexts decryptable. Client holds user_x_sk; TEE holds tee_user_x_sk. Forward-secure against TEE-only compromise.
DP budget Real — exhausts under repeated queries. Each query consumes from the (n, ε) budget against the same vector. Not applicable — CAPRISE is keyed, not statistical.
Stackable with the GELO embedder Yes (embed in TEE, then take the pooled vector through the RemoteRAG client protocol). Just not stackable with CAPRISE. Yes — natively composes (the runner uses both).

The deciding axis: who or what is the secret? RemoteRAG is the right answer when the query is the headline secret and doc embeddings are not — public knowledge bases queried privately, for instance. The target deployment for this prototype is the inverse: a regulated enterprise outsourcing a confidential corpus. The doc embeddings being readable on the server is the failure mode we explicitly built around.

§04

Threat model

PartyTrustWhat it seesWhat it does NOT see
Clienttrustedplaintext query · Paillier sk · AES chunk key · final decrypted top-k
Storage server (operator)untrusted (≈ honest-but-curious)plaintext doc embeddings · noisy query q′ · Paillier ciphertexts · AES chunk blobs · access patternsclean q · the dot products ⟨q, e_d⟩ (Paillier-encrypted) · plaintext chunk text
NetworkuntrustedTLS-wrapped Stage 1 + Stage 2 payloadsplaintext at any layer

Formal guarantee. (n, ε)-DistanceDP on the query against any non-colluding adversary observing only Stage-1 traffic. At ε ≈ 10n on a 1024-dim embedding the radius of indistinguishability is ~100 unit-norm steps — large enough to hide individual queries, small enough that Stage-2 PHE rerank recovers ranking exactly.

Gaps vs. our project threat model. (1) Doc-side breach (server compromise) exposes plaintext doc embeddings — the corpus itself. (2) DP budget exhausts after many queries against the same vector. (3) A compromised client leaks the Paillier sk, after which the server's stored ciphertexts become decryptable.

§05

Components

query DP

Planar-Laplaceremote_rag :: planar_laplace

Stage-1 query-side perturbation. Gamma-distributed radius + Gaussian-normalised direction; gives formal (n, ε)-DistanceDP.

method
r ~ Γ(n, 1/ε), v ~ U(S^{n−1}), q′ = q + r·v. n is embedding dim, not cluster count.
source
Cheng et al. · ACL Findings 2025 · arXiv 2412.12775
secures
the query operand against a plaintext-index server
cost
µs per query
PHE rerank

Paillierremote_rag :: paillier

Additively-homomorphic encryption. The single useful operation here: ∏ᵢ Enc(qᵢ)^{e_d[i]_int} = Enc(⟨q, e_d⟩) — server-blind exact dot products.

method
Custom Paillier on num-bigint · full CRT factor precompute (p², q², μ_p, μ_q, p_inv_q) · fixed-point quantisation · signed-result decode · rayon hot path
source
algorithms adapted from fast-paillier 0.3.2 (LFDT-Lockness, MIT/Apache). Pure-Rust backend — no LGPL footgun.
secures
exact query–doc cosine on a plaintext-index server without revealing the query
cost
~750 ms CRT-encrypt of one 1024-dim query · ~47 ms × k′ rerank
orchestrator

RemoteRagServiceremote_rag :: service

Parallel-shape service to GeloRagTwoPartyService. Client- and server-state in one struct for ergonomics; boundaries documented in source. Mutually exclusive with CAPRISE-at-rest at the type level.

method
ingest_chunks · query · over-fetch factor knob · HNSW DistCosine ≥ 256 docs / linear sweep below
source
in-house · implements Cheng et al. 2025
secures
(n, ε)-DistanceDP on queries with ~100% recall · AES-GCM chunks
threat
untrusted retrieval server. Doc embeddings NOT secret.
cost
23.5 ms / query end-to-end at 10k docs (Stage 1 + Stage 2 · 15-over-fetch · rayon)
shared with CAPRISE

AES-GCM chunksrag_core :: content

AES-256-GCM chunk-text cipher. Same primitive both storage paths use; under RemoteRAG the key is client-generated rather than HKDF-derived.

method
32-byte key · 96-bit nonce per chunk · GCM authenticator
source
RustCrypto aes-gcm 0.10
secures
raw chunk text returned alongside top-k embeddings
cost
negligible
§06

Compute flow & trust boundaries

FIG. 04 — RemoteRAG · planar-Laplace + Paillier rerank · query path solid plum = Paillier homomorphic transit · solid red = trusted secret
Client holds Paillier sk · chunk key TEE (optional; client-side acceptable) Storage server — plaintext index RATLS or TLS HTTP · noisy q′ + Paillier(q) ① Embed (locally or in TEE) → pooled query q q′ ← q + r·v · r ~ Γ(n, 1/ε) enc_q ← Paillier.encrypt_each(q) ⑥ Decrypt rerank Paillier.decrypt(result_i) ⟨q, e_d⟩ for each candidate sort · AES-decrypt chunks → ranked plaintext top-k (optional embedder TEE) use Approach-4 attestation ② Paillier keypair 256-bit · CRT factor table sk = (p, q, λ, μ_p, μ_q) pk = (n, g) ⚠ private key on client only ③ Stage 1 — HNSW ANN DistCosine over plaintext k′ candidates @ over_fetch · 3 ④ Stage 2 — Paillier rerank ∏ Enc(qᵢ)^{e_d[i]_int} = Enc(⟨q, e_d⟩) — server-blind plaintext doc index unit-norm embeddings + AES-GCM chunk blobs ⑤ Encrypted hits k′ × Paillier(⟨q, e_d⟩) + AES q′ + enc_q (Stage 1 + 2 payload) Paillier(⟨q, e_d⟩) + AES
#StepWho runs itWhat crosses
Client embeds (locally or via TEE) → q; samples r ~ Γ(n, 1/ε), v ~ U(S^{n−1}); computes q′ = q + r·v and enc_q = Paillier.encrypt_each(q)Client (or optional TEE for embedding)
Client holds the Paillier sk; only pk ever leaves the client (at ingest)Clientpk (one-time at ingest)
Server runs Stage-1 HNSW DistCosine over plaintext doc embeddings using the noisy q′ → top-k′ candidate doc idsStorage servernoisy q′ inbound
Server runs Stage-2 Paillier rerank: ∏ᵢ enc_q[i]^{e_d[i]_int} per candidateStorage server
Server returns Paillier ciphertexts of exact dot products + AES-encrypted chunk blobsStorage serverencrypted hits outbound
Client decrypts each ⟨q, e_d⟩ with sk, sorts to true top-k, AES-decrypts chunk textClient
§08

Performance & correctness

StageCostNotes
Ingest (HNSW build + AES-GCM + Paillier keygen)18.3 s total · ~1.83 ms / doc10k docs · 256-bit Paillier with full CRT factor precompute
Stage 1 — planar-Laplace + HNSW DistCosine~ms / queryef_search = max(64, k′) · M = 16
Stage 2 — Paillier PHE rerank~47 ms × k′ candidatesrayon-parallel across candidates
End-to-end mean (10k docs · k = 5 · over-fetch 3)23.5 ms / queryrecall vs linear-cosine ground truth = 96.0%
Paillier CRT-encrypt of one 1024-dim query~750 msonce per query · 1024 ciphertexts at 256-bit · can be cached per-session
Test that asserts rerank is load-bearingtests/remote_rag_e2e.rs — under ε = 4, Stage-1 alone misses top-1; Stage-2 rerank restores it

Correctness — what's asserted

TestWhat it asserts
planar_laplace::tests::*Empirical moment checks: Γ-distributed radius has mean n/ε and variance n/ε²; direction is unit-norm; ε > 0 panics on misuse; dimension mismatch panics.
paillier::tests::*Keygen invariants · Enc/Dec round-trips (signed + unsigned) · homomorphic add · homomorphic scalar-mul (positive + negative) · end-to-end signed dot product matches plaintext.
service::tests::round_trip_with_low_noise_recovers_top_hitFull ingest → query → expected chunk returned at low ε.
service::tests::paillier_rerank_recovers_when_stage1_is_noisyStage-1 cosine order alone misses the true top-1 under tight ε; Paillier-decrypted rerank restores it. This is the load-bearing test that justifies Stage 2.
tests/remote_rag_scale.rs::hnsw_recall_vs_linear_ground_truth (ignored)96.0% recall vs linear-cosine ground truth at 10k docs.
tests/remote_rag_scale.rs::hnsw_stage1_under_50ms_at_10k_docs (ignored)Stage-1 latency budget under 50 ms at 10k docs.
§09

Status & gaps

PieceStateNotes
Planar-Laplace mechanismworkingmodule + tests · validated against the paper's Γ-moment formulas
Paillier (keygen / Enc / Dec / hom. ops)workingpure-Rust on num-bigint; CRT factor table; ~750 ms encrypt of 1024-dim query is the dominant cost
RemoteRagServiceworkingfull two-stage protocol; HNSW Stage-1 + Paillier rerank; 96% recall at 10k docs
Runner wiringnot doneSEV-SNP runner ships only the CAPRISE path. A parallel HTTP route would be ~150 LOC if revived.
Multi-tenancy / per-tenant Paillier keysnot doneWould mirror the GeloRagTwoPartyService shape but with per-tenant Paillier keypairs instead of HKDF-derived material.

What would justify reviving this: a deployment where the corpus is public or low-sensitivity (so plaintext doc embeddings on the server are acceptable) but query confidentiality is the load-bearing requirement — for example, a public knowledge base queried by users who don't want their interests profiled. Under that threat model RemoteRAG is the right pick, the existing crate is shippable, and the runner glue is small.