Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions benchmarks/sigma_v4_results.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Sigma-v4 backend performance results

Measured on the same class of host as the v3 results (4-core x86-64
container, release builds, default fixed-point config `scale_bits=20,
value_bits=63, intermediate_bits=127`, trivial scaling). "v3" numbers are
from `native_v3_results.md` on identical hardware. The v4 backend has no
proving-key or SRS generation, so there is no warm/cold split: the first
proof of a shape costs the same as every other (v3 "cold" paid keygen on
every fresh process, which is the realistic first-use cost).

Reproduce with `cargo run --release --example bench_prove -- <in> <rank> <out> <reps> sigma`
(`halo2` as the last argument benchmarks the legacy backend) and
`python benchmarks/run_benchmarks.py`.

## Single proof + verify (Rust, `bench_prove`, steady state)

| shape (in×rank×out) | v3 prove warm | v3 prove cold | v4 prove | speedup warm / cold | v3 verify | v4 verify | proof bytes |
|---|---|---|---|---|---|---|---|
| 2×1×2 | 0.73 s | 2.9 s | 15 ms | 49× / 193× | 0.020 s | 16 ms | 31 KB |
| 8×2×8 | 1.4 s | 6.0 s | 17 ms | 82× / 353× | 0.034 s | 17 ms | 36 KB |
| 16×2×16 | 7.0 s | 27.2 s | 21 ms | 333× / 1295× | 0.16 s | 20 ms | 45 KB |
| 64×4×64 | 10.8 s | 47.2 s | 35 ms | 309× / 1349× | 0.22 s | 36 ms | 99 KB |
| 768×2×256 | 81 s | 429 s | 97 ms | 835× / 4423× | 1.3 s | 101 ms | 327 KB |
| 768×4×768 | infeasible (>15 GB) | infeasible | 183 ms | ∞ | — | 205 ms | 611 KB |
| 768×4×2304 | infeasible (est. >200 GB pk) | infeasible | 470 ms | ∞ | — | 587 ms | 2.2 MB |

Proving memory drops from gigabytes (12.1 GB at k=19; OOM beyond) to tens of
megabytes for every shape: the halo2 extended-domain evaluations are gone
entirely.

## End-to-end Python pipeline (`run_benchmarks.py`, 4 workers)

| shape | invocations | v3 prove wall | v4 prove wall | speedup | v3 verify wall | v4 verify wall |
|---|---|---|---|---|---|---|
| 16×2×16 | 8 | 71.4 s | 0.098 s | **729×** | 3.4 s | 0.23 s |
| 32×4×32 | 6 | 107.4 s | 0.097 s | **1107×** | 3.8 s | 0.61 s |
| 768×4×768 | 4 | infeasible | 0.48 s | ∞ | — | 12.7 s* |

\* dominated by the one-time adapter-setup verification (Bulletproofs over
all 24,576 committed weights), paid once per adapter per verifier process
and cached afterwards; the per-invocation verification is ~0.2 s.

## One-time adapter setup (manifest creation)

The per-weight work that v3 paid inside **every** invocation proof is paid
once per adapter when the manifest is written: Pedersen row commitments, an
aggregated exact range proof for every weight, and a linking proof.

| adapter shape | weights | setup prove | setup blob |
|---|---|---|---|
| 16×2×16 | 64 | 1.8 s | 17 KB |
| 768×2×256 | 2,048 | 14 s | 485 KB |
| 768×4×2304 | 12,288 | 82 s | 2.5 MB |

This artifact ships inside the pinned adapter manifest; it contains no
weight or salt material.

## Real-model end-to-end (distilgpt2 + ng0-k1/distilgpt2-finetuned-es)

The full quickstart flow from the README — contributor server, base-model
client over sockets, native proof generation, CLI verification — run on the
same 4-core host. The adapter has six rank-16 768×2304 c_attn modules
(~49k quantized weights each); under v3 a single such module exceeded the
host by an order of magnitude (estimated >200 GB of params/keys), so none
of this was previously possible on any realistic machine.

| stage | wall time |
|---|---|
| manifest write incl. one-time adapter setup proofs, 6 modules | ~6 min (one-time per adapter) |
| inference + proof generation, 60 invocation proofs (6 modules × 10 token rows) | 62 s |
| verification of all 60 proofs incl. one-time adapter-setup verification | 162 s |

