/ dead-drop-protocol.md
dead-drop-protocol.md
1 # Dead Drop Protocol: Direct Delivery Model 2 3 ## Overview 4 5 This document specifies the state machines and protocol flows for a proximity-based asynchronous messaging system. The core premise: messages are composed at any time but only delivered when sender and recipient devices come into physical proximity. 6 7 --- 8 9 ## 1. Cryptographic Primitives 10 11 ### Key Types 12 13 ``` 14 Identity Key Pair (long-term) 15 ├── id_private: Ed25519 private key (signing) 16 └── id_public: Ed25519 public key (verification) 17 18 Exchange Key Pair (long-term) 19 ├── ex_private: X25519 private key (key agreement) 20 └── ex_public: X25519 public key (shared with contacts) 21 22 Ephemeral Key Pair (per-message) 23 ├── eph_private: X25519 private key (one-time use) 24 └── eph_public: X25519 public key (sent with message) 25 ``` 26 27 ### Cryptographic Functions 28 29 ``` 30 ECDH(private, public) → shared_secret 31 HKDF(secret, salt, info) → derived_key 32 ChaCha20-Poly1305(key, nonce, plaintext) → ciphertext + tag 33 Ed25519_Sign(private, message) → signature 34 Ed25519_Verify(public, message, signature) → bool 35 BLAKE2b(data) → hash 36 ``` 37 38 --- 39 40 ## 2. Device States 41 42 ### 2.1 Global Device State Machine 43 44 ``` 45 ┌─────────────────────────────────────────────────────────────┐ 46 │ DEVICE STATES │ 47 ├─────────────────────────────────────────────────────────────┤ 48 │ │ 49 │ ┌──────────┐ │ 50 │ │ OFF │ ◄─────── App closed / BLE disabled │ 51 │ └────┬─────┘ │ 52 │ │ │ 53 │ │ App launched + BLE enabled │ 54 │ ▼ │ 55 │ ┌──────────┐ │ 56 │ │ IDLE │ ◄─────┐ │ 57 │ └────┬─────┘ │ │ 58 │ │ │ │ 59 │ │ Periodic │ Scan complete │ 60 │ │ scan timer │ │ 61 │ ▼ │ │ 62 │ ┌──────────┐ │ │ 63 │ │ SCANNING │ ──────┘ │ 64 │ └────┬─────┘ │ 65 │ │ │ 66 │ │ Peer discovered │ 67 │ ▼ │ 68 │ ┌──────────┐ │ 69 │ │HANDSHAKE │ ──────► Success ──────► EXCHANGING │ 70 │ └────┬─────┘ │ 71 │ │ │ 72 │ │ Timeout/Failure │ 73 │ ▼ │ 74 │ Back to IDLE │ 75 │ │ 76 └─────────────────────────────────────────────────────────────┘ 77 ``` 78 79 ### 2.2 State Descriptions 80 81 | State | Description | BLE Activity | 82 |-------|-------------|--------------| 83 | OFF | App not running or BLE disabled | None | 84 | IDLE | Awaiting next scan interval | Advertising only | 85 | SCANNING | Actively scanning for peers | Advertising + Scanning | 86 | HANDSHAKE | Establishing secure channel with peer | Connected, Noise handshake | 87 | EXCHANGING | Transferring messages | Connected, data transfer | 88 89 --- 90 91 ## 3. Contact Relationship States 92 93 ### 3.1 Contact State Machine 94 95 ``` 96 ┌─────────────────────────────────────────────────────────────┐ 97 │ CONTACT STATES │ 98 ├─────────────────────────────────────────────────────────────┤ 99 │ │ 100 │ ┌──────────────┐ │ 101 │ │ UNKNOWN │ No relationship exists │ 102 │ └──────┬───────┘ │ 103 │ │ │ 104 │ │ QR code scanned (in-person exchange) │ 105 │ ▼ │ 106 │ ┌──────────────┐ │ 107 │ │ PENDING │ Keys exchanged, awaiting confirmation │ 108 │ └──────┬───────┘ │ 109 │ │ │ 110 │ │ Confirmation handshake completed │ 111 │ ▼ │ 112 │ ┌──────────────┐ │ 113 │ │ ACTIVE │ Can send/receive messages │ 114 │ └──────┬───────┘ │ 115 │ │ │ 116 │ │ User deletes contact │ 117 │ ▼ │ 118 │ ┌──────────────┐ │ 119 │ │ REVOKED │ Relationship terminated │ 120 │ └──────────────┘ │ 121 │ │ 122 └─────────────────────────────────────────────────────────────┘ 123 ``` 124 125 ### 3.2 Contact Data Structure 126 127 ``` 128 Contact { 129 contact_id: [u8; 16] // Random identifier 130 nickname: String // User-assigned label 131 their_id_public: Ed25519Pub // Their identity key 132 their_ex_public: X25519Pub // Their exchange key 133 state: ContactState // PENDING | ACTIVE | REVOKED 134 created_at: Timestamp // When relationship established 135 last_seen: Timestamp // Last proximity event 136 last_delivery: Timestamp // Last successful message exchange 137 } 138 ``` 139 140 --- 141 142 ## 4. Message States 143 144 ### 4.1 Outbound Message State Machine 145 146 ``` 147 ┌─────────────────────────────────────────────────────────────┐ 148 │ OUTBOUND MESSAGE STATES │ 149 ├─────────────────────────────────────────────────────────────┤ 150 │ │ 151 │ ┌──────────────┐ │ 152 │ │ DRAFT │ User composing message │ 153 │ └──────┬───────┘ │ 154 │ │ │ 155 │ │ User confirms send │ 156 │ ▼ │ 157 │ ┌──────────────┐ │ 158 │ │ QUEUED │ Encrypted, awaiting proximity │ 159 │ └──────┬───────┘ │ 160 │ │ │ 161 │ │ Recipient in proximity, transmission started │ 162 │ ▼ │ 163 │ ┌──────────────┐ │ 164 │ │ TRANSMITTING │ Active BLE transfer │ 165 │ └──────┬───────┘ │ 166 │ │ │ 167 │ ├── Success ──────────────┐ │ 168 │ │ ▼ │ 169 │ │ ┌──────────────┐ │ 170 │ │ │ DELIVERED │ │ 171 │ │ └──────┬───────┘ │ 172 │ │ │ │ 173 │ │ │ Recipient sends ACK │ 174 │ │ ▼ │ 175 │ │ ┌──────────────┐ │ 176 │ │ │ ACKNOWLEDGED │ │ 177 │ │ └──────────────┘ │ 178 │ │ │ 179 │ └── Failure (timeout, disconnect) │ 180 │ │ │ 181 │ ▼ │ 182 │ Back to QUEUED │ 183 │ │ 184 │ ┌──────────────┐ │ 185 │ │ EXPIRED │ TTL exceeded without delivery │ 186 │ └──────────────┘ │ 187 │ │ 188 └─────────────────────────────────────────────────────────────┘ 189 ``` 190 191 ### 4.2 Inbound Message State Machine 192 193 ``` 194 ┌─────────────────────────────────────────────────────────────┐ 195 │ INBOUND MESSAGE STATES │ 196 ├─────────────────────────────────────────────────────────────┤ 197 │ │ 198 │ ┌──────────────┐ │ 199 │ │ RECEIVING │ BLE transfer in progress │ 200 │ └──────┬───────┘ │ 201 │ │ │ 202 │ │ Transfer complete, decryption successful │ 203 │ ▼ │ 204 │ ┌──────────────┐ │ 205 │ │ RECEIVED │ Stored locally, unread │ 206 │ └──────┬───────┘ │ 207 │ │ │ 208 │ │ User opens message │ 209 │ ▼ │ 210 │ ┌──────────────┐ │ 211 │ │ READ │ User has viewed content │ 212 │ └──────┬───────┘ │ 213 │ │ │ 214 │ │ Auto-delete timer OR user deletes │ 215 │ ▼ │ 216 │ ┌──────────────┐ │ 217 │ │ DELETED │ Securely wiped from storage │ 218 │ └──────────────┘ │ 219 │ │ 220 └─────────────────────────────────────────────────────────────┘ 221 ``` 222 223 ### 4.3 Message Data Structure 224 225 ``` 226 OutboundMessage { 227 message_id: [u8; 16] // Random unique ID 228 recipient_id: [u8; 16] // Contact ID 229 content_type: ContentType // TEXT | DOCUMENT 230 plaintext: Vec<u8> // Original content 231 ciphertext: Vec<u8> // Encrypted blob 232 ephemeral_public: X25519Pub // One-time key for this message 233 state: OutboundState // Current state 234 created_at: Timestamp // When composed 235 expires_at: Timestamp // TTL deadline 236 attempts: u32 // Delivery attempt count 237 } 238 239 InboundMessage { 240 message_id: [u8; 16] // From sender 241 sender_id: [u8; 16] // Contact ID 242 content_type: ContentType // TEXT | DOCUMENT 243 plaintext: Vec<u8> // Decrypted content 244 state: InboundState // Current state 245 received_at: Timestamp // When received 246 read_at: Option<Timestamp> // When opened 247 delete_at: Timestamp // Auto-delete deadline 248 } 249 ``` 250 251 --- 252 253 ## 5. Protocol Flows 254 255 ### 5.1 Contact Establishment (One-Time Setup) 256 257 This happens once, in person, to establish the relationship. 258 259 ``` 260 ┌─────────┐ ┌─────────┐ 261 │ Alice │ │ Bob │ 262 └────┬────┘ └────┬────┘ 263 │ │ 264 │ ┌─────────────────────────────────────────┐ │ 265 │ │ PHYSICAL MEETING (same location) │ │ 266 │ └─────────────────────────────────────────┘ │ 267 │ │ 268 │ 1. Alice displays QR code │ 269 │ ┌──────────────────────────────────┐ │ 270 │ │ QR contains: │ │ 271 │ │ - Alice's id_public │ │ 272 │ │ - Alice's ex_public │ │ 273 │ │ - Random session_id │ │ 274 │ │ - Timestamp │ │ 275 │ └──────────────────────────────────┘ │ 276 │ │ 277 │ Bob scans QR │ 278 │ ◄──────────────────────────────────────────────│ 279 │ │ 280 │ 2. Bob displays QR code │ 281 │ ┌──────────────────────────────────┐ │ 282 │ │ QR contains: │ │ 283 │ │ - Bob's id_public │ │ 284 │ │ - Bob's ex_public │ │ 285 │ │ - Same session_id (proof) │ │ 286 │ │ - Timestamp │ │ 287 │ └──────────────────────────────────┘ │ 288 │ │ 289 │ Alice scans QR │ 290 │ ───────────────────────────────────────────────► 291 │ │ 292 │ 3. Both devices now have each other's keys │ 293 │ │ 294 │ 4. BLE confirmation handshake (optional) │ 295 │ ◄──────────────────────────────────────────────► 296 │ Noise_XX handshake to verify keys │ 297 │ │ 298 │ 5. Contact state → ACTIVE on both devices │ 299 │ │ 300 ▼ ▼ 301 ``` 302 303 ### 5.2 BLE Advertising & Discovery 304 305 Devices continuously advertise their presence without revealing identity. 306 307 ``` 308 ┌─────────────────────────────────────────────────────────────┐ 309 │ BLE ADVERTISEMENT │ 310 ├─────────────────────────────────────────────────────────────┤ 311 │ │ 312 │ Advertisement Payload (31 bytes max): │ 313 │ ┌─────────────────────────────────────────────────────┐ │ 314 │ │ [2 bytes] Service UUID: 0xDD01 (Dead Drop) │ │ 315 │ │ [16 bytes] Rotating ID: HKDF(id_private, time_slot) │ │ 316 │ │ [1 byte] Flags: has_messages | accepts_messages │ │ 317 │ │ [4 bytes] Time slot (15-minute granularity) │ │ 318 │ └─────────────────────────────────────────────────────┘ │ 319 │ │ 320 │ Rotating ID changes every 15 minutes to prevent tracking. │ 321 │ Contacts can derive expected rotating IDs for each other. │ 322 │ │ 323 │ Detection: │ 324 │ 1. Scan for Service UUID 0xDD01 │ 325 │ 2. For each contact, compute expected rotating ID │ 326 │ 3. Compare against scanned rotating IDs │ 327 │ 4. Match found → contact is nearby │ 328 │ │ 329 └─────────────────────────────────────────────────────────────┘ 330 ``` 331 332 #### Rotating ID Calculation 333 334 ``` 335 time_slot = floor(unix_timestamp / 900) // 15-minute slots 336 337 rotating_id = HKDF( 338 secret: id_private, 339 salt: time_slot.to_bytes(), 340 info: "dead-drop-rotating-id-v1" 341 )[0..16] 342 ``` 343 344 Both parties can compute each other's rotating ID because they have exchanged public keys during setup, and derive a shared secret: 345 346 ``` 347 shared_secret = ECDH(my_ex_private, their_ex_public) 348 349 expected_rotating_id = HKDF( 350 secret: shared_secret, 351 salt: time_slot.to_bytes(), 352 info: "dead-drop-rotating-id-v1" 353 )[0..16] 354 ``` 355 356 ### 5.3 Proximity Detection & Connection 357 358 ``` 359 ┌─────────┐ ┌─────────┐ 360 │ Alice │ │ Bob │ 361 │(sender) │ │(recipient) 362 └────┬────┘ └────┬────┘ 363 │ │ 364 │ ══════ Both devices advertising ══════ │ 365 │ │ 366 │ 1. Alice's scan detects Bob's rotating ID │ 367 │ (Alice has queued messages for Bob) │ 368 │ │ 369 │ 2. Alice initiates BLE connection │ 370 │ ───────────────────────────────────────────────► 371 │ GATT Connect Request │ 372 │ │ 373 │ 3. Connection established │ 374 │ ◄─────────────────────────────────────────────── 375 │ GATT Connect Response │ 376 │ │ 377 │ 4. Noise XX Handshake begins │ 378 │ │ 379 │ ┌─────────────────────────────────────────┐ │ 380 │ │ Noise_XX_25519_ChaChaPoly_BLAKE2b │ │ 381 │ └─────────────────────────────────────────┘ │ 382 │ │ 383 │ → e │ 384 │ ───────────────────────────────────────────────► 385 │ Alice sends ephemeral public key │ 386 │ │ 387 │ ← e, ee, s, es │ 388 │ ◄─────────────────────────────────────────────── 389 │ Bob sends ephemeral + static, encrypted │ 390 │ │ 391 │ → s, se │ 392 │ ───────────────────────────────────────────────► 393 │ Alice sends static, encrypted │ 394 │ │ 395 │ 5. Handshake complete │ 396 │ Both parties authenticated │ 397 │ Transport keys established │ 398 │ │ 399 ▼ ▼ 400 ``` 401 402 ### 5.4 Message Encryption (Pre-Delivery) 403 404 Messages are encrypted immediately when sent, not when proximity is detected. 405 406 ``` 407 ┌─────────────────────────────────────────────────────────────┐ 408 │ MESSAGE ENCRYPTION │ 409 ├─────────────────────────────────────────────────────────────┤ 410 │ │ 411 │ Input: │ 412 │ - plaintext: message content │ 413 │ - recipient: Bob's contact record │ 414 │ │ 415 │ Step 1: Generate ephemeral key pair │ 416 │ eph_private, eph_public = X25519_Generate() │ 417 │ │ 418 │ Step 2: Derive shared secret │ 419 │ shared = ECDH(eph_private, bob.ex_public) │ 420 │ │ 421 │ Step 3: Derive message key │ 422 │ message_key = HKDF( │ 423 │ secret: shared, │ 424 │ salt: message_id, │ 425 │ info: "dead-drop-message-v1" │ 426 │ ) │ 427 │ │ 428 │ Step 4: Encrypt content │ 429 │ nonce = random(12) │ 430 │ ciphertext = ChaCha20Poly1305_Encrypt( │ 431 │ key: message_key, │ 432 │ nonce: nonce, │ 433 │ plaintext: plaintext, │ 434 │ aad: message_id || timestamp │ 435 │ ) │ 436 │ │ 437 │ Step 5: Assemble encrypted message │ 438 │ encrypted_message = { │ 439 │ message_id: [16 bytes] │ 440 │ ephemeral_public: eph_public [32 bytes] │ 441 │ nonce: [12 bytes] │ 442 │ ciphertext: [variable] │ 443 │ timestamp: [8 bytes] │ 444 │ } │ 445 │ │ 446 │ Step 6: Sign the package │ 447 │ signature = Ed25519_Sign( │ 448 │ alice.id_private, │ 449 │ BLAKE2b(encrypted_message) │ 450 │ ) │ 451 │ │ 452 │ Output: encrypted_message || signature │ 453 │ │ 454 └─────────────────────────────────────────────────────────────┘ 455 ``` 456 457 ### 5.5 Message Exchange (Post-Handshake) 458 459 ``` 460 ┌─────────┐ ┌─────────┐ 461 │ Alice │ │ Bob │ 462 └────┬────┘ └────┬────┘ 463 │ │ 464 │ ══════ Noise session established ══════ │ 465 │ │ 466 │ 1. Alice checks outbound queue for Bob │ 467 │ Found: 2 queued messages │ 468 │ │ 469 │ 2. Send message count │ 470 │ ───────────────────────────────────────────────► 471 │ MSG_COUNT { count: 2 } │ 472 │ │ 473 │ 3. Bob acknowledges, sends his count │ 474 │ ◄─────────────────────────────────────────────── 475 │ MSG_COUNT { count: 0 } │ 476 │ │ 477 │ 4. Alice sends first message │ 478 │ ───────────────────────────────────────────────► 479 │ MSG_DATA { index: 0, total: 2, │ 480 │ payload: encrypted_message_1 } │ 481 │ │ 482 │ 5. Bob acknowledges receipt │ 483 │ ◄─────────────────────────────────────────────── 484 │ MSG_ACK { index: 0, status: OK } │ 485 │ │ 486 │ 6. Alice sends second message │ 487 │ ───────────────────────────────────────────────► 488 │ MSG_DATA { index: 1, total: 2, │ 489 │ payload: encrypted_message_2 } │ 490 │ │ 491 │ 7. Bob acknowledges receipt │ 492 │ ◄─────────────────────────────────────────────── 493 │ MSG_ACK { index: 1, status: OK } │ 494 │ │ 495 │ 8. Exchange complete │ 496 │ ───────────────────────────────────────────────► 497 │ SESSION_COMPLETE { } │ 498 │ │ 499 │ 9. Disconnect │ 500 │ ◄──────────────────────────────────────────────► 501 │ BLE Disconnect │ 502 │ │ 503 │ 10. Alice marks messages DELIVERED │ 504 │ Bob decrypts and stores messages │ 505 │ │ 506 ▼ ▼ 507 ``` 508 509 ### 5.6 Message Decryption (Recipient Side) 510 511 ``` 512 ┌─────────────────────────────────────────────────────────────┐ 513 │ MESSAGE DECRYPTION │ 514 ├─────────────────────────────────────────────────────────────┤ 515 │ │ 516 │ Input: │ 517 │ - encrypted_message || signature │ 518 │ - sender: Alice's contact record │ 519 │ │ 520 │ Step 1: Verify signature │ 521 │ valid = Ed25519_Verify( │ 522 │ alice.id_public, │ 523 │ BLAKE2b(encrypted_message), │ 524 │ signature │ 525 │ ) │ 526 │ if (!valid) reject("Invalid signature") │ 527 │ │ 528 │ Step 2: Extract components │ 529 │ message_id = encrypted_message[0..16] │ 530 │ eph_public = encrypted_message[16..48] │ 531 │ nonce = encrypted_message[48..60] │ 532 │ ciphertext = encrypted_message[60..-8] │ 533 │ timestamp = encrypted_message[-8..] │ 534 │ │ 535 │ Step 3: Check for replay │ 536 │ if (message_id in seen_messages) reject("Replay") │ 537 │ if (timestamp too old) reject("Expired") │ 538 │ │ 539 │ Step 4: Derive shared secret │ 540 │ shared = ECDH(bob.ex_private, eph_public) │ 541 │ │ 542 │ Step 5: Derive message key │ 543 │ message_key = HKDF( │ 544 │ secret: shared, │ 545 │ salt: message_id, │ 546 │ info: "dead-drop-message-v1" │ 547 │ ) │ 548 │ │ 549 │ Step 6: Decrypt content │ 550 │ plaintext = ChaCha20Poly1305_Decrypt( │ 551 │ key: message_key, │ 552 │ nonce: nonce, │ 553 │ ciphertext: ciphertext, │ 554 │ aad: message_id || timestamp │ 555 │ ) │ 556 │ │ 557 │ Step 7: Store decrypted message │ 558 │ seen_messages.add(message_id) // Replay protection │ 559 │ store_message(plaintext) │ 560 │ │ 561 │ Output: plaintext │ 562 │ │ 563 └─────────────────────────────────────────────────────────────┘ 564 ``` 565 566 --- 567 568 ## 6. Wire Protocol 569 570 ### 6.1 Message Types 571 572 ``` 573 ┌─────────────────────────────────────────────────────────────┐ 574 │ WIRE MESSAGE TYPES │ 575 ├────────┬────────────────────────────────────────────────────┤ 576 │ Type │ Description │ 577 ├────────┼────────────────────────────────────────────────────┤ 578 │ 0x01 │ MSG_COUNT - Number of messages to exchange │ 579 │ 0x02 │ MSG_DATA - Encrypted message payload │ 580 │ 0x03 │ MSG_ACK - Acknowledge receipt of message │ 581 │ 0x04 │ MSG_NACK - Negative acknowledgement (error) │ 582 │ 0x05 │ SESSION_DONE - Exchange complete, disconnect │ 583 │ 0x06 │ PING - Keep-alive │ 584 │ 0x07 │ PONG - Keep-alive response │ 585 └────────┴────────────────────────────────────────────────────┘ 586 ``` 587 588 ### 6.2 Message Structures 589 590 ``` 591 All messages are sent inside Noise transport encryption. 592 593 MSG_COUNT (0x01) 594 ┌──────────┬──────────────────┐ 595 │ type: u8 │ count: u16 │ 596 │ 0x01 │ number of msgs │ 597 └──────────┴──────────────────┘ 598 599 MSG_DATA (0x02) 600 ┌──────────┬──────────┬──────────┬─────────────────────┐ 601 │ type: u8 │ index: u8│ total: u8│ payload: Vec<u8> │ 602 │ 0x02 │ 0-255 │ 0-255 │ encrypted message │ 603 └──────────┴──────────┴──────────┴─────────────────────┘ 604 605 MSG_ACK (0x03) 606 ┌──────────┬──────────┬──────────────────┐ 607 │ type: u8 │ index: u8│ status: u8 │ 608 │ 0x03 │ 0-255 │ 0=OK, 1+=error │ 609 └──────────┴──────────┴──────────────────┘ 610 611 MSG_NACK (0x04) 612 ┌──────────┬──────────┬──────────────────┐ 613 │ type: u8 │ index: u8│ error_code: u8 │ 614 │ 0x04 │ 0-255 │ see error table │ 615 └──────────┴──────────┴──────────────────┘ 616 617 SESSION_DONE (0x05) 618 ┌──────────┐ 619 │ type: u8 │ 620 │ 0x05 │ 621 └──────────┘ 622 623 PING (0x06) / PONG (0x07) 624 ┌──────────┬──────────────────┐ 625 │ type: u8 │ timestamp: u64 │ 626 │ 0x06/07 │ unix millis │ 627 └──────────┴──────────────────┘ 628 ``` 629 630 ### 6.3 Error Codes 631 632 ``` 633 ┌──────────┬────────────────────────────────────────┐ 634 │ Code │ Description │ 635 ├──────────┼────────────────────────────────────────┤ 636 │ 0x00 │ OK - Success │ 637 │ 0x01 │ INVALID_SIGNATURE - Sig check failed │ 638 │ 0x02 │ DECRYPT_FAILED - Could not decrypt │ 639 │ 0x03 │ REPLAY_DETECTED - Message ID seen │ 640 │ 0x04 │ EXPIRED - Timestamp too old │ 641 │ 0x05 │ UNKNOWN_SENDER - Not in contacts │ 642 │ 0x06 │ STORAGE_FULL - Cannot accept more │ 643 │ 0x07 │ INVALID_FORMAT - Malformed message │ 644 └──────────┴────────────────────────────────────────┘ 645 ``` 646 647 --- 648 649 ## 7. Storage Schema 650 651 ### 7.1 Local Database Structure 652 653 ```sql 654 -- Identity (single row, device keys) 655 CREATE TABLE identity ( 656 id INTEGER PRIMARY KEY DEFAULT 1, 657 id_private BLOB NOT NULL, -- Ed25519 private (encrypted at rest) 658 id_public BLOB NOT NULL, -- Ed25519 public 659 ex_private BLOB NOT NULL, -- X25519 private (encrypted at rest) 660 ex_public BLOB NOT NULL, -- X25519 public 661 created_at INTEGER NOT NULL -- Unix timestamp 662 ); 663 664 -- Contacts 665 CREATE TABLE contacts ( 666 contact_id BLOB PRIMARY KEY, -- 16 bytes random 667 nickname TEXT, -- User label 668 their_id_public BLOB NOT NULL, -- Ed25519 public 669 their_ex_public BLOB NOT NULL, -- X25519 public 670 state INTEGER NOT NULL, -- 0=PENDING, 1=ACTIVE, 2=REVOKED 671 created_at INTEGER NOT NULL, 672 last_seen INTEGER, -- Last proximity 673 last_delivery INTEGER -- Last message exchange 674 ); 675 676 -- Outbound message queue 677 CREATE TABLE outbound_messages ( 678 message_id BLOB PRIMARY KEY, -- 16 bytes random 679 recipient_id BLOB NOT NULL, -- FK to contacts 680 content_type INTEGER NOT NULL, -- 0=TEXT, 1=DOCUMENT 681 plaintext BLOB NOT NULL, -- Original (encrypted at rest) 682 ciphertext BLOB NOT NULL, -- Ready to transmit 683 eph_public BLOB NOT NULL, -- Ephemeral key used 684 state INTEGER NOT NULL, -- 0=QUEUED, 1=TRANSMITTING, etc 685 created_at INTEGER NOT NULL, 686 expires_at INTEGER NOT NULL, -- TTL 687 attempts INTEGER DEFAULT 0, 688 FOREIGN KEY (recipient_id) REFERENCES contacts(contact_id) 689 ); 690 691 -- Inbound messages 692 CREATE TABLE inbound_messages ( 693 message_id BLOB PRIMARY KEY, -- From sender 694 sender_id BLOB NOT NULL, -- FK to contacts 695 content_type INTEGER NOT NULL, 696 plaintext BLOB NOT NULL, -- Decrypted (encrypted at rest) 697 state INTEGER NOT NULL, -- 0=RECEIVED, 1=READ, 2=DELETED 698 received_at INTEGER NOT NULL, 699 read_at INTEGER, 700 delete_at INTEGER NOT NULL, -- Auto-delete deadline 701 FOREIGN KEY (sender_id) REFERENCES contacts(contact_id) 702 ); 703 704 -- Replay protection 705 CREATE TABLE seen_messages ( 706 message_id BLOB PRIMARY KEY, 707 seen_at INTEGER NOT NULL 708 ); 709 710 -- Cleanup old replay records 711 CREATE INDEX idx_seen_messages_time ON seen_messages(seen_at); 712 ``` 713 714 ### 7.2 Encryption at Rest 715 716 All sensitive fields (`plaintext`, private keys) are encrypted using a key derived from: 717 718 ``` 719 storage_key = HKDF( 720 secret: user_passphrase, // Or biometric-protected key 721 salt: device_random_salt, 722 info: "dead-drop-storage-v1" 723 ) 724 ``` 725 726 --- 727 728 ## 8. Security Considerations 729 730 ### 8.1 Threat Model 731 732 **Protected against:** 733 - Passive network observers (no network traffic to observe) 734 - Server compromise (no servers) 735 - Metadata collection (no logs of who contacts whom) 736 - Message content exposure (end-to-end encrypted) 737 - Replay attacks (message IDs tracked) 738 - Device tracking via BLE (rotating IDs) 739 740 **Not protected against:** 741 - Physical device seizure (mitigated by encryption at rest) 742 - Compromised endpoint (malware on device) 743 - Rubber hose cryptanalysis (coercion) 744 - Global passive adversary correlating physical movements 745 - RF fingerprinting of specific BLE hardware 746 747 ### 8.2 Forward Secrecy 748 749 Each message uses a fresh ephemeral key pair. Compromise of long-term keys does not reveal past messages (only future messages to that contact). 750 751 For enhanced forward secrecy, implement key ratcheting: 752 753 ``` 754 After each successful message exchange: 755 1. Both parties derive new exchange keys 756 2. new_ex_secret = HKDF(old_ex_secret || shared_random) 757 3. Old keys are securely deleted 758 ``` 759 760 ### 8.3 Deniability 761 762 The protocol provides limited deniability: 763 - Messages are signed, so sender cannot deny sending to the recipient 764 - However, signatures use identity keys known only to the two parties 765 - A recipient cannot prove to a third party that the sender sent a message 766 (recipient could have forged the signature using shared knowledge) 767 768 For stronger deniability, consider ring signatures or designated verifier proofs. 769 770 ### 8.4 Traffic Analysis Resistance 771 772 To prevent observers from inferring communication patterns: 773 774 1. **Constant-rate BLE activity** - Device advertises at fixed intervals regardless of message queue state 775 2. **Dummy connections** - Occasionally connect to random Dead Drop devices and exchange empty payloads 776 3. **Fixed message sizes** - Pad all messages to fixed sizes (e.g., 1KB, 4KB, 16KB buckets) 777 4. **Randomized timing** - Add jitter to all operations 778 779 --- 780 781 ## 9. Implementation Notes 782 783 ### 9.1 BLE Considerations 784 785 - MTU negotiation: Request maximum MTU (512 bytes on modern devices) 786 - Fragmentation: Messages larger than MTU must be fragmented at application layer 787 - Connection interval: Balance power consumption vs transfer speed 788 - Background operation: iOS and Android have different restrictions for background BLE 789 790 ### 9.2 Platform-Specific Issues 791 792 **iOS:** 793 - Background BLE advertising limited to specific service UUIDs 794 - Cannot scan in background unless looking for specific peripherals 795 - Use significant location changes or iBeacon regions to wake app 796 797 **Android:** 798 - Requires location permission for BLE scanning (even without using location) 799 - Doze mode restricts background operation 800 - Use foreground service for reliable operation 801 802 ### 9.3 Battery Optimization 803 804 - Scan in short bursts (e.g., 5 seconds every 60 seconds) 805 - Increase scan frequency when motion detected (accelerometer) 806 - Decrease scan frequency when stationary for extended periods 807 - Allow user to configure aggressiveness vs battery tradeoff 808 809 --- 810 811 ## 10. Future Extensions 812 813 ### 10.1 Relay Network 814 815 Add ability for messages to hop through intermediate devices: 816 - Onion encryption for multi-hop routing 817 - Store-and-forward on relay devices 818 - Incentive mechanism for relay participation 819 820 ### 10.2 Group Dead Drops 821 822 Multiple parties sharing a dead drop: 823 - Shared group key derived from all members' keys 824 - Any member can deposit, any member can retrieve 825 - Useful for small cells or teams 826 827 ### 10.3 Plausible Deniability Features 828 829 - Hidden volumes (decoy contacts/messages with different passphrase) 830 - Duress PIN (wipes real data, shows decoy data) 831 - Steganographic storage (messages hidden in normal-looking files) 832 833 --- 834 835 ## Appendix A: Example Session Transcript 836 837 ``` 838 [Time: 0ms] Alice device scanning... 839 [Time: 50ms] Detected BLE advertisement: Service=0xDD01, RotatingID=a1b2c3... 840 [Time: 51ms] Computing expected rotating ID for contact "Bob"... 841 [Time: 52ms] Match found! Bob is nearby. 842 [Time: 53ms] Checking outbound queue... 1 message queued for Bob 843 [Time: 54ms] Initiating BLE connection to Bob's device 844 [Time: 150ms] GATT connection established 845 [Time: 151ms] Starting Noise_XX handshake 846 [Time: 152ms] → e (32 bytes) 847 [Time: 200ms] ← e, ee, s, es (96 bytes) 848 [Time: 201ms] → s, se (48 bytes) 849 [Time: 250ms] Handshake complete. Transport keys established. 850 [Time: 251ms] Sending MSG_COUNT { count: 1 } 851 [Time: 300ms] Received MSG_COUNT { count: 0 } 852 [Time: 301ms] Sending MSG_DATA { index: 0, total: 1, payload: [...] } 853 [Time: 450ms] Received MSG_ACK { index: 0, status: OK } 854 [Time: 451ms] Sending SESSION_DONE 855 [Time: 500ms] BLE disconnected 856 [Time: 501ms] Message state → DELIVERED 857 [Time: 502ms] Updating last_seen for contact "Bob" 858 ``` 859 860 --- 861 862 ## Appendix B: Test Vectors 863 864 ### B.1 Rotating ID Derivation 865 866 ``` 867 Input: 868 shared_secret: 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 869 time_slot: 1234567 (as u64 big-endian: 0x000000000012d687) 870 871 Output: 872 rotating_id: 0x[first 16 bytes of HKDF output] 873 874 Verification: 875 HKDF-SHA256( 876 IKM: shared_secret, 877 salt: time_slot_bytes, 878 info: "dead-drop-rotating-id-v1", 879 L: 16 880 ) 881 ``` 882 883 ### B.2 Message Encryption 884 885 ``` 886 Input: 887 plaintext: "Hello, Bob!" 888 message_id: 0x00112233445566778899aabbccddeeff 889 sender_ex_private: [32 bytes] 890 recipient_ex_public: [32 bytes] 891 892 Output: 893 encrypted_message: [structured as defined in 5.4] 894 ``` 895 896 (Actual test vectors would include concrete hex values for implementation testing)