secure-fl 2026.3.16.dev1__tar.gz → 2026.3.17.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/PKG-INFO +1 -1
  2. secure_fl-2026.3.17.dev1/src/secure_fl/.build_number +1 -0
  3. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/benchmarks/__init__.py +6 -2
  4. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/benchmarks/pipeline.py +19 -4
  5. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/benchmarks/reporting.py +9 -4
  6. secure_fl-2026.3.17.dev1/src/secure_fl/cli/__main__.py +6 -0
  7. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/cli/handlers.py +17 -5
  8. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/data/__init__.py +2 -2
  9. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/data/benchmark_data.py +78 -18
  10. secure_fl-2026.3.16.dev1/src/secure_fl/.build_number +0 -1
  11. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/.gitignore +0 -0
  12. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/LICENSE +0 -0
  13. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/README.md +0 -0
  14. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/docs/research paper/README.md +0 -0
  15. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/experiments/README.md +0 -0
  16. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/pyproject.toml +0 -0
  17. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/__init__.py +0 -0
  18. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/__main__.py +0 -0
  19. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/benchmarks/defaults.py +0 -0
  20. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/benchmarks/execution.py +0 -0
  21. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/cli/__init__.py +0 -0
  22. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/cli/main.py +0 -0
  23. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/cli/setup.py +0 -0
  24. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/cli/zkp_check.py +0 -0
  25. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/core/__init__.py +0 -0
  26. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/core/_version.py +0 -0
  27. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/core/config.py +0 -0
  28. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/core/exceptions.py +0 -0
  29. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/core/types.py +0 -0
  30. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/__init__.py +0 -0
  31. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/aggregation.py +0 -0
  32. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/client.py +0 -0
  33. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/client_runtime.py +0 -0
  34. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/client_utils.py +0 -0
  35. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/server.py +0 -0
  36. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/server_runtime.py +0 -0
  37. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/federation/stability_monitor.py +0 -0
  38. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/models/__init__.py +0 -0
  39. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/models/factory.py +0 -0
  40. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/models/mnist_cnn.py +0 -0
  41. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/models/simple.py +0 -0
  42. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/__init__.py +0 -0
  43. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/client_circuits/__init__.py +0 -0
  44. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/client_circuits/delta_bound.py +0 -0
  45. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/client_circuits/sgd_full_trace.cairo +0 -0
  46. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/server/pot12_final.ptau +0 -0
  47. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/proofs/server/test.txt +0 -0
  48. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/py.typed +0 -0
  49. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/utils/__init__.py +0 -0
  50. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/utils/helpers.py +0 -0
  51. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/utils/logging_config.py +0 -0
  52. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/__init__.py +0 -0
  53. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/base.py +0 -0
  54. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/client_proof_manager.py +0 -0
  55. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/proof_manager.py +0 -0
  56. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/proof_schema.py +0 -0
  57. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/quantization.py +0 -0
  58. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/server_proof_manager.py +0 -0
  59. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/server_setup.py +0 -0
  60. {secure_fl-2026.3.16.dev1 → secure_fl-2026.3.17.dev1}/src/secure_fl/zkp/server_snark_io.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secure-fl
3
- Version: 2026.3.16.dev1
3
+ Version: 2026.3.17.dev1
4
4
  Summary: Dual-Verifiable Framework for Federated Learning using Zero-Knowledge Proofs
5
5
  Project-URL: Homepage, https://github.com/krishantt/secure-fl
6
6
  Project-URL: Bug Reports, https://github.com/krishantt/secure-fl/issues
@@ -0,0 +1 @@
1
+ 2026.03.17:1
@@ -1,10 +1,11 @@
1
1
  """Benchmark orchestration helpers for experiment entrypoints."""
2
2
 
3
3
  from secure_fl.data.benchmark_data import SUPPORTED_DATASETS
4
+ from secure_fl.data.benchmark_data import kfold_split_indices
4
5
  from secure_fl.data.benchmark_data import load_dataset
5
- from secure_fl.data.benchmark_data import maybe_subsample_dataset
6
6
  from secure_fl.data.benchmark_data import normalize_datasets
7
7
  from secure_fl.data.benchmark_data import partition_dataset
8
+ from secure_fl.data.benchmark_data import subsample_dataset
8
9
  from secure_fl.models.factory import model_fn
9
10
 
10
11
  from .defaults import PAPER_DEFAULT_ROUNDS
@@ -12,6 +13,7 @@ from .defaults import resolve_num_rounds
12
13
  from .execution import run_training_rounds
