0xffd6563c…95fe·#25,427,466·view on Etherscan
# Seedlings — Encryption Spec (client-side; open-source + etched on-chain)
The exact, frozen recipe for reading/writing a Seedling diary. Per contract spec §6
this is **open-sourced from day one AND etched on-chain (SSTORE2)** alongside the
renderer, so any community app can read/write diaries and they stay decryptable even if
every frontend (including ours) disappears. **The chain carries the instructions for
reading itself.**
> ✅ **LOCKED (2026-06-20) — single launch scheme, post-quantum from day one.** Algorithm
> choices below are final, implemented in the **reference client** (`../reference-client/`,
> audited `@noble/*` primitives), and pinned with **test vectors**
> (`../reference-client/test/test-vectors.json`) the client reproduces byte-for-byte in CI,
> and **etched on-chain via SSTORE2** (`EncryptionSpec.sol`, with keccak integrity anchors).
> Nothing here runs on-chain — the chain only ever sees ciphertext + opaque wrapped-key blobs.
>
> **Post-quantum:** the only quantum-exposed surface is the asymmetric **key wrap** (§4), and
> its blobs are public-forever on-chain (worst case for "harvest-now-decrypt-later"). The wrap
> is therefore a **hybrid KEM = X25519 + ML-KEM-768 (FIPS 203)** whose wrap key binds BOTH
> shared secrets → secure as long as **either** primitive holds. Entry encryption (AES-256-GCM,
> §3) and `eraKey` derivation (HKDF, §1) are symmetric and were already post-quantum-safe →
> **unchanged**. There is no separate "v1/v2": this is the one scheme, with a `SCHEME` version
> byte reserved for any future migration.
>
> **Locked byte conventions (the parts a second implementer must not guess):**
> - **`SIG`** = the **65-byte** EIP-191 `personal_sign` signature (`r‖s‖v`, `v ∈ {27,28}`);
> `seedBits = keccak256(SIG)`.
> - **HKDF** = HKDF-SHA256 (extract+expand). String **salts/info are their UTF-8 bytes**;
> `cloneAddress` (as HKDF `info` for the era key, and as AAD) is the **20 raw address bytes**
> — never a hex string, never checksummed. Output length is 32 except the ML-KEM keygen seed
> (length **64**).
> - **AES-256-GCM**: 12-byte IV (`nonce`/`wn`), 16-byte tag appended after the ciphertext.
> - **X25519** (classical half): the 32-byte HKDF output is the scalar (clamped internally);
> `xPub = X25519_base(xPriv)`.
> - **ML-KEM-768** (PQ half, FIPS 203): keygen is **seeded** (64-byte HKDF output) so keys are
> stateless/re-derivable; sizes pk **1184**, ct **1088**, shared secret **32**, encaps msg **32**.
> - **`SCHEME` = `0x01`** is the leading version byte of both the published `pubKey` and the
> wrap `blob` (`0x01` = hybrid X25519+ML-KEM-768).
> - All `‖` are byte concatenations.
---
## 0. Model — one key PER OWNER ERA (not one key for the whole diary)
The diary's life is a sequence of **ownership eras** (owner A, then B, then C…),
delimited by the deed's `Transfer` events. **Each owner encrypts the entries they write
under their own `eraKey`.** This is the privacy backbone:
- An owner can **always re-read their own era's entries** (they can re-derive their
`eraKey` from their wallet signature) — even long after they've sold. *Their* words.
- An owner **cannot read another era's entries** unless that era's owner **hands them the
key**. In particular a past owner can **never** read a *future* owner's entries — they
never hold a later `eraKey`. Selling truly hands over the future; the past stays the
seller's to share or keep.
- The **current owner** reads/writes their own era, owns the token + art, and may
**receive past eras' keys** from past owners to read the diary's history.
(A naïve "one key for the whole diary" would let a past owner read the buyer's *future*
entries — a leak. Per-era keys close that.)
**Trust:** sharing the past is **opt-in and open-ended** (revelation-on-sale, §6) — any
past owner may hand their `eraKey` to the current owner **at any time**, or never. The
contract provides the rail; it never coerces and never sees plaintext or private keys.
**Phishing (documented):** the derivation signature **is** the key. Anyone who obtains it
reads that era. Mitigations: the signed message is a self-describing warning (below), it
is **never reused** for login/verification, and open-source clients are the audit surface.
## 1. Key derivation (client-side, deterministic, per era)
Each user derives, from one wallet signature:
- a **hybrid encryption keypair `EK`** (X25519 + ML-KEM-768) — its public half (`pubKey`) is
published on-chain so others can wrap keys to them;
- their **`eraKey`** for a given diary — the AES key for entries they write in it.
```
SIG = personal_sign(account, MESSAGE) // EIP-191
seedBits = keccak256(SIG) // 32 bytes
// classical half (X25519)
xPriv = HKDF-SHA256(seedBits, salt="seedling/ek/v1", info="x25519", 32) // clamp per X25519
xPub = X25519_base(xPriv)
// post-quantum half (ML-KEM-768, FIPS 203) — seeded keygen ⇒ stateless
kemSeed = HKDF-SHA256(seedBits, salt="seedling/ek/v1", info="ml-kem-768", 64)
(kemPub, kemPriv) = ML-KEM-768.KeyGen(kemSeed)
// published, version-tagged hybrid public key (this is what registerEncryptionPubKey stores)
pubKey = 0x01 ‖ xPub(32) ‖ kemPub(1184) // 1217 bytes
// per-diary symmetric era key (UNCHANGED — already PQ-safe)
eraKey = HKDF-SHA256(seedBits, salt="seedling/era/v1", info=cloneAddress, 32) // AES-256, per diary
```
`MESSAGE` (exact bytes, **never** reused elsewhere — frozen at v1):
```
Seedling diary key — v1
This signature IS the encryption key to your diary. Anyone who gets this
signature can read everything you write here, forever, and you cannot undo
it. Only sign this in the official or an open-source Seedling client you
trust. Never sign it to "verify", "log in", or "connect".
```
All of these are **stateless** — re-deriving from the same account (incl. ML-KEM's seeded
keygen) always yields the same keys; no key storage needed. `registerEncryptionPubKey(pubKey)`
publishes the version-tagged hybrid `pubKey` (once per user, updatable).
## 2. Lifecycle
- **Mint / first write:** owner A derives `eraKey_A` and encrypts their entries with it.
(No key blob needs committing for *their own* reading — they re-derive it. Blobs are
only for sharing with *others*.)
- **Sale (A → B):** on `KeyHandoffRequested`, A's client may **hybrid-wrap `eraKey_A` to B's
published `pubKey`** and `commitWrappedKey(B, blob)` — now and/or anytime later, A's choice. B
writes their own entries under `eraKey_B`.
- **Read (owner X):** the client lists entries from the logs, maps each to its era via
the `Transfer` timeline (entry timestamp ↔ which owner held the deed then), and decrypts
each with the matching `eraKey` **if X holds it** (their own, re-derived; others', via a
`KeyCommitted` blob X can unwrap). Eras X lacks keys for render as "still sealed by a
previous keeper."
- **Open-ended history:** any past owner may, at any future time, `commitWrappedKey` their
era key to whoever owns the diary then — retroactively unlocking that era for them.
## 3. Entry encryption (per entry, in the log payload)
```
nonce = 12 random bytes (per entry)
ct, tag = AES-256-GCM-Encrypt(key=eraKey_of_writer, iv=nonce, aad=AAD, plaintext=UTF8(text))
payload = 0x01 ‖ nonce(12) ‖ ct ‖ tag(16) // 0x01 = encrypted format/version
```
- `AAD = abi.encodePacked(cloneAddress)` (binds ciphertext to the diary).
- `contentHash` stored with the log = `keccak256(payload)` → feeds the on-chain rolling
`artSeed` integrity chain (§3.2). Integrity is era-independent (covers all entries).
- **Public entries** (`isPublic = true`): `payload = 0x00 ‖ UTF8(text)` (plaintext, never
encrypted), same 495-byte cap. The `0x00`/`0x01` version byte tells a reader whether to
decrypt; public entries are always readable by anyone, no key needed.
- **495-byte budget** is the *payload* length (~≤466 plaintext after the 29-byte AEAD
overhead). The contract caps `payload.length ≤ 495`.
## 4. Key wrapping (`eraKey` → a recipient) — hybrid KEM (X25519 + ML-KEM-768)
The wrap encapsulates to **both** halves of the recipient's `pubKey` and binds **both** shared
secrets into the wrap key, so it stays secure if **either** X25519 or ML-KEM-768 holds. The
combiner is an explicit application-layer HKDF (not a library "hybrid" module), so any second
implementer can reproduce it byte-for-byte.
```
xPub, kemPub = parse(recipient pubKey) // strip 0x01, split 32 ‖ 1184
eph_priv, eph_pub = X25519 keypair (random, per wrap)
ss_x = X25519(eph_priv, xPub) // classical shared secret (32)
ct_kem, ss_kem = ML-KEM-768.Encapsulate(kemPub) // PQ ciphertext (1088) + shared secret (32)
wrapKey = HKDF-SHA256( ikm = ss_x ‖ ss_kem, // secure if EITHER holds
salt = eph_pub ‖ ct_kem ‖ xPub ‖ kemPub, // bind both cts + both recipient keys
info = "seedling/wrap/v1", 32 )
wn = 12 random bytes
wct, wtag = AES-256-GCM-Encrypt(key=wrapKey, iv=wn, plaintext=eraKey)
blob = 0x01 ‖ eph_pub(32) ‖ ct_kem(1088) ‖ wn(12) ‖ wct(32) ‖ wtag(16) // 1181 bytes
```
Unwrap reverses it with the recipient's hybrid private key: `ss_x = X25519(xPriv, eph_pub)`,
`ss_kem = ML-KEM-768.Decapsulate(ct_kem, kemPriv)`, recompute `wrapKey`, AES-GCM-decrypt. The
sender never learns the recipient's private key; the recipient never reveals it. The blob
**self-validates** — a wrong recipient yields the wrong `wrapKey` (ML-KEM's implicit rejection
gives a junk `ss_kem` rather than an error), so the AES-GCM tag check simply fails. An
open-ended/permissionless commit is therefore safe; the on-chain gate only requires the
committer to be a past/current owner (to limit spam).
## 5. On-chain rail (what the contracts provide — contract spec §6)
| Piece | Where | Purpose |
|---|---|---|
| `registerEncryptionPubKey(bytes pubKey)` | mother | publish a user's 1217-byte hybrid `pubKey` (event `EncryptionPubKey`) |
| `wasEverOwner(tokenId, who)` | mother | gate: only a past/current owner may hand off a key |
| `KeyHandoffRequested(tokenId, from, to)` | clone (on transfer) | prompt the seller's client to re-wrap |
| `commitWrappedKey(forOwner, blob)` | clone | carry a wrapped `eraKey`; **blob persisted to SSTORE2** (state, not log) → returns + emits the pointer in `KeyCommitted(tokenId, forOwner, pointer, ts)`; open-ended |
| `keyBlobCount/keyBlobPointer/keyBlob(forOwner[,i])` | clone | read committed blobs back from state (eternal availability) |
The contracts **never validate** the crypto and never see plaintext/private keys — they
carry opaque blobs + signals. All correctness lives in the reference client + this spec.
Writing/growth is gated to the **current owner only**, separately — a key-handoff
permission never grants write access.
**Durability (why the blob lives in state, not a log):** a handoff may target a *future*
owner who only appears years later (open-ended history). For **encrypted** data the wrapped
key **is** availability — there is no plaintext archive to fall back on, so if the only
copy were an event it could be lost to EIP-4444 history expiry and that era would become
permanently unreadable. `commitWrappedKey` therefore writes the blob to **SSTORE2** (every
node keeps it forever, like the etched entries §3.4 and this spec itself); the
`KeyCommitted` event carries only the **pointer** as a cheap index. A reader fetches a
blob with `keyBlob(forOwner, i)` (or `eth_getCode(pointer)[1:]`) and unwraps it (§4)
entirely client-side. The blob byte format (§4) is unchanged.
## 6. Test vectors (✅ pinned)
Pinned in **`../reference-client/test/test-vectors.json`** and reproduced byte-for-byte by
the reference client (`npm test` in `reference-client/`). Covered:
- **`SIG → EK / eraKey` derivation** — two users (A, B) from fixed test private keys, each
signing the exact `MESSAGE`; pins `signature`, `seedBits`, `xPriv`, `xPub`, `kemPub`, the
version-tagged `pubKey`, and `eraKey`.
- **Entry encrypt/decrypt** with a known nonce → the full `0x01 ‖ nonce ‖ ct ‖ tag` payload
+ its `contentHash` (= `keccak256(payload)`), round-tripped.
- **Public-entry no-op** → `0x00 ‖ UTF8(text)`, decoded with no key.
- **`eraKey` hybrid wrap/unwrap** with fixed ephemeral key + ML-KEM encaps msg + nonce → the
1181-byte blob, and the revelation-on-sale flow (B unwraps A's era key, then reads A's entry).
- **Negatives** (the privacy guarantees): wrong era key fails; AAD binds to the diary (a
different `cloneAddress` fails); a stranger (wrong hybrid keypair) can't unwrap a blob wrapped
to B; the 495-byte payload cap is enforced.
Regenerate with `npm run vectors` (deterministic). ✅ **Etched on-chain:** `EncryptionSpec.sol`
SSTORE2-writes this doc **and** the vectors as contract code, with keccak integrity anchors,
deployed by `Deploy.s.sol` — the chain carries the instructions for reading itself.
{
"_spec": "ENCRYPTION_SPEC.md — hybrid X25519 + ML-KEM-768 (single launch scheme)",
"_warning": "TEST-ONLY keys/nonces. Never use for a real diary.",
"message": "Seedling diary key — v1\n\nThis signature IS the encryption key to your diary. Anyone who gets this\nsignature can read everything you write here, forever, and you cannot undo\nit. Only sign this in the official or an open-source Seedling client you\ntrust. Never sign it to \"verify\", \"log in\", or \"connect\".",
"cloneAddress": "0x00000000000000000000000000000000000000c1",
"derivation": {
"description": "SIG (65-byte EIP-191 personal_sign of `message`) → hybrid keys; pubKey = 0x01 ‖ xPub(32) ‖ kemPub(1184)",
"userA": {
"privateKey": "0x1111111111111111111111111111111111111111111111111111111111111111",
"signature": "0x30ee322abba50eafce6e0ee26170af995e2c6eaa9b75f4fe3abddfb8483ff78a5723863fc0a50a6f70a1a06b47594c1df30a1e40a2713fe494b122e10cb5093c1b",
"seedBits": "0xc40b58775af20626866a56543e825a1a7cc1260fbe6c41d42a4129e3e0520aa8",
"xPriv": "0xae0a5cf044a52600e8586223625d441d9481357595af70f19712506c83441ac1",
"xPub": "0x9257a06ecb98575fc619a6eea8301bba0dcfa86e3c1cd5128702f02409821773",
"kemPub": "0x8ba58ce7b29dd1562fbcc575ca969a13386d84b33cce8b6c6b9523b941c5be73938c502dfefac1949708644014d34260325379919c2f3ec1ada0f2ccdfc700ffe29a16ca2b08849eb4f14931d49590eb06f4b32bc040abd9032e25168707528425f9421a4bb1bb510546c9030130915280af212657732a24863b0b727a1b50ab320f7846abbb2e231b9c2818afb6ac1b8243b37759ae6c9a80efc12ae919a90a772f63f7addfc45b3865b1c6335c2a9b23e7782118b46c46786de18ab77a448f8b7a5e2ce3b27176050110547e19450dd27086f94f5ff887eac201ba394978ca8216b59dd7d0517ab57bd4039956572043a887a9ea4a1d2ba0d76bbfee268b098256150927dc9234982c431e2767bd3a1962da2b1f545f650440c8ca02a5336570c46855c296e9fb2cf51a5a13503c882279d0b968d8b1773a38615687addb78950f37617a635e71c299d44b52515a93246cb249d76f9bc010bd47b59bba1091b946834a8cebd986582061ebf21d73b69b8667b9ad2a0cd2a9530b9475f16b17e4f0c3d7e19bb0f547743cba7b543243d44e4b361a9386c36d47b999a177020215f10472791cb5073194e8345ae4874cdf172482f1625125b3d720cc1dfa9cc84a8905c723f4c6c531a83a4a8061fb0a2faa9032640bc1beb17cca50ca7cda1851766f27699140d6478b64c4d732aca47bb97aa0b23fdaa90abc131eb86a05c1a76cc71216c286fc92c68cdb8b1a875b06dac19c8ab229d07a6506b77442b047117d89a48e7b460f622765b9c464051a283b6449ebbb2eebe2aefdf802cd9abc70a092154362f69353b7f12afd7411ea84a981894a52559b5f0cc2a8f7974932bd002018a231c8e5b471edd14cf7610aee1a91cbeaaee4742db17678ce0a358b34b1565446e63894e5b17638a9a4443c2dc10764b056a929e489b7684acc83cfc3b8ba906a3879983658078739d22c7db69373629d1c13cb5e4c72ea699b9029bdfa6a2ace5baac8e646a93590488cbbc568325fd01bb03b21a245cf4f1637580b9fa021851d97b02a636e0f589adbba0b7669c59347b973d14d4b13a7dbe8116c840e260192e35373fe3094dfb553e869bf8b0876182985c7002ce7b21e3900be31ab04ea2004a8b5b4b152a2fcb28893046100902bc2002f19195e3ffb2c0d1b0347b961f757a94b3b7687362512f8873a2c1482395f5e485aa81c64f767b66c7ac86baca48a07ceb8a4188a6932199c7c96440104812c4b1026f11871de64ce7062435d4302d566ad9bdc8885e27227a4742a586266fa1d1ab66059f07f3002cfe71363f25c4f7a59bcebe025717c01a51079053baa6aeb2ccbc0bbc9866079934720161d736a6f931a90ad3a885a5ca935810a8fc2902cf57bf3f5344227c3074a4530768dda9bc8d387317159810577b5cbb384ab426840507d7302b7e3a21252573999fa941f9183c1a6b313d640f30601b55024999c6f5b5c9312dab9a6a85ac6b57f9ad4511618564c22b122167db4785ab011124d90b6fd956b8b453cc7221163a14cf94a919d63c2bed8606f9c38c734253d217b8d1cb89bb3b1b2e3cc87a34f4d2b5da935bc0db3bd40d1920e09bbbc03938f5b519d8383a167a200758284caff2161859ebfdca871be22e8a792f668b13473373bc9377a698abc",
"pubKey": "0x019257a06ecb98575fc619a6eea8301bba0dcfa86e3c1cd5128702f024098217738ba58ce7b29dd1562fbcc575ca969a13386d84b33cce8b6c6b9523b941c5be73938c502dfefac1949708644014d34260325379919c2f3ec1ada0f2ccdfc700ffe29a16ca2b08849eb4f14931d49590eb06f4b32bc040abd9032e25168707528425f9421a4bb1bb510546c9030130915280af212657732a24863b0b727a1b50ab320f7846abbb2e231b9c2818afb6ac1b8243b37759ae6c9a80efc12ae919a90a772f63f7addfc45b3865b1c6335c2a9b23e7782118b46c46786de18ab77a448f8b7a5e2ce3b27176050110547e19450dd27086f94f5ff887eac201ba394978ca8216b59dd7d0517ab57bd4039956572043a887a9ea4a1d2ba0d76bbfee268b098256150927dc9234982c431e2767bd3a1962da2b1f545f650440c8ca02a5336570c46855c296e9fb2cf51a5a13503c882279d0b968d8b1773a38615687addb78950f37617a635e71c299d44b52515a93246cb249d76f9bc010bd47b59bba1091b946834a8cebd986582061ebf21d73b69b8667b9ad2a0cd2a9530b9475f16b17e4f0c3d7e19bb0f547743cba7b543243d44e4b361a9386c36d47b999a177020215f10472791cb5073194e8345ae4874cdf172482f1625125b3d720cc1dfa9cc84a8905c723f4c6c531a83a4a8061fb0a2faa9032640bc1beb17cca50ca7cda1851766f27699140d6478b64c4d732aca47bb97aa0b23fdaa90abc131eb86a05c1a76cc71216c286fc92c68cdb8b1a875b06dac19c8ab229d07a6506b77442b047117d89a48e7b460f622765b9c464051a283b6449ebbb2eebe2aefdf802cd9abc70a092154362f69353b7f12afd7411ea84a981894a52559b5f0cc2a8f7974932bd002018a231c8e5b471edd14cf7610aee1a91cbeaaee4742db17678ce0a358b34b1565446e63894e5b17638a9a4443c2dc10764b056a929e489b7684acc83cfc3b8ba906a3879983658078739d22c7db69373629d1c13cb5e4c72ea699b9029bdfa6a2ace5baac8e646a93590488cbbc568325fd01bb03b21a245cf4f1637580b9fa021851d97b02a636e0f589adbba0b7669c59347b973d14d4b13a7dbe8116c840e260192e35373fe3094dfb553e869bf8b0876182985c7002ce7b21e3900be31ab04ea2004a8b5b4b152a2fcb28893046100902bc2002f19195e3ffb2c0d1b0347b961f757a94b3b7687362512f8873a2c1482395f5e485aa81c64f767b66c7ac86baca48a07ceb8a4188a6932199c7c96440104812c4b1026f11871de64ce7062435d4302d566ad9bdc8885e27227a4742a586266fa1d1ab66059f07f3002cfe71363f25c4f7a59bcebe025717c01a51079053baa6aeb2ccbc0bbc9866079934720161d736a6f931a90ad3a885a5ca935810a8fc2902cf57bf3f5344227c3074a4530768dda9bc8d387317159810577b5cbb384ab426840507d7302b7e3a21252573999fa941f9183c1a6b313d640f30601b55024999c6f5b5c9312dab9a6a85ac6b57f9ad4511618564c22b122167db4785ab011124d90b6fd956b8b453cc7221163a14cf94a919d63c2bed8606f9c38c734253d217b8d1cb89bb3b1b2e3cc87a34f4d2b5da935bc0db3bd40d1920e09bbbc03938f5b519d8383a167a200758284caff2161859ebfdca871be22e8a792f668b13473373bc9377a698abc",
"eraKey": "0xe3c8fbf51f290cc62603ab8fef847a08cd172fd9cd57179435ba9671ab5a0c58"
},
"userB": {
"privateKey": "0x2222222222222222222222222222222222222222222222222222222222222222",
"signature": "0x1e665df522a3c5d47200898f16f3bd0d79ade3abcc1bd53a74fbfba49c493bb554dc49145f404aa70d5bac41891bfd6a3c93160e6535e95e45ac73b9cf86c1f31c",
"seedBits": "0x048212e4f4056c25a07544fcabd39792e520f56762d40849fc9c1d6ab4ac8736",
"xPriv": "0x51a15d67d3adf63f7883ed0c64bf121179546a3c982f5921ea1e6a4dd8dc6f2d",
"xPub": "0x92ccc54d8600c7f19cf6e3364673d0e8e0cb78895b17599d1cf9edd0c25c453b",
"kemPub": "0xadd23581526d80911ae78b9f5df0521d0590046a73af152701cab464b30a0d91b4e0f778cf726cdffc73f6d1599a39732945b21c74648cfb28c5e62de25ac2fb83545ff3736a5068ca632a7d28820ce14c8ad1ae6b78011af55533713d74a9b9bc68af113076791c791c0076abb4217c218881e5b0cda69896765f00b131b5e22f5b33321c2b7057b85ce10266206b372758b987fc01dfc1770cdcbaf7b4944ea4458a2a615a3a842ab9983c830bdf198481437adc727a378abe56824338c3c72ea9a87fda3addfc583d6380b0b8354603647fd9215b2aa746b64659a82dee4a8dd91b0d56581424da22e2b18ff9382fef652356ca1203c7799a3206e53671f8b90df6823cf2a46dffa0aa8af6c4ffe67406abaaa8495ffcd1680b28248c92501ee370cb9bb69c9915ad7c6cf7733d1e14c95c11187fa69208497a61b5c0ea0c8d778a9362b1bcd7141fb18a3055ba8b26fc88c104240a467b8866a099d090accbb4c2a279b04b1f7201123f63ab10a0c0bc029f6308722b96761b0c079e6435fe0bc5c3226f23100ee8f6b620d21d2b8c0882c56f38001862b05a4686be627279bb997c3bd96ec2f042db60bf7fc427e7511454d49c60d23173dcc375a75bdf8397f1e24df4f28ea35ca840a2a7b0240b42c00114a600ffb00ebd2b204f148bbb31549459684fbc5ef0ca638d80b581027ff5ca9d4d66cb1467507dd53ecae303dae3ce244511c166b1131ba2129c8a50aa20b0284553fbc1acd47e9279052c36478397bb51fb01d52a27c0a3a88df728c709cbcd79b6fecc818d5bb9303a666986c4e7831f1be2a673b91b6e345c375ab7e1837abe25946543948c4233b926628856b1013093143431165b889cec63132946248b7fa54473682b9e7979632527879ec8b686bbc7381b48e7c3bccf2c74af067d86fb302a02cd3001063ba146bcfccb45272e7fc8715d8b5969f7bd5f4901f17ac611742e8159002299bed4db830e902a817a3cd3051ea38a6a08394593783eb580586e3b468bc51b47e712be1c36621908330c59cc3158045739efb787d3cabb56944ec7d82484ac9d992b44451c66ba7542eff11b0c8cbf72d95394755f33f8bdf6b79dfa27aa7e567fc7f93e4ba6cfc47a248c8aa2bf667f0b42b9f7d08acb0cadb914417c225290150c307c357707bf6b2880ebac9561604a33f782ebb4872802834b47abeed192b1290edf2b1c0de6618728473f6b9cb5eb4ffff168371935e37cbb2143998dea8347f05f264a20b7c576778635364cb03c7a86b8a95dc19083dd715111ba0ff8c7c39b3948811c09580bafa332af1354a857e85694359140fa1eaf3224ef65adc42109eef4a7f099c5521c1491b991c075429ae44ca569378cb33caa463afa117515562dedd2c2e782a4c08306e313460df251998bc1656883fe3852b9c2af7b1a3cc6d3032a8ab6956a42c2c6431172686b4337551cad2540af1df93560f74e211804d4a5ce64046f5d24857101c8a25c121724c6a3332034645d230a21303252831c3855a590586a2a22eb2519460b0e885f925b3dfca7cb023963084c3284b2a45a57341f4a1d8a6a69b6ba0d908a138d83942cfa1003fa745ac72d3f1bac5724bfa4fcee88f83d8cc089a44ebbd2d9813c68e11a129a3cc3683156ef152b7bf9ec",
"pubKey": "0x0192ccc54d8600c7f19cf6e3364673d0e8e0cb78895b17599d1cf9edd0c25c453badd23581526d80911ae78b9f5df0521d0590046a73af152701cab464b30a0d91b4e0f778cf726cdffc73f6d1599a39732945b21c74648cfb28c5e62de25ac2fb83545ff3736a5068ca632a7d28820ce14c8ad1ae6b78011af55533713d74a9b9bc68af113076791c791c0076abb4217c218881e5b0cda69896765f00b131b5e22f5b33321c2b7057b85ce10266206b372758b987fc01dfc1770cdcbaf7b4944ea4458a2a615a3a842ab9983c830bdf198481437adc727a378abe56824338c3c72ea9a87fda3addfc583d6380b0b8354603647fd9215b2aa746b64659a82dee4a8dd91b0d56581424da22e2b18ff9382fef652356ca1203c7799a3206e53671f8b90df6823cf2a46dffa0aa8af6c4ffe67406abaaa8495ffcd1680b28248c92501ee370cb9bb69c9915ad7c6cf7733d1e14c95c11187fa69208497a61b5c0ea0c8d778a9362b1bcd7141fb18a3055ba8b26fc88c104240a467b8866a099d090accbb4c2a279b04b1f7201123f63ab10a0c0bc029f6308722b96761b0c079e6435fe0bc5c3226f23100ee8f6b620d21d2b8c0882c56f38001862b05a4686be627279bb997c3bd96ec2f042db60bf7fc427e7511454d49c60d23173dcc375a75bdf8397f1e24df4f28ea35ca840a2a7b0240b42c00114a600ffb00ebd2b204f148bbb31549459684fbc5ef0ca638d80b581027ff5ca9d4d66cb1467507dd53ecae303dae3ce244511c166b1131ba2129c8a50aa20b0284553fbc1acd47e9279052c36478397bb51fb01d52a27c0a3a88df728c709cbcd79b6fecc818d5bb9303a666986c4e7831f1be2a673b91b6e345c375ab7e1837abe25946543948c4233b926628856b1013093143431165b889cec63132946248b7fa54473682b9e7979632527879ec8b686bbc7381b48e7c3bccf2c74af067d86fb302a02cd3001063ba146bcfccb45272e7fc8715d8b5969f7bd5f4901f17ac611742e8159002299bed4db830e902a817a3cd3051ea38a6a08394593783eb580586e3b468bc51b47e712be1c36621908330c59cc3158045739efb787d3cabb56944ec7d82484ac9d992b44451c66ba7542eff11b0c8cbf72d95394755f33f8bdf6b79dfa27aa7e567fc7f93e4ba6cfc47a248c8aa2bf667f0b42b9f7d08acb0cadb914417c225290150c307c357707bf6b2880ebac9561604a33f782ebb4872802834b47abeed192b1290edf2b1c0de6618728473f6b9cb5eb4ffff168371935e37cbb2143998dea8347f05f264a20b7c576778635364cb03c7a86b8a95dc19083dd715111ba0ff8c7c39b3948811c09580bafa332af1354a857e85694359140fa1eaf3224ef65adc42109eef4a7f099c5521c1491b991c075429ae44ca569378cb33caa463afa117515562dedd2c2e782a4c08306e313460df251998bc1656883fe3852b9c2af7b1a3cc6d3032a8ab6956a42c2c6431172686b4337551cad2540af1df93560f74e211804d4a5ce64046f5d24857101c8a25c121724c6a3332034645d230a21303252831c3855a590586a2a22eb2519460b0e885f925b3dfca7cb023963084c3284b2a45a57341f4a1d8a6a69b6ba0d908a138d83942cfa1003fa745ac72d3f1bac5724bfa4fcee88f83d8cc089a44ebbd2d9813c68e11a129a3cc3683156ef152b7bf9ec"
}
},
"entryEncrypt": {
"description": "AES-256-GCM private entry, fixed nonce → 0x01 ‖ nonce(12) ‖ ct ‖ tag(16)",
"eraKey": "0xe3c8fbf51f290cc62603ab8fef847a08cd172fd9cd57179435ba9671ab5a0c58",
"nonce": "0xa0a1a2a3a4a5a6a7a8a9aaab",
"plaintext": "Day one. Planted in the dark — let's see what you become. 🌱",
"payload": "0x01a0a1a2a3a4a5a6a7a8a9aaabced18fcacb8a9ba999e6d1a9667db881e96ae446be0c9f92cab1bedede33eecc07ec01356fb921be2e593939a96a90aeaa4beb8bd5dd8e4fb68c753a6176c0ce6a02cab8d23c639844952a2a32db837e",
"contentHash": "0x55f71a3d186225ba6dafa99534d936f130a6ff972ed6d1b03801defe36f58c7a"
},
"publicEntry": {
"description": "Plaintext public entry → 0x00 ‖ UTF8(text) (no key, no encryption)",
"plaintext": "A public note anyone can read.",
"payload": "0x0041207075626c6963206e6f746520616e796f6e652063616e20726561642e",
"contentHash": "0x00a8f6044f6757a0fa5d433f317503f1126da9cc0111eba4c5146157dd4ff9a5"
},
"keyWrap": {
"description": "Hybrid (X25519 + ML-KEM-768) wrap of A's eraKey to B → 0x01 ‖ eph_pub(32) ‖ ct_kem(1088) ‖ wn(12) ‖ wct(32) ‖ wtag(16)",
"recipientPubKey": "0x0192ccc54d8600c7f19cf6e3364673d0e8e0cb78895b17599d1cf9edd0c25c453badd23581526d80911ae78b9f5df0521d0590046a73af152701cab464b30a0d91b4e0f778cf726cdffc73f6d1599a39732945b21c74648cfb28c5e62de25ac2fb83545ff3736a5068ca632a7d28820ce14c8ad1ae6b78011af55533713d74a9b9bc68af113076791c791c0076abb4217c218881e5b0cda69896765f00b131b5e22f5b33321c2b7057b85ce10266206b372758b987fc01dfc1770cdcbaf7b4944ea4458a2a615a3a842ab9983c830bdf198481437adc727a378abe56824338c3c72ea9a87fda3addfc583d6380b0b8354603647fd9215b2aa746b64659a82dee4a8dd91b0d56581424da22e2b18ff9382fef652356ca1203c7799a3206e53671f8b90df6823cf2a46dffa0aa8af6c4ffe67406abaaa8495ffcd1680b28248c92501ee370cb9bb69c9915ad7c6cf7733d1e14c95c11187fa69208497a61b5c0ea0c8d778a9362b1bcd7141fb18a3055ba8b26fc88c104240a467b8866a099d090accbb4c2a279b04b1f7201123f63ab10a0c0bc029f6308722b96761b0c079e6435fe0bc5c3226f23100ee8f6b620d21d2b8c0882c56f38001862b05a4686be627279bb997c3bd96ec2f042db60bf7fc427e7511454d49c60d23173dcc375a75bdf8397f1e24df4f28ea35ca840a2a7b0240b42c00114a600ffb00ebd2b204f148bbb31549459684fbc5ef0ca638d80b581027ff5ca9d4d66cb1467507dd53ecae303dae3ce244511c166b1131ba2129c8a50aa20b0284553fbc1acd47e9279052c36478397bb51fb01d52a27c0a3a88df728c709cbcd79b6fecc818d5bb9303a666986c4e7831f1be2a673b91b6e345c375ab7e1837abe25946543948c4233b926628856b1013093143431165b889cec63132946248b7fa54473682b9e7979632527879ec8b686bbc7381b48e7c3bccf2c74af067d86fb302a02cd3001063ba146bcfccb45272e7fc8715d8b5969f7bd5f4901f17ac611742e8159002299bed4db830e902a817a3cd3051ea38a6a08394593783eb580586e3b468bc51b47e712be1c36621908330c59cc3158045739efb787d3cabb56944ec7d82484ac9d992b44451c66ba7542eff11b0c8cbf72d95394755f33f8bdf6b79dfa27aa7e567fc7f93e4ba6cfc47a248c8aa2bf667f0b42b9f7d08acb0cadb914417c225290150c307c357707bf6b2880ebac9561604a33f782ebb4872802834b47abeed192b1290edf2b1c0de6618728473f6b9cb5eb4ffff168371935e37cbb2143998dea8347f05f264a20b7c576778635364cb03c7a86b8a95dc19083dd715111ba0ff8c7c39b3948811c09580bafa332af1354a857e85694359140fa1eaf3224ef65adc42109eef4a7f099c5521c1491b991c075429ae44ca569378cb33caa463afa117515562dedd2c2e782a4c08306e313460df251998bc1656883fe3852b9c2af7b1a3cc6d3032a8ab6956a42c2c6431172686b4337551cad2540af1df93560f74e211804d4a5ce64046f5d24857101c8a25c121724c6a3332034645d230a21303252831c3855a590586a2a22eb2519460b0e885f925b3dfca7cb023963084c3284b2a45a57341f4a1d8a6a69b6ba0d908a138d83942cfa1003fa745ac72d3f1bac5724bfa4fcee88f83d8cc089a44ebbd2d9813c68e11a129a3cc3683156ef152b7bf9ec",
"ephPriv": "0x3333333333333333333333333333333333333333333333333333333333333333",
"kemMsg": "0x4444444444444444444444444444444444444444444444444444444444444444",
"wn": "0xb0b1b2b3b4b5b6b7b8b9babb",
"wrappedEraKey": "0xe3c8fbf51f290cc62603ab8fef847a08cd172fd9cd57179435ba9671ab5a0c58",
"blob": "0x017b0d47d93427f8311160781c7c733fd89f88970aef490d8aa0ee19a4cb8a1b14f046e419c8ce8e95f1cd51f6ff1acb9e1b20aa1dcf75831c3c91fa60318fb4ddfb1f9ab7f9aae04b930d0afeffe5a7921b06bdbe647322cd3867b56f09a8cbb08cf8f1a3295c4299920e35d5499c5344b6deea1fd8f2671607b8868a6822e72a5418703e033dc5da0776a3e96a967bb7b256e67d99737a7b8ea6cc0eb2d6fa3e216c3f3f7d538017221a2c1fe6b5d98fe974c5401a122d02dc9d36cdfa1a96efbaa0c9457aa66979684b604d49cd0d469ce1d42859447deb183ad38c317d634de17b3958384527ec83d3d025eab183906d963ece7e511fb0f3d48567918800f56868d1c9a19b687dda7f862e3a967f6feeede6d2dac134e5d7263b35fdd22923d9a0eab18443d6e5b5978094892c40942483bb55ce9af12dfcd4b6af222694bdcb5dc8a6532aec93fe4305794a6fd47e60c7216459cf30b4cf26a17c659fe0deb6a41eda7558ca42f0b05805b48f4551b34a667fc5ee4bb53cc66b15c24813a15cc1a9cdbcbd580eea3507140a23466f97dd1ebbf1ebc17e0947ec68d17d85a6b5a18e580a533238f517093d1415de2ebf17e2546b99ce3bf6c04f4d3736dc40e5325165ead40eda8b353cfff45081b85d5ba245d81e222c28abc9a31a19898ddd87fb6349a91d67c7f233a7c27f8a1d28e253db1408ddfb2f44fda3db9769ccac1915fd87efd2dd1daf021a401c6d87dd369d7605f35d184bfb794f6e165eeafd2822e2d3998ad93df92fdf40183b2a0dfc343d36f84167b387bb6380b24f8cdcd820d2deb9a56bbaa5344a80881b139c89560d921aa499bc505a77ce7aab31d51f1c55c402e765582bf9f8715584e67b66d96e1cba9d25d24efdf0120052f6c9d4df9c518923e9654f7fbe3a20837ec7961e1db6fa78bef9ec3e177cb385ee84a09663017eaf0b2c1b885047fdddae65f6e41f22430de0cf9723f54956428d0d85a86cec6bc077b80d1db247652d40c67f7bcf58734f141d1d35af96fcfbbd025960ed8895eb3297ff7dd385c8eb710418e1cefa442ab872a2be02b1db5fb52ef99061793863b8f8bd2cbd47cea2ff1d03720de4acb01045622fdd52788056ed4fcaace1e70e8074961584fee328404b5d2c0be2ffba93cf1a43be04cd6bb742447bd7054a66146c25ba8b4371034979eb1c3d021f3d57854a9a8bb37b8de28b943c3817f945fe52b3f5d6321c50e7aae7c0b1affd591109c358ba5ed299efe9832d763be93b37f076b8416249a3018bafebfa2e7c1a77b3a8ce1d7a7704507624419bd9e1f709aae501290e7a819c26ad1d19a61f4bcfdc552241e0afa84130780de070f76f6b8c583891ee0e2eafb784031f9fe7c4afb71eaddcf1616918bcdb26a82b4c4b99da62e5f7dff95da43a409d226268eb3aceb951d8d06f42c01fea5ec7e59b8ec26b80a9f44796b053254aaed7b8a0506932bc2d1b7102cb35b71f71bb0bb56727d0f72052c86f69b234750f9ede59be7222c5f8cbdc5d79dc9301b9ae61366c2e8add8b7af4b1113c73975a8f15c86b433d83892216b2d0acb0b1b2b3b4b5b6b7b8b9babbbeb037d5ca6e8b227461bf573bdee9efde925ced5447fea8b2c14255f0bb595bde639ee24507ffb831d196597b420dd9"
}
}