efema-proto 0.4.0

The wire format of efema: streams, positions, cursors and epochs as a client and a relay exchange them
Documentation

Your devices leave their changes for each other in one place: efema keeps them in order, hands each device what it missed, and cannot read any of it.

Why

A local-first app keeps your data on your device and works without a network. Then you install it on a second device, and the two need somewhere to leave their changes for each other. The usual answers are a sync service that reads everything it carries, or a database server that has to understand your data to store it.

efema is the third answer: a relay that knows nothing about your data, and a client library that seals every change on the device before it leaves. The relay keeps sealed entries in one order for everyone and hands each device what came after its cursor. It never merges, never decides who is right, and cannot read what it stores.

A day in the life

A relay, and a folder synced between a laptop and a desktop by mini-vault, the example app. Both edit plan.md before hearing from each other, and both versions survive:

$ mini-vault --relay http://127.0.0.1:2451 --folder ./laptop --state ./laptop-state sync --once
applied 0 change(s), sent 3
$ mini-vault --relay http://127.0.0.1:2451 --folder ./desktop --state ./desktop-state sync --once
applied 3 change(s), sent 0
$ mini-vault --relay http://127.0.0.1:2451 --folder ./desktop --state ./desktop-state sync --once
applied 0 change(s), sent 1
$ mini-vault --relay http://127.0.0.1:2451 --folder ./laptop --state ./laptop-state sync --once
applied 0 change(s), sent 2
conflict: both versions kept, the other one as notes/plan (conflict 4d3934d1).md
$ mini-vault --relay http://127.0.0.1:2451 --folder ./laptop --state ./laptop-state doctor
relay    http://127.0.0.1:2451 - answered in 3 ms
stream   vault - stream 9d43e105, epoch 1, 7 entries
cursor   at 5 - 2 entries to read
queue    empty
key      1005a7587958c346 - locked 2026-10-11 (today), argon2id 64 MiB × 3 × 4
device   22f164e5
epoch    1 - written, and read up to

What the relay's operator sees is positions, sizes and links - the first entry is the stream's key, locked under the passphrase; the rest is ciphertext:

$ efema-server streams vault --data ./relay
SEQ  EPOCH      SIZE  HASH
  1  1         172 B  64035340
  2  1         123 B  8e510f17
  3  1      47.0 KiB  001f9967
  4  1         139 B  4ddf0520
  5  1         199 B  d3ca7bbc
  6  1         183 B  3f14e4b4
  7  1         184 B  4bc3d32d

What you get

  • Sealed on the device. A client cannot be made without the stream's passphrase or key, and has no way to send an entry unsealed. The relay cannot read, forge or alter an entry, nor tell which device wrote it.
  • One order for everyone. Batches land whole, on consecutive positions, and every device reads the same sequence however many wrote at once.
  • Cursors that cannot lie. After a restore from an old backup, a device is told its copy and the relay's have parted - and never writes into a stream that is not the one it knew.
  • Epochs. An app that changed its format moves the stream on; older versions stop in front of the first newer entry instead of misreading it.
  • Delivery that survives a broken network. What a device pushes waits on it, sealed, until the relay has it - through a torn connection, a relay that is down, the app closing. A batch sent again after a lost answer lands once. A device's cursor moves only when the app says it applied what it read.
  • One doctor for every app built on it.

Install

The relay:

$ cargo install efema-server

Or download the archive for your platform - Linux on x86_64 and ARM64, Windows, macOS on Apple silicon - from the latest release.

The client, in a Rust app:

$ cargo add efema

Then Getting started for the relay, and Syncing an app for the client.

Documentation

The model, sealing, the threat model, every command and the protocol: lacodda.github.io/efema. The API: docs.rs/efema.

Status

0.4.0 is the relay and the client library: entries compressed and sealed on the device, kept there until the relay has them, and written once however often a broken network sends them again. Tests listed in the threat model show that the relay keeps nothing readable and that a torn connection loses and doubles nothing. Alongside ship two crates of the line, lacodda-seal and lacodda-tear. No access control and no TLS of its own yet: run the relay on loopback or behind a proxy. Changes: CHANGELOG.

Contributing

The gate, the layout and how commits are written: CONTRIBUTING.md.

License

MIT (c) Kirill Lakhtachev