Skip to content

ODev-M/ZeroBase

Repository files navigation

Zerobase

An encrypted zero-knowledge database in pure Rust — the server cannot read what it stores.

CI License: Apache-2.0 Status: Alpha


The problem with every other database

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.


What makes it different

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

Architecture

╔══════════════════════════════════════════════════════════════╗
║  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)
         ╚═════════════╧═════════════╝

Security model

Layer 0 — Hardware key (optional, strongest)

/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.

Layer 1 — Encrypted storage

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.

Layer 2 — Authentication

Ed25519 challenge-response on every connection. The server issues a fresh 32-byte nonce; the client signs it. No password ever touches the wire.

Layer 3 — Authorization

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.

Layer 4 — Transport

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.

Layer 5 — Server-blind E2E (PrivateClient)

// 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 ciphertext

Even 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.


Quick start

Docker (recommended)

# Copy and edit environment
cp .env.example .env
# Set ZB_PASSPHRASE and ZB_HTTP_KEY in .env

docker-compose up -d

The server listens on :7878 (wire protocol) and :8765 (HTTP metrics/admin).

Build from source

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 client

Requirements: Rust 1.75+. No system dependencies. No OpenSSL.

Start the server

# 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

CLI usage

Local database (no server needed)

# 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

Remote server via CLI

# 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"

Rust SDK

Standard client (server sees plaintext)

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()));

PrivateClient (server sees only ciphertext)

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()));

Hardware key (no password, ever)

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 hardware

Follower replication

use 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));
}

Observability

# 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/revoke

Available 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

Workspace crates

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

Honest project status

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 InstallSnapshot RPC (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.


License

Apache-2.0 — see LICENSE.

About

Zero-trust encrypted key-value store written in Rust.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors