Implementation details — architecture & analysis

Summary

VIA is a lattice (RLWE/MLWE) single-server PIR family whose headline trick is to eliminate the offline/preprocessing phase while keeping online communication at \(\tilde O(\log N)\). It does this by replacing the usual "coefficient-expansion" query-expansion step with a DMux tree (logarithmic-depth homomorphic de-multiplexers) and, in the VIA-C variant, a new LWE→RLWE conversion that compresses the whole query into a handful of \(\mathbb Z_{q_1}\) elements.1

The via-rs repository is a clean, no_std, layered Rust port of a Python reference.2 Its primitive stack (algebra → sampling → encryption → switching → gates → conversion) maps faithfully to the paper's §2–§5, and a real Query→Answer→Recover round-trip passes at both toy and (release-only) paper-scale parameters — verified here, 693/693 tests green.3 VIA-C and VIA-B are implemented (VIA-B behind the via-b cargo feature, merged after the original audit); plain VIA is not.4

This page describes the codebase as built — the construction it implements, its crate/layer architecture, the primitive→paper map, and the build/test status. The correctness and security audit — severity-ranked findings for VIA-C and VIA-B, the paper-vs-code parameter and noise-budget divergences, and the open questions — now lives on its own page: Audit ↗. In brief it confirms the concerns external reviewers (Yue et al.) raised in the VIA Security thread, and refutes two suspected ones.

The scheme — variants & construction

Variant Offline comm. Online comm. Key idea In via-rs?
VIA none \(\tilde O(1)\) DMux tree replaces coefficient expansion; fresh control bits + ring-switch before CMux no not implemented
VIA-C allowed (public params) \(\tilde O(1)\) VIA + LWE→RLWE / RLWE→RGSW query compression; ring-switch inside response compression yes fully built
VIA-B allowed batched VIA-C + homomorphic repacking (MLWEs→RLWE) for many small records yes implemented (feature via-b)

