Secure storage and distribution of cryptographic keys in ATProto applications
1# Client Implementation Strategy
2
3## Purpose
4
5This document outlines the design philosophy and implementation strategy for the ATP Keyserver client library. It serves as a roadmap for contributors and explains key architectural decisions.
6
7For protocol details, see [ENCRYPTION_PROTOCOL.md](./ENCRYPTION_PROTOCOL.md).
8For security guidelines, see [SECURITY.md](./SECURITY.md).
9For usage instructions, see [Client README](../packages/client/README.md).
10
11## Design Philosophy
12
13### Client-Side Encryption
14
15**Decision:** Cryptographic operations (encryption/decryption) happen on the client, not the server.
16
17**Rationale:**
18- **True end-to-end encryption**: Server never sees plaintext
19- **Better performance**: <1ms local crypto vs 100-500ms server round-trip
20- **Lower server load**: 50% less CPU, 97% less bandwidth
21- **Aligned with ATProto**: Decentralized architecture, user sovereignty
22- **Cost efficiency**: Scales with user devices, not server capacity
23
24**Trade-offs:**
25- Clients must handle cryptography correctly
26- Requires distributing crypto library (~52KB)
27- Key caching responsibility on client
28
29**Alternative considered:** Server-side encryption (rejected due to trust model and performance)
30
31### Single Package Architecture
32
33**Decision:** One npm package `@atpkeyserver/client` with optional imports via subpath exports.
34
35**Rationale:**
36- Crypto library (@noble/ciphers) is ~52KB and represents 90% of bundle
37- High-level client adds only ~8KB on top of crypto functions
38- Maintenance overhead of separate packages not justified by small size difference
39- Subpath exports allow tree-shaking for minimal bundle impact
40
41**Package structure:**
42```
43@atpkeyserver/client
44├── /crypto → Just crypto functions (52KB)
45├── /client → Just KeyserverClient (60KB, includes crypto)
46└── / (main) → Both exports (60KB, tree-shaken)
47```
48
49**Alternative considered:** Separate `@atpkeyserver/client-crypto` and `@atpkeyserver/client` packages (rejected due to minimal size benefit)
50
51### Service Auth Integration
52
53**Decision:** Client library abstracts service auth token management via callback pattern.
54
55**Rationale:**
56- PDS clients vary by platform (@atproto/api, custom implementations)
57- Callback pattern allows any PDS client integration
58- Client library handles token caching automatically
59- Separates concerns: client lib = crypto + keyserver API, user provides = PDS auth
60
61**Implementation:**
62```typescript
63new KeyserverClient({
64 keyserverDid: 'did:web:keyserver.example.com',
65 getServiceAuthToken: async (aud, lxm) => {
66 // User provides: obtain token from their PDS client
67 return await pdsClient.getServiceAuth({ aud, lxm })
68 }
69})
70```
71
72See [ENCRYPTION_PROTOCOL.md](./ENCRYPTION_PROTOCOL.md#authentication-flow) for service auth details.
73
74## Component Architecture
75
76### Core Components
77
78**1. Crypto Module (`crypto.ts`)**
79- XChaCha20-Poly1305 encryption/decryption
80- Uses @noble/ciphers library
81- Stateless, pure functions
82- No network dependencies
83
84**2. KeyserverClient (`client.ts`)**
85- HTTP client for keyserver API
86- Service auth token management
87- Key caching with TTL
88- Error handling and retries
89
90**3. Cache Module (`cache.ts`)**
91- In-memory LRU cache
92- Separate TTLs for active/historical keys
93- Service auth token caching
94- Automatic expiry
95
96**4. Service Auth Module (`service-auth.ts`)**
97- Token cache management
98- Expiry checking with safety margin
99- Automatic refresh logic
100
101**5. Types Module (`types.ts`)**
102- TypeScript interfaces
103- Configuration types
104- Response types
105
106**6. Errors Module (`errors.ts`)**
107- Custom error classes
108- Structured error handling
109- HTTP status code mapping
110
111### Dependency Graph
112
113```
114index.ts
115├── crypto.ts
116│ └── @noble/ciphers
117├── client.ts
118│ ├── crypto.ts
119│ ├── cache.ts
120│ ├── service-auth.ts
121│ ├── types.ts
122│ └── errors.ts
123├── types.ts
124└── errors.ts
125```
126
127## Key Features
128
129### Automatic Caching
130
131**Key Caching:**
132- Active keys: 1 hour TTL (may change with rotation)
133- Historical keys: 24 hour TTL (immutable)
134- LRU eviction when max size reached
135
136**Service Auth Token Caching:**
137- 60 second TTL (or server-specified `exp`)
138- Refresh when <10 seconds remain
139- Memory-only storage
140
141**Benefits:**
142- Reduces keyserver load
143- Improves client performance
144- Handles token refresh automatically
145
146See [SECURITY.md](./SECURITY.md#token-caching) for security considerations.
147
148### Request Deduplication
149
150Multiple simultaneous requests for the same key are collapsed into a single network request:
151
152```typescript
153// Both calls trigger only one network request
154const [key1, key2] = await Promise.all([
155 client.getGroupKey('did:plc:abc#followers'),
156 client.getGroupKey('did:plc:abc#followers')
157])
158```
159
160### Automatic Retry
161
162Network failures retry with exponential backoff:
163- Retry on: 5xx errors, network timeouts
164- Don't retry on: 4xx errors (client errors are permanent)
165- Max retries: 3 attempts
166- Backoff: 100ms, 200ms, 400ms
167
168### Type Safety
169
170Full TypeScript support:
171- Strict typing for all APIs
172- IntelliSense support
173- Compile-time error detection
174- Generated type definitions
175
176## Bundle Size Analysis
177
178Tree-shaking eliminates unused code. Actual bundle sizes depend on what you import:
179
180### Scenario 1: Crypto Functions Only
181
182```typescript
183import { encryptMessage, decryptMessage } from '@atpkeyserver/client/crypto'
184```
185
186**Bundled:** ~52KB (minified + gzipped ~15KB)
187- @noble/ciphers: 50KB
188- crypto.ts: 2KB
189
190**Use case:** You handle keyserver API calls manually, just need crypto.
191
192### Scenario 2: Full Client
193
194```typescript
195import { KeyserverClient } from '@atpkeyserver/client'
196```
197
198**Bundled:** ~60KB (minified + gzipped ~18KB)
199- @noble/ciphers: 50KB
200- Client code: 10KB (includes crypto, caching, HTTP, errors)
201
202**Use case:** Full-featured client with automatic caching and token management.
203
204### Scenario 3: Both
205
206```typescript
207import { KeyserverClient, encryptMessage } from '@atpkeyserver/client'
208```
209
210**Bundled:** ~60KB (same as scenario 2)
211- Client includes crypto, no duplication
212
213**Use case:** Mix high-level and low-level APIs as needed.
214
215### Bundle Optimization
216
217**Automatic:**
218- Tree-shaking removes unused exports
219- Minification reduces code size
220- Gzip compresses for network transfer
221
222**Manual optimization:**
223- Use subpath imports (`/crypto`, `/client`) for smaller bundles
224- Import only what you need
225- Modern bundlers (webpack, vite, rollup) handle this automatically
226
227**Conclusion:** Crypto library dominates bundle size. Single package vs separate packages saves only ~8KB, not worth maintenance complexity.
228
229## Implementation Phases
230
231### Phase 1: Foundation (Complete)
232
233- [x] Crypto functions (encryptMessage, decryptMessage)
234- [x] Type definitions
235- [x] Package structure with subpath exports
236- [x] Build configuration (ESM + CJS)
237
238### Phase 2: Client Library (Complete)
239
240- [x] KeyserverClient class
241- [x] Service auth token management
242- [x] Key caching with TTL
243- [x] HTTP client with retry logic
244- [x] Error handling
245- [x] Request deduplication
246
247### Phase 3: Documentation (Complete)
248
249- [x] Protocol specification (ENCRYPTION_PROTOCOL.md)
250- [x] Security guidelines (SECURITY.md)
251- [x] API reference (API_REFERENCE.md)
252- [x] Client README with usage examples
253- [x] Minimal example implementation (basic-usage.ts)
254
255### Phase 4: Testing
256
257- [ ] Unit tests for crypto functions
258- [ ] Integration tests for client
259- [ ] Mock keyserver for testing
260- [ ] E2E encryption flow test
261- [ ] Cache behavior tests
262- [ ] Service auth token refresh tests
263
264### Phase 5: Polish
265
266- [ ] Performance benchmarks
267- [ ] Bundle size analysis
268- [ ] Documentation review
269- [ ] Example applications
270- [ ] npm package publication
271
272## Success Metrics
273
274### Performance Targets
275
276| Operation | Target | Measurement |
277|-----------|--------|-------------|
278| Encryption | <1ms | Local crypto operation |
279| Decryption (cached key) | <5ms | Cache lookup + crypto |
280| Decryption (cache miss) | <200ms | Network fetch + crypto |
281| Service auth (cached) | <1ms | Cache lookup |
282| Service auth (cache miss) | <100ms | PDS round-trip |
283
284### Developer Experience
285
286- [ ] Complete TypeScript types
287- [ ] Comprehensive JSDoc comments
288- [ ] Working examples for 3+ platforms
289- [ ] <5 minutes from install to first encrypted message
290- [ ] Clear error messages with actionable guidance
291
292### Security
293
294- [ ] Zero plaintext exposure to server
295- [ ] Keys cached in memory only
296- [ ] Service auth tokens cached for max 60 seconds
297- [ ] Proper cleanup on logout
298- [ ] No key or token leakage in logs
299- [ ] Audience binding prevents token misuse
300- [ ] Short-lived tokens limit attack window
301
302### Code Quality
303
304- [ ] >80% test coverage
305- [ ] Zero runtime dependencies beyond @noble/ciphers
306- [ ] Tree-shakeable exports
307- [ ] CommonJS + ESM support
308- [ ] Works in Node.js, browser, React Native
309
310## Testing Strategy
311
312### Unit Tests
313
314**Crypto module:**
315- Encrypt/decrypt round-trip
316- Nonce uniqueness
317- AAD binding
318- Error handling
319
320**Cache module:**
321- TTL expiry
322- LRU eviction
323- Get/set operations
324- Clear operation
325
326**Service auth module:**
327- Token expiry checking
328- Refresh logic
329- Cache key generation
330
331### Integration Tests
332
333**Client:**
334- Key fetch with caching
335- Service auth integration
336- Retry logic
337- Error handling
338
339**E2E:**
340- Full encryption flow (PDS → keyserver → encrypt → store)
341- Full decryption flow (fetch → keyserver → decrypt → display)
342- Key rotation handling
343
344### Mock Server
345
346Build lightweight mock keyserver for testing:
347- Simulates auth verification
348- Returns mock keys
349- Configurable latency
350- Error injection
351
352## Open Questions
353
354### Rate Limiting
355
356**Question:** Should client enforce rate limits?
357
358**Options:**
3591. No client-side limits (rely on server)
3602. Simple debouncing (prevent rapid duplicate requests)
3613. Full rate limit tracking (mirror server limits)
362
363**Current decision:** Option 2 (debouncing via request deduplication)
364
365### Cache Persistence
366
367**Question:** Should keys be persisted to disk?
368
369**Options:**
3701. Memory-only (current implementation)
3712. Encrypted disk cache (requires key derivation)
3723. Platform-specific secure storage (iOS Keychain, Android KeyStore)
373
374**Current decision:** Memory-only for security and simplicity
375
376**Rationale:**
377- Simpler implementation
378- Better security (no disk persistence)
379- Works across all platforms
380- User can implement custom cache if needed
381
382### Error Recovery
383
384**Question:** How should clients handle persistent decryption failures?
385
386**Options:**
3871. Fail silently (hide post)
3882. Show error message
3893. Retry with exponential backoff
3904. Prompt user action (e.g., "Request access")
391
392**Current decision:** Show error message, no retry (decryption failures are permanent)
393
394## Related Documentation
395
396- [Encryption Protocol](./ENCRYPTION_PROTOCOL.md) - Protocol specification
397- [Security Best Practices](./SECURITY.md) - Security guidelines
398- [API Reference](./API_REFERENCE.md) - Complete endpoint reference
399- [Architecture](./ARCHITECTURE.md) - Server architecture
400- [Client README](../packages/client/README.md) - Usage instructions
401
402## Contributing
403
404Contributions welcome! Please:
405
4061. Read existing documentation
4072. Follow code style (Prettier)
4083. Add tests for new features
4094. Update documentation as needed
4105. Submit PR with clear description
411
412For questions, open an issue in the [Codeberg repository](https://codeberg.org/juandjara/atp-keyserver).