A gRPC service that streams freshly measured quantum noise from Crypta Labs QRNG devices (Firefly and QCicada). This is built for entropy research.
Not for cryptographic or security use. Although Crypta Labs devices support cryptographic use cases, the output of this particular service isn't intended for such. If you need a cryptographic RNG service, look elsewhere.
- Fresh and exclusive data per request — no pooling, buffering, or pre-gen; every byte is measured upon request and served exclusively to the requester
- Low latency and efficient on the wire — gRPC over HTTP/2 with compact Protobuf framing minimizes both per-call latency and bytes-per-byte overhead
- Per-request provenance — response includes
device_idandtimestampso samples can be attributed and reproduced in datasets - Multiple post-processing modes — raw noise (zero post-processing), with noise conditioning, or SHA-256 post-processed. Selectable per device, for research needs
- Device failover — automatic fallback across configured devices
- API key management — per-key rate limits, daily byte caps, and per-request byte caps, with usage tracked in SQLite
Each response carries raw bytes sampled at request time, tagged with the source device_id and a microsecond timestamp, so every byte is traceable to a specific device and measurement window.
Client (Python/Go/Java/etc.)
↓ gRPC (HTTP/2 + Protobuf)
gRPC Server (port 50051)
↓
Device Manager (thread-safe)
↓
Quantum Devices (Firefly/QCicada)
cd /your/directory/qrng-grpc
# Install dependencies
pip install -r requirements.txt
# Install pyqcc (obtain wheel from Crypta Labs)
pip install /path/to/pyqcc-x.y.z-py3-none-any.whlmake protoThis creates:
proto/qrng_pb2.py- Message classesproto/qrng_pb2_grpc.py- Service stubs
# Copy example config
cp config.yaml.example config.yaml
# Edit config.yaml
# - Set admin_api_key for bootstrap admin
# - Configure your devices (paths, types, modes)
# - Adjust service settings (port, limits, etc.)python run.pyThe server will:
- Initialize the database (
qrng_grpc.db) - Connect to configured QRNG devices
- Start listening on port 50051
See proto/qrng.proto for the complete definition.
Request (RandomRequest):
num_bytes(uint32): Number of raw bytes to return- API key: passed via gRPC metadata key
api-key(not in the message body)
Response (RandomResponse):
data(bytes): Raw quantum random bytestimestamp(uint64): Server timestamp (Unix epoch, microseconds)device_id(string): Device that served the request
import grpc
from proto import qrng_pb2, qrng_pb2_grpc
channel = grpc.insecure_channel('localhost:50051')
stub = qrng_pb2_grpc.QuantumRNGStub(channel)
request = qrng_pb2.RandomRequest(num_bytes=100)
metadata = [("api-key", "your-api-key-here")]
try:
response = stub.GetRandomBytes(request, metadata=metadata)
print(f"Received {len(response.data)} bytes")
print(f"From device: {response.device_id}")
print(f"Timestamp: {response.timestamp}")
# Interpret raw bytes as needed on the client side:
import struct
uint8_array = list(response.data)
uint16_array = [struct.unpack('<H', response.data[i:i+2])[0]
for i in range(0, len(response.data), 2)]
except grpc.RpcError as e:
print(f"Error: {e.code()} - {e.details()}")grpcurl -plaintext \
-proto proto/qrng.proto \
-H 'api-key: YOUR_API_KEY' \
-d '{"num_bytes": 1024}' \
localhost:50051 qrng.QuantumRNG/GetRandomBytesSet admin_api_key in config.yaml before first startup:
admin_api_key: "sample-only-your-secure-bootstrap-key"On first startup this creates an admin key in the database. If you change the value and restart, the new key is added as a second admin — the old one is not removed. To revoke the old key use manage_keys.py disable or delete (see below).
Use the included CLI tool to manage keys. Must be run from the project directory.
List all keys:
python manage_keys.py listCreate a user key:
python manage_keys.py create --name "my-client" --device firefly-1The raw key is printed once at creation and cannot be retrieved again. All key details (ID, name, device, limits) are also printed.
Create with custom limits:
python manage_keys.py create --name "high-volume" --device firefly-1 \
--rate-limit 500 --daily-bytes 524288000 --max-bytes 65536Create an admin key:
python manage_keys.py create --name "ops-admin" --device "*" --adminUpdate limits on an existing key:
python manage_keys.py update --id <key-id> --rate-limit 100
python manage_keys.py update --id <key-id> --max-bytes 4096 --daily-bytes 10485760
python manage_keys.py update --id <key-id> --device firefly-2Disable / re-enable a key:
python manage_keys.py disable --id <key-id>
python manage_keys.py enable --id <key-id>View usage stats:
python manage_keys.py usage --id <key-id>
python manage_keys.py usage --id <key-id> --days 30Delete a key (prompts for confirmation):
python manage_keys.py delete --id <key-id>Available device IDs for --device: firefly-1, firefly-2, or * (any available device).
Per-key limits (all optional; omit to use the service-wide config default):
| Flag | Description |
|---|---|
--rate-limit RPM |
Max requests per minute |
--daily-bytes BYTES |
Max bytes served per day |
--max-bytes BYTES |
Max bytes per individual request |
service:
host: "0.0.0.0"
port: 50051 # gRPC port
request_timeout: 5.0 # Device wait timeout
max_bytes_per_request: 16384 # Default max bytes per request (overridable per key)
database_path: "./qrng_grpc.db"
failover_enabled: true # Device failover
default_rate_limit: 200 # Requests/minute
default_daily_byte_limit: 104857600 # 100 MB/day
max_message_size: 16777216 # 16 MB maxdevices:
- id: "firefly-1"
type: "firefly"
path: "/dev/ttyACM0"
enabled: true
post_processing_mode: 1 # 0=SHA256, 1=raw noise, 2=raw samplesgRPC errors are returned as standard status codes:
| gRPC Status | Reason |
|---|---|
UNAUTHENTICATED |
Missing or invalid API key |
RESOURCE_EXHAUSTED |
Rate limit or daily byte limit exceeded |
INVALID_ARGUMENT |
num_bytes is 0 or exceeds per-request limit |
UNAVAILABLE |
No devices available or device error |
DEADLINE_EXCEEDED |
Request timed out waiting for device |
The service logs to stdout:
2026-01-15 10:30:00 - app.server - INFO - Starting gRPC server on 0.0.0.0:50051
2026-01-15 10:30:01 - app.device_manager - INFO - Device firefly-1 connected successfully
Query the database directly:
-- Today's usage
SELECT key_id, requests, bytes_served
FROM usage_records
WHERE date = '2024-01-15';
-- Top users
SELECT k.name, SUM(u.bytes_served) as total_bytes
FROM api_keys k
JOIN usage_records u ON k.id = u.key_id
GROUP BY k.id
ORDER BY total_bytes DESC;qrng-grpc/
├── proto/
│ ├── qrng.proto # Protocol buffer definition
│ ├── qrng_pb2.py # Generated (don't edit)
│ └── qrng_pb2_grpc.py # Generated (don't edit)
├── app/
│ ├── __init__.py
│ ├── config.py # Configuration management
│ ├── database.py # API keys & usage tracking
│ ├── device_manager.py # Device communication
│ └── server.py # gRPC service implementation
├── example_client.py # Example client
├── manage_keys.py # API key management CLI
├── run.py # Entry point
├── config.yaml # Configuration
├── requirements.txt # Dependencies
└── README.md # This file
After modifying proto/qrng.proto:
make protoGenerate the protobuf code:
make protoInstall pyqcc from the wheel file:
pip install /path/to/pyqcc-x.y.z-py3-none-any.whlChange the port in config.yaml:
service:
port: 50052 # Or any available portAdd user to dialout group:
sudo usermod -a -G dialout $USER
# Log out and back inLicensed under the Apache License, Version 2.0 (January 2004). See LICENSE for the full text.
Copyright 2026 Entropic Science, Bradley Stephenson (orphiceye)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
For issues or questions:
- Check device connections:
ls -l /dev/ttyACM* /dev/ttyUSB* - Review logs for error messages
- Verify config.yaml settings