Add initial protocol specification
Documents the messagebox protocol with implementation-agnostic state machines for sender, store, and reader components.
This commit is contained in:
commit
184702057b
1 changed files with 801 additions and 0 deletions
801
SPECIFICATION.md
Normal file
801
SPECIFICATION.md
Normal file
|
|
@ -0,0 +1,801 @@
|
|||
# Anonymous Messaging System Specification
|
||||
|
||||
## System Overview
|
||||
|
||||
A secure anonymous messaging system where:
|
||||
- Visitors leave messages via web form (name + message)
|
||||
- Messages are cryptographically sealed in browser before transmission
|
||||
- Encrypted messages stored on semi-untrusted VPS
|
||||
- Only recipient can decrypt messages locally on their laptop
|
||||
- Message content is opaque to all intermediaries
|
||||
|
||||
---
|
||||
|
||||
## System Architecture
|
||||
|
||||
### Three Components
|
||||
|
||||
1. **Sender Client** - Browser-based message composition and encryption
|
||||
2. **Message Store** - VPS-hosted storage for encrypted messages
|
||||
3. **Reader Client** - Laptop-based decryption and archive
|
||||
|
||||
### Trust Model
|
||||
|
||||
```
|
||||
Sender Browser ─(sealed_box)─→ Message Store ─(sealed_box)─→ Reader Laptop
|
||||
↓
|
||||
Sees metadata only
|
||||
Cannot read content
|
||||
```
|
||||
|
||||
**Security Property:** Only holder of private key can decrypt message content.
|
||||
|
||||
---
|
||||
|
||||
## Core Data Structures
|
||||
|
||||
### Message Record
|
||||
```
|
||||
{
|
||||
message_id: UUID,
|
||||
sender_name: string, // PLAINTEXT
|
||||
created_at: timestamp, // PLAINTEXT
|
||||
key_id: string, // Which public key was used
|
||||
sealed_box: bytes // ENCRYPTED message content
|
||||
}
|
||||
```
|
||||
|
||||
### Public Key Record
|
||||
```
|
||||
{
|
||||
key_id: string,
|
||||
public_key: bytes,
|
||||
created_at: timestamp,
|
||||
status: ACTIVE | INACTIVE
|
||||
}
|
||||
```
|
||||
|
||||
### Key Pool Entry (Reader only)
|
||||
```
|
||||
{
|
||||
key_id: string,
|
||||
public_key: bytes,
|
||||
private_key: bytes,
|
||||
created_at: timestamp,
|
||||
status: ACTIVE | ARCHIVED
|
||||
}
|
||||
```
|
||||
|
||||
### Decrypted Message (Reader local storage)
|
||||
```
|
||||
{
|
||||
message_id: UUID,
|
||||
sender_name: string,
|
||||
sent_at: timestamp,
|
||||
message_body: string,
|
||||
retrieved_at: timestamp,
|
||||
decrypted_with: string // key_id used for decryption
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component 1: Sender Client
|
||||
|
||||
### State Machine
|
||||
|
||||
```
|
||||
States:
|
||||
IDLE
|
||||
READY
|
||||
COMPOSING
|
||||
ENCRYPTING
|
||||
SUBMITTED
|
||||
ERROR
|
||||
|
||||
Transitions:
|
||||
IDLE → READY (on load_public_key)
|
||||
READY → COMPOSING (on user_input)
|
||||
COMPOSING → ENCRYPTING (on submit)
|
||||
ENCRYPTING → SUBMITTED (on encryption_complete)
|
||||
SUBMITTED → IDLE (on confirmation)
|
||||
|
||||
* → ERROR (on any failure)
|
||||
ERROR → IDLE (on reset)
|
||||
```
|
||||
|
||||
### State Context
|
||||
|
||||
The sender state machine maintains:
|
||||
- **current_state**: One of {IDLE, READY, COMPOSING, ENCRYPTING, SUBMITTED, ERROR}
|
||||
- **public_key**: The active public key loaded from the message store
|
||||
- **plaintext**: Temporary storage for sender_name and message_body
|
||||
- **sealed_message**: Encrypted message ready for transmission
|
||||
|
||||
### State Transitions
|
||||
|
||||
#### IDLE → READY
|
||||
**Trigger:** `load_public_key(public_key)`
|
||||
|
||||
**Actions:**
|
||||
1. Store public_key in context
|
||||
2. Transition to READY state
|
||||
|
||||
**Postconditions:**
|
||||
- public_key is available for encryption
|
||||
- System ready to accept message composition
|
||||
|
||||
---
|
||||
|
||||
#### READY → COMPOSING
|
||||
**Trigger:** `compose_message(sender_name, message_body)`
|
||||
|
||||
**Actions:**
|
||||
1. Validate sender_name (non-empty, ≤200 chars)
|
||||
2. Validate message_body (non-empty, ≤10,000 chars)
|
||||
3. Store plaintext in context
|
||||
4. Transition to COMPOSING state
|
||||
|
||||
**Postconditions:**
|
||||
- plaintext message is in memory
|
||||
- Ready for encryption
|
||||
|
||||
---
|
||||
|
||||
#### COMPOSING → ENCRYPTING
|
||||
**Trigger:** `seal_message()`
|
||||
|
||||
**Actions:**
|
||||
1. Retrieve public_key from context
|
||||
2. Apply cryptographic seal: `sealed_box = seal(message_body, public_key)`
|
||||
3. Create sealed_message with:
|
||||
- sender_name (plaintext)
|
||||
- key_id (from public_key)
|
||||
- sealed_box (ciphertext)
|
||||
4. Destroy plaintext from memory
|
||||
5. Transition to ENCRYPTING state
|
||||
|
||||
**Postconditions:**
|
||||
- plaintext no longer in memory
|
||||
- sealed_message ready for transmission
|
||||
- Message content is cryptographically sealed
|
||||
|
||||
---
|
||||
|
||||
#### ENCRYPTING → SUBMITTED
|
||||
**Trigger:** `submit_to_store()`
|
||||
|
||||
**Actions:**
|
||||
1. Generate unique message_id (UUID)
|
||||
2. Capture current timestamp
|
||||
3. Construct MessageRecord:
|
||||
- message_id
|
||||
- sender_name
|
||||
- created_at
|
||||
- key_id
|
||||
- sealed_box
|
||||
4. Transmit MessageRecord to message store
|
||||
5. Wait for confirmation
|
||||
6. Transition to SUBMITTED state
|
||||
|
||||
**Postconditions:**
|
||||
- Message stored remotely
|
||||
- Confirmation received
|
||||
|
||||
---
|
||||
|
||||
#### SUBMITTED → IDLE
|
||||
**Trigger:** `reset()`
|
||||
|
||||
**Actions:**
|
||||
1. Clear sealed_message from memory
|
||||
2. Clear any remaining context
|
||||
3. Transition to IDLE state
|
||||
|
||||
**Postconditions:**
|
||||
- No message data retained
|
||||
- Ready for next message
|
||||
|
||||
---
|
||||
|
||||
#### Any State → ERROR
|
||||
**Trigger:** Any operation failure
|
||||
|
||||
**Actions:**
|
||||
1. Capture error details
|
||||
2. Transition to ERROR state
|
||||
3. Preserve context for debugging
|
||||
|
||||
**Recovery:** Manual reset to IDLE
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User Input (name, message_body)
|
||||
↓
|
||||
Store in memory as plaintext
|
||||
↓
|
||||
Load active public_key from store
|
||||
↓
|
||||
Encrypt: message_body + public_key → sealed_box
|
||||
↓
|
||||
Destroy plaintext from memory
|
||||
↓
|
||||
Create MessageRecord with sealed_box + metadata
|
||||
↓
|
||||
Transmit to Message Store
|
||||
↓
|
||||
Receive confirmation
|
||||
↓
|
||||
Reset state (sender retains nothing)
|
||||
```
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Message length:** Max 10,000 characters (reasonable essay length)
|
||||
- **Sender name:** Max 200 characters
|
||||
- **Memory safety:** Plaintext destroyed immediately after encryption
|
||||
- **No persistence:** Sender client stores nothing after submission
|
||||
|
||||
---
|
||||
|
||||
## Component 2: Message Store
|
||||
|
||||
### State Machine (per message)
|
||||
|
||||
```
|
||||
States:
|
||||
RECEIVED
|
||||
VALIDATED
|
||||
STORED
|
||||
REJECTED
|
||||
|
||||
Transitions:
|
||||
RECEIVED → VALIDATED (on validate_structure)
|
||||
VALIDATED → STORED (on persist)
|
||||
RECEIVED → REJECTED (on validation_failure)
|
||||
VALIDATED → REJECTED (on storage_failure)
|
||||
```
|
||||
|
||||
### State Context
|
||||
|
||||
The message store maintains:
|
||||
- **messages**: Collection of MessageRecord entries
|
||||
- **keys**: Collection of PublicKeyRecord entries
|
||||
|
||||
### Operations
|
||||
|
||||
#### receive_message(incoming_message)
|
||||
|
||||
**State Flow:** RECEIVED → VALIDATED → STORED (or REJECTED)
|
||||
|
||||
**Validation Phase (RECEIVED → VALIDATED):**
|
||||
1. Check sender_name: non-empty and ≤200 characters
|
||||
2. Check sealed_box: non-empty and ≤50KB
|
||||
3. Verify key_id exists in keys collection
|
||||
4. If any check fails: transition to REJECTED, return error
|
||||
|
||||
**Storage Phase (VALIDATED → STORED):**
|
||||
1. Generate unique message_id (UUID)
|
||||
2. Capture current timestamp
|
||||
3. Construct MessageRecord with all fields
|
||||
4. Add to messages collection
|
||||
5. Return message_id as confirmation
|
||||
|
||||
---
|
||||
|
||||
#### get_all_messages()
|
||||
|
||||
**Action:** Return all MessageRecord entries from messages collection
|
||||
|
||||
**Use Case:** Reader client batch retrieval
|
||||
|
||||
---
|
||||
|
||||
#### get_messages_since(timestamp)
|
||||
|
||||
**Action:** Return MessageRecord entries where created_at > timestamp
|
||||
|
||||
**Use Case:** Incremental message retrieval
|
||||
|
||||
---
|
||||
|
||||
#### delete_message(message_id)
|
||||
|
||||
**Actions:**
|
||||
1. Locate MessageRecord by message_id
|
||||
2. If not found: return error
|
||||
3. Remove from messages collection
|
||||
4. Return success
|
||||
|
||||
**Use Case:** Reader cleanup after successful decryption
|
||||
|
||||
---
|
||||
|
||||
#### add_public_key(public_key)
|
||||
|
||||
**Actions:**
|
||||
1. Find all keys with status=ACTIVE
|
||||
2. Update them to status=INACTIVE
|
||||
3. Generate new key_id
|
||||
4. Create PublicKeyRecord:
|
||||
- key_id
|
||||
- public_key
|
||||
- created_at (current timestamp)
|
||||
- status = ACTIVE
|
||||
5. Add to keys collection
|
||||
6. Return key_id
|
||||
|
||||
**Side Effect:** Only one key is ACTIVE at any time
|
||||
|
||||
---
|
||||
|
||||
#### get_active_key()
|
||||
|
||||
**Action:** Return PublicKeyRecord where status=ACTIVE
|
||||
|
||||
**Use Case:** Sender client retrieving current encryption key
|
||||
|
||||
---
|
||||
|
||||
#### get_all_keys()
|
||||
|
||||
**Action:** Return all PublicKeyRecord entries
|
||||
|
||||
**Use Case:** Reader client key synchronization
|
||||
|
||||
### Storage Schema
|
||||
|
||||
```
|
||||
messages: [MessageRecord]
|
||||
keys: [PublicKeyRecord]
|
||||
```
|
||||
|
||||
### Validation Rules
|
||||
|
||||
**On message submission:**
|
||||
- `sender_name`: non-empty, max 200 chars
|
||||
- `sealed_box`: non-empty, max 50KB
|
||||
- `key_id`: must exist in keys table
|
||||
|
||||
**On key addition:**
|
||||
- Authenticated request (recipient only)
|
||||
- Valid public key format
|
||||
- Automatically deactivates previous ACTIVE keys
|
||||
|
||||
### Data Visibility
|
||||
|
||||
**Store can see:**
|
||||
- Sender name (plaintext)
|
||||
- Timestamp (plaintext)
|
||||
- Which public key was used (key_id)
|
||||
- Number and size of messages
|
||||
|
||||
**Store cannot see:**
|
||||
- Message content (encrypted in sealed_box)
|
||||
- Decryption success/failure
|
||||
- Reader's retrieval patterns (stateless)
|
||||
|
||||
---
|
||||
|
||||
## Component 3: Reader Client
|
||||
|
||||
### State Machine (Batch Operation)
|
||||
|
||||
```
|
||||
States:
|
||||
IDLE
|
||||
FETCHING_BATCH
|
||||
DECRYPTING_BATCH
|
||||
SAVING_BATCH
|
||||
ERROR
|
||||
|
||||
Transitions:
|
||||
IDLE → FETCHING_BATCH (on fetch_messages)
|
||||
FETCHING_BATCH → DECRYPTING_BATCH (on messages_received)
|
||||
DECRYPTING_BATCH → SAVING_BATCH (on batch_decrypted)
|
||||
SAVING_BATCH → IDLE (on save_complete)
|
||||
|
||||
FETCHING_BATCH → ERROR (on network_failure)
|
||||
DECRYPTING_BATCH → IDLE (on partial_success, logs failures)
|
||||
SAVING_BATCH → ERROR (on storage_failure)
|
||||
ERROR → IDLE (on reset)
|
||||
```
|
||||
|
||||
### State Context
|
||||
|
||||
The reader client maintains:
|
||||
- **current_state**: One of {IDLE, FETCHING_BATCH, DECRYPTING_BATCH, SAVING_BATCH, ERROR}
|
||||
- **key_pool**: Collection of KeyPoolEntry (all historical private keys)
|
||||
- **local_archive**: Collection of DecryptedMessage entries
|
||||
- **undecryptable**: List of message_id values that failed decryption
|
||||
- **last_fetch**: Timestamp of most recent successful fetch
|
||||
|
||||
### State Transitions
|
||||
|
||||
#### IDLE → FETCHING_BATCH
|
||||
**Trigger:** `fetch_messages()`
|
||||
|
||||
**Actions:**
|
||||
1. Transition to FETCHING_BATCH state
|
||||
2. Request all MessageRecord entries from message store
|
||||
3. Receive batch of encrypted messages
|
||||
|
||||
**Error Handling:** Network failure → ERROR state
|
||||
|
||||
---
|
||||
|
||||
#### FETCHING_BATCH → DECRYPTING_BATCH
|
||||
**Trigger:** `decrypt_batch(messages)`
|
||||
|
||||
**Actions:**
|
||||
1. Transition to DECRYPTING_BATCH state
|
||||
2. For each MessageRecord in batch:
|
||||
- Call decrypt_single_message()
|
||||
- On success: add to decrypted list
|
||||
- On failure: add message_id to failed list
|
||||
3. Return BatchDecryptResult containing both lists
|
||||
|
||||
**Decryption Strategy (per message):**
|
||||
1. **Primary attempt:** Find key in pool matching message.key_id
|
||||
2. **Try unseal:** `unseal(sealed_box, private_key)`
|
||||
3. **If fails:** Iterate through all keys in pool
|
||||
4. **If any succeeds:** Return plaintext + key_id
|
||||
5. **If all fail:** Return decryption error
|
||||
|
||||
**Partial Success:** Successfully decrypted messages are saved; failures are logged
|
||||
|
||||
---
|
||||
|
||||
#### DECRYPTING_BATCH → SAVING_BATCH
|
||||
**Trigger:** `save_batch(result)`
|
||||
|
||||
**Actions:**
|
||||
1. Transition to SAVING_BATCH state
|
||||
2. Append all decrypted messages to local_archive
|
||||
3. Append all failed message_ids to undecryptable list
|
||||
4. Update last_fetch to current timestamp
|
||||
5. Persist to local storage
|
||||
|
||||
**Error Handling:** Storage failure → ERROR state
|
||||
|
||||
---
|
||||
|
||||
#### SAVING_BATCH → IDLE
|
||||
**Trigger:** `complete()`
|
||||
|
||||
**Actions:**
|
||||
1. Transition to IDLE state
|
||||
2. Ready for next fetch cycle
|
||||
|
||||
---
|
||||
|
||||
### Key Pool Operations
|
||||
|
||||
#### rotate_key()
|
||||
|
||||
**Actions:**
|
||||
1. Generate new cryptographic keypair (public_key, private_key)
|
||||
2. Find all keys in pool with status=ACTIVE
|
||||
3. Update them to status=ARCHIVED
|
||||
4. Generate new key_id
|
||||
5. Create KeyPoolEntry:
|
||||
- key_id
|
||||
- public_key
|
||||
- private_key
|
||||
- created_at (current timestamp)
|
||||
- status = ACTIVE
|
||||
6. Add to key_pool
|
||||
7. Publish public_key to message store (external call to add_public_key)
|
||||
|
||||
**Key Retention:** Archived keys are NEVER deleted (required for decrypting old messages)
|
||||
|
||||
---
|
||||
|
||||
#### sync_keys()
|
||||
|
||||
**Actions:**
|
||||
1. Fetch all PublicKeyRecord entries from message store
|
||||
2. Extract key_ids from local key_pool
|
||||
3. Identify remote keys not in local pool
|
||||
4. Return KeySyncReport listing missing private keys
|
||||
|
||||
**Use Case:** Detecting key pool desynchronization (e.g., backup restore scenario)
|
||||
|
||||
### Local Storage
|
||||
|
||||
```
|
||||
key_pool.json:
|
||||
[KeyPoolEntry, ...]
|
||||
|
||||
messages.json:
|
||||
[DecryptedMessage, ...]
|
||||
|
||||
state.json:
|
||||
{
|
||||
last_fetch: timestamp,
|
||||
undecryptable: [UUID, ...]
|
||||
}
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
Request all messages from Message Store
|
||||
↓
|
||||
Receive Vec<MessageRecord> (batch)
|
||||
↓
|
||||
For each message in batch:
|
||||
↓
|
||||
Find matching private_key by key_id
|
||||
↓
|
||||
Attempt decrypt with matched key
|
||||
↓
|
||||
If fail: try all keys in pool
|
||||
↓
|
||||
If success: add to decrypted list
|
||||
If fail: add to undecryptable list
|
||||
↓
|
||||
Save all decrypted messages to local archive
|
||||
↓
|
||||
Update state (last_fetch timestamp)
|
||||
↓
|
||||
Return to IDLE
|
||||
```
|
||||
|
||||
### Key Pool Management
|
||||
|
||||
**Key pool properties:**
|
||||
- Maintains ALL historical private keys (never deletes)
|
||||
- One key marked ACTIVE (for rotation operations)
|
||||
- Old keys marked ARCHIVED (still used for decryption)
|
||||
- Keys never removed (would make old messages undecryptable)
|
||||
|
||||
**Rotation process:**
|
||||
```
|
||||
Generate new keypair
|
||||
↓
|
||||
Add to local pool as ACTIVE
|
||||
↓
|
||||
Mark previous ACTIVE → ARCHIVED
|
||||
↓
|
||||
Publish new public_key to Message Store
|
||||
↓
|
||||
Store marks new key ACTIVE, old key INACTIVE
|
||||
↓
|
||||
Future messages encrypted with new key
|
||||
↓
|
||||
Old messages still decryptable with archived keys
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System-Wide Flows
|
||||
|
||||
### End-to-End Message Flow
|
||||
|
||||
```
|
||||
1. SENDER SIDE
|
||||
User enters name + message
|
||||
→ Sender loads active public key from store
|
||||
→ Sender seals message with public key
|
||||
→ Sender transmits sealed_box + metadata
|
||||
→ Sender destroys plaintext
|
||||
→ Sender receives confirmation
|
||||
|
||||
2. STORAGE
|
||||
Store receives MessageRecord
|
||||
→ Validates structure
|
||||
→ Persists to storage
|
||||
→ Returns message_id
|
||||
|
||||
3. READER SIDE
|
||||
Reader fetches all messages (batch)
|
||||
→ For each message:
|
||||
Try decrypt with key_id match
|
||||
Fallback to all keys in pool
|
||||
→ Save successful decryptions
|
||||
→ Log failed decryptions
|
||||
→ Update local state
|
||||
```
|
||||
|
||||
### Key Rotation Flow
|
||||
|
||||
```
|
||||
1. READER INITIATES ROTATION
|
||||
Generate new keypair
|
||||
→ Add to local key_pool (ACTIVE)
|
||||
→ Archive old keys (ARCHIVED)
|
||||
→ Publish new public_key to store
|
||||
|
||||
2. STORE UPDATES
|
||||
Receive new public_key
|
||||
→ Deactivate old keys (INACTIVE)
|
||||
→ Activate new key (ACTIVE)
|
||||
|
||||
3. CONCURRENT SENDERS
|
||||
Sender A: fetched old key before rotation
|
||||
→ Encrypts with old key
|
||||
→ Reader still has old private key (ARCHIVED)
|
||||
→ Decryption succeeds
|
||||
|
||||
Sender B: fetches new key after rotation
|
||||
→ Encrypts with new key
|
||||
→ Reader has new private key (ACTIVE)
|
||||
→ Decryption succeeds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases & Failure Modes
|
||||
|
||||
### Undecryptable Messages
|
||||
|
||||
**Causes:**
|
||||
- Message encrypted with unknown key_id
|
||||
- Corrupted sealed_box during transmission
|
||||
- Key rotation timing edge case
|
||||
- Malicious tampering
|
||||
|
||||
**Handling:**
|
||||
- Add message_id to undecryptable list
|
||||
- Preserve raw sealed_box for manual inspection
|
||||
- Periodic retry (in case missing key added later)
|
||||
- Log for debugging
|
||||
|
||||
### Key Sync Mismatch
|
||||
|
||||
**Scenario 1: Store has key X, Reader doesn't**
|
||||
```
|
||||
Reader fetches messages encrypted to X
|
||||
↓
|
||||
Decryption fails (no matching private key)
|
||||
↓
|
||||
Reader calls sync_keys()
|
||||
↓
|
||||
Discovers missing private key for X
|
||||
↓
|
||||
Flags for manual intervention
|
||||
```
|
||||
|
||||
**Scenario 2: Reader has key Y, Store doesn't**
|
||||
```
|
||||
No impact
|
||||
↓
|
||||
Key Y is historical/archived
|
||||
↓
|
||||
No new messages encrypted to Y
|
||||
↓
|
||||
Reader keeps Y for old messages
|
||||
```
|
||||
|
||||
### Store Compromise
|
||||
|
||||
**Attacker gains access to VPS:**
|
||||
|
||||
**Can:**
|
||||
- Read all metadata (sender names, timestamps)
|
||||
- See all sealed_box ciphertexts (useless without private keys)
|
||||
- Delete messages (availability attack)
|
||||
- Serve malicious public key (MITM future messages)
|
||||
|
||||
**Cannot:**
|
||||
- Decrypt existing messages (no private keys)
|
||||
- Forge messages that decrypt properly
|
||||
- Retroactively decrypt past messages
|
||||
|
||||
**Mitigation:**
|
||||
- Regular backups of message store
|
||||
- Monitor for unexpected key rotations
|
||||
- Out-of-band public key verification (future enhancement)
|
||||
|
||||
### Concurrent Key Rotation
|
||||
|
||||
**Scenario:**
|
||||
```
|
||||
T0: Sender fetches public_key A
|
||||
T1: Reader rotates to key B
|
||||
T2: Store updates active key → B
|
||||
T3: Sender submits message encrypted with A
|
||||
|
||||
Result:
|
||||
Message encrypted with old key A
|
||||
→ Reader still has private_key A in pool (ARCHIVED)
|
||||
→ Decryption succeeds
|
||||
→ No data loss
|
||||
```
|
||||
|
||||
### Message Store Full
|
||||
|
||||
**Not currently specified** - future consideration:
|
||||
- Max storage quota
|
||||
- Auto-deletion after N days
|
||||
- Reader notification when approaching limit
|
||||
|
||||
---
|
||||
|
||||
## Security Properties
|
||||
|
||||
### Confidentiality
|
||||
- **Message content:** Only reader with private key can decrypt
|
||||
- **Sender name:** Visible to store (plaintext)
|
||||
- **Timing:** Message timestamps visible to store
|
||||
|
||||
### Integrity
|
||||
- Sealed box cryptography provides authentication
|
||||
- Tampering detection built into crypto scheme
|
||||
- Failed authentication → decryption failure
|
||||
|
||||
### Availability
|
||||
- Stateless retrieval (no read locks)
|
||||
- Store compromise → messages still readable from backups
|
||||
- Key loss → messages permanently lost (by design)
|
||||
|
||||
### Anonymity
|
||||
- No sender authentication required
|
||||
- IP addresses, user agents: implementation detail
|
||||
- Sender name is self-asserted (no verification)
|
||||
|
||||
---
|
||||
|
||||
## Constraints & Limits
|
||||
|
||||
### Message Constraints
|
||||
- Maximum message length: 10,000 characters
|
||||
- Maximum sender name: 200 characters
|
||||
- Maximum sealed_box size: 50KB
|
||||
|
||||
### Storage Constraints
|
||||
- Messages persist indefinitely (no auto-deletion)
|
||||
- No maximum message count (unbounded growth)
|
||||
|
||||
### Performance Constraints
|
||||
- Batch operations preferred (reader fetches all at once)
|
||||
- Stateless protocol (no session management)
|
||||
- Crypto operations: single-threaded acceptable for low volume
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements (Out of Scope)
|
||||
|
||||
- Public key fingerprint verification
|
||||
- Sender reply channel (optional contact info)
|
||||
- Message categories/tags
|
||||
- Read receipts
|
||||
- Storage quotas and auto-cleanup
|
||||
- Multi-device reader support
|
||||
- Message threading
|
||||
- Rate limiting and spam prevention
|
||||
|
||||
---
|
||||
|
||||
## Cryptographic Primitives
|
||||
|
||||
**Sealed Box:** NaCl/libsodium compatible
|
||||
- Combines public key encryption + authentication
|
||||
- No separate nonce management required
|
||||
- All-in-one ciphertext blob
|
||||
|
||||
**Key Generation:**
|
||||
- Public/private keypair generation
|
||||
- Implementation agnostic (can be RSA, ECC, X25519, etc.)
|
||||
|
||||
**Operations:**
|
||||
- `seal(plaintext, public_key) → sealed_box`
|
||||
- `unseal(sealed_box, private_key) → plaintext | error`
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Authentication mechanism** for Reader → Store operations (key rotation, deletion)
|
||||
2. **Public key verification** - how does sender know public_key is authentic?
|
||||
3. **Rate limiting** - should store impose submission limits?
|
||||
4. **Message expiry** - auto-delete after N days?
|
||||
5. **Multi-device reader** - how to distribute private keys securely?
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- v0.1 - Initial specification (2026-01-19)
|
||||
Loading…
Reference in a new issue