How it works
When you pass anencryption map to MeridianClient.create(), matching CRDT handles automatically:
- Encrypt the value with AES-GCM-256 before sending any op
- Decrypt incoming deltas before surfacing values to your app
Supported CRDT types
Quick start
1. Generate or import a key
2. Pass keys to the client
Theencryption config is a map from CRDT ID pattern (glob supported) to CryptoKey. Any handle whose crdtId matches a pattern uses that key.
3. Use handles normally
No API change —set(), value(), heartbeat(), online(), add(), remove(), addNode(), updateNode() all work identically. Encryption is transparent.
Key distribution
Meridian does not manage keys. You are responsible for distributing them to clients that need access. Common patterns:- Symmetric per-user key — derive a key from a password or passphrase using PBKDF2, store encrypted under the user’s auth token
- Per-room key — generate once, share via your app server’s invite flow (e.g. encrypted with the recipient’s public key)
- Key server — a separate service that issues keys after verifying access rights
Glob patterns
Theencryption map keys support a single * wildcard (same syntax as V2 permission patterns):
ORSet — deterministic encryption
Standard AES-GCM uses a random nonce for every encryption, which means encrypting the same element twice produces different ciphertexts. This would break ORSet: the server uses the element value as a map key, so two clients encrypting"alice" would create two unrelated server-side entries and remove() would only remove one of them.
Meridian solves this with deterministic nonce derivation: the nonce is the first 12 bytes of SHA-256(plaintext). Same element → same nonce → same ciphertext → same server key. Remove operations work correctly across all clients.
Security properties are preserved: different plaintext values produce different SHA-256 digests and thus different nonces, so there is no cross-value nonce reuse. The only information leaked is element equality (two clients that add the same value produce the same ciphertext), which is already implied by ORSet semantics.
Use encryptJsonDeterministic directly if you need this primitive outside of ORSet:
What the server can still see
Even with encryption enabled, the server observes:- CRDT structure — timestamps (HLC), client IDs, op ordering, number of ops
- Ciphertext size — reveals approximate plaintext length
- Access patterns — which clients write to which CRDTs and when