Skip to content

Releases: BAder82t/fairlearn-fhe

v0.2.3 — security + correctness fixes

01 May 22:58

Choose a tag to compare

Highlights

Bug-fix release rolling up the security + correctness remediation from a code/security re-review of v0.2.2.

Security

  • Secret-key isolationmake_evaluator_context now serialises the inner TenSEAL Context without the secret key and rebuilds the wrapper from explicit fields. The keyholder's context is never mutated and shares no mutable state with the evaluator copy.
  • Sub-128-bit CKKS refusedbuild_context validates (poly_modulus_degree, coeff_mod_bit_sizes) against the HE-standard 128-bit table. Sub-128-bit configurations require explicit insecure_allow_low_security=True and emit InsecureCKKSParametersWarning.
  • Signature downgrade closed — Signed envelopes write the canonical signature_b64 field. validate_envelope rejects legacy value-only blocks (closes the relay-strip downgrade vector). verify_envelope_signature still reads value for back-compat verification of v0.2.2 envelopes.
  • min_security_bits floorvalidate_envelope defaults to 128. Pass 0 to opt out.
  • CLI --require-signature enforces verification — Single error per failure mode (no key, key read failure, missing signature block, bad signature). No more silent satisfaction when only a signature block is present.
  • ANSI / Unicode sanitisation_safe_str (used by inspect) strips full CSI/OSC sequences and Unicode bidi / zero-width characters.

Correctness

  • Per-thread op_session() — New context manager with threading.local stack. True isolation under concurrent audits — the previous reset_op_counters + snapshot pattern interleaved across threads.
  • Encrypted-mask MSE depth fix_per_group_mse_terms re-folds mask · sw into a single ct×pt before the ct×ct against ŷ², landing at depth 2 (was 3).
  • MAE opt-inmean_absolute_error_group_max(..., approximate=True) (default) acknowledges the sqrt(MSE) approximation; approximate=False raises NotImplementedError.
  • _safe_div consolidated — Single canonical implementation in _circuits used by base, scoring, and regression metrics.
  • EncryptedVector.encrypt bounds-checks — Rejects vectors larger than ctx.n_slots instead of silently truncating.
  • Scoring closures_group_min/_group_max/_group_difference/_group_ratio capture reduction as an explicit closure variable. Plaintext fallback when fairlearn lacks the helper.
  • audit_metric records n_groups=0 when no sensitive features.
  • Misc — Fixed double-indented _build_encrypted body, lifted functools import out of hot loop, vectorised group_masks for single-column sensitive features.

Tests

Test fixture upgraded to N=16384 with an 8-prime chain (depth 6, 128-bit security). Two new regression suites — tests/test_review_v0_2_3_fixes.py and tests/test_review_v0_2_4_fixes.py — totalling 44 tests. 385 tests passing.

Migration

  • Envelopes signed by v0.2.2 still verify cryptographically via verify_envelope_signature. To pass validate_envelope they must be re-signed with v0.2.3 (the canonical signature_b64 field is now required).
  • Callers building contexts with custom coeff_mod_bit_sizes may need to pick parameters from the HE-standard 128-bit table or pass insecure_allow_low_security=True.

Full diff: v0.2.2...v0.2.3

v0.2.2 - 100% line coverage

27 Apr 20:35

Choose a tag to compare

Patch release. 100% line coverage. No API changes.

Test coverage

  • 341 tests pass (was 232 in v0.2.1).
  • Line coverage: 100%.

What's covered now

  • Mode B (encrypted-mask) paths for every v0.2 metric port (per-rate _difference / _ratio family, scoring disaggregations, regression disaggregations).
  • Every validate_envelope negative branch: tampered hash, bad schema version, allowed-metric mismatch, depth ceiling/non-integer, security-bits floor, age ceiling, op_counts non-mapping/non-int, unsigned signature block, RSA-instead-of-Ed25519 signature.
  • Every CLI subcommand: verify (stdin, oversized, signed, RSA-key TypeError, unsigned-with-key, legacy-positional), inspect (legacy metric field, missing key), schema, doctor (with both backends, with one missing).
  • Every aggregate_difference / aggregate_ratio branch: empty input, between_groups, to_overall with explicit and default overall, zero-ref, zero-value, unknown method.
  • All scoring-helper zero-division edges (f1, zero-one-loss).
  • All plaintext fallback paths in _per_rate_metrics and _fairness_metrics (when upstream Fairlearn lacks the helper).
  • make_derived_metric reserved-arg, unknown-transform, sample-param, and group-min/group-max paths.
  • _regression_metrics zero-weight-group branch.
  • _json_safe over numpy scalars + nested containers + repr fallback.
  • OpenFHE backend sum_all window-clamp branch.

Pinned defensive branches

Three branches are marked # pragma: no cover with inline documentation:

  1. CKKSContext.make_evaluator_context() OpenFHE branch — the KeyPair binding is unpickleable; the branch is pinned in tests as an expected TypeError. A future fix (wrapper class around KeyPair) flips the pin from passing to failing.
  2. _pos_neg_counts hasattr guard — positives/negatives are dataclass fields, so the guard only fires for manually-constructed malformed instances.
  3. build_context final ValueError — unreachable because get_backend validates the backend name first.

Backward compatibility

No breaking changes. pip install --upgrade fairlearn-fhe.

v0.2.1 - test coverage closeout (86% → 94%)

27 Apr 17:41

Choose a tag to compare

Patch release. No API changes; extends test coverage and pins behaviour known to be broken so future fixes show up as flipped tests.

Test coverage

  • 232 tests pass (was 154 in v0.2.0).
  • Line coverage 86% → 94%.

What's covered now

  • Mode B (encrypted-mask) paths for the v0.2 metric ports (per-rate _difference / _ratio family, scoring disaggregations, regression disaggregations).
  • Plaintext fallback when upstream Fairlearn lacks equal_opportunity_difference / _ratio and the per-rate _difference / _ratio helpers.
  • Context lifecycle: set_default_context, reset_default_context, make_evaluator_context (TenSEAL).
  • CLI residuals: stdin verify, oversize envelope, missing public key, legacy metric envelope key.
  • Audit edge paths: SmallGroupWarning emission, no_sensitive_features trust-model label, encrypted_sensitive_features trust-model label, unknown-metric KeyError.
  • validate_envelope negative branches: tampered hash, bad schema version, allowed-metric mismatch, depth ceiling, age ceiling, security-bits floor.
  • EncryptedVector edges: __neg__, __sub__ (ct−pt and ct−ct), mul_scalar, first_slot, __radd__.
  • Multi-column sensitive features (covers _to_dataframe arr.ndim==2 branch).

Known issue pinned

CKKSContext.make_evaluator_context() on the OpenFHE backend currently raises TypeError: cannot pickle 'openfhe.openfhe.KeyPair'. The TenSEAL backend works correctly. A test pins the OpenFHE behaviour so a future fix (wrapper around KeyPair) flips the test from passing to failing instead of going unnoticed.

Backward compatibility

No breaking changes. pip install --upgrade fairlearn-fhe.

v0.2.0 - extended metric coverage + CLI + JSON Schema + threat model

27 Apr 16:27

Choose a tag to compare

Feature release on top of 0.1.0.

Highlights

Metric coverage

The encrypted metric catalog grows from 12 to 35 functions, closing
most of the gap with upstream Fairlearn:

  • Per-rate _difference / _ratio family (10 new) —
    selection_rate_difference / _ratio,
    true_positive_rate_difference / _ratio,
    true_negative_rate_difference / _ratio,
    false_positive_rate_difference / _ratio,
    false_negative_rate_difference / _ratio.
  • Scoring disaggregations (9 new) — accuracy_score_difference,
    accuracy_score_group_min, balanced_accuracy_score_group_min,
    precision_score_group_min, recall_score_group_min,
    f1_score_group_min, zero_one_loss_difference,
    zero_one_loss_group_max, zero_one_loss_ratio.
  • Regression disaggregations (3 new) —
    mean_squared_error_group_max, mean_absolute_error_group_max,
    r2_score_group_min. MSE is exact (one ct×ct multiply for ŷ²);
    MAE is approximated as sqrt(MSE) (exact for constant residuals,
    upper bound otherwise — documented).
  • selection_rate(pos_label=0) now works under encryption (computed
    as 1 - selection_rate(pos_label=1)). Other pos_label values still
    raise NotImplementedError with a clear message.

Backend / context

  • OpenFHE noise_flooding is now wired through build_context.
    Pass noise_flooding="openfhe-NOISE_FLOODING_DECRYPT" (or
    "noise-flooding", True) to enable OpenFHE's EXEC_NOISE_FLOODING
    execution mode where the linked openfhe-python build supports it.
    Underscore / hyphen / case variants are normalised. TenSEAL ignores
    the flag (no NOISE_FLOODING_DECRYPT mode in SEAL).

