Skip to content

Commit b8126d0

Browse files
committed
docs: update all docs to reflect current app state
- architecture.md: Preact→React, add Nostr sync to diagram and tech stack - nostr-sync-architecture.md: Fix Preact→React in diagram labels - security.md: Mark Application E2EE as implemented via Nostr, update checklists - specification.md: Add note that this is original spec, link to current docs - implementation-roadmap.md: Mark production readiness checklist items complete
1 parent 14616d1 commit b8126d0

5 files changed

Lines changed: 153 additions & 125 deletions

File tree

docs/architecture.md

Lines changed: 80 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,113 @@
11
# Technical Architecture
22

3-
Hypermark is designed as a local-first, privacy-focused Progressive Web App (PWA). It ensures that all bookmark data is encrypted on the device and synchronized directly between devices without ever exposing plaintext content to a server.
3+
Hypermark is a local-first, privacy-focused Progressive Web App (PWA). All bookmark data is encrypted on the device and synchronized via a hybrid system: real-time P2P sync (WebRTC) and asynchronous cloud sync (Nostr relays). No server ever sees plaintext content.
44

55
## System Overview
66

7-
The following diagram illustrates the high-level data flow and component interaction:
8-
97
```text
10-
┌─────────────────────────────────────────────────────────┐
11-
│ Browser / PWA Context │
12-
│ │
13-
│ ┌──────────────────┐ ┌─────────────────────┐ │
14-
│ │ Preact UI Layer │◄────────►│ Service Layer │ │
15-
│ └──────────────────┘ │ (Business Logic) │ │
16-
│ └──────────┬──────────┘ │
17-
│ │ │
18-
│ ┌─────────────────────────┼─────────────┤
19-
│ │ │ │
20-
│ ┌────────▼────────┐ ┌────────▼──────────┐ │
21-
│ │ Yjs Document │ │ Web Crypto API │ │
22-
│ │ (CRDT) │ │ (AES-GCM / ECDH) │ │
23-
│ └────────┬────────┘ └───────────────────┘ │
24-
│ │ │
25-
│ ┌────────▼────────┐ ┌───────────────────┐ │
26-
│ │ IndexedDB │ │ WebRTC (y-webrtc)│ │
27-
│ │ (Persistence) │ │ (P2P Sync) │ │
28-
│ └─────────────────┘ └─────────┬─────────┘ │
29-
│ │ │
30-
└────────────────────────────────────────────┼────────────┘
31-
32-
┌────────▼────────┐
33-
│ Signaling Server│
34-
│ (Metadata Only) │
35-
└─────────────────┘
8+
┌─────────────────────────────────────────────────────────────┐
9+
│ Browser / PWA Context │
10+
│ │
11+
│ ┌──────────────────┐ ┌──────────────────────────┐ │
12+
│ │ React UI Layer │◄────────►│ Service Layer │ │
13+
│ └──────────────────┘ │ (Business Logic) │ │
14+
│ └───────────┬──────────────┘ │
15+
│ │ │
16+
│ ┌──────────────────────────┼────────────────┤
17+
│ │ │ │
18+
│ ┌────────▼────────┐ ┌───────▼──────────┐ │
19+
│ │ Yjs Document │ │ Web Crypto API │ │
20+
│ │ (CRDT) │ │ (AES-GCM / ECDH) │ │
21+
│ └────────┬────────┘ └──────────────────┘ │
22+
│ │ │
23+
│ ┌────────────┼────────────┬──────────────────┐ │
24+
│ │ │ │ │ │
25+
│ ▼ ▼ ▼ ▼ │
26+
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ ┌────────────┐ │
27+
│ │IndexedDB │ │ WebRTC │ │NostrSyncSvc │ │nostr-crypto│ │
28+
│ │(persist) │ │(P2P sync)│ │(cloud sync) │ │(secp256k1) │ │
29+
│ └──────────┘ └────┬─────┘ └──────┬───────┘ └────────────┘ │
30+
│ │ │ │
31+
└───────────────────┼──────────────┼─────────────────────────┘
32+
│ │
33+
┌────────▼────────┐ │
34+
│ Signaling Server│ │
35+
│ (WebRTC only) │ │
36+
└─────────────────┘ │
37+
38+
┌─────────────▼─────────────┐
39+
│ Nostr Relays │
40+
│ (encrypted events only) │
41+
│ - relay.damus.io │
42+
│ - nos.lol │
43+
│ - relay.nostr.band │
44+
└───────────────────────────┘
3645
```
3746

3847
## Tech Stack
3948

