feat: add AnonymityGuard, opt-in-by-default DHT sharing proof suite, and canonical chart pipeline docs
da3d6db verified Download test_anonymous_opt_in_dht_sharing.py from NorviTech/armiger: direct link, hf CLI and curl.
- Browser
- Download file 8.85 kB
-
https://huggingface.co/NorviTech/armiger/resolve/main/test_anonymous_opt_in_dht_sharing.py
- Command line
-
hf download hf://NorviTech/armiger/test_anonymous_opt_in_dht_sharing.py
-
curl -L -o test_anonymous_opt_in_dht_sharing.py https://huggingface.co/NorviTech/armiger/resolve/main/test_anonymous_opt_in_dht_sharing.py
8.85 kB
| """test_anonymous_opt_in_dht_sharing.py | |
| Comprehensive Pytest Suite for Armiger Distributed Anonymous & Opt-In-By-Default | |
| Weight and Catalog Data Sharing (`opt_in_sharing=False` by default + `AnonymityGuard`). | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import json | |
| import math | |
| import os | |
| from pathlib import Path | |
| import sys | |
| import pytest | |
| ROOT_DIR = Path(__file__).resolve().parent | |
| if str(ROOT_DIR) not in sys.path: | |
| sys.path.insert(0, str(ROOT_DIR)) | |
| for sub in ("examples/dh-waypoint", "examples/bitmagnet", "examples/arr-stack"): | |
| p_sub = ROOT_DIR / sub | |
| if str(p_sub) not in sys.path: | |
| sys.path.insert(0, str(p_sub)) | |
| from armiger import ArmigerPipeline | |
| from armiger_arr_webhook_receiver import ArmigerArrWebhookService | |
| from armiger_dht_db_sync import ArmigerDHTDBSync, ArmigerDHTDatabaseSync | |
| from armiger_dht_rsi import ( | |
| AnonymityGuard, | |
| AnonymityViolationError, | |
| ArmigerDHTRSIEngine, | |
| AudioWebhookAdapter, | |
| MovieWebhookAdapter, | |
| PublicationWebhookAdapter, | |
| QBittorrentAdapter, | |
| SeriesWebhookAdapter, | |
| TransmissionAdapter, | |
| resolve_opt_in_sharing, | |
| ) | |
| from armiger_ultra import ArmigerUltraPipeline | |
| from armiger_waypoint_processor import ArmigerWaypointAdapter | |
| from bep44_udp_wire_harness import LocalKademliaSwarm | |
| from benchmarks.proof_anonymous_opt_in_sharing import ( | |
| generate_500_adversarial_canaries, | |
| run_proof_1_default_zero_egress, | |
| run_proof_2_canary_pii_firewall, | |
| run_proof_3_unlinkability_and_dp_convergence, | |
| ) | |
| from bitmagnet_armiger_bridge import BitmagnetArmigerBridge | |
| def test_opt_in_sharing_env_and_default_resolution(monkeypatch: pytest.MonkeyPatch) -> None: | |
| """Verify resolve_opt_in_sharing defaults to False and only enables on explicit True or env 1/true/yes.""" | |
| monkeypatch.delenv("ARMIGER_DHT_SHARE_OPT_IN", raising=False) | |
| assert resolve_opt_in_sharing() is False | |
| assert resolve_opt_in_sharing(False) is False | |
| assert resolve_opt_in_sharing(True) is True | |
| for falsy in ("0", "false", "no", "off", "", "random"): | |
| monkeypatch.setenv("ARMIGER_DHT_SHARE_OPT_IN", falsy) | |
| assert resolve_opt_in_sharing() is False | |
| for truthy in ("1", "true", "TRUE", "yes", "YES"): | |
| monkeypatch.setenv("ARMIGER_DHT_SHARE_OPT_IN", truthy) | |
| assert resolve_opt_in_sharing() is True | |
| # Explicit False argument still overrides env var | |
| assert resolve_opt_in_sharing(False) is False | |
| def test_all_adapters_and_engines_default_opt_in_false(monkeypatch: pytest.MonkeyPatch) -> None: | |
| """Verify every adapter, engine, DB sync, pipeline, and bridge defaults to opt_in_sharing=False.""" | |
| monkeypatch.delenv("ARMIGER_DHT_SHARE_OPT_IN", raising=False) | |
| rsi = ArmigerDHTRSIEngine() | |
| db = ArmigerDHTDBSync() | |
| s_ad = SeriesWebhookAdapter() | |
| m_ad = MovieWebhookAdapter() | |
| a_ad = AudioWebhookAdapter() | |
| p_ad = PublicationWebhookAdapter() | |
| q_ad = QBittorrentAdapter() | |
| t_ad = TransmissionAdapter() | |
| for obj in (rsi, db, s_ad, m_ad, a_ad, p_ad, q_ad, t_ad): | |
| assert obj.opt_in_sharing is False | |
| st = obj.sharing_status() | |
| assert st["opt_in_sharing_enabled"] is False | |
| assert st["outbound_bundles_shared"] == 0 | |
| assert st["outbound_bytes_shared"] == 0 | |
| assert rsi.export_signed_bundle(delta_w=[0.1, 0.2], delta_b=[0.0], n_samples=50) is None | |
| assert rsi.export_signed_delta_bundle(delta_w=[0.1, 0.2], delta_b=[0.0], n_samples=50) is None | |
| assert rsi.broadcast_weights([0.1, 0.2], [0.0], n_samples=50) is None | |
| assert rsi.get_pending_telemetry_batch() == [] | |
| assert rsi.sharing_status()["outbound_packets_blocked_opt_out"] >= 4 | |
| assert db.export_signed_bundle() is None | |
| assert db.export_signed_delta_bundle() is None | |
| assert db.sharing_status()["outbound_packets_blocked_opt_out"] >= 2 | |
| db.close() | |
| def test_proof_1_default_zero_egress_100_events() -> None: | |
| """Execute Proof 1: 100 synthetic webhook/catalog events across default instances -> 0 outbound packets.""" | |
| res = asyncio.run(run_proof_1_default_zero_egress()) | |
| assert res["passed"] is True | |
| assert res["synthetic_events_processed"] == 100 | |
| assert res["all_components_default_opt_in_false"] is True | |
| assert res["outbound_bundles_shared"] == 0 | |
| assert res["outbound_bytes_shared"] == 0 | |
| assert res["udp_put_packets_emitted"] == 0 | |
| assert res["outbound_attempts_blocked_by_opt_out"] >= 100 | |
| assert res["inbound_readonly_verification_passed"] is True | |
| def test_anonymity_guard_ephemeral_key_rotation_and_coarse_timestamps() -> None: | |
| """Verify Ephemeral Ed25519 key rotation and coarse UTC hour timestamp bucketing.""" | |
| guard = AnonymityGuard(ephemeral_identity=True, min_k_samples=25, clip_norm=1.0, dp_sigma=0.05) | |
| seen_keys = {guard.current_pubkey_hex()} | |
| for ep in range(20): | |
| new_pk = guard.rotate_ephemeral_identity(epoch=ep) | |
| assert len(new_pk) == 64 | |
| assert new_pk not in seen_keys | |
| seen_keys.add(new_pk) | |
| # Coarse UTC hour bucketing: floor(ts / 3600) * 3600 | |
| for offset in (0, 1, 42, 1799, 3599): | |
| b_ts = guard.bucket_timestamp(1792350000 + offset) | |
| assert b_ts == 1792350000 | |
| assert b_ts % 3600 == 0 | |
| def test_anonymity_guard_l2_clipping_dp_and_k_anonymity() -> None: | |
| """Verify L2 norm clipping, Gaussian DP noise, int8 quantization, and k-anonymity enforcement.""" | |
| guard = AnonymityGuard(min_k_samples=25, clip_norm=1.0, dp_sigma=0.05) | |
| # Rejects n_samples < 25 | |
| for bad_n in (0, 1, 5, 12, 24): | |
| with pytest.raises(AnonymityViolationError, match="k_anonymity"): | |
| guard.clip_and_privatize_weights([0.5, -0.5], [0.1], n_samples=bad_n) | |
| # Clips large vectors with L2 norm >> 1.0 down to <= 1.0 | |
| huge_w = [10.0, -10.0, 10.0, -10.0] # L2 norm = 20.0 | |
| huge_b = [5.0, -5.0] | |
| res = guard.clip_and_privatize_weights(huge_w, huge_b, n_samples=55) | |
| assert res["pre_dp_l2_norm"] > 20.0 | |
| assert res["clipped_l2_norm"] <= 1.0 + 1e-9 | |
| post_dp_l2 = math.sqrt(sum(x * x for x in res["delta_w_dp"]) + sum(x * x for x in res["delta_b_dp"])) | |
| assert post_dp_l2 <= 1.05 | |
| assert res["n_bucket"] == 50 | |
| assert all(-127 <= q <= 127 for q in res["delta_w_q8"]) | |
| # Reliability bins k-anonymity check | |
| with pytest.raises(AnonymityViolationError, match="k_anonymity"): | |
| guard.sanitize_reliability_bins([{"bin": 0, "conf": 0.5, "acc": 0.5, "n": 10}]) | |
| clean_bins = guard.sanitize_reliability_bins([{"bin": 0, "conf": 0.8, "acc": 0.78, "n": 42}]) | |
| assert len(clean_bins) == 1 | |
| assert clean_bins[0][3] == 40 | |
| def test_anonymity_guard_domain_separated_blake2b_catalog_hashing() -> None: | |
| """Verify 16-hex-char BLAKE2b-64(domain='armiger-catalog-v2' || normalized_key) and PII rejection.""" | |
| h1 = AnonymityGuard.hash_catalog_key("sintel|2010") | |
| h2 = AnonymityGuard.hash_catalog_key(" SINTEL | 2010 ") | |
| assert h1 == h2 | |
| assert len(h1) == 16 | |
| assert all(c in "0123456789abcdef" for c in h1) | |
| # Rejects raw filenames, release titles, IPs, paths, URLs, infohashes, external IDs | |
| bad_keys = [ | |
| "/" + "Users" + "/alice/Downloads/Sintel.2010.mkv", | |
| "Sintel.2010.2160p.UHD.BluRay.REMUX.HEVC.TrueHD.7.1", | |
| "magnet:?xt=urn:btih:1111222233334444555566667777888899990000", | |
| "198.51.100.42", | |
| "tt1234567", | |
| "tmdb:693134", | |
| "https://tracker.example.org/announce?passkey=secret123", | |
| ] | |
| for bk in bad_keys: | |
| with pytest.raises(AnonymityViolationError): | |
| AnonymityGuard.hash_catalog_key(bk) | |
| def test_proof_2_500_adversarial_canaries_blocked_zero_wire_leak() -> None: | |
| """Execute Proof 2: 500 adversarial canaries across 12-node UDP swarm -> 500/500 blocked, 0/500 on wire.""" | |
| canaries = generate_500_adversarial_canaries() | |
| assert len(canaries) == 500 | |
| res = asyncio.run(run_proof_2_canary_pii_firewall()) | |
| assert res["passed"] is True | |
| assert res["total_adversarial_canaries_injected"] == 500 | |
| assert res["canaries_blocked_or_stripped_before_wire"] == 500 | |
| assert res["block_rate_pct"] == 100.0 | |
| assert res["canary_substrings_on_udp_wire"] == 0 | |
| assert res["wire_leak_rate_pct"] == 0.0 | |
| def test_proof_3_12_node_udp_swarm_unlinkability_and_dp_convergence() -> None: | |
| """Execute Proof 3: 12-node UDP swarm over 5 epochs -> 60/60 unique keys, ECE & F1 convergence.""" | |
| res = asyncio.run(run_proof_3_unlinkability_and_dp_convergence()) | |
| assert res["passed"] is True | |
| assert res["swarm_nodes"] == 12 | |
| assert res["epochs_run"] == 5 | |
| assert res["unique_ephemeral_ed25519_pubkeys"] == 60 | |
| assert res["cross_epoch_key_reuse_count"] == 0 | |
| assert res["coarse_hour_timestamp_bucketing_rate_pct"] == 100.0 | |
| assert res["final_calibration_ece"] < res["initial_calibration_ece"] * 0.35 | |
| assert res["final_armiger_ultra_f1"] > res["initial_armiger_ultra_f1"] + 0.025 | |