CLI

  • New fairlearn-fhe entry point with subcommands:
    • verify — validate an envelope (the original behaviour);
    • inspect — pretty-print or JSON-dump an envelope summary
      (--json);
    • schema — emit the envelope JSON Schema (--pretty to indent);
    • doctor — show backend availability.
  • The legacy fairlearn-fhe-verify entry point is preserved and
    maps directly to fairlearn-fhe verify. Existing CI scripts keep
    working.

Envelope schema

  • ENVELOPE_JSON_SCHEMA (draft 2020-12) and
    envelope_json_schema() factory are now exported at the package
    level. Validators can check envelopes against a formal schema rather
    than the bare version-tag string.

Documentation

  • New Threat model
    page formalising Mode A vs Mode B trust boundaries, what the auditor
    learns vs does not learn, recommended deployments, and CKKS-specific
    caveats. Linked from mkdocs.yml nav.

Backward compatibility

No breaking changes vs 0.1.0. Every public import from 0.1.0 continues
to work; the new helpers are purely additive.

Quality

  • 154 tests pass, ruff clean.
  • Coverage 86%. Plaintext-fallthrough and Mode A paths of the new
    metrics are covered; encrypted-mask Mode B paths for the new
    regression / scoring helpers are wired but pinned to follow-up
    fixtures.

Install

pip install --upgrade fairlearn-fhe
- uses: BAder82t/fairlearn-fhe@v0.2.0

Full changelog: see docs/changelog.md.

v0.1.0 - first release

27 Apr 15:54

Choose a tag to compare

First release of `fairlearn-fhe` — drop-in encrypted Fairlearn metrics
over CKKS via TenSEAL (default) or OpenFHE (opt-in).

Highlights

  • 12 canonical Fairlearn metrics ported to CKKS:
    `selection_rate`, `true_positive_rate`, `true_negative_rate`,
    `false_positive_rate`, `false_negative_rate`, `mean_prediction`,
    `demographic_parity_difference`, `demographic_parity_ratio`,
    `equalized_odds_difference`, `equalized_odds_ratio`,
    `equal_opportunity_difference`, `equal_opportunity_ratio`.
  • `MetricFrame`, `EncryptedMetricFrame`, and
    `make_derived_metric` mirroring the Fairlearn API surface.
  • TenSEAL and OpenFHE backends behind a single `build_context`
    dispatch.
  • Two trust models: encrypted `y_pred` with plaintext sensitive
    features (Mode A, depth 1), or fully-encrypted `y_pred` and
    sensitive features (Mode B, depth 2).
  • `MetricEnvelope` captures parameter-set hash, observed depth,
    and op counts. `validate_envelope()` provides dependency-light
    audit-envelope verification.
  • `fairlearn-fhe-verify` CLI for regulator-side envelope checks
    without importing an FHE backend.
  • Optional Ed25519 envelope signing (`pip install
    'fairlearn-fhe[signing]'`) plus signature verification.
  • Replay metadata records metric kwargs, trust model, and input
    hashes so audits are reproducible.
  • `audit_metric()` one-call wrapper produces an audit envelope.

Numerical fidelity

Default settings yield `< 1e-4` absolute error vs the plaintext
Fairlearn baseline. The included benchmark
(`benchmarks/bench_metrics.py`, n=1024, 3 sensitive groups, depth-6
circuit) gives:

backend dp_diff dp abs err eo_diff eo abs err
tenseal 284 ms 1e-7 562 ms 2e-7
openfhe 505 ms 2e-10 1015 ms 4e-11

OpenFHE wins on numeric error; TenSEAL wins on speed and ships via
`pip` on every supported platform.

Install

```bash
pip install fairlearn-fhe # tenseal backend (default)
pip install 'fairlearn-fhe[openfhe]' # add openfhe backend (requires C++ build)
pip install 'fairlearn-fhe[signing]' # add Ed25519 envelope signing helpers
```

Quality

  • 102 tests, 87% line coverage, ruff clean.
  • Tests cover edge cases, envelope validation, backend dispatch,
    encrypted arithmetic, CLI verification, and optional signature
    verification.

Full changelog: see docs/changelog.md.