random
DRBG-derived random bytes (base64), anonymous and rate-limited. Capped at 64 bytes/request; powers the public dice player.
bytes
eu.qeaas由 qeaas.eu 發布官方發布者v1.0.0更新於 Oct 24, 2026
QRNG-seeded random bytes, seeds, and ML-KEM-768 keys with signed provenance receipts.
遠端 MCP 伺服器,讓助理取得以量子熵播種的隨機位元組、公平骰子、可驗證來源憑證,以及 ML-KEM-768 後量子金鑰示範。
Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。
把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。
閱讀 https://sourceweft.com/mcp/eu.qeaas%2Fqeaas-mcp/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。把它新增到你客戶端的 mcpServers 設定中。
{
"mcpServers": {
"qeaas": {
"type": "http",
"url": "https://api.qeaas.eu/mcp"
}
}
}Want an API key? Email [email protected].
FastAPI service that turns preloaded QRNG (quantum random number generator) bits into served randomness, dice rolls, cryptographic seeds, and post-quantum ML-KEM key material — each issue carrying a signed, offline-verifiable provenance receipt.
Monorepo layout: api/ (this document — FastAPI service), web/ (Next.js explainer + demo),
shared/ (docs, spikes), claude/ (epic build plans). The web app is a thin client over the
API; everything substantive happens here.
Honest framing, stated once and meant everywhere. QRNG does not "defeat quantum attackers." Raw QRNG bits are never served. They are entropy that seeds a standards HMAC-DRBG (NIST SP 800-90A). Every byte this API returns —
/random,/dice,/v1/random/bytes, ML-KEM seeds — is DRBG output. The quantum part is the entropy source; any quantum resistance comes from ML-KEM-768 (FIPS 203), not from the randomness being "quantum."
db.connect);
there is no long-lived pool object. DRBG state is never read-modify-written per request — see §4.WEB_ORIGIN (comma-separated).X-Quantum-Entropy: healthy|degraded header (added by middleware
in main.py).Source map (api/qeaas/):
Every served byte flows through one choke point, generation.random_bytes(n) →
keyed_drbg.output(n). Nothing bypasses it.
A. Ingest (admin, offline of the request path).
POST /admin/ingest → pool.ingest_bits_bytes() → pool.parse_bits_bytes() packs the 0/1
text MSB-first into bytes (discarding a trailing partial byte, rejecting any non-0/1 char) →
pool.encrypt_chunk() AES-256-GCM-encrypts it → db.insert_pool_chunk() stores
(ciphertext, nonce, tag, plaintext_len, source_label) in entropy_pool. Plaintext never
touches disk; the upload buffer is burn()-ed.
B. Serve (per request). keyed_drbg.output(n):
maybe_reseed() — loads the warm root-key cache (_load_cache), bootstrapping the root key
from the pool on very first use (_bootstrap_root_key). If the reseed schedule is due (§4),
pool.pull_reseed_material(32) decrypts the next unconsumed 32-byte slice from entropy_pool,
encrypts it as the new drbg_root row, advances consumed_offset, and burn()s the plaintext.redis_client.incr_counter() — atomic INCR drbg:counter, returns a globally unique integer.HmacDrbg is instantiated with the decrypted root_key and generate(n, additional = counter.to_bytes(8) + additional) is called — the counter guarantees two concurrent requests
can never produce identical output even with identical root key.db.bump_outputs_since_reseed() increments the reseed odometer.C. Wrap (per endpoint). generation.new_issue_meta(size) mints a request_id (uuid4), reads
entropy_epoch (= drbg_root.reseed_counter), timestamps, and calls receipts.sign(...) to
produce the Ed25519 receipt. Keyed endpoints then write db.insert_usage_log() (abuse spotting)
and db.insert_issue_log() (metadata-only provenance — no output bytes are ever stored).
See the deep-dive comment at the end of this file / in the chat analysis for the exact call graph.
They protect against completely different failure modes and have opposite consistency needs.
Holds everything that must survive and must be transactional & durable. Five tables:
Durability matters: lose the pool ciphertext or the root key and the service can no longer prove provenance or reseed. Postgres is ACID; this is where correctness lives.
Holds only fast, disposable, atomically-mutated counters that would be a correctness hazard in Postgres under serverless concurrency:
Why not put these in Postgres? On stateless serverless you cannot safely read-modify-write shared
state per request — two concurrent requests would race. Redis INCR/INCRBY are atomic, so
there is no read-modify-write. These values are meant to expire (per-minute, per-day) and losing
them is harmless — throttling fails open on a Redis outage (logs a warning, allows the request),
because the reseed-frequency floor (§4) still guarantees the pool can't be drained.
One-line summary: Postgres = durable secrets + audit that must be correct forever; Redis = ephemeral atomic counters that must be fast and race-free but are disposable.
drbg.py). Not thread-safe — each request gets
its own instance.keyed_drbg.py): output(n) = HMAC-DRBG(root_key, INCR(drbg:counter), n).root_key rotates from the pool on whichever fires first:
RESEED_INTERVAL_SECONDS), orRESEED_OUTPUT_LIMIT (100 000) but only if ≥ RESEED_MIN_INTERVAL_SECONDS
(5 minutes) has elapsed.
Each reseed pulls RESEED_PULL_BYTES (32) from the pool.THRESHOLD (64 KiB) of unconsumed pool bytes, /health reports
degraded and all gated routes 503 low_quantum_entropy (gate.require_entropy).One env-held MASTER_KEY (256-bit hex). pool.derive_subkey(name) (HKDF-SHA256, RFC 5869
one-block Expand) derives exactly four named sub-keys — there are no other secrets:
AES-256-GCM is authenticated: a flipped ciphertext byte fails the tag check and decryption raises —
the service fails closed on tampered storage. Sensitive plaintext (decrypted slices, the warm
root-key cache, upload buffers) is held in bytearray and best-effort burn()-ed after use (CPython
cannot guarantee zeroization; serverless teardown keeps in-process secrets short-lived by design).
hash_api_key(key) = HMAC-SHA256(pepper, key); only the hash is stored. require_api_key
reads the row fresh every request, so revoked = true takes effect immediately. Missing header →
401 missing_api_key; unknown/revoked → 401 invalid_api_key.hmac.compare_digest) against ADMIN_TOKEN. Wrong/missing →
401 unauthorized.All counters are atomic Redis ops; fail open on a Redis error.
Anon per-IP: 60 req/min on /random and /dice (ANON_IP_PER_MIN).
Anon global daily: 5 MB/day output ceiling on /random (ANON_DAILY_BYTES).
Per-key (/v1/*), tier-driven:
A key's explicit daily_quota_bytes overrides the tier default; NULL falls back to it. Rate is
checked before quota (cheaper to reject). An over-quota request's bytes are refunded (DECRBY)
so a rejection never burns quota it didn't use.
Every 429 carries a Retry-After header (seconds until the window resets).
Every failure returns a flat JSON body: {"error": "<slug>"} (see errors.py). Common slugs:
GET /health — anonymousUsage: uptime checks and monitoring dashboards — confirms the pool/DRBG isn't degraded before you rely on it for real traffic. Liveness + pool/DRBG status. Never gated.
Fires: db.get_root_key, db.pool_bytes_remaining, gate.entropy_level.
GET /random?bytes=<1..64> — anonymous, ungatedUsage: quick no-signup random bytes for demos, small scripts, or anything lightweight that doesn't need a key.
DRBG-derived random bytes, base64. Survives a degraded pool. Rate-limited per IP + anon daily.
Fires: ratelimit.check_ip_rate → ratelimit.check_anon_daily → generation.random_bytes.
Errors: 422 (bytes out of range), 429 rate_limited / daily_limit_reached.
POST /dice — anonymous, ungatedUsage: fair dice rolls for tabletop bots and giveaways, where players can audit every DRBG byte drawn behind a roll.
Rejection-sampled dice (no modulo bias). Rate-limited per IP.
Request: { "sides": 2..100 (default 6), "count": 1..6 (default 1) }
bytes_used / bytes_count echo every DRBG byte drawn for the roll — accepted and rejected —
so the web dice player's "bytes behind this roll" toggle is literal. Still DRBG output, never raw
QRNG bits. Fires: ratelimit.check_ip_rate → dice.roll (loops keyed_drbg.output(1)).
Errors: 422 (out of range), 429, 500 dice_sampling_failed.
GET /v1/random/bytes?size=<32..4096>&format=<hex|base64> — API key, gatedUsage: the main developer integration — production-grade random bytes paired with a signed receipt to prove provenance later. Canonical developer endpoint. Per-key rate limit + daily quota, then usage + issue logs.
Fires: require_entropy (gate) → require_api_key → ratelimit.enforce_key →
generation.issue_v1 (→ random_bytes + new_issue_meta + receipts.sign) →
db.insert_usage_log + db.insert_issue_log.
Errors: 401, 429 rate_limited/quota_exceeded, 503 low_quantum_entropy, 422.
POST /v1/verify — anonymous, per-IP rate-limitedUsage: proving a given output really came from this service, useful for audits or disputes, without ever re-exposing the underlying bytes.
Verify provenance, not the secret. Request: { "request_id"?: str, "receipt"?: str } (at least
one required — else 422).
receipt: signature is cryptographically verified; response resolves size,
entropy_epoch, timestamp, and the pool's qrng_source_labels.request_id: plain issue_log lookup (the note says so honestly).verified: false, provenance: null.Fires: ratelimit.check_ip_rate → receipts.verify (→ receipts.verify_receipt and/or
db.get_issue_log, db.get_pool_source_labels). Never an oracle — it never accepts or compares
an output value.
GET /v1/pubkey — anonymousUsage: verifying receipts fully offline using the published signing key, with no call back to this service needed. Published Ed25519 receipt-signing public key, for external offline verification.
POST /v1/kem/keypair — API key, gatedUsage: generating post-quantum ML-KEM-768 key material for testing; real deployments should keygen client-side, this route is a demo path.
QRNG-seeded ML-KEM-768 (FIPS 203) keygen. Request: { "include_secret_key"?: false }.
public_key (ek) always returned; secret_key (dk) only in the demo flow, with a loud
"demo only" note (real keygen happens client-side; the secret key never leaves the holder).
Fires: gate → require_api_key → ratelimit.enforce_key → kem.generate_keypair
(generation.random_bytes(64) → ML_KEM_768.key_derive) → new_issue_meta → usage/issue logs.
POST /v1/kem/encapsulate — API key, gatedUsage: demoing the encapsulation half of ML-KEM-768 against a public key you already hold; no server-side decapsulation route exists.
Encapsulate against a supplied public_key. Request:
{ "public_key": "<ek base64>", "include_shared_secret"?: false }.
ciphertext always returned; shared_secret + an illustrative HKDF-derived demo_key only with
include_shared_secret. There is no server-side decapsulate route — decapsulation is
client-side, on the holder of dk.
Fires: gate → require_api_key → ratelimit.enforce_key → kem.encapsulate
(generation.random_bytes(32) → ML_KEM_768._encaps_internal) → new_issue_meta → logs.
Malformed/wrong-length ek → 422 bad_request.
kyber-pyis educational and not constant-time — correct for a thesis demo, not production (which would useliboqson a persistent host).
POST /admin/ingest — admin tokenUsage: operators refilling the entropy pool from a fresh batch of raw QRNG bits, encrypted before it ever touches disk.
Multipart .txt upload of 0/1 characters (≤ 10 MB) → refills the pool. Parses in memory,
encrypts, stores; no plaintext ever touches disk; upload buffer is burned.
Fires: require_admin → pool.ingest_bits_bytes (→ parse_bits_bytes → encrypt_chunk →
db.insert_pool_chunk) → pool.burn. Errors: 401, 413 file_too_large, 422 (non-.txt or
non-0/1 content).
POST /admin/purge — admin tokenUsage: operators wiping the entropy pool clean for rotation, incident response, or test resets — an irreversible action. Deletes all stored pool chunks. Irreversible — the pool must be re-ingested afterward.
Fires: require_admin → db.purge_pool().
POST /admin/keys — admin tokenUsage: operators provisioning a brand-new API key for a developer or customer, shown once at mint time.
Mint an API key (same logic as scripts/mint_key.py). The plaintext key is returned once.
Request: { "owner": str, "tier"?: "default", "daily_quota_bytes"?: int|null }.
Fires: require_admin → secrets.token_urlsafe(32) → auth.hash_api_key → db.insert_api_key.
POST /admin/keys/revoke — admin tokenUsage: operators killing a compromised or offboarded key immediately, effective on the very next request.
Instant revocation. Request: { "key_hash": str }.
Fires: require_admin → db.revoke_api_key. Unknown hash → 404 not_found.
Every route touches Postgres and most touch Redis — with empty .env values every request 500s.
For a disposable local backend (Docker Postgres + Redis, auto-applies all SQL migrations):
Seed the pool (any 0/1 text exercises the pipeline locally — real deploys ingest actual QRNG output):
Smoke test:
Tear down: pkill -f "uvicorn main:app" && ./scripts/dev_db_down.sh.
Rate limits, quotas, tiers, and reseed timing are module constants (ratelimit.py,
keyed_drbg.py), not env vars.
api/scripts/)Production is two separate Vercel projects (qeaas-api → qrng-eaas/api, qeaas-web →
qrng-eaas/web), not one — GET /dice (web page) vs POST /dice (API) collide on one domain and
path-based rewrites can't route by HTTP method. qrng-eaas/api/vercel.json pins the Python 3.13
runtime and rewrites every path to main.py. Provision Neon (run sql/001…005 in order), Upstash
(regional, rediss:// TCP URL), then set per-project env vars (§10). Full runbook, troubleshooting,
and the EPIC 7–11 write-ups (seed-quality report, networking demo, provenance, secure-storage burn
lifecycle, theming) live in claude/plans/ and shared/docs/.
The service is discoverable by AI agents and correctly indexable by classic + LLM crawlers.
Canonical domains: web https://qeaas.eu, API https://api.qeaas.eu.
Discovery documents (API):
Web maps: /llms.txt, /llms-full.txt, /agents page, sitemap.xml, robots.txt
(full AI-crawler allowlist), manifest.webmanifest, and site-wide JSON-LD.
Getting a key: public endpoints (health, random, dice, verify, pubkey) need no key.
Developer/KEM endpoints require the header X-API-Key (admin-minted, revocable, quota-metered).
One catalog, no drift: qeaas/agent.py holds a single ENDPOINTS catalog; agent.json,
tool descriptors, the manifest, and the MCP tool list all derive from it, and schemas come
from model_json_schema().
Base URLs from env: API reads PUBLIC_API_URL / PUBLIC_WEB_URL (localhost defaults,
qeaas/urls.py); web reads NEXT_PUBLIC_API_URL / NEXT_PUBLIC_WEB_URL (lib/urls.ts).
WEB_ORIGIN (CORS) must include https://qeaas.eu.
Worked example:
來源:qrng-eaas/README.md,提交 2c39801
DRBG-derived random bytes (base64), anonymous and rate-limited. Capped at 64 bytes/request; powers the public dice player.
bytesRejection-sampled dice rolls (no modulo bias), anonymous and rate-limited. Echoes the DRBG bytes drawn for transparency.
countsidesCanonical developer endpoint: DRBG-derived bytes in hex or base64 with a signed provenance receipt. Requires X-API-Key; 32..4096 bytes; per-key rate limit + daily quota.
size必填formatVerify the provenance of an issued value from its request_id and/or receipt. Provenance only -- never a value-confirmation oracle. Anonymous, rate-limited.
receiptrequest_idGenerate an ML-KEM-768 (Kyber, post-quantum) keypair seeded from the QRNG->DRBG chain. Public key always returned; secret key only for the demo flow (loud demo-only note). Requires X-API-Key.
include_secret_keyEncapsulate against a supplied ML-KEM-768 public key to produce a ciphertext (and, for the demo, the shared secret). Requires X-API-Key.
public_key必填include_shared_secret已滿足適用檢查項的 90%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
請在儲存庫或註冊中心條目中宣告授權條款。
┌──────────── admin (X-Admin-Token) ────────────┐ │ │ QRNG .txt │ POST /admin/ingest │ POST /admin/keys (0/1 bits) ▼ ▼ POST /admin/keys/revoke ┌─────────────┐ AES-256-GCM ┌───────────────────────────────┐ │ pool.parse │ ───────────────▶ │ Postgres (Neon) │ │ + encrypt │ │ entropy_pool (ciphertext) │ └─────────────┘ │ drbg_root (ciphertext) │ │ api_keys / usage_log / │ │ issue_log │ └───────────────┬───────────────┘ │ decrypt slice on reseed ▼ client ┌───────────────────────────────────────┐ ─────────▶ entropy gate │ keyed_drbg.output(n) │ request (503 if low) │ root_key ← reseeded from pool │ + rate limit ──▶│ counter ← Redis atomic INCR │──▶ HMAC-DRBG(SHA-256) + API key │ HMAC-DRBG(root_key, counter) → bytes │ = served bytes └───────────────────────────────────────┘ │ ┌───────────────────────────┴───────────┐ │ Redis (Upstash) │ │ drbg:counter (output uniqueness) │ │ rl:* / quota:* / anon:* (throttling) │ └────────────────────────────────────────┘MASTER_KEY (env / KMS only — never written to Postgres) └── HKDF-SHA256 ├── "pool-encryption-key" → entropy_pool AES-256-GCM ├── "drbg-root-encryption-key" → drbg_root.root_key AES-256-GCM ├── "api-key-pepper" → API-key hashing (HMAC-SHA256) └── "receipt-signing-key" → Ed25519 receipt signing seed{ "status": "ok", "quantum_entropy_level": "healthy", // or "degraded" "pool_bytes_remaining": 699000, "drbg_reseeds": 3, "uptime": 1234.5 }{ "bytes": 32, "format": "base64", "data": "<base64>" }{ "sides": 6, "count": 2, "rolls": [3, 6], "format": "base64", "bytes_used": "<base64>", "bytes_count": 2 }{ "request_id": "…", "format": "hex", "data": "…", "entropy_epoch": 3, "timestamp": "2026-…Z", "receipt": "qeaas1.<payload>.<sig>" }{ "request_id": "…", "verified": true, "provenance": { "size": 64, "entropy_epoch": 3, "timestamp": "…", "qrng_source_labels": ["fez", "marrakesh"] }, "note": "receipt signature verified cryptographically" }{ "algorithm": "Ed25519", "format": "base64", "public_key": "<base64 32-byte raw>" }{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64", "public_key": "<ek base64>", "secret_key": null, "entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }{ "request_id": "…", "algorithm": "ML-KEM-768", "format": "base64", "ciphertext": "<base64>", "shared_secret": null, "demo_key": null, "entropy_epoch": 3, "timestamp": "…", "receipt": "…", "note": null }{ "ingested": true, "bytes_added": 87500, "pool_bytes_remaining": 786500 }{ "purged": true, "chunks_removed": 42, "pool_bytes_remaining": 0 }{ "api_key": "<plaintext — shown once>", "owner": "devtest", "tier": "default", "daily_quota_bytes": null }{ "key_hash": "…", "revoked": true }cd apipython -m venv venv && source venv/bin/activatepip install -r requirements.txtcp .env.example .env # fill DATABASE_URL, REDIS_URL, MASTER_KEY, ADMIN_TOKENeval "$(./scripts/dev_db_up.sh --print-env)" # starts qeaas-pg + qeaas-redis, exports envuvicorn main:app --reload --port 8000 --env-file .envpython3 -c "import random; random.seed(1); open('/tmp/bits.txt','w').write(''.join(random.choice('01') for _ in range(700000)))"python scripts/ingest_bits.py /tmp/bits.txt seedpython -m scripts.mint_key --owner devtest --tier default # prints the plaintext key onceuvicorn main:app --port 8000curl -s localhost:8000/healthcurl -s "localhost:8000/random?bytes=32"curl -s -X POST localhost:8000/dice -H 'content-type: application/json' -d '{"sides":6,"count":2}'curl -s -H "X-API-Key: <key>" "localhost:8000/v1/random/bytes?size=64&format=hex"open localhost:8000/docs # interactive Swagger UI, every route typed# 1. an agent finds the manifestcurl -s https://api.qeaas.eu/.well-known/agent.json | jq
# 2. follows to its framework-shaped quickstartcurl -s "https://api.qeaas.eu/v1/agent/manifest?profile=openai-tools" | jq
# 3. calls a public endpoint, or (with a key) a developer onecurl -s "https://api.qeaas.eu/random?bytes=32"curl -s -H "X-API-Key: $QEAAS_API_KEY" "https://api.qeaas.eu/v1/random/bytes?size=64&format=hex"
# or connect an MCP client to POST https://api.qeaas.eu/mcpcurl -s -X POST https://api.qeaas.eu/mcp -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq