1-on-1 Lanes & Byte-Borrowing
Send pipelines, optimistic bubbles, failed message refunds, and disjoint keystream borrowing.
Overview
Wiltkey 1-on-1 conversations are structured as a pair of unidirectional lanes sliced out of a single shared keystream.pad file. The initiator of the chat writes outgoing messages to the lower half (offset 0 to size/2) and reads incoming messages from the upper half (offset size/2 to size). The responder writes and reads from the inverse segments.
Because these partitions are rigid, if one user sends significantly more data than the other, their sending lane will deplete (wilt) first, even if the other lane has megabytes of unused capacity. To solve this, Wiltkey implements keystream byte-borrowing, allowing a sender to claim disjoint fragments of the peer's unused sending space.
Keystream Partition Model
The Send Pipeline
-
Capacity Check & Offset Allocation:
The client checks remaining bytes. If the primary sending lane has insufficient bytes (< 74 bytes), the client scans
additionalSlotsfor a borrowed range. If none exists, it triggers arequestBorrowcall and fails the send. - Synchronous Pointer Reservation: To prevent race conditions where a rapid sequence of taps triggers multiple messages consuming the same offset, the client advances the offset pointer synchronously in RAM before starting any async operations.
-
Optimistic Placeholder Bubble:
A message bubble is added instantly to the UI window with
isPending = true. The database is not yet touched. -
XOR & Socket Transmission:
The client ensures the WebSocket is connected, performs the XOR operation on the plaintext bytes, saves the message record to the SQLite DB, and sends a
SEND_MESSAGEframe containing the ciphertext envelope over the WebSocket.
Failed Message Refund
When a message send fails (or if the user deletes a pending/failed bubble), Wiltkey recovers the key bytes. If the deleted message's offset matches the tail pointer (i.e. offset + payloadBytes == outgoingOffset), it rolls the pointer back to the message's offset. This prevents gaps in the keystream so that capacity is not permanently lost due to simple connectivity issues.
Byte-Borrowing Mechanics
If a sender's space drops below 500 bytes (proactive top-up) or 74 bytes (hard block), a borrow_request is sent to the peer. The peer divides their own remaining unused sending lane in half:
outgoingMaxOffset -= grant) and sends a borrow_grant containing the range [start, end). The borrower registers this as a triple [start, ptr=start, end] in additionalSlots, expanding their sending capacity.
Delivery Reconciliation & Resync
Each delivered message normally fires a one-shot delivery_receipt back to the sender (single check → double check). That receipt is queued on the relay for 24h if the sender is offline — so a sender that stays closed past the TTL can be left showing a permanent single check even though the peer received the message. Symmetrically, the receiver only auto-pulls missing history when a newer message arrives to expose the gap (offset > incomingOffset → _requestChatResync); a gap with nothing after it stays unfilled.
syncOneOnOneChat() reconciles both directions on demand. It is wired to a sync button in the chat header (always tappable; its icon switches from sync to the sync_problem glyph — attention via shape, not colour, so it reads on both the dark and light themes — when a sent message sits on a single check while a later one already double-checked) and runs automatically in two more cases when that stuck-delivery (or inbound-gap) signal is present: once on chat open, and again whenever a peer's message lands while the app is open (maybeAutoReconcileOnPeerMessage, fired from the inbound path). The peer's arrival proves they're reachable, so it's the ideal moment to answer our pending delivery_check — the user no longer has to tap Sync. It's guarded so it only fires when there's actually something to reconcile, and a per-chat in-flight lock coalesces a burst of arrivals into a single sync, so it's never a loop or relay spam.
- Inbound pull: requests a resync for the whole range past our contiguous
incomingOffset, recovering any messages we never received. - Outbound check: sends a
delivery_checklisting our undelivered sent messages ({id, offset}). The peer answersdelivery_check_responsewithconfirmed(offsets it holds → we flip those to delivered) andmissing(offsets it lacks → we resend them the normal way, which yields a fresh receipt).
Both delivery_check frames carry only metadata (ids/offsets); resends reuse the already-encrypted OTP ciphertext, so nothing is exposed in plaintext to the relay.
The peer does not need to be online at the moment you trigger a sync. The relay store-and-forwards
delivery_check and its response with a 24h TTL, and the background isolate buffers these frames but cannot act on them (no master key). The check is therefore answered — and the response applied — when each side next brings the app to the foreground, where the buffer is drained (on unlock or resume). Net effect: reconciliation converges on the next foreground of each side, not on both apps being open at the same time. See Notifications & Background Modes.
Key Files & Symbols
| File Path | Symbol Name | Description |
|---|---|---|
lib/core/state_chats.dart |
sendMessage() |
Handles offset checks, reserves offsets, creates pending bubbles, and transmits envelopes. |
lib/core/state_chats.dart |
clearFailedMessage() |
Wipes a failed bubble and rolls back pointers to refund keystream. |
lib/core/state_chats.dart |
syncOneOnOneChat() |
Manual/one-shot reconciliation: pulls missing inbound history and sends a delivery_check for undelivered outbound messages. Backs the header sync button. |
lib/core/state_chats.dart |
maybeAutoReconcileOnPeerMessage() |
Fires syncOneOnOneChat automatically when a peer message arrives and there's an inbound gap or stuck-undelivered send. Guarded (only when needed) and per-chat in-flight-locked, so a burst of arrivals coalesces into one sync. |
lib/core/state_inbound.dart |
_handleDeliveryCheck() / _handleDeliveryCheckResponse() |
Peer confirms/flags which listed messages it holds; sender marks confirmed ones delivered and resends the missing ones. |
lib/core/state_borrow.dart |
AppStateBorrow |
Extension coordinating byte-borrowing requests, grants, and offset mapping. |
lib/core/state_borrow.dart |
handleBorrowRequest() |
Calculates grant size, shrinks local outgoing ceiling, and transmits borrow_grant envelope. |
Gotchas & Edge Cases
Refunds only work if the message being cleared is at the head of the sending boundary. If a user deletes an older failed message while newer messages have already advanced the pointer, the refund cannot roll back the pointer (to avoid double-allocation), resulting in a dead hole in the pad.