Postage (pay-to-reach)
Postage is OC Chat's anti-spam for strangers: a first message from someone who is not a contact must carry a Lightning preimage proving a payment to the recipient. Attention is priced, not filtered — sats as signal, the inverse of hashcash, where the cost accrues to the recipient as value. OC operates no payment rail and collects nothing; sender and recipient transact directly.
This is the pay-to-reach mode (kind="chat" with a postage block). Contacts
message for free; only stranger → inbox is gated.
Recipient policy
A recipient MAY publish a postage policy: a floor_sats and a Lightning
receiving endpoint resolving to a wallet the recipient controls. The
endpoint string MUST live inside a record signed by the recipient's Bitcoin
identity (e.g. the kind-30078-bound directory listing) —
otherwise an attacker substitutes their own endpoint and the binding binds to
the wrong party. No OC service mints, proxies, or relays this invoice.
The endpoint is one of:
- A BOLT12 offer (RECOMMENDED, stronger), or
- The shipped v0 rail: an LNURL Lightning Address (LUD-16) with LUD-18 payerData.
v0 ships LNURL because BOLT12 invoice_request cannot reliably echo a per-DM
nonce today. For the binding to hold, the endpoint MUST mint a fresh per-DM
invoice committing the sender's nonce; a static/reusable invoice degrades the
proof to settlement-evidence-only.
The sender postage block
In v0, postage rides inside the encrypted payload, not as a top-level
envelope field — the shipped lock-core seal() cannot commit a postage
parameter field-by-field in the chat_envelope_id. Integrity rests on the
AEAD: postage is inside the ciphertext, which is committed in the id
and signed by the device, so any tamper changes the ciphertext → the id →
breaks the signature. (True per-field commitment is a deferred lock-core
change.)
"postage": {
"floor_sats": 100,
"amount_sats": 100, // >= floor_sats
"payment_hash": "<64-char hex>",
"preimage": "<64-char hex>",
"nonce": "<hex; the sender's per-DM nonce, carried in payerData>",
"recipient": "<recipient btc address — must match the endpoint's identifier>",
// carrier fields the recipient needs to re-verify the binding OFFLINE:
"bolt11": "<the paid invoice; carries payment_hash + the description_hash 'h' tag>",
"lnurl_metadata": "<the recipient endpoint's metadata, VERBATIM bytes>",
"payerdata": "<the url-encoded payerData the sender sent (carries the nonce)>"
}
Recipient-side verification (mostly offline) — NORMATIVE
A recipient verifying inbound postage MUST run all six checks:
- Settlement —
SHA-256(preimage) == payment_hash. The load-bearing Lightning bearer proof. - Binding (re-derived by OC itself) — decode
bolt11, read its description-hash (h) tag, and assert it equalsSHA-256( lnurl_metadata || urlDecode(payerdata) ). The verifier MUST recompute this hash itself — wallets stopped enforcing the LNURL description-hash (lnurl PR #234, 2026-05) — and MUST hash the verbatimlnurl_metadatabytes (neverJSON.stringify(JSON.parse(x)), which reorders and breaks the match). - Recipient — the
lnurl_metadatatext/identifierequals the claimedrecipient, resolved from an identity-signed endpoint. - Amount — the
bolt11carries an explicit amount ANDamount_sats >= floor_sats(reject amountless invoices). - Nonce — the in-body
nonceis the one committed inpayerdata. - Anti-replay — the
payment_hashis not in the recipient's local spent-ledger (one-time use). On accept, record it.
The recipient does not re-enforce the invoice's expiry: the preimage already proves the HTLC settled, and a short invoice expiry would wrongly reject valid postage that took a while to deliver. Invoice freshness is the sender's pay-time concern; per-DM freshness comes from the nonce + the spent-ledger.
All checks pass + not-spent → inbox. Any fail OR already-spent → the message is delivered but held in the filtered/pending state (the "Requests" surface), never dropped.
Why this is non-replayable — and its honest ceiling
Because the recipient's own endpoint minted the per-DM invoice committing this recipient + amount + nonce into the description-hash, a preimage replayed to a different recipient fails the binding there; a preimage replayed to the same recipient is caught by the local spent-ledger. The residual ceiling, which MUST be disclosed and MUST NOT be over-claimed:
- Recipient-scoped, NOT third-party transferable. A party seeing only
{preimage, nonce}— without the recipient-signed invoice + carrier fields — cannot verify the binding. Never render a verified-postage badge as "proof anyone can verify."- The recipient's endpoint is a NAMED trust anchor. It chooses what the invoice commits to and MUST mint a fresh per-DM invoice; a pre-minted shared invoice reintroduces replay. The endpoint string MUST be identity-signed and surfaced plaintext.
- Wallets no longer enforce the description-hash — OC MUST recompute it itself, over verbatim bytes, or the binding silently does nothing.
- The spent-ledger is local and per-recipient-device — best-effort across that recipient's synced devices, not a global consensus ledger.
- Bearer proof, not on-chain settlement proof. A cold observer can check the hash and the binding, but cannot independently confirm the HTLC settled.
This is Security S7 stated in full. The value is real and the limits are named — that is the family posture.
OC operates no payment rail
OC operates no postage gateway. Any Lightning gateway in a recipient's postage path is operated by a named third party, never OC. Zap receipts (NIP-57) MUST NOT be used as postage proof — they are server-signed and forgeable; only the BOLT11/BOLT12 preimage is a bearer proof. This is the OC-never-pays-users corollary applied to receiving: OC's only money flow is inbound subscription billing, and even that has no outbound leg.
Named-Fedimint custodial fallback (institutional last-mile)
A recipient with no Lightning endpoint of their own — an institution, a non-technical staff member — MAY name a Fedimint federation as their postage last-mile. The federation's LNURL/Lightning-Address endpoint is published in the recipient's own kind-30078-bound listing exactly like any other endpoint, so the six-step verification is unchanged and endpoint-agnostic. The structural difference is custody: the federation receives the sats and settles ecash to the recipient's federation account.
- OC stays absent + non-custodial. OC touches no sats, runs no guardian share, holds no hot wallet, and is never in the HTLC path — the federation custodies and settles, exactly as the family corollary requires ("federations settle to users; OC has no payout hot wallet").
- Named trust anchor. The federation is surfaced in plaintext in the listing
— its
federation_id, human name, and invite — so a sender sees who custodies before paying, and MUST be able to decline a federation-fronted endpoint it does not trust. - Deployment gate. Because a federation custodies inbound funds, a
deployment MUST clear a money-transmitter analysis before enabling this
path. The failure codes are
E_FEDIMINT_UNAVAILABLEandE_FEDIMINT_BINDING_MISMATCH. - Ed25519 verdict. Lightning settlement IS the Bitcoin-load-bearing hook (a preimage has no Ed25519 analog). The fallback adds custody convenience, not a new Bitcoin claim, and is honest that the federation is a trusted custodian, not a trustless rail.
Next
- The three send modes — where pay-to-reach sits.
- Security posture — S7 (postage replay) and the custody boundary.
- Specification — the normative §6 rules and error codes.