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
|
## Constraints & Limits
|
||||||
|
|
||||||
### Message Constraints
|
### Message Constraints
|
||||||
- Maximum message length: 10,000 characters
|
- Maximum message length: 10,000 characters (not bytes - Unicode aware)
|
||||||
- Maximum sender name: 200 characters
|
- Maximum sender name: 200 characters (not bytes - Unicode aware)
|
||||||
- Maximum sealed_box size: 50KB
|
- 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
|
### Storage Constraints
|
||||||
- Messages persist indefinitely (no auto-deletion)
|
- Messages persist indefinitely (no auto-deletion)
|
||||||
|
|
@ -775,17 +779,140 @@ Result:
|
||||||
- Combines public key encryption + authentication
|
- Combines public key encryption + authentication
|
||||||
- No separate nonce management required
|
- No separate nonce management required
|
||||||
- All-in-one ciphertext blob
|
- 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:**
|
**Key Generation:**
|
||||||
- Public/private keypair generation
|
- Public/private keypair generation using Curve25519
|
||||||
- Implementation agnostic (can be RSA, ECC, X25519, etc.)
|
- Public key: 32 bytes
|
||||||
|
- Private key: 32 bytes
|
||||||
|
- Implementation: libsodium/sodiumoxide crypto_box keypair
|
||||||
|
|
||||||
**Operations:**
|
**Operations:**
|
||||||
- `seal(plaintext, public_key) → sealed_box`
|
- `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`
|
- `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
|
## Open Questions
|
||||||
|
|
||||||
1. **Authentication mechanism** for Reader → Store operations (key rotation, deletion)
|
1. **Authentication mechanism** for Reader → Store operations (key rotation, deletion)
|
||||||
|
|
@ -793,6 +920,9 @@ Result:
|
||||||
3. **Rate limiting** - should store impose submission limits?
|
3. **Rate limiting** - should store impose submission limits?
|
||||||
4. **Message expiry** - auto-delete after N days?
|
4. **Message expiry** - auto-delete after N days?
|
||||||
5. **Multi-device reader** - how to distribute private keys securely?
|
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