Update specification with implementation insights
Add comprehensive implementation notes based on sender client development: - Detailed cryptographic primitive specifications (Curve25519, XSalsa20-Poly1305) - Memory safety patterns (move semantics for plaintext) - State machine enforcement strategies - Unicode and character counting considerations - Cryptographic property documentation (randomness, overhead) - Design decisions with rationales - Additional open questions discovered during implementation - Updated constraints with actual size calculations
This commit is contained in:
parent
f0f33e0934
commit
f3ab842cc2
1 changed files with 135 additions and 5 deletions
140
SPECIFICATION.md
140
SPECIFICATION.md
|
|
@ -741,9 +741,13 @@ Result:
|
|||
## Constraints & Limits
|
||||
|
||||
### Message Constraints
|
||||
- Maximum message length: 10,000 characters
|
||||
- Maximum sender name: 200 characters
|
||||
- Maximum sealed_box size: 50KB
|
||||
- Maximum message length: 10,000 characters (not bytes - Unicode aware)
|
||||
- Maximum sender name: 200 characters (not bytes - Unicode aware)
|
||||
- Maximum sealed_box size: ~40KB (10,000 chars × 4 bytes/char UTF-8 max + ~48 bytes overhead)
|
||||
- Note: Actual sealed_box size depends on plaintext encoding
|
||||
- Conservative estimate: 50KB covers worst case
|
||||
- Minimum message length: 1 character (empty rejected)
|
||||
- Minimum sender name: 1 character (empty rejected)
|
||||
|
||||
### Storage Constraints
|
||||
- Messages persist indefinitely (no auto-deletion)
|
||||
|
|
@ -775,17 +779,140 @@ Result:
|
|||
- Combines public key encryption + authentication
|
||||
- No separate nonce management required
|
||||
- All-in-one ciphertext blob
|
||||
- Uses Curve25519 for public key cryptography (X25519)
|
||||
- Encrypts to a public key without requiring the sender's private key
|
||||
- Provides IND-CCA2 security with authenticated encryption
|
||||
|
||||
**Key Generation:**
|
||||
- Public/private keypair generation
|
||||
- Implementation agnostic (can be RSA, ECC, X25519, etc.)
|
||||
- Public/private keypair generation using Curve25519
|
||||
- Public key: 32 bytes
|
||||
- Private key: 32 bytes
|
||||
- Implementation: libsodium/sodiumoxide crypto_box keypair
|
||||
|
||||
**Operations:**
|
||||
- `seal(plaintext, public_key) → sealed_box`
|
||||
- Internally generates ephemeral keypair
|
||||
- Encrypts using XSalsa20-Poly1305
|
||||
- Returns ciphertext || ephemeral_public_key
|
||||
- `unseal(sealed_box, private_key) → plaintext | error`
|
||||
- Extracts ephemeral public key from sealed_box
|
||||
- Derives shared secret
|
||||
- Decrypts and authenticates
|
||||
- Returns plaintext on success, error on tampering/wrong key
|
||||
|
||||
---
|
||||
|
||||
## Implementation Insights
|
||||
|
||||
### Sender Client Implementation Notes
|
||||
|
||||
**State Enforcement:**
|
||||
- Strict state machine prevents invalid transitions
|
||||
- Each operation checks current state before proceeding
|
||||
- Invalid transitions return explicit error messages
|
||||
- Error state allows for graceful recovery via reset
|
||||
|
||||
**Memory Safety:**
|
||||
- Plaintext is consumed (moved) during seal operation
|
||||
- After sealing, plaintext is automatically dropped
|
||||
- Sealed message is consumed during submit operation
|
||||
- No plaintext remains in memory after encryption
|
||||
|
||||
**Validation Edge Cases:**
|
||||
- Empty strings are rejected (both sender_name and message_body)
|
||||
- Unicode characters are supported and count correctly
|
||||
- Character limits are enforced, not byte limits
|
||||
- Newlines and special characters are preserved
|
||||
|
||||
**Cryptographic Properties:**
|
||||
- Same plaintext produces different ciphertext each time (due to ephemeral keys)
|
||||
- Sealed box is larger than plaintext by ~48 bytes (ephemeral PK + auth tag + nonce)
|
||||
- Encryption cannot fail given valid public key
|
||||
- No key management required on sender side (stateless)
|
||||
|
||||
**UUIDs and Timestamps:**
|
||||
- UUIDs are v4 (random), not v1 (time-based)
|
||||
- Timestamps are Unix epoch seconds (i64)
|
||||
- Message IDs are globally unique with high probability
|
||||
|
||||
**Reset Behavior:**
|
||||
- Reset can be called from any state (not just SUBMITTED)
|
||||
- Allows error recovery by forcing return to IDLE
|
||||
- Clears all intermediate state (plaintext, sealed_message)
|
||||
- Public key is retained after reset (stays in IDLE, not destroyed)
|
||||
|
||||
**Key ID Management:**
|
||||
- Key ID is opaque string (implementation-defined format)
|
||||
- Sender doesn't validate key_id format
|
||||
- Key ID is preserved from PublicKey through MessageRecord
|
||||
- Enables key rotation without sender awareness
|
||||
|
||||
### Testing Considerations
|
||||
|
||||
**Essential Test Coverage:**
|
||||
1. State transitions (valid and invalid)
|
||||
2. Input validation (empty, max, over-max)
|
||||
3. Cryptographic randomness (same input → different output)
|
||||
4. Memory cleanup (plaintext destroyed after sealing)
|
||||
5. Unicode and special character handling
|
||||
6. Multiple messages in sequence
|
||||
7. Timestamp correctness
|
||||
8. UUID uniqueness
|
||||
|
||||
**Error Cases:**
|
||||
- Invalid state transitions
|
||||
- Missing public key
|
||||
- Missing plaintext
|
||||
- Missing sealed message
|
||||
- Invalid sender name (empty, too long)
|
||||
- Invalid message body (empty, too long)
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions (from Implementation)
|
||||
|
||||
### Public Key Structure
|
||||
**Decision:** PublicKey contains both key_id and public_key bytes
|
||||
- **Rationale:** Sender needs key_id to include in MessageRecord
|
||||
- **Alternative considered:** Fetch key_id separately from store
|
||||
- **Chosen approach:** Bundle them together to reduce round trips
|
||||
|
||||
### State Machine Strictness
|
||||
**Decision:** Enforce state transitions with compile-time types would be ideal, but runtime checks are practical
|
||||
- **Rationale:** Makes invalid states unrepresentable
|
||||
- **Trade-off:** More complex type system vs runtime flexibility
|
||||
- **Implementation:** Runtime checks with explicit error types
|
||||
|
||||
### Plaintext Ownership
|
||||
**Decision:** Move (consume) plaintext during seal operation
|
||||
- **Rationale:** Prevents accidental reuse or logging
|
||||
- **Alternative considered:** Clone and clear
|
||||
- **Chosen approach:** Rust's move semantics provide memory safety guarantee
|
||||
|
||||
### Reset from Any State
|
||||
**Decision:** Allow reset from any state, not just SUBMITTED or ERROR
|
||||
- **Rationale:** Provides escape hatch for error recovery
|
||||
- **Trade-off:** Less strict state machine vs operational flexibility
|
||||
- **Chosen approach:** Flexible reset for better UX
|
||||
|
||||
### Timestamp Precision
|
||||
**Decision:** Unix epoch seconds (not milliseconds)
|
||||
- **Rationale:** Second precision sufficient for message ordering
|
||||
- **Trade-off:** Less precision vs simpler arithmetic
|
||||
- **Chosen approach:** Seconds for simplicity
|
||||
|
||||
### Character vs Byte Limits
|
||||
**Decision:** Enforce character limits, not byte limits
|
||||
- **Rationale:** User-facing (WYSIWYG behavior)
|
||||
- **Trade-off:** Unicode overhead vs user expectations
|
||||
- **Chosen approach:** Character counting for better UX
|
||||
|
||||
### Error Type Design
|
||||
**Decision:** Use enum with contextual error messages
|
||||
- **Rationale:** Structured errors enable better error handling
|
||||
- **Alternative considered:** String errors
|
||||
- **Chosen approach:** Typed errors with Display impl for messages
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Authentication mechanism** for Reader → Store operations (key rotation, deletion)
|
||||
|
|
@ -793,6 +920,9 @@ Result:
|
|||
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?
|
||||
6. **Sealed box size** - current spec says max 50KB, but actual max message is 10KB chars + overhead (~48 bytes). Should store validate based on plaintext limit or total size?
|
||||
7. **Key ID format** - should it be standardized (e.g., hash of public key) or implementation-defined?
|
||||
8. **Concurrent key rotation** - if sender caches public key, how long is cache valid?
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue