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.