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.