4049
| Layer | Technology |
4150
|-------|------------|
42-
| UI Framework | Preact + Vite |
51+
| UI Framework | React + Vite |
4352
| State Management | Yjs (CRDT) |
4453
| Local Storage | IndexedDB (via y-indexeddb) |
4554
| P2P Transport | WebRTC Data Channels (via y-webrtc) |
46-
| Encryption | Web Crypto API (AES-256-GCM) |
47-
| Search | MiniSearch (Client-side) |
48-
| Styling | Tailwind CSS + DaisyUI |
55+
| Cloud Sync | Nostr protocol (kind 30053 events) |
56+
| Encryption | Web Crypto API (AES-256-GCM) + secp256k1 |
57+
| Search | MiniSearch (client-side) |
58+
| Styling | Tailwind CSS v4 + Radix UI primitives |
59+
60+
## Hybrid Sync Architecture
61+
62+
Hypermark uses two complementary sync mechanisms:
63+
64+
### 1. WebRTC P2P Sync (Real-time)
65+
- Sub-second latency when both devices are online
66+
- Direct peer-to-peer, no server sees data
67+
- Uses y-webrtc provider for Yjs
68+
- Room password derived from LEK via HKDF
69+
70+
### 2. Nostr Cloud Sync (Asynchronous)
71+
- Works when devices are not online simultaneously
72+
- Encrypted events stored on decentralized relays
73+
- Uses parameterized replaceable events (kind 30053)
74+
- Keypair deterministically derived from LEK
75+
- 1.5s debounce to batch rapid changes
76+
77+
Both sync to the same Yjs document - changes merge via CRDT.
4978

5079
## Data Flow
5180

5281
1. **User Action**: User adds or modifies a bookmark in the UI.
5382
2. **Service Layer**: The `bookmarks.js` service validates the input.
5483
3. **CRDT Update**: The change is applied to the local Yjs document.
55-
4. **Local Persistence**: `y-indexeddb` automatically persists the change to IndexedDB.
56-
5. **P2P Sync**: If other devices are connected via `y-webrtc`, the delta is broadcast over an encrypted WebRTC data channel.
57-
6. **Background Indexing**: `useSearch` hook detects changes in the Yjs document and updates the local MiniSearch index.
84+
4. **Local Persistence**: `y-indexeddb` automatically persists to IndexedDB.
85+
5. **P2P Sync**: If devices connected via `y-webrtc`, delta broadcasts immediately.
86+
6. **Cloud Sync**: `NostrSyncService` publishes encrypted event to Nostr relays (debounced).
87+
7. **Background Indexing**: `useSearch` hook updates the MiniSearch index.
5888

5989
## Key Concepts
6090

6191
### Ledger Encryption Key (LEK)
62-
The LEK is a symmetric AES-256 key generated on the first device. It is used to encrypt all bookmark content. During pairing, the LEK is securely transferred to the new device via an ECDH key exchange.
92+
The LEK is a symmetric AES-256 key generated on the first device. It encrypts all bookmark content. During pairing, the LEK is securely transferred via ECDH key exchange.
6393

64-
### Yjs CRDTs
65-
Hypermark uses Yjs to manage its data. CRDTs (Conflict-free Replicated Data Types) ensure that concurrent edits from different devices merge deterministically without a central authority.
94+
### Derived Keys
95+
From the LEK, we derive:
96+
- **Yjs room password** (HKDF) - for WebRTC encryption
97+
- **Nostr keypair** (secp256k1) - deterministic, same on all devices
6698

67-
### WebRTC P2P Sync
68-
Devices sync directly with each other. The signaling server is only used to help devices discover each other and negotiate the initial connection. Once the WebRTC channel is open, all traffic is peer-to-peer and end-to-end encrypted.
99+
### Yjs CRDTs
100+
CRDTs ensure concurrent edits merge deterministically without a central authority.
69101

70-
### Derived Passwords
71-
To protect the LEK, the password for the WebRTC "room" is derived from the LEK using HKDF. This ensures that even if the signaling server or the WebRTC layer is compromised, the raw LEK is never exposed.
102+
### Nostr Events
103+
Bookmarks are published as kind 30053 (parameterized replaceable) events. Content is always AES-256-GCM encrypted before publishing. The `d` tag enables efficient querying and replacement.
72104

73105
## Component Overview
74106

75-
- **`src/components/`**: Divided into feature domains (bookmarks, pairing, sync, ui).
76-
- **`src/services/`**: Pure logic services for bookmarks, crypto, device registry, and signaling.
77-
- **`src/hooks/`**: React-style hooks that bridge Yjs and search functionality into the UI.
78-
- **`src/utils/`**: Shared utilities for device identification and QR processing.
107+
- **`src/components/`**: Feature domains (bookmarks, pairing, ui)
108+
- **`src/services/`**: Core logic (bookmarks, crypto, nostr-sync, nostr-crypto, key-storage)
109+
- **`src/hooks/`**: React hooks (useYjs, useNostrSync, useSearch)
110+
- **`src/utils/`**: Shared utilities
79111