Sources: paper Figures 4 (VIA), 8 (VIA-C), 9 (VIA-B).1 Implementation scope confirmed by grep: the pipeline is monomorphised against VIA-C presets and no Figure-4 / repacking code paths exist at the audited commit 9575286 — VIA-B repacking landed later (PR #10).4

Database layout & query decomposition

paper The database \(X\) is encoded as an \(I\times J\) grid of \(R_{n_1,p}\) polynomials, each cell packing \(d = n_1/n_2\) records.5 code The client splits an index as \(\texttt{idx} = \gamma\,(IJ) + \alpha J + \beta\) into \((\alpha,\beta,\gamma)\) = (row, column, slot); \(\alpha\) drives DMux (MSB-first), \(\beta\) drives CMux (LSB-first), \(\gamma\) drives CRot.6

Architecture — as built

Crate & layer dependency graph

graph TD
  subgraph PRIM["via-primitives (no_std)"]
    L0["Layer 0 algebra: zq, rns CRT, ring NTT"]
    L1["Layer 1 sampling: SHAKE-256 PRG, uniform, ternary, Gaussian"]
    L2["Layer 2 encryption: SecretKey, RLWE, RLev, RGSW, MLWE, gadget, keyswitch"]
    L3["Layer 3 switching: mod-switch, ring-switch, rekey"]
    L4["Layer 4 gates: CMux, DMux, trees, CRot, RLWE-to-RGSW"]
    L5["Layer 5 conversion: LWE-to-RLWE cascade, MLWE embed, Extr_d"]
    L0 --> L1 --> L2 --> L3 --> L4 --> L5
    L2 --> L4
  end
  subgraph PROTO["via-protocol"]
    P1["PIRParams and presets TOY / REALISTIC"]
    P2["wire: Query, Answer, keys (serialization partial)"]
  end
  CL["via-client: setup, query idx, recover"]
  SV["via-server: setup_db, query_decomp, first_dim, answer, resp_comp"]
  INT["via-integration: e2e tests, KATs, benches"]
  PRIM --> PROTO
  PROTO --> CL
  PROTO --> SV
  PRIM --> CL
  PRIM --> SV
  CL --> INT
  SV --> INT
  

VIA-C end-to-end sequence

sequenceDiagram
  participant C as Client
  participant S as Server DB I x J
  Note over C: setup yields keys s, S1, S2 and public params pp_qck, pp_rck
  C->>C: split idx into alpha row, beta col, gamma slot, then encrypt bits as LWE at q1
  C->>S: query = L log N LWE ciphertexts
  Note over S: Answer pipeline, Figure 8
  S->>S: 1. QueryDecomp LWE to RLWE to RGSW yields C_ctrl, C_sel, C_rot
  S->>S: 2. DMux tree on C_ctrl yields one-hot rows at q1
  S->>S: 3. ModSwitch q1 to q2
  S->>S: 4. First dimension sum over i of c_i times db at i,j at q2
  S->>S: 5. CMux tree on C_sel selects column
  S->>S: 6. CRot on C_rot rotates to slot gamma
  S->>S: 7. RespComp ModSwitch q2 to q3, RingSwitch n1 to n2, scale to q4
  S->>C: answer = mask at q3, body at q4
  C->>C: Recover m = round of p times B' minus round of q4 A' S2 / q3, all over q4, mod p
  

Answer noise/modulus journey

flowchart TD
  A["LWE at q1 (compressed query bits)"]
  B["RGSW gate keys at q1: C_ctrl, C_sel, C_rot"]
  C["one-hot rows at q1 (noise + theta_DMux times log I)"]
  D["rows at q2"]
  E["column sums at q2 (noise times I n1 p^2 / 4, dominant term)"]
  F["selected column at q2 (noise + theta_CMux times log J)"]
  G["rotated to slot at q2"]
  H["answer, small ring n2 at q4 (decodes iff theta_ans under Delta/2 = 128)"]
  A -->|"LWE-to-RLWE-to-RGSW decompress"| B
  B -->|"DMux tree x log I"| C
  C -->|"ModSwitch q1 to q2"| D
  D -->|"FirstDim sum of c times db, x I"| E
  E -->|"CMux tree x log J"| F
  F -->|"CRot"| G
  G -->|"RespComp: q2 to q3, RingSwitch n1 to n2, q3 to q4"| H
  style E fill:#fdecea,stroke:#c0392b
  style H fill:#eafaf1,stroke:#1f9d6b
  

Modulus chain \(q_1{>}q_2{>}q_3{>}q_4{>}p\); step labels follow Appendix C's variance recursion.8 The first-dimension \(\times I\,n_1\,p^2/4\) term (red) and the per-level \(\log I,\log J\) additions are where the budget is spent; the answer (green) decodes only if the accumulated variance stays under \(\Delta/2\).

Primitive → paper map

Primitive Where (code) Paper § Notes
Ring \(R_{n,q}\), NTT, RNS \(q_1\) algebra/{zq,rns,ring} §2, §0 2-prime RNS for \(q_1\approx2^{75}\); const & runtime modulus impls
RLWE / RLev / RGSW / MLWE encryption/{rlwe,rlev,rgsw,mlwe}.rs §2.1–§2.3 RLev = \(\ell\) RLWE at gadget powers; RGSW = pair of RLev
Gadget decomposition (approximate) encryption/gadget.rs §2.3 / [40] signed-balanced digits; reconstruction error absorbed into noise7
CMux / DMux + trees gates/mux.rs §3.1, Lemma C.1/C.2 external-product based; cmux_tree / dmux_tree allocation-free
CRot (controlled rotation) gates/rotate.rs §4.2 RGSW-controlled \(X^{-\gamma}\) rotation
RLWE→RGSW conversion gates/convert.rs §4.2 / [45] gadget product with RLev of \(S^2\)
LWE→RLWE conversion cascade conversion/{conv,cascade,extract}.rs §4.1, Lemma 4.1/4.2 iterated MLWE-to-MLWE; \(O(n\log n)\) noise
Mod-switch / ring-switch / rekey switching/* §2.4, §3.3 ring-switch is a homomorphic projection \(n_1\!\to\!n_2\)
Sampling (PRG, ternary, Gaussian) sampling/* §1.1–§1.6 SHAKE-256 counter PRG; Box-Muller Gaussian (only float path)

Build & test status

code Built and run on Rust stable 1.96.0. cargo test --workspace693 passed, 0 failed, 4 ignored.9

Footnotes

  1. Liu et al. (2025) (local: via-paper.pdf) §1 Introduction & §3.1, p.1,8 — "VIA substitutes coefficient expansion techniques with DMux … introducing only logarithmic noise variance"; Figures 4/8/9 give the three constructions.
  2. protocol/presets.rs and many provenance comments (e.g. client.py:117, server.py:196, server/resp_comp.rs) reference a Python reference; find … *.py over the tree returns only a CI script — the Python source is not in this checkout.
  3. server/e2e_toy.rs and integration/client_server_e2e.rs — full-index assert_eq!(recover(query(i)), record[i]) at toy params.
  4. grep over crates/{client,server,protocol}: pipeline monomorphised against ViaCPublicParams; no Figure-4 (ring-switch-before-CMux) path, no repack/via_b/MLWEs→RLWE. "VIA-B (Layer 7) will wrap it" — server/answer.rs. Accurate as of commit 9575286; VIA-B repacking (repack/via_b) was subsequently merged in PR #10 and postdates this audit.
  5. Liu et al. (2025) (local: via-paper.pdf) §3.1 (Database structure), p.8 — \(db[i,j]=\iota_{n_2\to n_1}((X_{kIJ+iJ+j})_k)\), grid \(I\times J\).
  6. client/decompose.rsindex = γ·(I·J)+α·J+β; DMux bits MSB-first, CMux/CRot bits LSB-first.
  7. primitives/gadget.rs — "signed-balanced digits in \((-B/2,B/2]\) … the '≈' hides a reconstruction error bounded by \(\mathrm{round}(q/B^L)/2\)."
  8. Liu et al. (2025) (local: via-paper.pdf) Appendix C.2, p.25-27 — the per-step variance recursion (θctrl→θdmux→θms→θfirst→θcmux→θcrot→θrep→θans) and condition \(\lVert E_{\text{final}}\rVert_\infty\le\lfloor(q_3-q_4)/p\rceil/2-1\).
  9. Executed in this environment after installing rustup/stable (1.96.0): cargo test --workspace → 693 passed / 0 failed / 4 ignored across the workspace crates' unit, integration, and doctest targets.
  10. crates/via-primitives/tests/{layer3,layer4,layer5}_kats.rs and crates/via-integration/tests/layer6_kats.rs — byte-equality vs embedded Python-generated constants.
  11. integration/client_server_e2e_paper.rs (NUM_ROWS=2,NUM_COLS=2), :69-71 (Distribution::Ternary), :146 (#[ignore]). Both ignored tests pass in release (verified, 35.7s).
  12. via-rs/fuzz/fuzz_targets/{zq,rns,sampling,switching,encryption,gates,conversion,ring,protocol} and fuzz/Cargo.toml target list.