Encrypted, ephemeral, private memos on atproto

docs(claude): add CLAUDE.md

graham.systems 840d19e3 29199ed4

verified
Changed files
+241
+241
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with 4 + code in this repository. 5 + 6 + ## Commands 7 + 8 + ### Testing 9 + 10 + ```bash 11 + # Run all tests across the monorepo 12 + deno test --allow-env 13 + 14 + # Run tests for a specific package 15 + deno test packages/crypto/ 16 + 17 + # Run E2E tests (requires CISTERN_HANDLE and CISTERN_APP_PASSWORD environment variables) 18 + deno test --allow-env --allow-net e2e.test.ts 19 + ``` 20 + 21 + ### Lexicon Code Generation 22 + 23 + ```bash 24 + # Generate TypeScript types from JSON lexicon definitions 25 + cd packages/lexicon 26 + deno task generate 27 + ``` 28 + 29 + This generates types in `packages/lexicon/src/types/` from the JSON schemas in 30 + `packages/lexicon/lexicons/`. Run this after modifying any `.json` files in the 31 + lexicons directory. 32 + 33 + ### Type Checking 34 + 35 + ```bash 36 + # Deno executes TypeScript directly - no build step needed 37 + # Check types explicitly with: 38 + deno check <file.ts> 39 + ``` 40 + 41 + ## Architecture Overview 42 + 43 + Cistern is a **Deno monorepo** implementing a private, encrypted quick-capture 44 + system on AT Protocol. Items are end-to-end encrypted using post-quantum 45 + cryptography and stored temporarily in the user's PDS (Personal Data Server). 46 + 47 + ### Monorepo Structure 48 + 49 + Five packages with clear separation of concerns: 50 + 51 + - **`@cistern/crypto`** - Core cryptographic primitives 52 + (encryption/decryption/keys) 53 + - **`@cistern/lexicon`** - AT Protocol schema definitions (pubkey + item 54 + records) 55 + - **`@cistern/shared`** - Authentication utilities and common code 56 + - **`@cistern/producer`** - Creates and encrypts items for storage 57 + - **`@cistern/consumer`** - Retrieves, decrypts, and deletes items 58 + 59 + Internal imports use the `@cistern/*` namespace defined in each package's 60 + `deno.jsonc`. 61 + 62 + ### Producer/Consumer Pattern 63 + 64 + **Producer** workflow (packages/producer/mod.ts): 65 + 66 + 1. Select a public key from those registered in the user's PDS 67 + 2. Encrypt plaintext using the public key 68 + 3. Create an `app.cistern.lexicon.item` record with the encrypted payload 69 + 4. Upload to PDS 70 + 71 + **Consumer** workflow (packages/consumer/mod.ts): 72 + 73 + 1. Generate an X-Wing keypair (post-quantum) 74 + 2. Upload public key to PDS as `app.cistern.lexicon.pubkey` record 75 + 3. Keep private key locally (never uploaded) 76 + 4. Retrieve items via **polling** (`listItems()`) or **streaming** 77 + (`subscribeToItems()`) 78 + 5. Decrypt items matching the local keypair 79 + 6. Delete items after consumption 80 + 81 + ### Encryption Architecture 82 + 83 + **Algorithm**: `x_wing-xchacha20_poly1305-sha3_512` 84 + 85 + The encryption system uses a hybrid approach combining: 86 + 87 + - **X-Wing KEM** (Key Encapsulation Mechanism) - post-quantum hybrid combining 88 + ML-KEM-768 and X25519 89 + - **XChaCha20-Poly1305** - authenticated encryption cipher 90 + - **SHA3-512** - content integrity verification 91 + 92 + **Encryption flow** (packages/crypto/src/encrypt.ts): 93 + 94 + 1. X-Wing encapsulation generates a shared secret from the public key 95 + 2. XChaCha20-Poly1305 encrypts the plaintext using the shared secret 96 + 3. SHA3-512 hash computed for integrity verification 97 + 4. Returns `EncryptedPayload` containing ciphertext, nonce, hash, and metadata 98 + 99 + **Decryption flow** (packages/crypto/src/decrypt.ts): 100 + 101 + 1. X-Wing decapsulation recovers the shared secret using the private key 102 + 2. XChaCha20-Poly1305 decrypts the content 103 + 3. Integrity verification: check content length and SHA3-512 hash match 104 + 4. Returns plaintext or throws error if verification fails 105 + 106 + ### AT Protocol Integration 107 + 108 + Cistern uses two record types in the user's PDS: 109 + 110 + **`app.cistern.lexicon.pubkey`** 111 + (packages/lexicon/lexicons/app/cistern/lexicon/pubkey.json): 112 + 113 + - Stores public keys with human-readable names 114 + - Referenced by items via AT-URI 115 + - Schema: `{name, algorithm, content, createdAt}` 116 + 117 + **`app.cistern.lexicon.item`** 118 + (packages/lexicon/lexicons/app/cistern/lexicon/item.json): 119 + 120 + - Stores encrypted items temporarily 121 + - Schema: 122 + `{tid, ciphertext, nonce, algorithm, pubkey, payload, contentLength, contentHash}` 123 + - The `pubkey` field is an AT-URI reference to the public key record 124 + 125 + ### Real-time Streaming 126 + 127 + The consumer can subscribe to new items via **Jetstream** 128 + (packages/consumer/mod.ts:150-190): 129 + 130 + - Connects to Bluesky's Jetstream WebSocket service 131 + - Filters for `app.cistern.lexicon.item` creates matching user DID 132 + - Decrypts items as they arrive in real-time 133 + - Used for instant delivery (e.g., Obsidian plugin waiting for new memos) 134 + 135 + ### Key Management 136 + 137 + **Private keys never leave the consumer's device.** The security model depends 138 + on: 139 + 140 + - Private key stored off-protocol (e.g., in an Obsidian vault) 141 + - Public key stored in PDS as a record 142 + - Items encrypted with public key can only be decrypted by matching private key 143 + - Each keypair can have a human-readable name (e.g., "Work Laptop", "Phone") 144 + 145 + ### Dependencies 146 + 147 + **Cryptography** (JSR packages): 148 + 149 + - `@noble/post-quantum` - X-Wing KEM implementation 150 + - `@noble/ciphers` - XChaCha20-Poly1305 151 + - `@noble/hashes` - SHA3-512 152 + 153 + **AT Protocol** (npm packages): 154 + 155 + - `@atcute/client` - RPC client for PDS communication 156 + - `@atcute/jetstream` - Real-time event streaming 157 + - `@atcute/lexicons` - Schema validation 158 + - `@atcute/tid` - Timestamp identifiers 159 + 160 + ## Key Files and Locations 161 + 162 + ### Cryptographic Operations 163 + 164 + - `packages/crypto/src/keys.ts` - Keypair generation (X-Wing) 165 + - `packages/crypto/src/encrypt.ts` - Encryption logic 166 + - `packages/crypto/src/decrypt.ts` - Decryption + integrity verification 167 + - `packages/crypto/src/*.test.ts` - Crypto unit tests 168 + 169 + ### Producer Implementation 170 + 171 + - `packages/producer/mod.ts` - Main producer class and encryption workflow 172 + 173 + ### Consumer Implementation 174 + 175 + - `packages/consumer/mod.ts` - Keypair management, item retrieval, Jetstream 176 + subscription 177 + 178 + ### Authentication 179 + 180 + - `packages/shared/produce-requirements.ts` - DID resolution and session 181 + creation 182 + - Uses Slingshot service for handle → DID resolution 183 + - Creates authenticated RPC client with app password 184 + 185 + ### Schema Definitions 186 + 187 + - `packages/lexicon/lexicons/app/cistern/lexicon/*.json` - AT Protocol record 188 + schemas 189 + - `packages/lexicon/src/types/` - Generated TypeScript types (run 190 + `deno task generate` to update) 191 + - `packages/lexicon/lex.config.ts` - Lexicon generator configuration 192 + 193 + ## Important Patterns 194 + 195 + ### Error Handling in Decryption 196 + 197 + Decryption can fail for multiple reasons (packages/crypto/src/decrypt.ts): 198 + 199 + - Wrong private key (decapsulation fails) 200 + - Corrupted ciphertext (authentication fails) 201 + - Length mismatch (integrity check fails) 202 + - Hash mismatch (integrity check fails) 203 + 204 + Always wrap decrypt calls in try-catch and handle gracefully. 205 + 206 + ### Pagination in Consumer 207 + 208 + `listItems()` returns an async generator that handles pagination automatically. 209 + It yields decrypted items and internally manages cursors. Consumers should 210 + iterate with `for await` loops. 211 + 212 + ### Resource URIs 213 + 214 + AT Protocol uses AT-URIs to reference records: `at://<did>/<collection>/<rkey>` 215 + 216 + The consumer caches the public key's AT-URI with the local keypair to filter 217 + which items it can decrypt. 218 + 219 + ## Testing 220 + 221 + ### Unit Tests 222 + 223 + Each package contains unit tests following these conventions: 224 + - Test files use `.test.ts` suffix 225 + - Use `@std/expect` for assertions 226 + - Mock external dependencies (RPC clients, credentials) 227 + - Test both success and error paths 228 + 229 + **Test locations:** 230 + - `packages/crypto/src/*.test.ts` - Cryptographic operations 231 + - `packages/consumer/mod.test.ts` - Consumer functionality 232 + - `packages/producer/mod.test.ts` - Producer functionality 233 + 234 + ### End-to-End Tests 235 + 236 + `e2e.test.ts` contains integration tests that use real AT Protocol credentials: 237 + - Requires `CISTERN_HANDLE` and `CISTERN_APP_PASSWORD` environment variables 238 + - Tests full workflow: keypair generation, encryption, decryption, deletion 239 + - Uses Deno test steps to segment each phase 240 + - Automatically skipped if environment variables are not set 241 + - Cleans up all test data after execution