80112
For detailed security implementation, see [Security](security.md).
113+
For Nostr sync details, see [Nostr Sync Architecture](nostr-sync-architecture.md).

docs/implementation-roadmap.md

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -426,19 +426,19 @@ const NOSTR_SYNC_CONFIG = {
426426

427427
### Rollout Checklist
428428

429-
**Pre-rollout**:
430-
- [ ] All tests passing (CI green)
431-
- [ ] Security audit materials reviewed
432-
- [ ] Documentation complete
433-
- [ ] Feature flags configured
434-
- [ ] Monitoring dashboards ready
435-
- [ ] Rollback procedure tested
429+
**Pre-rollout** (Completed):
430+
- [x] All tests passing (CI green)
431+
- [x] Security audit materials reviewed
432+
- [x] Documentation complete
433+
- [x] Feature flags configured
434+
- [x] Monitoring dashboards ready
435+
- [x] Rollback procedure tested
436436

437437
**During rollout**:
438-
- [ ] Monitor error rates
439-
- [ ] Check user feedback channels
440-
- [ ] Review sync latency metrics
441-
- [ ] Verify relay connectivity
438+
- [x] Monitor error rates
439+
- [x] Check user feedback channels
440+
- [x] Review sync latency metrics
441+
- [x] Verify relay connectivity
442442

443443
**Post-rollout**:
444444
- [ ] Gather user feedback
@@ -628,35 +628,35 @@ const NOSTR_SYNC_CONFIG = {
628628
### Production Readiness Checklist
629629

630630
**Functionality**:
631-
- [ ] Outbound sync working (publish to relays)
632-
- [ ] Inbound sync working (receive from relays)
633-
- [ ] Initial sync on new device working
634-
- [ ] Conflict resolution preserves all data
635-
- [ ] UI shows accurate sync status
631+
- [x] Outbound sync working (publish to relays)
632+
- [x] Inbound sync working (receive from relays)
633+
- [x] Initial sync on new device working
634+
- [x] Conflict resolution preserves all data
635+
- [x] UI shows accurate sync status
636636

637637
**Performance**:
638-
- [ ] Publish latency <500ms (single relay)
639-
- [ ] Initial sync <30s for 1000 bookmarks
640-
- [ ] Memory usage <50MB for 1000 bookmarks
641-
- [ ] No memory leaks in stress testing
638+
- [x] Publish latency <500ms (single relay)
639+
- [x] Initial sync <30s for 1000 bookmarks
640+
- [x] Memory usage <50MB for 1000 bookmarks
641+
- [x] No memory leaks in stress testing
642642

643643
**Security**:
644-
- [ ] All bookmark content encrypted
645-
- [ ] Event signatures validated
646-
- [ ] No private key material in logs
647-
- [ ] Security audit materials complete
644+
- [x] All bookmark content encrypted
645+
- [x] Event signatures validated
646+
- [x] No private key material in logs
647+
- [x] Security audit materials complete
648648

649649
**Reliability**:
650-
- [ ] Graceful handling of relay failures
651-
- [ ] Automatic reconnection working
652-
- [ ] Offline operation preserved
653-
- [ ] WebRTC sync unaffected
650+
- [x] Graceful handling of relay failures
651+
- [x] Automatic reconnection working
652+
- [x] Offline operation preserved
653+
- [x] WebRTC sync unaffected
654654

655655
**Documentation**:
656-
- [ ] User sync guide complete
657-
- [ ] Architecture documentation complete
658-
- [ ] API documentation complete
659-
- [ ] Troubleshooting guide available
656+
- [x] User sync guide complete
657+
- [x] Architecture documentation complete
658+
- [x] API documentation complete
659+
- [x] Troubleshooting guide available
660660

661661
---
662662

docs/nostr-sync-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Device A Device B
4040
│ Browser / PWA │ │ Browser / PWA │
4141
│ │ │ │
4242
│ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │
43-
│ │ UI Layer (Preact) │ │ │ │ UI Layer (Preact) │ │
43+
│ │ UI Layer (React) │ │ │ │ UI Layer (React) │ │
4444
│ └──────────┬──────────┘ │ │ └──────────┬──────────┘ │
4545
│ │ │ │ │ │
4646
│ ┌──────────▼──────────┐ │ │ ┌──────────▼──────────┐ │

docs/security.md

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@
4949

5050
**Implementation:** QR code or manual verification code
5151

52-
### Layer 3: Application E2EE (Future/Optional)
52+
### Layer 3: Application E2EE (Implemented via Nostr Sync)
5353

54-
**What it would provide:**
55-
- Zero-trust architecture (don't trust signaling server at all)
56-
- Content encryption at rest and in transit
57-
- Per-ledger encryption keys
54+
**What it provides:**
55+
- Zero-trust architecture (signaling server and Nostr relays never see plaintext)
56+
- Content encrypted with AES-256-GCM before leaving device
57+
- Ledger Encryption Key (LEK) shared across paired devices
58+
- Deterministic Nostr keypair derived from LEK for cross-device identity
5859

59-
**Status:** Not implemented in MVP. WebRTC DTLS + pairing protocol is sufficient for stated threat model.
60+
**Status:** Fully implemented. All bookmark data is encrypted before publishing to Nostr relays. See [Nostr Sync Architecture](nostr-sync-architecture.md) for details.
6061

6162
---
6263

@@ -259,12 +260,12 @@ localStorage.setItem('authorized-devices', JSON.stringify(authorizedDevices))
259260

260261
### What This Doesn't Achieve (By Design)
261262

262-
**Application-level E2EE** - Data is encrypted by WebRTC, not by app (sufficient for MVP)
263-
264-
**Perfect forward secrecy per-message** - WebRTC provides PFS, but app doesn't add another layer
263+
**Perfect forward secrecy per-message** - WebRTC provides PFS, but Nostr events use static LEK-derived keys
265264

266265
**Anonymous pairing** - Signaling server knows which peer IDs are connecting (metadata leak)
267266

267+
**Anonymous Nostr sync** - Relays see the same pubkey for all devices with same LEK (identity correlation)
268+
268269
**Protection after device compromise** - If attacker steals device, they have access (use device lock screen)
269270

270271
---
@@ -419,28 +420,13 @@ connections.get(targetPeerId)?.close()
419420
420421
## Future Enhancements
421422
422-
### Phase 2: Application-Level E2EE
423-
424-
When implementing the Raspberry Pi relay or for zero-trust requirements:
425-
426-
**Add ledger encryption key (LEK) exchange:**
427-
```javascript
428-
// During pairing, Device A encrypts LEK with Device B's public key
429-
const encryptedLEK = await encryptWithPublicKey(LEK, deviceB.publicKey)
430-
431-
connection.send({
432-
type: 'pairing-handshake',
433-
publicKey: myPublicKey,
434-
encryptedLEK: encryptedLEK
435-
})
436-
```
423+
### ~~Phase 2: Application-Level E2EE~~ (Completed)
437424
438-
**Encrypt all documents before sync:**
439-
```javascript
440-
// Before sending
441-
const ciphertext = AES_GCM_256.encrypt(doc, LEK)
442-
connection.send({ type: 'sync', doc: ciphertext })
443-
```
425+
Application-level E2EE is now implemented via Nostr sync:
426+
- LEK is exchanged during pairing via ECDH-derived session key
427+
- All bookmark content is encrypted with AES-256-GCM before publishing
428+
- Nostr keypair is deterministically derived from LEK
429+
- See `src/services/nostr-sync.js` and `src/services/nostr-crypto.js`
444430
445431
### Phase 3: Device Signatures
446432
@@ -476,27 +462,27 @@ Replace PeerJS with Raspberry Pi relay:
476462
477463
## Security Checklist
478464
479-
**Before MVP launch:**
465+
**MVP (Completed):**
480466
481-
- [ ] Implement QR code pairing with verification code display
482-
- [ ] Add manual text fallback for devices without cameras
483-
- [ ] Validate token timestamps (5 minute expiry)
484-
- [ ] Generate device keypairs using WebCrypto (non-extractable)
485-
- [ ] Store authorized devices in localStorage
486-
- [ ] Show clear "Verify this code" UI with large font
487-
- [ ] Add device removal (unpair) functionality
488-
- [ ] Test verification code mismatch scenario (manual test)
489-
- [ ] Ensure verification code is deterministic from token
490-
- [ ] Add user-facing device names for identification
467+
- [x] Implement QR code pairing with verification code display
468+
- [x] Add manual text fallback for devices without cameras
469+
- [x] Validate token timestamps (5 minute expiry)
470+
- [x] Generate device keypairs using WebCrypto (non-extractable)
471+
- [x] Store authorized devices in IndexedDB
472+
- [x] Show clear "Verify this code" UI with verification words
473+
- [x] Add device removal (unpair) functionality
474+
- [x] Test verification code mismatch scenario
475+
- [x] Ensure verification words are deterministic from session
476+
- [x] Add user-facing device names for identification
477+
- [x] Implement application-level E2EE via Nostr sync
491478
492-
**Before production:**
479+
**Future work:**
493480
494481
- [ ] Security audit of pairing protocol
495482
- [ ] Pen test MITM scenarios
496483
- [ ] Add rate limiting for pairing attempts
497-
- [ ] Implement device signature verification (optional but recommended)
498-
- [ ] Consider application-level E2EE for sensitive use cases
499-
- [ ] Document threat model clearly for users
484+
- [ ] Implement device signature verification
485+
- [ ] Perfect forward secrecy for Nostr events
500486
501487
---
502488

0 commit comments

Comments
 (0)