Skip to main content

Encryption and Key Custody

toda uses envelope encryption. Each user has a random data key. The data key is wrapped by a key derived from the platform master key and the user ID.

MASTER_ENCRYPTION_KEY
-> HKDF-SHA256(userId, "toda:kek:v1")
-> per-user KEK
-> wrapped data key
-> encrypted memory bodies

Envelope model​

The data key is random and independent for each user. The key-encryption key (KEK) is bound to userId through the HKDF salt.

Moving a wrapped key to another user record causes AES-GCM authentication to fail.

Legacy rows with blobs of 32 bytes or less use the deprecated derived-key path.

Parameters​

ParameterValue
Body cipherAES-256-GCM
Key size256 bits
IV96 random bits per encryption
Authentication tag128 bits
KEK derivationHKDF-SHA256
HKDF infotoda:kek:v1
Key wrapAES-256-GCM

IVs must not be reused with the same key. The crypto package generates IVs internally and does not accept caller-provided values.

Enrollment​

The first successful sign-in creates a user data key:

upsertUser(db, publicKey, authorityPubkey, (userId) =>
wrapDataKey(masterKey, userId, generateDataKey()),
);

The unwrapped key is not written to the database or logs.

Who can decrypt​

The API can decrypt memory content. It holds the master key and unwraps the user data key for authenticated reads and recall.

toda is encrypted at rest, not end-to-end encrypted.

CompromiseResult
Database onlyEncrypted content is exposed
Database and master keyAll content is exposed
User sessionThat user's content is exposed through the API

Client-side encryption​

Clients that do not trust the operator can encrypt content before writing it.

const sealed = await myEncrypt(plaintext, myKey);
await toda.remember(sealed, { alg: "xchacha20-poly1305" });

Commitments and proofs continue to work over the ciphertext.

Tradeoffs:

  • server-side recall cannot search the content
  • ingestion providers cannot extract memory
  • the client must manage recovery and rotation

Non-sensitive search tags can be stored in metadata. Metadata remains visible to database readers.

Master key​

PropertyRequirement
VariableMASTER_ENCRYPTION_KEY
FormatBase64
Recommended size32 bytes
StorageSecrets manager

Generate a key with:

openssl rand -base64 32

Master-key rotation is not implemented. Replacing the key without rewrapping every user data key makes existing memory unreadable.

Key material at a glance​

KeyLocationAccess
Master encryption keyAPI environmentAll user data keys
User data keyAPI memoryOne user's content
JWT secretAPI environmentSession issuance
Anchor authorityAnchor serviceCommitment writes only
Wallet keyUser walletSign-in and stake control

Next: Authentication.