A byte-flipped proof artifact is rejected
(`proof bytes failed native sigma-v4 verification`), as are tampered
statements, transcripts, manifests, and verification keys (covered by the
test suites).

## Range engines

Per-invocation proofs default to the sumcheck-based LogUp range engine
(microseconds of field work per range entry). `ZKLORA_RANGE_ENGINE=bulletproofs`
opts into Bulletproofs instead: proofs shrink ~5-8× at ~5-10× slower proving
(still 50-100× faster than v3). Both engines prove the identical exact
intervals under the same discrete-log + Fiat-Shamir assumptions, and the
verifier accepts either.
13 changes: 8 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Low-Rank Adaptation (LoRA) is a widely adopted method for customizing large-scal

To solve this, we created **zkLoRA** a zero-knowledge verification protocol that relies on commitments, succinct proofs, and multi-party inference to verify exact LoRA delta computation for a pre-agreed adapter without exposing LoRA weights.

This implementation uses a native Halo2 backend for transcript-bound proof artifacts. The v2 proof contract verifies exact quantized LoRA delta correctness for the statement the base user actually sent and received, and binds the proof to a pre-inference adapter manifest. It does not claim an end-to-end proof that the base model computed those activations.
This implementation uses a native commit-and-prove backend (sigma-v4) for transcript-bound proof artifacts. The proof contract verifies exact quantized LoRA delta correctness for the statement the base user actually sent and received, and binds the proof to a pre-inference adapter manifest. It does not claim an end-to-end proof that the base model computed those activations.

Verifier trust boundary: `expected_adapters` must be obtained and pinned by the verifier out-of-band before inference starts, for example by recording the exact manifest file or digest. A contributor-generated adapter manifest is only a convenience handoff artifact; if it is first generated after inference or supplied only alongside proofs, it is not trusted verifier input.

Expand Down Expand Up @@ -348,7 +348,7 @@ For detailed information about the codebase organization and implementation deta
<td>✓</td><td><strong>Trust-Minimized Delta Verification:</strong> Zero-knowledge proofs validate exact quantized LoRA deltas for a pre-agreed adapter</td>
</tr>
<tr>
<td>✓</td><td><strong>Native Halo2 Backend:</strong> Proofs no longer depend on EZKL/ONNX artifacts</td>
<td>✓</td><td><strong>Native Sigma-v4 Backend:</strong> Commit-and-prove protocol over ristretto255; per-proof cost no longer scales with adapter size, so every real LoRA shape proves in milliseconds-to-subsecond on a laptop-class host (previously minutes to infeasible). Legacy Halo2 (v3) artifacts remain verifiable</td>
</tr>
<tr>
<td>✓</td><td><strong>Multi-Party Inference:</strong> Protected activation exchange between parties</td>
Expand All @@ -357,10 +357,10 @@ For detailed information about the codebase organization and implementation deta
<td>✓</td><td><strong>Adapter Weight Privacy:</strong> LoRA weights remain confidential while the committed adapter identity is checked</td>
</tr>
<tr>
<td>✓</td><td><strong>Benchmark Required:</strong> Real-shape proving and verification performance should be measured for each deployment target (see <code>benchmarks/run_benchmarks.py</code> and <code>cargo run --release --example bench_prove</code>)</td>
<td>✓</td><td><strong>Benchmark Required:</strong> Real-shape proving and verification performance should be measured for each deployment target (see <code>benchmarks/run_benchmarks.py</code>, <code>benchmarks/sigma_v4_results.md</code>, and <code>cargo run --release --example bench_prove</code>)</td>
</tr>
<tr>
<td>✓</td><td><strong>Fast Native Backend (v3):</strong> Lookup-based range checks, shape-keyed SRS/key caches, and parallel batch proving/verification deliver order-of-magnitude speedups over v2 while keeping the same statement format and adapter commitment scheme</td>
<td>✓</td><td><strong>Fast Backend (v4):</strong> Pedersen-committed adapters with one-time exact weight range proofs, Fiat-Shamir random-projection sigma protocols for the quantized LoRA relation, and a zero-knowledge sumcheck LogUp range engine deliver 300-1100× faster proving than v3 on previously-feasible shapes and make all real LoRA shapes feasible, with the same statement semantics, the same discrete-log assumption class, and improved (salted, perfectly hiding) adapter commitments</td>
</tr>
</table>

