> ## Documentation Index
> Fetch the complete documentation index at: https://docs.terminal3.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Encryption

> Private data is always encrypted in transit and at rest

T3N implements robust encryption for both data in transit and data at rest, leveraging post-quantum cryptography, Trusted Execution Environments (TEEs), and industry-standard symmetric encryption algorithms.

## Encryption in transit

### Client-to-node traffic

T3N uses two layers of encryption to protect data in transit:

1. **HTTPS** protects the client connection to the deployment’s public load balancer.
2. Core **Session API** paths add application-layer encryption. The client and node establish a shared secret with ML-KEM-768, derive directional keys with HKDF-SHA256, and encrypt payloads with AES-256-GCM.

Once the ML-KEM public key and an expected runtime measurement obtained through a trusted, out-of-band channel have been verified, the application-layer channel limits plaintext exposure to the client and the TEE. Without that verification, the client only knows that it encrypted to the key returned by the node's `/status` endpoint.

<Note>
  In some cases (e.g., trusted B2B integrations), session-authenticated endpoints only rely on HTTPS and trusted party's specific authentication (e.g, HMAC, EOA signatures, and bearer tokens).
</Note>

### Handshake

An end-to-end encrypted channel is established using ML-KEM (FIPS 203):

```mermaid theme={null}
sequenceDiagram
    participant C as Client
    participant TEE as TEE Node

    C->>C: ML-KEM.encaps(trinity_public_key) → (K, ciphertext)
    C->>TEE: auth.handshake(ciphertext)
    TEE->>TEE: ML-KEM.decaps(secret_key, ciphertext) → K
    TEE->>TEE: Generate session_id, store {key: K, expiry}
    TEE-->>C: Session-Id header + expiry (TLS-protected)
```

The shared secret K is never transmitted: the client derives it during encapsulation and the node recovers it by decapsulating inside the TEE. The handshake response itself carries only the session identifier and expiry — a lookup handle with no key material — protected by HTTPS like the rest of the exchange.

The node does not contact other nodes during each client handshake to jointly decrypt the ML-KEM ciphertext. Instead:

1. During startup, the node participates in the threshold process and reconstructs/recovers the wrapping secret key.
2. It keeps that key inside the TEE.
3. For each handshake, it uses the already-recovered key locally.

“Threshold” protects key provisioning/recovery at startup, but handshake decapsulation itself is fast and node-local.

Both client and server derive two keys from the ML-KEM shared secret:

* `t3n-session-v1-c2s`: client encrypts, server decrypts.
* `t3n-session-v1-s2c`: server encrypts, client decrypts.

Using different keys for each direction prevents a sender from decrypting its own outgoing ciphertext with its receive key.

<Note>
  Sessions are node-affine: the node that handles the handshake stores the session state in memory, and later requests in that session must return to that node.
</Note>

### Requests and responses

For paths that use Session API application-layer encryption, every encryption operation:

1. Generate a fresh random 12-byte nonce
2. Encrypt the payload with AES-256-GCM
3. Prepend the nonce to the ciphertext
4. Verify the authentication tag during decryption

Changing the nonce, ciphertext, or authentication tag causes decryption to fail.

### Node-to-node traffic

Node-to-node traffic first uses libp2p Noise for an encrypted, peer-authenticated channel. T3N then applies a TEE protocol upgrade in which peers:

1. Exchange fresh challenges
2. Bind their libp2p identity to a TEE attestation quote
3. Verify the quote and approved TEE measurements
4. Compare a Keccak-256 digest of the cluster configuration

A peer is rejected when its quote matches no effective trusted measurement. The
effective set includes the node's own startup measurement, even when the configured allow-list is absent or empty. [Raft](/t3n/how-t3n-works/consensus) traffic and key-recovery messages use this attested channel.

## Encryption at rest

### Private ledger data

T3N stores application state in named key-value collections called **maps**. Every committed transaction is recorded in the replicated ledger as a set of changes to those maps. Each ledger entry separates these changes into:

* a **public domain**, containing changes to maps whose names start with `public:`
* a **private domain**, containing changes to all other maps

The private domain is encrypted using AES-256-GCM with the 32-byte ledger encryption key. Each committed entry is identified by its [Raft](/t3n/how-t3n-works/consensus) <Tooltip headline="Raft term" tip="The leader-election generation. Each new election increments it."> term</Tooltip> and log index. T3N encodes this pair into the entry’s unique 12-byte encryption nonce. Conceptually,

```
nonce = derive_nonce(term=7, index=42)
ciphertext = AES-256-GCM(ledger_secret, nonce, private_data, public_data)
```

The entry’s wire header and complete public domain are authenticated but not encrypted. They remain readable, but modifying either will cause authentication to fail when the private domain is decrypted.

Public maps are intentionally stored as plaintext in the replicated ledger. They must not contain personally identifiable information or other secrets.

### Private binary large objects (blobs)

Content addressable storage (CAS) can be configured for storing large binary objects:

1. Compute a SHA-256 content identifier (CID) from the plaintext;
2. Encrypt the blob with AES-256-GCM under the CAS content encryption key (CEK);
3. Store the blob under the CID-derived object key; and
4. Store a `ValueRef` in the KV map.

`ValueRef` records the CID, size, storage location, encryption status, and last-touch time. On read, T3N decrypts encrypted blobs and verifies that the plaintext hash matches the CID.

## Quick reference

| Area                         | Current implementation                                                                     |
| ---------------------------- | ------------------------------------------------------------------------------------------ |
| Client session establishment | ML-KEM-768                                                                                 |
| Session key derivation       | HKDF-SHA256 with separate client-to-server and server-to-client keys                       |
| Session payload              | AES-256-GCM with a fresh random 12-byte nonce per message                                  |
| Node-to-node transport       | libp2p Noise plus mutual Intel TDX attestation and a Keccak-256 configuration-digest check |
| Private data in the ledger   | AES-256-GCM                                                                                |
| Private blobs                | AES-256-GCM with a separate CAS Content Encryption Key (CEK), when encryption is enabled   |
