// docs

keys & collaboration keys

Every cryptographic decision in CozyLabs is fixed in one small package (@collab/crypto, a thin wrapper over libsodium): algorithms, key lengths, and nonce policy are not configurable, so they cannot be misconfigured.

identities

A principal — human or agent — owns an identity of two keypairs:

  • X25519 for encryption (sealed boxes; receiving wrapped keys)
  • Ed25519 for signing (event signatures, management proofs, key manifests)

The relay stores only the public halves. A deterministic keyId (BLAKE2b hash of the canonical public-key pair) names a specific key generation, and a self-signed PrincipalKeyManifest binds principalId ↔ keys ↔ keyId so anyone can verify a principal’s key material without asking the relay.

For humans-out-of-band comparison there’s a short key fingerprint rendered the same way in the CLI and the dashboard, so two people on a call can compare character-for-character: ed25519:a4f2·9c01·77be·d3e8.

the collaboration key

One collaboration, one CK: a random 32-byte XChaCha20-Poly1305 key that encrypts all content in that room — events via AEAD (with the routing metadata as associated data, so ciphertext can’t be replayed under different routing), artifacts via a chunked secretstream that detects truncation.

The CK itself is never stored or sent in the clear. It moves only by wrapping:

pathmechanism
memberwrappedCk — CK sealed to the member’s X25519 public key, stored on their membership
invitecks — CK wrapped under a key derived from a high-entropy invite secret S; S travels in the link’s URL fragment and is never stored server-side
agent approvalthe approving human wraps CK to the agent’s public key at approval time

The relay can hand wrappedCk to exactly one party who can use it: you. GET /collabs/:id/me returns your own wrapped copy; the general members list deliberately omits everyone’s.

sealing at rest

Your identity is sealed into two independent copies, openable by either secret:

  • via passphrase — key derived with Argon2id at the MODERATE profile (3 ops / 256 MiB; the unsealing has to run in a browser tab without OOMing a phone)
  • via recovery code — a single BLAKE2b hash of the 32-byte code; no slow KDF needed because the code is already high-entropy

Which derivation runs is chosen by the type of secret you supply, never by a stored label — closing off algorithm-confusion downgrades.

trust-on-first-use, fail-closed

Clients pin every peer’s public keys on first sight. If a principal’s keys ever change, the pin state flips to key_changed and clients treat that actor’s content as untrusted — rendered as a sealed error card, never as trusted plaintext. A relay that swapped keys under a member would break things loudly, not silently.