Phase 4: Encryption and Sealed Tesseras
-2026-02-14
-Some memories are not meant for everyone. A private journal, a letter to be -opened in 2050, a family secret sealed until the grandchildren are old enough. -Until now, every tessera on the network was open. Phase 4 changes that: Tesseras -now encrypts private and sealed content with a hybrid cryptographic scheme -designed to resist both classical and quantum attacks.
-The principle remains the same — encrypt as little as possible. Public memories -need availability, not secrecy. But when someone creates a private or sealed -tessera, the content is now locked behind AES-256-GCM encryption with keys -protected by a hybrid key encapsulation mechanism combining X25519 and -ML-KEM-768. Both algorithms must be broken to access the content.
-What was built
-AES-256-GCM encryptor (tesseras-crypto/src/encryption.rs) — Symmetric
-content encryption with random 12-byte nonces and authenticated associated data
-(AAD). The AAD binds ciphertext to its context: for private tesseras, the
-content hash is included; for sealed tesseras, both the content hash and the
-open_after timestamp are bound into the AAD. This means moving ciphertext
-between tesseras with different open dates causes decryption failure — you
-cannot trick the system into opening a sealed memory early by swapping its
-ciphertext into a tessera with an earlier seal date.
Hybrid Key Encapsulation Mechanism (tesseras-crypto/src/kem.rs) — Key
-exchange using X25519 (classical elliptic curve Diffie-Hellman) combined with
-ML-KEM-768 (the NIST-standardized post-quantum lattice-based KEM, formerly
-Kyber). Both shared secrets are combined via blake3::derive_key with a fixed
-context string ("tesseras hybrid kem v1") to produce a single 256-bit content
-encryption key. This follows the same "dual from day one" philosophy as the
-project's dual signing (Ed25519 + ML-DSA): if either algorithm is broken in the
-future, the other still protects the content.
Sealed Key Envelope (tesseras-crypto/src/sealed.rs) — Wraps a content
-encryption key using the hybrid KEM, so only the tessera owner can recover it.
-The KEM produces a transport key, which is XORed with the content key to produce
-a wrapped key stored alongside the KEM ciphertext. On unsealing, the owner
-decapsulates the KEM ciphertext to recover the transport key, then XORs again to
-recover the content key.
Key Publication (tesseras-crypto/src/sealed.rs) — A standalone signed
-artifact for publishing a sealed tessera's content key after its open_after
-date has passed. The owner signs the content key, tessera hash, and publication
-timestamp with their dual keys (Ed25519, with ML-DSA placeholder). The manifest
-stays immutable — the key publication is a separate document. Other nodes verify
-the signature against the owner's public key before using the published key to
-decrypt the content.
EncryptionContext (tesseras-core/src/enums.rs) — A domain type that
-represents the AAD context for encryption. It lives in tesseras-core rather than
-tesseras-crypto because it's a domain concept (not a crypto implementation
-detail). The to_aad_bytes() method produces deterministic serialization: a tag
-byte (0x00 for Private, 0x01 for Sealed), followed by the content hash, and for
-Sealed, the open_after timestamp as little-endian i64.
Domain validation (tesseras-core/src/service.rs) —
-TesseraService::create() now rejects Sealed and Private tesseras that don't
-provide encryption keys. This is a domain-level validation: the service layer
-enforces that you cannot create a sealed memory without the cryptographic
-machinery to protect it. The error message is clear: "missing encryption keys
-for visibility sealed until 2050-01-01."
Core type updates — TesseraIdentity now includes an optional
-encryption_public: Option<HybridEncryptionPublic> field containing both the
-X25519 and ML-KEM-768 public keys. KeyAlgorithm gained X25519 and MlKem768
-variants. The identity filesystem layout now supports node.x25519.key/.pub
-and node.mlkem768.key/.pub.
Testing — 8 unit tests for AES-256-GCM (roundtrip, wrong key, tampered -ciphertext, wrong AAD, cross-context decryption failure, unique nonces, plus 2 -property-based tests for arbitrary payloads and nonce uniqueness). 5 unit tests -for HybridKem (roundtrip, wrong keypair, tampered X25519, KDF determinism, plus -1 property-based test). 4 unit tests for SealedKeyEnvelope and KeyPublication. 2 -integration tests covering the complete sealed and private tessera lifecycle: -generate keys, create content key, encrypt, seal, unseal, decrypt, publish key, -and verify — the full cycle.
-Architecture decisions
--
-
- Hybrid KEM from day one: X25519 + ML-KEM-768 follows the same philosophy -as dual signing. We don't know which cryptographic assumptions will hold over -millennia, so we combine classical and post-quantum algorithms. The cost is -~1.2 KB of additional key material per identity — trivial compared to the -photos and videos in a tessera. -
- BLAKE3 for KDF: rather than adding
hkdf+sha2as new dependencies, we -useblake3::derive_keywith a fixed context string. BLAKE3's key derivation -mode is specifically designed for this use case, and the project already -depends on BLAKE3 for content hashing.
- - Immutable manifests: when a sealed tessera's
open_afterdate passes, the -content key is published as a separate signed artifact (KeyPublication), not -by modifying the manifest. This preserves the append-only, content-addressed -nature of tesseras. The manifest was signed at creation time and never -changes.
- - AAD binding prevents ciphertext swapping: the
EncryptionContextbinds -both the content hash and (for sealed tesseras) theopen_aftertimestamp -into the AES-GCM authenticated data. An attacker who copies encrypted content -from a "sealed until 2050" tessera into a "sealed until 2025" tessera will -find that decryption fails — the AAD no longer matches.
- - XOR key wrapping: the sealed key envelope uses a simple XOR of the content -key with the KEM-derived transport key, rather than an additional layer of -AES-GCM. Since the transport key is a fresh random value from the KEM and is -used exactly once, XOR is information-theoretically secure for this specific -use case and avoids unnecessary complexity. -
- Domain validation, not storage validation: the "missing encryption keys"
-check lives in
TesseraService::create(), not in the storage layer. This -follows the hexagonal architecture pattern: domain rules are enforced at the -service boundary, not scattered across adapters.
-
What comes next
--
-
- Phase 4 continued: Resilience and Scale — Shamir's Secret Sharing for heir -key distribution, advanced NAT traversal (STUN/TURN), performance tuning, -security audits, OS packaging -
- Phase 5: Exploration and Culture — Public tessera browser by -era/location/theme/language, institutional curation, genealogy integration, -physical media export (M-DISC, microfilm, acid-free paper with QR) -
Sealed tesseras make Tesseras a true time capsule. A father can now record a -message for his unborn grandchild, seal it until 2060, and know that the -cryptographic envelope will hold — even if the quantum computers of the future -try to break it open early.
- -