mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-15 07:06:11 +00:00
Upstream's send-gated `transferSprayCopies` already closes the
link-drop-before-send loss case: it only decrements copies, inserts the
courier into `sprayedTo`, and persists after the local BLE send is
accepted. But courier writes are `.withoutResponse`, so send-accepted is
not taker-received: the spend still stands when the taker deterministically
refuses the deposit (bad/missing signature, unknown peer, policy nil,
quota full, invalid), and those copies leave the network unretryable.
This adds the orthogonal second half on top of the send-gated commit: a
capability-gated, signed receipt that gives the budget back only when the
taker deterministically declines.
- Wire (additive, ignored by old peers): courierSprayAck 0x2A ("stored it",
retire the offer early) and courierSprayDecline 0x2B ("deterministically
refused" -> restore). New capability bit PeerCapabilities.courierAck
(1 << 8), advertised in localSupported; givers branch on the taker's
advertised capability, so old takers keep the unchanged path.
- Receipt payload is the envelope's ciphertext hash (existing dedup key),
so no CourierEnvelope wire change.
- Giver: offerSprayCopies is transferSprayCopies verbatim plus one
in-memory pendingSprayOffers restore record in the commit block. Signed
decline -> cancelSpray (restore, keep courier in sprayedTo); signed ack or
timeout -> confirmSpray (spend stands). Restore map is memory-only and
cleared by wipe(), so a restart can never inflate copies.
- Receipts are recipient-bound and authenticated exactly like a deposit:
packet.recipientID must equal our peer id, the claimed sender must be the
direct ingress peer, and the signature must verify against that peer's
registry-bound signing key. This blocks a valid signed receipt addressed
to a different giver from mutating our pending offer.
Tests: CourierStoreTests (offer/confirm/cancel conservation, ack-loss
timeout and restart cannot re-inflate, replayed decline cannot inflate)
and CourierEndToEndTests recipient-binding regressions (mis-addressed
decline does not restore; correctly-addressed decline does; stray ack does
not clear the restore window).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>