13
14
  from .pipeline import run_single
14
15
  from .reporting import build_tradeoff_report
16
+ from .reporting import summarize_folds
15
17
  from .reporting import summarize_repeats
16
18
 
17
19
  __all__ = [
@@ -21,10 +23,12 @@ __all__ = [
21
23
  "normalize_datasets",
22
24
  "load_dataset",
23
25
  "partition_dataset",
24
- "maybe_subsample_dataset",
26
+ "subsample_dataset",
27
+ "kfold_split_indices",
25
28
  "model_fn",
26
29
  "run_single",
27
30
  "run_training_rounds",
31
+ "summarize_folds",
28
32
  "summarize_repeats",
29
33
  "build_tradeoff_report",
30
34
  ]
@@ -10,8 +10,8 @@ from secure_fl import FedJSCMAggregator
10
10
  from secure_fl import create_client
11
11
  from secure_fl import torch_to_ndarrays
12
12
  from secure_fl.data.benchmark_data import load_dataset
13
- from secure_fl.data.benchmark_data import maybe_subsample_dataset
14
13
  from secure_fl.data.benchmark_data import partition_dataset
14
+ from secure_fl.data.benchmark_data import subsample_dataset
15
15
  from secure_fl.models.factory import model_fn
16
16
 
17
17
  from .execution import run_training_rounds
@@ -30,11 +30,26 @@ def run_single(
30
30
  min_proof_coverage: float,
31
31
  max_train_samples: int,
32
32
  max_test_samples: int,
33
+ train_set_override: torch.utils.data.Dataset | None = None,
34
+ eval_set_override: torch.utils.data.Dataset | None = None,
33
35
  ) -> dict[str, Any]:
34
36
  """Run one dataset/config benchmark and return rounds+summary."""
35
- train_set, test_set = load_dataset(dataset, seed)
36
- train_set = maybe_subsample_dataset(train_set, max_train_samples, seed)
37
- test_set = maybe_subsample_dataset(test_set, max_test_samples, seed + 1)
37
+ if train_set_override is not None and eval_set_override is not None:
38
+ train_set = train_set_override
39
+ test_set = eval_set_override
40
+ else:
41
+ loaded_train_set, loaded_test_set = load_dataset(dataset, seed)
42
+ train_set = (
43
+ train_set_override
44
+ if train_set_override is not None
45
+ else subsample_dataset(loaded_train_set, max_train_samples, seed)
46
+ )
47
+ test_set = (
48
+ eval_set_override
49
+ if eval_set_override is not None
50
+ else subsample_dataset(loaded_test_set, max_test_samples, seed + 1)
51
+ )
52
+
38
53
  client_subsets = partition_dataset(train_set, num_clients, config["iid"], seed)
39
54
  model_factory = model_fn(dataset)
40
55
 
@@ -7,11 +7,11 @@ from typing import Any
7
7
  import numpy as np
8
8
 
9
9
 
10
- def summarize_repeats(runs: list[dict[str, Any]]) -> dict[str, float]:
11
- """Summarize repeated benchmark runs into mean/std/CI metrics."""
10
+ def summarize_folds(runs: list[dict[str, Any]]) -> dict[str, float]:
11
+ """Summarize fold-level benchmark runs into mean/std/CI metrics."""
12
12
  if not runs:
13
13
  return {
14
- "repeats": 0.0,
14
+ "folds": 0.0,
15
15
  "final_accuracy_mean": 0.0,
16
16
  "final_accuracy_std": 0.0,
17
17
  "final_accuracy_ci95": 0.0,
@@ -82,7 +82,7 @@ def summarize_repeats(runs: list[dict[str, Any]]) -> dict[str, float]:
82
82
  ci95 = float(1.96 * final_acc.std(ddof=1) / np.sqrt(len(final_acc)))
83
83
 
84
84
  return {
85
- "repeats": float(len(runs)),
85
+ "folds": float(len(runs)),
86
86
  "final_accuracy_mean": float(final_acc.mean()),
87
87
  "final_accuracy_std": float(final_acc.std(ddof=0)),
88
88
  "final_accuracy_ci95": ci95,
@@ -104,6 +104,11 @@ def summarize_repeats(runs: list[dict[str, Any]]) -> dict[str, float]:
104
104
  }
105
105
 
106
106
 
107
+ def summarize_repeats(runs: list[dict[str, Any]]) -> dict[str, float]:
108
+ """Backward-compatible alias for fold-based summarization."""
109
+ return summarize_folds(runs)
110
+
111
+
107
112
  def build_tradeoff_report(results: dict[str, dict[str, Any]]) -> dict[str, Any]:
108
113
  """Build dataset-level baseline vs ZKP comparison report."""
109
114
  report: dict[str, Any] = {"datasets": {}}
@@ -0,0 +1,6 @@
1
+ """Entry point for `python -m secure_fl.cli`."""
2
+
3
+ from .main import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -48,19 +48,27 @@ def run_server(
48
48
  with open(config) as f:
49
49
  cfg = yaml.safe_load(f)
50
50
  else:
51
- cfg = get_default_config()
51
+ cfg = get_default_config().to_dict()
52
52
 
53
- cfg["server"].update({"host": host, "port": port, "num_rounds": rounds})
53
+ server_cfg = cfg.get("server", {})
54
+ zkp_cfg = cfg.get("zkp", {})
55
+ server_runtime_cfg = {"host": host, "port": port, "num_rounds": rounds}
54
56
 
55
- strategy_config = {**cfg["strategy"], **cfg["aggregation"], **cfg["zkp"]}
57
+ strategy_config = {
58
+ "momentum": float(server_cfg.get("momentum", 0.9)),
59
+ "learning_rate": float(server_cfg.get("learning_rate", 0.01)),
60
+ "enable_zkp": bool(zkp_cfg.get("enable_client_proofs", enable_zkp)),
61
+ "proof_rigor": proof_rigor,
62
+ "blockchain_verification": blockchain,
63
+ }
56
64
  strategy_config.update(
57
65
  {
58
66
  "min_available_clients": min_clients,
59
67
  "min_fit_clients": min_clients,
60
68
  "min_evaluate_clients": min_clients,
61
- "momentum": momentum,
62
69
  "enable_zkp": enable_zkp,
63
70
  "proof_rigor": proof_rigor,
71
+ "momentum": momentum,
64
72
  "blockchain_verification": blockchain,
65
73
  }
66
74
  )
@@ -89,7 +97,11 @@ def run_server(
89
97
  console=console,
90
98
  ) as progress:
91
99
  task = progress.add_task("Starting Secure FL Server...", total=None)
92
- server = SecureFlowerServer(strategy=strategy, **cfg["server"])
100
+ server = SecureFlowerServer(
101
+ strategy=strategy,
102
+ **server_runtime_cfg,
103
+ config=server_cfg,
104
+ )
93
105
  progress.update(task, description="Server ready! Waiting for clients...")
94
106
 
95
107
  table = Table(title="Server Configuration")
@@ -14,9 +14,9 @@ from torch.utils.data import Dataset
14
14
 
15
15
  from secure_fl.data.benchmark_data import SUPPORTED_DATASETS
16
16
  from secure_fl.data.benchmark_data import load_dataset
17
- from secure_fl.data.benchmark_data import maybe_subsample_dataset
18
17
  from secure_fl.data.benchmark_data import normalize_datasets
19
18
  from secure_fl.data.benchmark_data import partition_dataset
19
+ from secure_fl.data.benchmark_data import subsample_dataset
20
20
 
21
21
 
22
22
  class FederatedDataLoader:
@@ -81,5 +81,5 @@ __all__ = [
81
81
  "normalize_datasets",
82
82
  "load_dataset",
83
83
  "partition_dataset",
84
- "maybe_subsample_dataset",
84
+ "subsample_dataset",
85
85
  ]
@@ -158,19 +158,12 @@ def partition_dataset(
158
158
  chunks = np.array_split(indices, num_clients)
159
159
  return [Subset(dataset, chunk.tolist()) for chunk in chunks]
160
160
 
161
- labels = getattr(dataset, "targets", None)
162
- if labels is None:
163
- if hasattr(dataset, "tensors") and len(dataset.tensors) >= 2:
164
- labels = dataset.tensors[1]
165
- else:
166
- rng.shuffle(indices)
167
- chunks = np.array_split(indices, num_clients)
168
- return [Subset(dataset, chunk.tolist()) for chunk in chunks]
161
+ labels_arr = extract_dataset_labels(dataset)
162
+ if labels_arr is None:
163
+ rng.shuffle(indices)
164
+ chunks = np.array_split(indices, num_clients)
165
+ return [Subset(dataset, chunk.tolist()) for chunk in chunks]
169
166
 
170
- if isinstance(labels, torch.Tensor):
171
- labels_arr = labels.detach().cpu().numpy()
172
- else:
173
- labels_arr = np.asarray(labels)
174
167
  unique_labels = np.unique(labels_arr)
175
168
  client_bins: list[list[int]] = [[] for _ in range(num_clients)]
176
169
  classes_per_client = max(2, len(unique_labels) // max(1, num_clients // 2))
@@ -190,15 +183,82 @@ def partition_dataset(
190
183
  return [Subset(dataset, sorted(set(bin_idx))) for bin_idx in client_bins]
191
184
 
192
185
 
193
- def maybe_subsample_dataset(
194
- dataset: Dataset[Any], max_samples: int, seed: int
186
+ def extract_dataset_labels(dataset: Dataset[Any]) -> np.ndarray | None:
187
+ """Extract label array from supported dataset wrappers."""
188
+ labels = getattr(dataset, "targets", None)
189
+ if labels is None:
190
+ labels = getattr(dataset, "labels", None)
191
+
192
+ if labels is not None:
193
+ if isinstance(labels, torch.Tensor):
194
+ return labels.detach().cpu().numpy().reshape(-1)
195
+ return np.asarray(labels).reshape(-1)
196
+
197
+ if isinstance(dataset, Subset):
198
+ base_labels = extract_dataset_labels(cast("Dataset[Any]", dataset.dataset))
199
+ if base_labels is None:
200
+ return None
201
+ subset_indices = np.asarray(dataset.indices, dtype=np.int64)
202
+ return base_labels[subset_indices]
203
+
204
+ if hasattr(dataset, "tensors") and len(dataset.tensors) >= 2:
205
+ labels_tensor = dataset.tensors[1]
206
+ if isinstance(labels_tensor, torch.Tensor):
207
+ return labels_tensor.detach().cpu().numpy().reshape(-1)
208
+ return np.asarray(labels_tensor).reshape(-1)
209
+
210
+ return None
211
+
212
+
213
+ def kfold_split_indices(
214
+ dataset: Dataset[Any],
215
+ k_folds: int,
216
+ seed: int,
217
+ *,
218
+ stratified: bool = True,
219
+ ) -> list[tuple[list[int], list[int]]]:
220
+ """Create train/validation index splits for k-fold cross-validation."""
221
+ n = len(cast("Sized", dataset))
222
+ if k_folds < 2:
223
+ raise ValueError("k_folds must be >= 2")
224
+ if k_folds > n:
225
+ raise ValueError("k_folds cannot exceed dataset size")
226
+
227
+ rng = np.random.default_rng(seed)
228
+ all_indices = np.arange(n)
229
+ labels = extract_dataset_labels(dataset) if stratified else None
230
+
231
+ if labels is not None and len(labels) == n:
232
+ fold_val_indices: list[list[int]] = [[] for _ in range(k_folds)]
233
+ for cls in np.unique(labels):
234
+ cls_indices = np.where(labels == cls)[0]
235
+ rng.shuffle(cls_indices)
236
+ cls_parts = np.array_split(cls_indices, k_folds)
237
+ for fold_idx, part in enumerate(cls_parts):
238
+ fold_val_indices[fold_idx].extend(part.tolist())
239
+ else:
240
+ shuffled = all_indices.copy()
241
+ rng.shuffle(shuffled)
242
+ fold_val_indices = [part.tolist() for part in np.array_split(shuffled, k_folds)]
243
+
244
+ splits: list[tuple[list[int], list[int]]] = []
245
+ for fold_idx in range(k_folds):
246
+ val_indices = np.array(sorted(set(fold_val_indices[fold_idx])), dtype=np.int64)
247
+ train_indices = np.setdiff1d(all_indices, val_indices, assume_unique=False)
248
+ splits.append((train_indices.tolist(), val_indices.tolist()))
249
+
250
+ return splits
251
+
252
+
253
+ def subsample_dataset(
254
+ dataset: Dataset[Any], sample_limit: int, seed: int
195
255
  ) -> Dataset[Any]:
196
- """Subsample a dataset for faster benchmark runs when requested."""
197
- if max_samples <= 0:
256
+ """Return a deterministic random subset when sample limit is active."""
257
+ if sample_limit <= 0:
198
258
  return dataset
199
259
  n = len(cast("Sized", dataset))
200
- if max_samples >= n:
260
+ if sample_limit >= n:
201
261
  return dataset
202
262
  rng = np.random.default_rng(seed)
203
- indices = rng.choice(n, size=max_samples, replace=False)
263
+ indices = rng.choice(n, size=sample_limit, replace=False)
204
264
  return Subset(dataset, indices.tolist())
@@ -1 +0,0 @@
1
- 2026.03.16:1