./explain --fhe --audit-ready
CipherExplain provides SHAP FHE for regulators: encrypted feature-attribution explanations for AI systems where sensitive input data must remain private. It computes SHAP values under Fully Homomorphic Encryption, helping regulated teams produce audit-ready explanations without exposing plaintext features to the server.
Homomorphic encrypted SHAP explanations. Computes SHAP feature attributions entirely under Fully Homomorphic Encryption — with fhe_mode='ckks', the server never sees your plaintext data. Register your own models via a secure JSON API — no training data leaves your environment. Reproducible benchmarks, signed audit artefacts, and a verifiable SDK — see the Validation and SDK sections.
Encrypted AI blocks explainability — and regulators now require both.
GDPR, HIPAA-aligned programs, and sectoral controls often require strong safeguards for personal data, and encryption is one important technical measure. The EU AI Act (Art. 13 & 86) requires transparency and explanations for high-risk AI systems, with obligations phasing in from August 2026 through 2027. Feature-level explanations are one established approach to meet these transparency requirements. Combining the two is operationally hard: most explainability methods need plaintext access, which leaves banks, healthcare networks, and hiring platforms juggling pseudonymisation, on-prem builds, and contractual controls to bridge the gap.
Reproducible reference prototype and validated benchmarks. Patents filed (PCT/IB2026/053378 + PCT/IB2026/053405); sub-licensable patent grants on the Enterprise tier.
Compute feature attribution entirely under FHE with fhe_mode='ckks'. Plaintext input never leaves your machine; server processes ciphertexts only.
Linear (LR / SVM / MLP) encrypted by default. Tree ensembles opt-in via enable_fhe_octe=True. Tree-attested SHAP beta on SDK 0.4.0. CatBoost full-FHE per-customer (Enterprise).
Known limitations & honest disclosures
Register a trained model via JSON-only weights. No training data, no pickle, no code execution. With fhe_mode='ckks', SHAP runs server-side under FHE; results return encrypted.
register_xgboost(), register_lightgbm(), register_mlp(), register_pytorch_mlp(), register_catboost(), register_pipeline(), from_weights(). Tree-SHAP fidelity 0.05%.linear_surrogate=true → ~7s/explain, L∞ error 0.062. Default path is audit-grade.model_version_id on every response — list via GET /models/{id}/versions.Five verifier layers, all live in production:
__reduce__. Pydantic-typed weights only.sha256(API-Key); cross-tenant lookups 404.Plain English. No maths required.
You send a feature vector — the attributes of the specific case you want explained. These are the same numbers your model used to make its prediction.
{
"model_id": "loan-risk-v1",
"features": [35, 55000, 0.3, 1, 8 ]
// ↑ ↑ ↑ ↑ ↑
// age income debt new yrs_emp
}
{
"prediction": 0.72,
"base_rate": 0.50,
"shap_values": [0.08, 0.18, -0.06, 0.02, 0.00],
"feature_names": ["age","income","debt","new","yrs"]
}
prediction: 0.72 — the model is 72% confident this applicant will repay. Your application maps this to "Approved" or "Low risk" — the label is your code's job, not ours.
base_rate: 0.50 — the average prediction across all applicants. This is the neutral starting point before any features are considered.
The SHAP values explain the gap from 0.50 to 0.72. Income drove most of it (+0.18). Debt ratio pulled it back (−0.06).
Each SHAP value is a signed number. Positive = pushed the prediction up. Negative = pushed it down. The size tells you how much relative to the other features.
Main approval driver. Income was the single biggest reason the model said yes.
Moderate positive signal. Added some confidence but was not the deciding factor.
Worked against approval. Still approved overall, but the debt ratio reduced confidence.
Near-zero impact. Being a new customer barely changed this prediction either way.
Every number below is reproducible from the working prototype.
shap.TreeExplainer. Dev-measured on EU production cloud infrastructure (T = 100, D = 6); 96% per-tree leaf-routing match vs plaintext walk. Available since SDK 0.4.0; production soak pending.Encrypted SHAP explanations — hosted API with a single key.
Sign up instantly — no waitlist. Enter your work email, verify with a 6-digit code, and your key arrives immediately.
→ Get a free key or upgrade to Pro (£49/mo).
vb_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Load the demo model, then call /explain_raw with raw (unscaled) values:
import requests
BASE = "https://cipherexplain.vaultbytes.com"
HDR = {"X-API-Key": "vb_..."}
# One-time: load the built-in demo credit model
requests.post(f"{BASE}/startup", headers=HDR)
# Send the raw feature values for one person
r = requests.post(f"{BASE}/explain_raw", headers=HDR,
json={
"model_id": "credit_model",
"features": [38, 13, 0, 0, 40 ]
# ↑ ↑ ↑ ↑ ↑
# age edu marital occup hrs/week
}
)
data = r.json()
# What comes back:
# data["prediction"] → 0.74 (74% probability — your app maps to a label)
# data["base_rate"] → 0.50 (average across all cases — the neutral baseline)
# data["shap_values"] → [0.12, -0.31, 0.05, 0.03, 0.09]
# data["feature_names"] → ["age", "education-num", "marital", "occup", "hours"]
#
# Reading the SHAP values:
# education-num: -0.31 → biggest factor, pushed prediction DOWN
# age: 0.12 → pushed it up
# hours/week: 0.09 → positive signal
# occupation: 0.05 → small positive
# marital: 0.03 → almost no effect
curl -s -X POST \
https://cipherexplain.vaultbytes.com/explain_raw \
-H "X-API-Key: vb_..." \
-H "Content-Type: application/json" \
-d '{
"model_id": "credit_model",
"features": [38, 13, 0, 0, 40]
}' | python3 -m json.tool
POST /startup → load demo credit model
GET /models → list your registered models
POST /models/register → register your own model
GET /models/{id}/versions → list model versions (audit)
GET /models/{id}/commitment → KZG commitment for FreiKZG verify
DELETE /models/{id} → remove a model
POST /explain → SHAP (pre-scaled features)
POST /explain_raw → SHAP (raw values, auto-scaled)
POST /explain/batch → async batch — webhook delivery
GET /explain/batch/{job_id} → poll batch job status
POST /report → generate PDF audit report
POST /keys/rotate → rotate your API key
GET /usage → quota used this month
GET /usage/dp → remaining DP privacy budget (ε)
GET /health → status (no key needed)
Your model and data stay local. Only trained weights (numbers) are sent — no training data, no pickle files, no arbitrary code.
pip install cipherexplain
from cipherexplain_sdk import CipherExplainClient
client = CipherExplainClient(api_key="vb_...")
# --- sklearn Pipeline auto-unwrap (embedded scaler stripped) ---
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = Pipeline([("scaler", StandardScaler()),
("clf", LogisticRegression())]).fit(X, y)
client.register_pipeline("my_model", pipe, feature_names, X_train=X)
# --- XGBoost binary classifier ---
import xgboost as xgb
booster = xgb.XGBClassifier().fit(X, y)
client.register_xgboost("my_xgb", booster, feature_names)
# --- LightGBM binary classifier ---
import lightgbm as lgb
gbm = lgb.LGBMClassifier().fit(X, y)
client.register_lightgbm("my_lgb", gbm, feature_names)
# --- MLP (ReLU) — CKKS-evaluated ---
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(16, 8)).fit(X, y)
client.register_mlp("my_mlp", mlp, feature_names, X_train=X)
# --- Any other framework — raw weights ---
from cipherexplain_sdk import from_weights
spec = from_weights(coef, intercept, "my_linear",
feature_names, classes=[0, 1])
client.register(spec)
# Explain with full FHE + optional DP noise
result = client.explain_raw("my_model", x_raw,
fhe_mode="execute", apply_dp=True)
print(result["shap_values"])
print(result["model_version_id"]) # audit pin
print(result["fhe_mode_used"]) # "ckks_engine"
canonical source of truth
Each API key has a model slot quota by tier:
Delete a model to free its slot:
client.delete("my_model")
Rotate your API key at any time — all registered models move automatically:
result = client.rotate_key() # result["new_key"] → "vb_..." # Your old key stops working immediately.
Register models, run explanations, rotate keys — all from Python.
Python 3.9+ · License: AGPL v3 (commercial licence available).
Latest SDK live on PyPI (v0.5.1 — counterfactuals + ECOA reason codes):
pip install cipherexplain
For client-side CKKS encryption (fhe_mode='ckks') add the [fhe] extra:
pip install 'cipherexplain[fhe]'
from cipherexplain_sdk import CipherExplainClient, from_weights
client = CipherExplainClient(api_key="vb_...")
# Gradient-boosted trees
client.register_xgboost("my_xgb", xgb_model, feature_names)
client.register_lightgbm("my_lgb", lgb_model, feature_names)
# sklearn Pipeline — scaler auto-unwrapped
client.register_pipeline("my_pipe", pipe,
feature_names, X_train=X)
# MLP (CKKS-evaluated)
client.register_mlp("my_mlp", mlp, feature_names, X_train=X)
# Raw weights (TF, JAX, statsmodels, R, ...)
spec = from_weights(coef, intercept, "my_linear",
feature_names, classes=[0, 1])
client.register(spec)
# Explain with full FHE + optional DP noise
result = client.explain_raw("my_mlp", x_raw,
fhe_mode="execute", apply_dp=True)
print(result["shap_values"])
print(result["fhe_mode_used"]) # "ckks_engine"
print(result["model_version_id"]) # audit trail
# Tree-attested SHAP — server FHE routing + local TreeExplainer (BETA)
from cipherexplain_sdk import from_sklearn_ensemble
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, max_depth=6).fit(X, y)
client.register(from_sklearn_ensemble(rf, "my_rf", feature_names))
result = client.explain_tree("my_rf", x_raw, model=rf)
print(result["tree_attested"].shap_values)
print(result["tree_attested"].attestation_verified)
# Async batch (compliance workflows)
job = client.explain_batch([x1, x2, x3],
model_id="my_mlp",
webhook_url="https://you/hook")
status = client.explain_batch_status(job["job_id"])
# Counterfactuals + ECOA Reg-B Form C-1 reason codes (NEW in v0.5.1)
# Customer's x stays local; server constructs Enc(x') homomorphically.
exp = client.explain_raw("my_lr", x_denied)
cf = client.counterfactual("my_lr", x_denied, exp)
print(cf["x_prime"]) # x + delta_star (minimal-L2 recourse)
print(cf["decision_flipped"]) # True
for code in cf["reason_codes"]:
print(code["form_c1_code"], "—", code["form_c1_text"])
# DP budget
print(client.usage_dp()) # {"epsilon_remaining": 87.3, ...}
# Key rotation — old key deactivated immediately
new = client.rotate_key()
print(new["new_key"]) # save this
Interactive reference — try every endpoint directly in your browser.
Try every endpoint live. Paste your API key once and run requests directly from the browser.
Clean read-only API reference. Best for sharing with your team or reading offline.
vb_... key into the X-API-Key fieldfhe_mode='ckks' enables full CKKS homomorphic encryption. Your input is encrypted on your machine before transmission. The server evaluates the model and computes SHAP values without decrypting at any point. Results are returned encrypted and decrypted locally by your SDK. Per-family FHE support level, opt-in flags, and measured prod latency live in the canonical Model support matrix. For long-running compliance workflows, use POST /explain/batch — async webhook delivery.
Cryptographic integrity (LIVE): every response carries a Bulletproofs binding proof — soundness 2−128, +0.7% latency overhead, verified client-side by the SDK. See the Verifiable Encrypted SHAP section for the full spec.
Three levels of privacy. Pick the one that matches your data-handling contract.
Fast, no encryption. Your features travel over HTTPS and the server computes SHAP in plaintext.
client.explain_raw(
"my_model", x_raw)
Full CKKS homomorphic encryption. Your input is encrypted on your machine; the server never sees plaintext.
client.explain_raw(
"my_model", x_raw,
fhe_mode="execute")
Encrypted compute plus a clipped Gaussian (ε,δ)-DP mechanism on the published SHAP vector. Reduces the leakage from repeated queries against the same subject under the documented neighbouring relation.
client.explain_raw(
"my_model", x_raw,
fhe_mode="execute",
apply_dp=True)
DP-SHAP applies a clipped Gaussian mechanism to the published SHAP vector, providing (ε, δ)-differential privacy with respect to a documented neighbouring relation on the client input (l1_fractional, l1_single, or linf). The L₂ sensitivity Δ₂ is derived per model class — closed-form for logistic regression, leaf-bound for RandomForest / GradientBoosting; other model families fall back to plaintext SHAP. Production composition is linear in ε (a stricter accountant than zCDP); a zCDP PrivacyAccountant is available as a research utility. The mechanism protects the published attribution against input-reconstruction attacks; it does not provide DP for the underlying training data. Each apply_dp=True call consumes from a per-key daily ε budget.
GET /usage/dp
{
"epsilon_budget_daily": 100.0,
"epsilon_spent_today": 12.7,
"epsilon_remaining": 87.3,
"resets_at": "2026-04-19T00:00:00Z"
}
Enterprise quoted against regulatory exposure, not compute cost.
MANAGE YOUR ACCOUNT
Enter your API key to manage billing, cancel, enable PAYG, or check usage — all automated, no emails needed.
Manage subscription: cancel, update card, download invoices · No cancellation fees · Access continues to end of billing period
For procurement, vendor onboarding, NDA evaluations, design partnerships, and pilots. Replies within one business day.