Expand All @@ -376,7 +376,10 @@ zkLoRA is built upon these outstanding open source projects:
| [Transformers](https://github.com/huggingface/transformers) | State-of-the-art Natural Language Processing |
| [dusk-merkle](https://github.com/dusk-network/dusk-merkle) | Merkle tree implementation in Rust |
| [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) | Cryptographic hash function |
| [Halo2](https://github.com/zcash/halo2) | Native zero-knowledge proving system used by the zkLoRA backend |
| [curve25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek) | Ristretto255 group arithmetic underlying the sigma-v4 backend |
| [Bulletproofs](https://github.com/dalek-cryptography/bulletproofs) | Aggregated range proofs for one-time adapter setup |
| [merlin](https://github.com/dalek-cryptography/merlin) | Fiat-Shamir transcripts |
| [Halo2](https://github.com/zcash/halo2) | Proving system used by the legacy v3 backend |

<hr>

Expand Down
6 changes: 6 additions & 0 deletions src/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
thiserror = "2"
bulletproofs = "4"
merlin = "3"
curve25519-dalek-ng = "4"
bincode = "1"
base64 = "0.22.1"
rand_chacha = "0.3"

[dev-dependencies]
tempfile = "3"
23 changes: 12 additions & 11 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ src/
│ │ └── merkle/ # Rust Merkle tree implementation
│ ├── proof_contract.py # Transcript and artifact contract
│ └── zk_proof_generator.py # Native proof generation core
├── src/lib.rs # Halo2/PyO3 native prover
├── src/lib.rs # PyO3 native prover (legacy halo2 circuit + fast paths)
├── src/sigma.rs # Sigma-v4 commit-and-prove backend (default)
├── src/logup.rs # Zero-knowledge sumcheck LogUp range engine
├── scripts/ # Sample usage scripts
├── pyproject.toml # Build configuration
└── requirements.txt # Dependencies
Expand All @@ -25,10 +27,10 @@ src/

### Zero-Knowledge Architecture

The zero-knowledge proof system in zkLoRA is built on transcript-bound LoRA delta statements and native Halo2 proofs. The `zk_proof_generator.py` module orchestrates the proof generation process by:
The zero-knowledge proof system in zkLoRA is built on transcript-bound LoRA delta statements and a native commit-and-prove backend (sigma-v4). The `zk_proof_generator.py` module orchestrates the proof generation process by:

1. Capturing the base user's local transcript of activations and returned LoRA deltas
2. Binding each proof to a verifier-pinned pre-inference adapter manifest with a Poseidon adapter commitment
2. Binding each proof to a verifier-pinned pre-inference adapter manifest whose entries carry salted Pedersen row commitments plus a one-time exact range proof for every committed weight
3. Generating native `.zklora.*` proof artifacts for contributor-side LoRA invocations
4. Verifying proof artifacts against both the base user's transcript and expected adapter manifest before accepting a module

Expand Down Expand Up @@ -56,18 +58,17 @@ The Rust implementation is wrapped with Python bindings in the `libs/merkle` dir

### Performance Considerations

Native Halo2 performance should be measured for the specific LoRA shapes being proven. The v3 backend keeps the v2 proof contract (same statement format, same Poseidon adapter-commitment scheme) while making proving and verification dramatically faster:
The sigma-v4 backend restructures the statement so that per-proof work no longer scales with the number of adapter weights (the v3 halo2 circuit re-hashed and re-range-checked every weight inside every invocation proof):

- **Lookup-based range checks**: signed interval checks decompose values into window-sized limbs via a running sum constrained against a lookup table, instead of one boolean row per bit. The interval semantics are exact (the top limb is scaled to its residual width), and the circuit drops provably redundant per-product checks whose bounds already follow from the range-checked operands. At a 768×4×2304 LoRA shape this reduces the circuit from k≈26 to k=21 (32× fewer rows).
- **Keyed SRS/proving-key/verifying-key caches**: params and keys are derived deterministically from the statement shape (dims, fixed-point bits, scaling) and reused across invocations of the same module. First proof per shape pays keygen; subsequent proofs and all verifications are keygen-free. Cache sizes are tunable via `ZKLORA_PARAMS_CACHE_CAP`, `ZKLORA_PK_CACHE_CAP`, and `ZKLORA_VK_CACHE_CAP`.
- **Parallel batch operations**: the PyO3 bindings release the GIL, and `generate_proofs` / `batch_verify_proofs` fan out across a thread pool (`ZKLORA_PROVE_WORKERS`, `ZKLORA_VERIFY_WORKERS`).
- **One-time adapter setup**: at manifest time the contributor produces salted Pedersen row commitments to A and B, per-weight commitments, an aggregated Bulletproofs range proof pinning every weight to the exact `[-value_bound, value_bound]` interval, and a Schnorr proof linking the two commitment forms. The pinned adapter commitment string is the SHA-256 of the deterministic commitment core, so pinning the manifest pins the whole setup.
- **Random-projection sigma protocols**: each invocation proof commits to the rounding quotients and remainders of the exact three-stage quantized pipeline, then Fiat-Shamir challenges project the matrix equations onto scalar equations over committed values (Schwartz-Zippel), proven with generalized Schnorr proofs plus one rank-sized quadratic inner-product argument. Per-proof group work is O(in + rank + out).
- **Zero-knowledge LogUp range engine** (`logup.rs`): rounding remainders and quotients are pinned to their exact intervals via 8-bit digit lookups proven with two sumchecks whose round polynomials are sent as Pedersen commitments; all sumcheck verifier relations are linear over committed scalars and fold into one Schnorr proof. `ZKLORA_RANGE_ENGINE=bulletproofs` opts into compact Bulletproofs instead (~5-8× smaller invocation proofs, ~5-10× slower proving).
- **Parallel batch operations**: the PyO3 bindings release the GIL, and `generate_proofs` / `batch_verify_proofs` fan out across a thread pool (`ZKLORA_PROVE_WORKERS`, `ZKLORA_VERIFY_WORKERS`); MSMs, range chunks, and sumcheck rounds parallelize internally with rayon.
- **Native fast paths with exact fallbacks**: quantized delta computation and the hiding Merkle commitment have Rust implementations that are value-identical to the Python reference paths (covered by parity tests); the Python implementations remain as exact fallbacks.

Proofs and verifying keys from the v2 backend are not compatible with v3 (the `backend` field in statements changed to `zklora-halo2-v3`), but pinned adapter manifests remain valid: the adapter commitment scheme is unchanged.
Statement semantics are identical to v3: the same canonical half-up rounding, the same exact remainder intervals, the same value/intermediate bounds, the same transcript binding and verifier trust boundary. Binding rests on discrete log over ristretto255 plus SHA-256/BLAKE3 collision resistance (the same assumption class as halo2-IPA over Pasta), and adapter hiding improves: commitments are perfectly hiding, and the deterministic blinding factors are derived from the contributor's secret salt keyed together with the full adapter content, so two adapters never share blindings even when they share a shape and a salt (commitment differences across manifests therefore reveal nothing; the v3 Poseidon chain was unsalted). The salt persists via `ZKLORA_ADAPTER_SALT_FILE` (`LoRAServer` defaults it to a file next to its artifacts; keep it stable across restarts so pinned manifests keep matching proofs). v3 artifacts (schema 2, backend `zklora-halo2-v3`) remain verifiable through the legacy path, covered by a byte-faithful regression test.

Measured on a 4-core machine (warm key cache): a 2×1×2 relation proves in ~0.7s and verifies in ~0.02s (previously ~23s / ~18s), an 8×2×8 relation proves in ~1.4s (previously >5 minutes), and the real 768×4×2304 c_attn shape becomes feasible at k=21.

Memory note: proving memory is dominated by halo2's extended-domain evaluations (the Poseidon chip's gate degree implies an 8× extended domain), and halo2 0.3 keeps all advice cosets resident during proof creation. Measured on a 15 GB host: a k=19 shape (768×2×256) proves in 81 s warm with a 12.1 GB peak, while k=20 and k=21 shapes (e.g. 768×4×768 and the full 768×4×2304 c_attn) exceed 15 GB and require a 32–64 GB prover host. Verification is lightweight once the verifying key is cached.
Measured on a 4-core host (see `benchmarks/sigma_v4_results.md` for the full tables): 768×2×256 proves in 97 ms and verifies in ~0.1 s (v3: 81 s warm / 429 s cold, 1.3 s verify); 16×2×16 proves in 21 ms (v3: 7 s warm); the real 768×4×768 and 768×4×2304 c_attn shapes, which exceeded a 15 GB host under v3, prove in 183 ms and 470 ms. Proving memory drops from gigabytes to tens of megabytes. There is no warm/cold split because there is no keygen.

For detailed usage examples and high-level architecture, please refer to the [main README](../../README.md) in the project root.

Expand Down
Loading
Loading