An encrypted zero-knowledge database in pure Rust — the server cannot read what it stores.
Every database — Redis, Postgres, etcd, RocksDB — shares one fundamental flaw: whoever operates the server can read all your data. Root access = full data access. Encrypted at rest helps against stolen disks, not against the operator.
Zerobase breaks that assumption. With PrivateClient, data is encrypted on the client before any network call. The server stores ciphertext it cannot decrypt. Even with full root access, an attacker sees nothing.
| Zerobase | Redis | etcd | RocksDB | Postgres | |
|---|---|---|---|---|---|
| Encrypted at rest (AES-256-GCM) | ✅ | ✗ | ✗ | opt | opt |
| Server-blind E2E encryption | ✅ | ✗ | ✗ | ✗ | ✗ |
| Hardware-derived key (no password) | ✅ | ✗ | ✗ | ✗ | ✗ |
| Ed25519 identity + mTLS | ✅ | ✗ | opt | ✗ | opt |
| Capability tokens with revocation | ✅ | ✗ | ✗ | ✗ | ✗ |
| Tamper-evident audit log (BLAKE3-chained) | ✅ | ✗ | ✗ | ✗ | ✗ |
| Key TTL | ✅ | ✅ | ✗ | ✗ | ✗ |
| ACID transactions | ✅ | partial | ✗ | ✗ | ✅ |
| SQL (SELECT / JOIN / WHERE) | ✅ | ✗ | ✗ | ✗ | ✅ |
| Leader-follower replication | ✅ | ✅ | ✅ | ✗ | ✅ |
| Rate limiting per connection | ✅ | ✗ | ✗ | ✗ | ✗ |
Pure Rust, #![forbid(unsafe_code)] |
✅ | ✗ | ✗ | ✗ | ✗ |
| No OpenSSL, no C FFI | ✅ | ✗ | ✗ | ✗ | ✗ |
╔══════════════════════════════════════════════════════════════╗
║ Your process — PrivateClient ║
║ ║
║ master_secret ──▶ KEK + VEK (never leaves your machine) ║
║ key ──▶ BLAKE3(KEK, key) stored_key (opaque) ║
║ value ──▶ AES-256-GCM(value, VEK) stored_value (opaque) ║
╚══════════════════════╤═══════════════════════════════════════╝
│ TLS 1.3 (rustls — zero OpenSSL)
╔══════════════════════╧═══════════════════════════════════════╗
║ zerobased — the server ║
║ ║
║ ┌─ Handshake ─────────────────────────────────────────┐ ║
║ │ Ed25519 challenge-response + capability token ACL │ ║
║ └──────────────────────────────────────────────────────┘ ║
║ ┌─ Per-request ───────────────────────────────────────┐ ║
║ │ Scope check · Rate limit · Audit entry │ ║
║ └──────────────────────────────────────────────────────┘ ║
║ ┌─ Storage engine ────────────────────────────────────┐ ║
║ │ WAL (AES-256-GCM + Ed25519-signed + BLAKE3-chain) │ ║
║ │ MemTable (BTreeMap, in-memory) │ ║
║ │ SSTables (AES-256-GCM + Merkle root + Bloom) │ ║
║ │ Compaction (merge + drop tombstones + purge TTL) │ ║
║ └──────────────────────────────────────────────────────┘ ║
║ ┌─ Audit log ─────────────────────────────────────────┐ ║
║ │ BLAKE3-chained JSONL · Ed25519-signed per entry │ ║
║ └──────────────────────────────────────────────────────┘ ║
╚══════════════════════╤═══════════════════════════════════════╝
│ WAL replication stream (over TLS)
╔════════╧════════╗
║ follower nodes ║ read-only replicas
╚═════════════════╝
── OR ──
│ Raft RPCs (separate port, TCP)
╔═════════════╪═════════════╗
║ node 1 │ node 2 ║ Raft cluster (automatic
║ (leader) │ (follower) ║ failover, no SPOF)
╚═════════════╧═════════════╝
/etc/machine-id ──┐
board UUID ──┤── BLAKE3 ──▶ 32-byte master key ──▶ AES-256-GCM
board serial ──┤
MAC address ──┘
No password. No key file. The database is permanently unreadable if moved to any other machine. Not even you can open it without the original hardware.
Every WAL frame and every SSTable block is encrypted with AES-256-GCM, BLAKE3-keyed MAC, and Ed25519-signed. A modified bit in any frame causes the database to refuse to open.
Ed25519 challenge-response on every connection. The server issues a fresh 32-byte nonce; the client signs it. No password ever touches the wire.
Signed capability tokens specify subject, scope, and expiry. The server verifies the signature and checks the scope on every single request. Tokens can be revoked instantly via the admin API.
TLS 1.3 via rustls. Optional mutual TLS (--client-ca) so the server also validates the client's certificate. Zero OpenSSL — the entire TLS stack is pure Rust.
// Server stores only this — it cannot decrypt it:
stored_key = BLAKE3(KEK, plaintext_key) // pseudorandom token
stored_value = nonce || AES-256-GCM(value, VEK) // opaque ciphertextEven with full root access, the server operator sees nothing. The two keys (KEK, VEK) are derived from master_secret using blake3::derive_key and never leave the client process.
# Copy and edit environment
cp .env.example .env
# Set ZB_PASSPHRASE and ZB_HTTP_KEY in .env
docker-compose up -dThe server listens on :7878 (wire protocol) and :8765 (HTTP metrics/admin).
git clone https://github.com/your-org/zerobase
cd zerobase
cargo build --release
# Binary locations:
# target/release/zerobased — the server daemon
# target/release/zerobase — the CLI clientRequirements: Rust 1.75+. No system dependencies. No OpenSSL.
# With passphrase
ZB_PASSPHRASE=mysecret zerobased \
--root /var/lib/zerobase \
--identity /etc/zerobase/server.identity \
--listen 0.0.0.0:7878
# With TLS
ZB_PASSPHRASE=mysecret zerobased \
--root /var/lib/zerobase \
--identity /etc/zerobase/server.identity \
--tls-cert /etc/zerobase/tls/server.crt \
--tls-key /etc/zerobase/tls/server.key# Create a database
ZEROBASE_PASSPHRASE=secret zerobase init --db ./mydb
# Write and read
ZEROBASE_PASSPHRASE=secret zerobase put --db ./mydb hello world
ZEROBASE_PASSPHRASE=secret zerobase get --db ./mydb hello
# → world
# Key with TTL (expires in 60 seconds)
ZEROBASE_PASSPHRASE=secret zerobase put --db ./mydb session:abc xyz --expires-in 60
# Scan by prefix
ZEROBASE_PASSPHRASE=secret zerobase scan --db ./mydb session:
# Rotate passphrase
ZEROBASE_PASSPHRASE=secret ZEROBASE_NEW_PASSPHRASE=newsecret zerobase rekey --db ./mydb
# Backup
ZEROBASE_PASSPHRASE=secret zerobase backup --db ./mydb ./mydb-backup
# Export / import (portable JSON snapshot)
ZEROBASE_PASSPHRASE=secret zerobase export --db ./mydb > snapshot.json
ZEROBASE_PASSPHRASE=newsecret zerobase import --db ./newdb snapshot.json# Generate an identity (one-time)
zerobase identity gen --path ./my.identity
# Put / get / scan / SQL against the server
zerobase remote put --server 127.0.0.1:7878 --identity ./my.identity users:alice '{"age":30}'
zerobase remote get --server 127.0.0.1:7878 --identity ./my.identity users:alice
zerobase remote scan --server 127.0.0.1:7878 --identity ./my.identity users:
zerobase remote sql --server 127.0.0.1:7878 --identity ./my.identity \
"SELECT * FROM users WHERE age > 25"use zerobase_auth::Identity;
use zerobase_caps::{Capability, CapabilityClaims, Scope};
use zerobase_client::Client;
let identity = Identity::generate();
let mut client = Client::connect("127.0.0.1:7878", &identity, caps)?;
client.kv_put(b"hello", b"world")?;
assert_eq!(client.kv_get(b"hello")?, Some(b"world".to_vec()));use zerobase_client::PrivateClient;
// master_secret never touches the network
let master_secret: [u8; 32] = derive_master_secret();
let inner = Client::connect("127.0.0.1:7878", &identity, caps)?;
let mut pc = PrivateClient::new(inner, &master_secret);
pc.kv_put(b"credit_card", b"4242-4242-4242-4242")?;
// Server stores: BLAKE3 token + AES ciphertext — nothing readable
let val = pc.kv_get(b"credit_card")?;
assert_eq!(val, Some(b"4242-4242-4242-4242".to_vec()));use zerobase_hwkey::derive_machine_key;
// Derived from: machine-id + board UUID + board serial + MAC address
let master_secret = derive_machine_key()?;
let mut pc = PrivateClient::new(inner, &master_secret);
// Database permanently bound to this hardwareuse zerobase_client::Follower;
use std::time::Duration;
let mut follower = Follower::connect("leader:7878", &identity, replica_caps)?;
let mut local_db = Db::open("/var/lib/zerobase-replica", &pass)?;
loop {
let applied = follower.sync(&mut local_db)?;
println!("Applied {applied} ops");
std::thread::sleep(Duration::from_secs(1));
}# Prometheus metrics
curl -H "Authorization: Bearer $ZB_HTTP_KEY" http://localhost:8765/metrics
# Health check
curl -H "Authorization: Bearer $ZB_HTTP_KEY" http://localhost:8765/health
# Revoke a capability token
curl -X POST -H "Authorization: Bearer $ZB_HTTP_KEY" \
-d '{"hash":"<64-hex-chars>"}' \
http://localhost:8765/revokeAvailable metrics:
zerobase_active_connections zerobase_connections_total
zerobase_ops_get_total zerobase_ops_put_total
zerobase_ops_delete_total zerobase_ops_scan_total
zerobase_ops_sql_total zerobase_tx_commit_total
zerobase_tx_rollback_total zerobase_errors_total
zerobase_sstable_count zerobase_memtable_bytes
zerobase_wal_bytes
| Crate | Role |
|---|---|
zerobase-core |
LSM-tree engine: WAL, MemTable, SSTables, compaction, Bloom filters |
zerobase-auth |
Ed25519 identities, challenge-response |
zerobase-caps |
Capability tokens: issue, verify, scope, revocation |
zerobase-proto |
Wire protocol: frame encoding, request/response types |
zerobase-sql |
SQL: CREATE TABLE, INSERT, SELECT, JOIN, WHERE, ORDER BY |
zerobase-server |
Daemon: TLS, sessions, audit log, HTTP API, replication |
zerobase-client |
SDK: plain + TLS + E2E PrivateClient + Follower |
zerobase-hwkey |
Hardware key: machine-id + DMI + MAC → 32-byte key |
zerobase-cli |
CLI: local DB access + remote server commands |
zerobase-raft |
Raft consensus: pure state machine, TCP transport, log compaction |
Alpha. The cryptographic design and storage engine are solid. The test suite has 94 tests covering unit, integration, crash-recovery, concurrency, edge cases, stress, and end-to-end scenarios — all passing.
What is ready:
- ✅ Storage engine (LSM-tree, fsync durability, crash recovery)
- ✅ Full crypto stack (AES-256-GCM, Ed25519, Argon2id, BLAKE3)
- ✅ Server-blind E2E encryption (PrivateClient)
- ✅ Hardware key derivation
- ✅ TLS 1.3 + mTLS
- ✅ Capability-based auth with revocation
- ✅ Tamper-evident audit log
- ✅ SQL layer (basic subset)
- ✅ Leader-follower replication
- ✅ Rate limiting, session timeout, max connections
- ✅ Backup, restore, export, import, key rotation
- ✅ Prometheus metrics
- ✅ Docker image + docker-compose
- ✅ GitHub Actions CI with security audit
- ✅ Raft consensus (election, log replication, persistence, log compaction)
- ✅ Cluster mode: writes routed through Raft, non-leader redirect with address hint
What is not yet ready for production:
- ⏳ Raft
InstallSnapshotRPC (followers very far behind must rejoin from scratch) - ⏳ Client SDKs in other languages (Python, Go, TypeScript)
- ⏳ Real-world battle testing
Do not use for production data without understanding these limitations.
Apache-2.0 — see LICENSE.