Metadata-Version: 2.5
Name: secure-fl
Version: 26.10.191
Summary: Dual-Verifiable Framework for Federated Learning using Zero-Knowledge Proofs
Project-URL: Homepage, https://github.com/krishantt/secure-fl
Project-URL: Bug Reports, https://github.com/krishantt/secure-fl/issues
Project-URL: Source, https://github.com/krishantt/secure-fl
Project-URL: Documentation, https://github.com/krishantt/secure-fl/blob/main/README.md
Author-email: Krishant Timilsina <krishtimil@gmail.com>, Bindu Paudel <binduupaudel565@gmail.com>
Maintainer-email: Krishant Timilsina <krishtimil@gmail.com>, Bindu Paudel <binduupaudel565@gmail.com>
License: MIT
License-File: LICENSE
Keywords: cryptography,federated-learning,machine-learning,privacy,zero-knowledge-proofs,zk-snarks,zk-starks
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.12
Requires-Dist: click>=8.0.0
Requires-Dist: grpcio>=1.70
Requires-Dist: numpy>=1.24.0
Requires-Dist: protobuf>=6.33.5
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0.0
Requires-Dist: securefl-core>=26.10.147
Requires-Dist: textual>=6.2.1
Requires-Dist: torch>=2.8.0
Requires-Dist: torchvision>=0.23.0
Provides-Extra: benchmark
Requires-Dist: memory-profiler>=0.61.0; extra == 'benchmark'
Requires-Dist: pytest-benchmark>=5.2.3; extra == 'benchmark'
Provides-Extra: dev
Requires-Dist: mypy>=1.19.0; extra == 'dev'
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
Requires-Dist: psutil>=5.9.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
Requires-Dist: pytest-xdist>=3.3.0; extra == 'dev'
Requires-Dist: pytest>=7.4.0; extra == 'dev'
Requires-Dist: ruff>=0.14.8; extra == 'dev'
Requires-Dist: types-psutil; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: medical
Requires-Dist: medmnist>=2.2.0; extra == 'medical'
Provides-Extra: viz
Requires-Dist: matplotlib>=3.7.0; extra == 'viz'
Description-Content-Type: text/markdown

# Secure FL: Zero-Knowledge Federated Learning

A dual-verifiable framework for federated learning using zero-knowledge proofs to ensure training integrity and aggregation correctness.

**Documentation site:** https://krishantt.github.io/secure-fl/ · [interactive architecture map](https://krishantt.github.io/secure-fl/architecture/secure-fl-architecture.html)

## Core Features

- **Dual ZKP Verification**: Client-side Groth16 zk-SNARK (transferable) + server-side Groth16 zk-SNARK for aggregation, both proved and verified in-process by the Rust core
- **FedJSCM Aggregation**: Momentum-based federated optimization with auto-reinit on architecture changes
- **Dynamic Proof Rigor**: `StabilityMonitor` adapts proof complexity (`low`/`medium`/`high`/`adaptive`) based on training stability
- **Deterministic Hashing**: SHA-256 over the full parameter state dict — safe across Python/numpy versions
- **Attack Detection**: Norm-bound enforcement, hash verification, and ZKP circuit checks block poisoned updates

## Architecture

```
Client: Local Training → Groth16 delta-bound proof (transferable)
      → Send full state_dict + proof
Server: Receive updates → verify (hash + norm + Groth16) → FedJSCM aggregation
      → Groth16 SNARK proof → Distribute model
```

![Secure-FL round architecture](docs/figures/secure-fl-architecture.png)

Everything touching proofs, aggregation and the stability heuristic runs in one
Rust core loaded into both processes; Python keeps the PyTorch training loop and
the round strategy.

Interactive pages (generated from this repository): [architecture map](docs/architecture/secure-fl-architecture.html),
[data flow](docs/architecture/secure-fl-dataflow.html), [round sequence](docs/architecture/secure-fl-sequence.html).

<details>
<summary>Data flow and round sequence</summary>

![Data flow from local samples to a proven global model](docs/figures/secure-fl-dataflow.png)

![One aggregated round, Python and Rust](docs/figures/secure-fl-sequence.png)

</details>

**Dual verification:**
1. **Clients** generate a transferable Groth16 proof of correct local training (delta norm bound), independently server-verifiable, plus a SHA-256 hash commitment over the full parameter state
2. **Server** generates a Groth16 proof of correct FedJSCM aggregation

## Quick Start

### Installation

```bash
# From source with uv (recommended)
git clone https://github.com/krishantt/secure-fl
cd secure-fl
uv sync --all-extras
```

### ZKP Prerequisites

```bash
# Automated setup (installs Rust, Circom, SnarkJS + circuit artifacts)
make setup-zkp

# Build the native proof core (required for any ZKP path)
make build-rust

# Verify
uv run secure-fl setup --action check
```

### Run the attack defense demo

```bash
# Synthetic data — fastest, no download needed
uv run python demo/simulate_attack.py --dataset synthetic_small --num-rounds 6

# MNIST
uv run python demo/simulate_attack.py --dataset mnist

# CIFAR-10 with more malicious clients
uv run python demo/simulate_attack.py --dataset cifar10 --malicious 2 --proof-rigor low

# MedMNIST
uv run python demo/simulate_attack.py --dataset medmnist --malicious 2 --proof-rigor low
```

The TUI shows real-time ZKP verification, attack detection, and accuracy comparison (with ZKP vs projected without). Attackers appear in ~65% of rounds; the rest are all-honest.

### Start a federated learning session

```bash
# Server: the standalone Rust round server (build once, or download the release
# binary and put it on PATH)
make install-server
uv run secure-fl-server --config experiments/config.yaml   # REST :8080, gRPC :8081

# Client (in separate terminals)
uv run secure-fl-client --server-address 127.0.0.1:8081 --dataset mnist --client-id client_1
uv run secure-fl-client --server-address 127.0.0.1:8081 --dataset mnist --client-id client_2
```

## Python API

```python
from secure_fl.federation.client_runtime import create_client
from secure_fl.models.factory import model_fn

# Create a secure FL client
factory = model_fn("mnist")   # returns MNISTCNN factory
client = create_client(
    client_id="client_1",
    model_fn=factory,
    train_data=train_dataset,
    enable_zkp=True,
    proof_rigor="low",    # "low" | "medium" | "high"
    local_epochs=1,
    learning_rate=0.01,
)

# client.fit(parameters, config) → (updated_params, num_examples, metrics)
# metrics["zkp_proof"] contains the JSON proof string when enable_zkp=True
```

```python
from secure_fl.federation.aggregation import FedJSCMAggregator
from secure_fl.backend import make_proof_manager

aggregator = FedJSCMAggregator(momentum=0.9, learning_rate=1.0)
server_pm  = make_proof_manager()

# Verify a client proof
ok = server_pm.verify_client_proof(proof_json, updated_params, global_params)

# Aggregate verified updates
global_params = aggregator.aggregate(
    client_updates=verified_updates,
    client_weights=normalized_weights,
    server_round=rnd,
    global_params=global_params,
)
```

## Configuration

```yaml
# experiments/config.yaml
server:
  host: "localhost"
  port: 8080
  num_rounds: 10

strategy:
  min_fit_clients: 2
  fraction_fit: 1.0
  momentum: 0.9

zkp:
  enable_zkp: true
  proof_rigor: "high"   # "low" | "medium" | "high" | "adaptive"
```

## Models and Datasets

| Dataset | Model | Notes |
|---|---|---|
| `mnist` | `MNISTCNN` | 2-layer CNN, fast |
| `cifar10` | `ResNet18Model` | Full ResNet18 |
| `medmnist` | `ResNet18Model` | Medical imaging |
| `synthetic_small` | `SimpleModel` | MLP, no download needed |

```python
from secure_fl.models.factory import model_fn
factory = model_fn("cifar10")   # returns callable → ResNet18Model instance
model = factory()
```

