Order-preserving encryption — the classic way to run BETWEEN
on ciphertext — was broken by a decade of inference attacks, because
revealing order reveals nearly everything. blindrange gives up
server-side sorting and keeps only range filters, which is enough to
change the game: a range collapses into a handful of dyadic intervals, each
interval becomes a pseudorandom key via a PRF only the data owner can
compute, and the network answers exact-match lookups on opaque keys.
Nodes never see a comparison, an ordering, or a byte of plaintext.
AES-256-GCM covers the payloads; the owner decrypts and post-filters at
home.
# WHERE amount BETWEEN 250 AND 500 — on data the nodes can't read owner.query("amount", 25000, 50000) owner.query_prefix("name", "ac") # LIKE 'ac%' owner.query_multi([...]) # AND across fields owner.delete(rid); owner.compact() # real forgetting
I:39d8286d9d20f107ce3e03e74045ac44 → utqJUnFcSPs=
I:98a16f60dd371e5eff15b038b7acb55f → ZG32xdJmFUU=
R:b59b23f85755164b → eDF7iT+qCl4K2/LuQDdJv6…
no keys · no order · no equality · no co-occurrence · forward private
Because the owner walks the index (nodes never receive a label key), forward privacy comes from plain HMAC counter chains — no public-key crypto in the hot path. A full snapshot of every disk in the network is unlinkable pseudorandom pairs.
Membership is gossip; joining — as a node or a client — takes the address of any one live peer. Ed25519 identities make heartbeats unforgeable, and placement follows stable node ids, not addresses.
Nodes continuously re-push their keys to each key's current replica set. Data migrates to newcomers and replication recovers from churn with no owner involvement — proven by tests that kill every original holder.
Per-writer append-only chains make the index a grow-only CRDT. Sync is galloping probes against the network itself; counters are a cache, never a single point of failure.
LSM-style compaction merges streams, drops deleted entries for real, and is safe under concurrent writes via an open/drain/seal epoch protocol.
Every queryable-encryption scheme leaks something. Most products bury that. blindrange ships an attack harness that runs the real attacks from the literature against its own design and prints the numbers:
| What | Result, measured |
|---|---|
| Payload confidentiality | Holds unconditionally. A fully-colluding network holds keys it cannot link and blobs it cannot decrypt. |
| At rest | Zero structure — the snapshot equality attack that recovers value-groups from naive schemes finds nothing. |
| Query watcher | An adversary logging every query
can reconstruct a column's values over time —
bounded structurally by each field's leaf_width: no
observer ever resolves finer than the leaf, no matter how long it
watches. A privacy budget you cannot accidentally overspend. |
| Who it's for | Not healthcare, not high-stakes PII. Ordinary business data on infrastructure you don't fully trust — with the trade-offs in front of you, not in a footnote. |
# three nodes, one machine (or three machines — see README) pip install -e . && blindrange-node --port 7501 --data ~/.br/n1 --secret s blindrange-node --port 7502 --data ~/.br/n2 --seed 127.0.0.1:7501 --secret s blindrange-node --port 7503 --data ~/.br/n3 --seed 127.0.0.1:7501 --secret s # or the full sample app: encrypted orders, live network panel, # kill-a-node buttons, and a window into what nodes actually see python3 examples/webdemo/app.py # → http://127.0.0.1:8600