passnote 1.1.0
passnote: ^1.1.0 copied to clipboard
End-to-end encrypted, server-less, local-first CRDT sync engine for Dart: typed collections, queries, SQLite storage, device identity, sync protocol.
Changelog #
1.1.0 #
Security release; see the repository CHANGELOG.md for the summary and the
upgrade note (every device moves to 1.1 together).
- Wire version 2, and version 1 is no longer spoken. Sessions are rooted in
an ephemeral X25519 agreement signed by each device's roster identity
(
RatchetHelloMessagecarries the key, the device id and an Ed25519 signature), and the root isHKDF(groupKey ‖ agreement). With an identity configured, user-collection deltas, reconciliation and watermarks are accepted only inside a session; a frame under the passphrase key carries the handshake, ratchet hellos and the roster only. The handshake keeps frame version 1 so 1.0 peers reportPeerIncompatible. - Inbound frames are handled one at a time per peer, in arrival order.
PassnoteConfig.maxClockDrift(default 10 minutes): inbound documents with a record stamped further ahead are refused.- Roster: revocations judged in epoch order with the trust chain recomputed
after each; records signed by a revoked device no longer introduce anyone,
and
revokeDevicere-signs the records the revoked device last wrote; device ids must equal the fingerprint of their key; rotation rows arerotation:<epoch>:<device>, so concurrent rotations both survive; newRosterPinsCorruptExceptionwhen stored pins are unreadable. - Pairing: the grant carries the sponsor's trust pins
(
PairingBundle.rosterPins), adopted before the first sync; a grant without them is refused. Tries are budgeted 3 per peer and 20 per code, pending exchanges are kept per peer, and a joiner fails only when every greeted peer has refused. The channel key is HKDF of the PAKE output and the rendezvous tag a hash of the app id (no Argon2id runs per exchange). - A database auto-founds only when this device has never held trust pins for
it;
pairWithnever auto-founds. Passnote.forgetalso clears the key store entries of a database that is not open.engine.dartchanges:deriveRatchetRootnow takessharedSecret,ephemeralAandephemeralBinstead ofnonceAandnonceB; newRatchetEphemeral;newRatchetNonceis deprecated and unused.- New dependency:
cryptography(for the session's X25519).
1.0.0 #
First release.
- Typed collections over a CRDT store:
put,get,delete,watch, and indexed queries with keyset pagination and live watching. - Deterministic merging by hybrid logical clock, with per-field CRDT types
(
LwwRegister,OrSet,PnCounter) declared through a merge strategy. - A SQLite storage engine with SQLCipher encryption at rest (verified on open), transactional schema management, corruption recovery from periodic snapshots, and an in-memory engine for tests.
- A binary wire protocol (version 1) with a fail-closed decoder, streamed bounded sync frames, periodic anti-entropy, and range-fingerprint reconciliation that finds any gap in a few round trips.
- Device membership: a per-device keypair port, a synced device roster with Ed25519-signed rows, pairing over a PAKE code, revocation with group-key rotation, a forward-secrecy ratchet per connection, and an opt-in recovery phrase.
- Two library surfaces:
package:passnote/passnote.dart(frozen app-facing API) andpackage:passnote/engine.dart(advanced wiring, engine and transport implementations, conformance suites). SeeVERSIONING.md.