cloud_keys: device private key + master key live in the OS keychain
(keyring), namespaced per cloud account — never in the local DB. Headless
installs without a keychain get a hard error, not a plaintext fallback.
cloud_api: async httpx client for the bearer-key surface (devices,
account-key escrow, object push/commit, sync pull). Presigned blob
transfers use a separate unauthenticated client so the bearer key never
reaches the storage host.
cloud_account: the design-doc §9 flows — first-device setup mints MK +
recovery phrase (returned for one-time display), later devices register
and either adopt a wrapped MK provisioned by an existing device or
restore from the phrase. sync_device_id lands on cloud_settings with an
idempotent migration.
Tests run the real flows against an in-process fake cloud and assert the
master key and phrase never appear in any request body. A full round-trip
integration test (encrypt -> push -> pull -> decrypt -> tombstone) runs
against a live dev server when VOICEBOX_CLOUD_TEST_API/KEY are set.
Client-side half of the cloud backup/sync privacy model (cloud repo
docs/DESIGN.md): master key generation, BIP39 recovery phrase escrow
(Argon2id KEK), X25519 device keypairs with sealed-box MK wrapping, and
the VBX1 blob envelope — XChaCha20-Poly1305 under a per-blob content key
wrapped by MK, with AAD binding each blob to its (object, role, version)
slot so the server can't swap blobs undetected.
Pure functions over bytes, no I/O. Keychain persistence and the sync
engine come next. Deps: pynacl, mnemonic.