botdb

docs / security

Threat model and cryptography

What botdb protects, what relays and folder hosts can see and do, and the cryptographic design behind it.

Summary

Assets and trust

Party Trusted with
Your devices Everything: plaintext data and keys.
Holders of your phrase Everything: the phrase decrypts the vault catalog, which holds every key.
Invited members Plaintext of that one database, including history. Writes only with a read-write invite.
Relays and folder hosts Availability only. They hold ciphertext.
The network Nothing. Use TLS for relays.

What a relay can see

A folder host (iCloud, Dropbox, and so on) sees the equivalent: file names (database ids, replica ids, sequence numbers and epochs), padded file sizes, modification times, and each snapshot’s .vec file, which lists in plaintext the sequence numbers it covers per replica.

What a relay can and can’t do

Action Possible? Notes
Read data No Batches and snapshots are encrypted with keys the relay never sees.
Modify, forge or re-label a batch No Authenticated encryption with the database id, replica id, sequence number and epoch as associated data.
Replay a batch into another database or position No The associated data wouldn’t match. Re-delivering the same batch has no effect.
Accept writes from read-only members No Pushes require a signature from the write key, which read-only invites don’t contain.
Accept writes from members removed by rotate-key No The old keys are no longer registered; their requests are rejected.
Withhold or delay batches Yes Gaps in a replica’s sequence numbers are detectable.
Delete everything Yes Every device has a full copy and pushes it again on the next sync.
Correlate activity Yes Timing, sizes and IP addresses are visible. Use a VPN or Tor if that matters.

Shared folders

A shared folder has no request signing, but that doesn’t widen who can write:

botdb cloud

botdb cloud is a relay like any other, run by us on Cloudflare. Everything above applies to it: it holds the same padded ciphertext and metadata a self-hosted relay would. In addition it keeps an account per vault (a random id, the hash of its token, the plan and an optional email) and which database ids belong to which account, to enforce plan limits. It never receives your recovery phrase, database keys or plaintext. If you’d rather not trust anyone with metadata, self-host a relay or sync through a folder; you can switch at any time with botdb remote add.

Local storage

Local copies are ordinary SQLite files in ~/.botdb/replicas/, readable by any process running as your user. botdb doesn’t encrypt them itself; protect them with full-disk encryption (FileVault, BitLocker, LUKS). On Unix the home directory is created with mode 0700 and config.json with 0600.

The vault key is stored in the OS keychain on macOS and Windows, or in ~/.botdb/vault.key (mode 0600) on other platforms, with BOTDB_KEYSTORE=file, or when writing to the keychain fails. With BOTDB_PHRASE, no key is stored; treat the environment of such machines as holding your phrase.

Automatic local snapshots (~/.botdb/backups/) are encrypted with each database’s own key.

Members and revocation

An invite contains the database’s keys. Once shared, it can’t be taken back. botdb rotate-key <db> starts a new key epoch:

Out of scope

Cryptography

Primitives

Purpose Algorithm
Encryption XChaCha20-Poly1305 with random 24-byte nonces
Key derivation HKDF-SHA256, salt botdb/v1
Request signing Ed25519 (strict verification)
Change signing Ed25519 over every sealed batch and snapshot
Size hiding PADMÉ padding
Recovery phrase BIP39, 24 words (256 bits)
Hashing SHA-256

Keys

recovery phrase (24 words)
└─ master secret (256 bits)
   ├─ catalog secret        HKDF "botdb/v1/catalog/{id,key,write}"
   │  └─ vault catalog: every database's secret and remotes
   └─ backup key            HKDF "botdb/v1/backup"         archives (botdb backup create)

database secret (random, per database, per epoch)
├─ root key
│  ├─ data key              HKDF "botdb/v1/data"           batches, snapshots, local snapshot files
│  └─ read signing key      HKDF "botdb/v1/read-auth"      Ed25519
└─ write secret             (omitted in read-only invites)
   └─ write signing key     HKDF "botdb/v1/write-auth"     Ed25519: relay writes and change signatures
      (its public key is in every invite, so read-only members can verify changes)

The catalog’s id and keys are derived from the master secret, so a new machine with only the phrase can find and decrypt it on a remote.

rotate-key generates a new root key and write secret. Root keys of earlier epochs are kept (and included in invites) so older history can still be decrypted locally, but replicas refuse new data sealed under an older epoch.

Encryption and binding

Every batch is padded, then encrypted with the data key for its epoch, then signed with the write key. What leaves the device is signature || nonce || ciphertext, where the signature covers "botdb/v1/sealed-sig" ‖ length of the associated data ‖ associated data ‖ nonce || ciphertext. Snapshots on remotes are sealed the same way. The associated data binds the ciphertext to its context:

Object Key Associated data
Batch data key "botdb/v1/batch" ‖ database id ‖ replica id ‖ sequence ‖ epoch
Snapshot on a remote data key "botdb/v1/snapshot" ‖ database id ‖ epoch
Local snapshot file data key "botdb/v1/backup" ‖ database id
Archive backup key "botdb/v1/archive"

Decryption fails if any part of the context differs, so a batch can’t be moved to another database, replica, position or epoch.

Padding

Plaintexts are terminated with a 0x80 byte and zero-padded to a PADMÉ length before encryption. PADMÉ leaks at most O(log log n) bits of the length and adds at most about 12% overhead.

Request signing

Relay requests are signed with Ed25519 over the method, path and query, a timestamp and the SHA-256 of the body. The relay rejects requests with timestamps more than 5 minutes from its clock. See Relay protocol.

Secrets in memory

Key material is zeroized when dropped.