Threat model
BFT signing protects against compromised infrastructure:
An authenticated client can still send any op permitted by its token. BFT signing provides origin integrity, not access control — use permissions for that.
How it works
- On
MeridianClient.create()withsigning: true, the SDK loads (or generates) a per-client Ed25519 keypair fromlocalStorage - Every outgoing op is signed over its raw msgpack bytes (
op_bytes) before sending - The server verifies the signature against
client_pubkeyembedded in the token - Ops with an invalid or missing signature are rejected with HTTP 401
Quick start
1. Enable signing on the client
2. Register the public key when issuing a token
The token must embed the client’s public key before signing is enforced. This requires a two-step flow: Step A — get the client’s public key (before or after create, but before issuing the token):client_pubkey to Meridian:
client_pubkey is in the token, the server will require a valid signature on every op from that client.
3. Use the client normally
Signing is fully transparent —gcounter.increment(), lwwregister.set(), etc. work identically. Every op is signed automatically before sending. Ops queued while offline are signed on reconnect.
Keypair persistence
Keypairs are persisted inlocalStorage under two keys:
On next page load,
loadOrGenerateKeypair() reloads the same keypair — the client_pubkey embedded in the token remains valid across sessions.
If localStorage is unavailable (SSR, Node.js), a fresh keypair is generated per session and client.publicKeyBytes will differ each time. In that case, issue a new token with the updated client_pubkey on each session start.
Combining with E2E encryption
BFT signing and E2E encryption are independent features and compose freely:API reference
client.publicKeyBytes exposes the raw public key bytes after create() resolves. This is equivalent to keypair.publicKeyBytes and is null when signing is not enabled.