```python
from secure_fl.data.benchmark_data import load_dataset, partition_dataset, subsample_dataset

train, test = load_dataset("mnist", seed=42)
train = subsample_dataset(train, max_samples=2000, seed=42)
client_subsets = partition_dataset(train, n=6, iid=False, seed=42)  # Dirichlet non-IID
```

## Technical Details

### FedJSCM Aggregation

```
m^{t+1} = γ·m^t + (1−γ)·Δ
w^{t+1} = w^t + η·m^{t+1}
```
where `Δ = weighted_avg(client_updates) − global_params`. Momentum is reinitialized automatically if the model architecture changes between rounds. Integer-dtype buffers (e.g. BatchNorm `num_batches_tracked`) are preserved through aggregation.

### ZKP Proof Schema

Each client proof is a JSON object containing:
- `initial_hash`, `updated_hash`, `delta_hash` — SHA-256 commitments over the **full parameter state dict** (weights, biases, and BatchNorm running stats) — catches any silent parameter substitution, including in buffers not covered by the norm check
- `delta_norm_l2`, `max_delta_norm_l2` — L2 norm bound enforcement over **learnable parameters only** (BN buffers excluded — their early-round deltas are large and not adversarially controlled)
- `circom_proof` — transferable Groth16 proof (`{"proof": ..., "public": ...}`) when Circom/SnarkJS tools are available client-side; independently verifiable by the server via the same mechanism, without re-running any client code

Server verification order: proof present → valid JSON → hash matches (full state dict) → delta hash matches (full state dict) → norm within bound (learnable-only) → `circom_proof` verified when present.

### Parameter Representations

Two lists exist and must not be mixed:

| | Source | Contents | Used for |
|---|---|---|---|
| Full state dict | `model.state_dict().values()` | weights + biases + BN buffers | hash commitment, aggregation, eval |
| Learnable only | `model.parameters()` | weights + biases only | norm-bound enforcement, ZKP circuits |

`fit()` returns full state dict. Mixing the two representations with `zip` causes shape-mismatch errors on BN models (ResNet18, MNISTCNN) — see `AGENTS.md`'s "Parameter list conventions" section for the full detail.

### Dynamic Proof Rigor

`StabilityMonitor` tracks gradient variance and convergence across rounds and adapts `ProofRigor`:
- High variance / early rounds → `high` (more proof coverage)
- Stable training → `low` (faster proofs)
- `adaptive` mode enables automatic adjustment each round

## Experiments & Benchmarks

`experiments/full_benchmark.py` is the single source of truth for paper-quality
results — k-fold accuracy, overhead, and attack resilience in one run.

```bash
# Fast smoke run
uv run python experiments/full_benchmark.py --quick

# Paper-quality run (real ZKP proofs enforced, resumable — skips
# already-completed config JSONs unless --force)
uv run python experiments/full_benchmark.py \
  --datasets mnist --k-folds 5 --num-clients 20 --clients-per-round 5 \
  --num-rounds 100 --require-real-proofs --force \
  --output-dir results/full_benchmark
```

Outputs per-dataset, per-config k-fold JSONs under `--output-dir`
(`aggregate` summary stats + `folds[i].rounds[j]` per-round detail). See
`AGENTS.md`'s "Benchmark results status" section for which result set is
currently canonical and why.

## Repository Structure

```
secure-fl/
├── src/secure_fl/
│   ├── core/            # Types, config (thread-safe singleton), exceptions
│   ├── federation/      # SecureFlClient, SecureFlServer, FedJSCMAggregator,
│   │                    #   StabilityMonitor, client_utils, helpers
│   ├── zkp/             # ClientProofManager, CircuitSetup, Rust proof backend
│   ├── models/          # SimpleModel, MNISTCNN, ResNet18Model, factory
│   ├── data/            # benchmark_data: load/partition/subsample datasets
│   ├── cli/             # Click CLI: secure-fl, secure-fl-server, secure-fl-client, secure-fl-setup
│   ├── proofs/
│   │   └── server/           # Circom aggregation SNARK circuits
│   └── utils/           # helpers (compute_hash, compute_parameter_norm), logging
├── demo/
│   └── simulate_attack.py    # Textual TUI: real ZKP attack detection demo
├── experiments/
│   ├── full_benchmark.py     # Canonical paper benchmark (accuracy + overhead + attacks)
│   ├── demo.py                 # CLI demo entry point
│   └── config.yaml
└── tests/
    ├── unit/            # No external dependencies (fast)
    └── integration/     # Requires Circom + SnarkJS
```

## Development

```bash
make dev          # Full setup (uv sync + ZKP tools)
make lint         # Ruff lint
make format       # Ruff format  ← run before every commit
make type-check   # mypy (strict)
make test         # Full suite
make test-quick   # Fast, --exitfirst
make test-cov     # With coverage (threshold: 30%)
```

### Rust proof core (required for ZKP)

All ZKP proving and verification runs in a typed Rust core — client
delta-bound proofs, server aggregation proofs, and both verifiers — with no
`snarkjs`/`rapidsnark` subprocess at runtime. Clients and training stay in
PyTorch, so benchmark results remain comparable.

```bash
make build-rust        # after uv sync; installs src/securefl_rust.so
```

Running any ZKP path without the extension fails with an import error; there
is no Python fallback. `pip install secure-fl` pulls the matching prebuilt
`securefl-core` wheel; `make build-rust` overrides it for local Rust work, and
`make build-rust-wheel` builds one locally.

FedJSCM aggregation, the stability heuristic and all proof handling run in the
same Rust core; there is no Python backend switch. Aggregation is within ~5% of
the old numpy implementation at ResNet18 scale
([benchmark](docs/dev/RUST_BACKEND_BENCHMARK.md)).

ZKP requires `make setup-zkp` once to build the circuit artifacts; circom and
snarkjs are build-time tools only. See [`rust/README.md`](rust/README.md) for
the port scope and parity tests.

Networking runs on the standalone Rust server; there is no in-process FL
framework. PyTorch clients reach it over gRPC (`SecureFlGrpcClient` +
`SecureFlRoundRunner`, or `secure-fl-client`): the server samples clients, closes
a round as soon as every assigned client submits, and returns the aggregation
proof with the model. Pass `--token`/`SECUREFL_SERVER_TOKEN` and, for real
deployments, `--tls-cert/--tls-key/--client-ca` — with mTLS the certificate CN is
the client's identity, which is what a proof can be bound to. In-process
simulations (demos, tests) call `create_server_strategy` and
`SecureFlStrategy.aggregate_fit` directly.

## Known Limitations

- Server ZKP (Circom/SnarkJS) requires `make setup-zkp`; subprocess calls time out after 300 s. Proving and verification run in-process in the Rust core — see [`rust/README.md`](rust/README.md)
- ZKP requires the `securefl_rust` extension (`make build-rust` or the `securefl-core` wheel); there is no weaker local-audit fallback
- Coverage threshold is 30% — being raised incrementally
- Norm-bound enforcement does not defend against within-bound poisoning (adversarial updates crafted to satisfy the norm constraint) — see `docs/paper/main.tex` §Limitations
- Both circuits are **statistical**: the client delta-bound proof and the aggregation proof cover 200 sampled learnable parameters, not the full model. The SHA-256 commitment and the norm bound still cover every parameter

## License

MIT License — see [LICENSE](LICENSE) for details.

## Citation

```bibtex
@misc{timilsina2026securefl,
  title={Secure-FL: Zero-Knowledge Proofs for Dual-Verifiable Federated Learning},
  author={Timilsina, Krishant and Paudel, Bindu and Timilsina, Arun Kumar},
  year={2026},
  url={https://github.com/krishantt/secure-fl},
  note={Preprint}
}
```

## Acknowledgments

- [Circom](https://github.com/iden3/circom) / [SnarkJS](https://github.com/iden3/snarkjs) — zk-SNARK toolchain
