FHE Service
HTTP microservice for session-based FHE operations. Key material stays on the server; only ciphertexts travel over the wire.
Source files: crates/fhe-service/src/main.rs, session.rs, handlers.rs, wire.rs, http.rs.
What It Does
The FHE service wraps RNSFHEContext in a stateful HTTP server that manages per-client sessions. Each session holds:
- An
RNSFHEContext(DualRNS with 5-anchor K-Elimination) - A
DualRNSFullKeySet(secret + public + evaluation keys) - A
NoiseBudgettracker - An operation counter and timestamps
The secret key never leaves the server. Clients send plaintext values for encryption and receive base64-encoded ciphertexts. Homomorphic operations are performed server-side.
REST API
| Method | Endpoint | Description |
|---|---|---|
| GET | /healthz | Health check (returns active session count) |
| GET | /v1/version | Service version |
| GET | /v1/metrics | Request counts, uptime, session stats |
| POST | /v1/sessions | Create a new FHE session |
| GET | /v1/sessions/{id} | Get session info (config, noise budget, op count) |
| DELETE | /v1/sessions/{id} | Destroy a session (zeros key material) |
| POST | /v1/sessions/{id}/encrypt | Encrypt plaintext values |
| POST | /v1/sessions/{id}/decrypt | Decrypt ciphertexts |
| POST | /v1/sessions/{id}/evaluate | Homomorphic operations |
Create Session
POST /v1/sessions
{
"config": "secure_128"
}
Supported configs: secure_128, secure_192, secure_256.
Response:
{
"session_id": "sess_a1b2c3d4...",
"config": "secure_128",
"params": {
"n": 4096,
"log_q": 90,
"t": 65537,
"security_bits": 128
},
"noise_budget_estimate_millibits": 30000
}
Session IDs are generated from 16 bytes of OS CSPRNG entropy, hex-encoded with a sess_ prefix.
Encrypt
POST /v1/sessions/{id}/encrypt
{
"values": [42, 7, 100]
}
Response:
{
"ciphertexts": ["<base64 bincode>", "..."],
"noise_budget_estimate_millibits": 29500
}
Each value must be < t. Ciphertexts are serialized as bincode, then base64-encoded.
Decrypt
POST /v1/sessions/{id}/decrypt
{
"ciphertexts": ["<base64 bincode>", "..."]
}
Response:
{
"values": [42, 7, 100],
"noise_budget_estimate_millibits": 29500
}
Evaluate (Homomorphic Operations)
POST /v1/sessions/{id}/evaluate
{
"operations": [
{
"op": "add",
"inputs": ["<ct_a base64>", "<ct_b base64>"]
},
{
"op": "mul_plain",
"inputs": ["<ct base64>"],
"scalar": 5
}
]
}
Supported operations:
add: ct + ctsub: ct - ctnegate: -ctadd_plain: ct + scalarmul_plain: ct * scalarmul: ct * ct (requires eval key)
Session Management
SessionStore
Thread-safe session storage using Arc<RwLock<HashMap>>.
pub struct SessionStore {
sessions: Arc<RwLock<HashMap<String, Session>>>,
max_sessions: usize,
ttl_seconds: u64,
}
Configurable via environment variables:
FHE_MAX_SESSIONS: maximum concurrent sessions (default: 64)FHE_SESSION_TTL: session time-to-live in seconds
Session
Each session holds a complete DualRNS FHE context:
pub struct Session {
pub session_id: String,
pub config_name: String,
pub config: FHEConfig,
pub noise_budget: NoiseBudget,
pub operation_count: u64,
pub created_at: u64,
pub last_accessed: u64,
pub rns_ctx: RNSFHEContext,
pub dual_keys: DualRNSFullKeySet,
}
Session creation uses OS CSPRNG for all key generation:
let session = Session::new("secure_128")?;
// Internally calls:
// RNSFHEContext::try_new(&config)
// rns_ctx.generate_keys_dual_full_secure()
Wire Types
Defined in wire.rs. All request/response types use serde for JSON serialization.
Size Limits (DoS Protection)
| Constant | Value | Purpose |
|---|---|---|
MAX_STRING_FIELD_LEN | 1 KB | Non-ciphertext string fields |
MAX_CIPHERTEXT_FIELD_LEN | 4 MB | Single ciphertext (N=16384 DualRNS) |
MAX_REQUEST_ALLOCATION | 64 MB | Total per-request memory cap |
Serialization
Ciphertexts are serialized using bincode (compact binary format) and base64-encoded for JSON transport. The DualRNSCiphertext type supports serde when the serde feature is enabled on the nine65 crate.
The service validates all deserialized ciphertexts before use:
- Polynomial degree matches expected N
- Number of RNS limbs matches expected prime count
- All coefficients are within bounds
Cloud Run Deployment
| Parameter | Value |
|---|---|
| Platform | Google Cloud Run |
| Service name | nine65-v7 |
| Region | us-south1 (Dallas) |
| Project | astro-resonance |
| Container port | 8080 |
| URL | https://nine65-v7-517338038154.us-south1.run.app |
| Status | Disabled (billing paused) |
| Deploy method | Push to main triggers Cloud Build |
Environment Variables
| Variable | Default | Description |
|---|---|---|
FHE_SERVICE_HOST | 127.0.0.1 | Bind address |
FHE_SERVICE_PORT | 8080 | Bind port |
FHE_MAX_SESSIONS | 64 | Maximum concurrent sessions |
FHE_MAX_CONNECTIONS | 256 | Maximum concurrent TCP connections |
FHE_SESSION_TTL | (none) | Session TTL in seconds |
Security
#![deny(clippy::float_arithmetic)]enforced at crate levelallow_insecurefeature blocked in release builds viacompile_error!- Session IDs generated from OS CSPRNG (16 random bytes)
- All key generation uses
generate_keys_dual_full_secure()(OS CSPRNG) - Ciphertext validation on every deserialization (prevents DoS via malformed inputs)
- Connection limiting prevents resource exhaustion
Running Locally
cd crates/fhe-service
cargo run --release
# Custom config
FHE_SERVICE_PORT=9090 FHE_MAX_SESSIONS=128 cargo run --release
Testing
# Create a session
curl -X POST http://localhost:8080/v1/sessions \
-H "Content-Type: application/json" \
-d '{"config": "secure_128"}'
# Health check
curl http://localhost:8080/healthz
Feature Flag Requirements
The fhe-service crate requires the serde feature on nine65 for ciphertext serialization:
[dependencies]
nine65 = { path = "../nine65", features = ["serde", "ntt_fft"] }
Without the serde feature, ciphertexts cannot be serialized for network transport.
Where to go next
- Cookbook – code recipes for the operations the service wraps
- Key Management – how session keys are generated and protected
- Security Configs – the
secure_128/secure_192/secure_256parameters