Private messaging on Bitcoin SV with pre-paid digital energy
April 2026
KeyChat is a peer-to-peer messaging application in which every message is a Bitcoin SV transaction. Each transaction's OP_RETURN payload contains opaque bytes that only the sender or the recipient can interpret as a message; to any third-party observer — including the BSV node operators that store the transaction — the payload is computationally indistinguishable from random data. KeyChat does not maintain a server-side message store, does not require user accounts or phone numbers, and does not have privileged access to message contents. Users pay a fraction-of-a-cent Compute fee per message, directly and transparently. If you don't pay for your messages, someone else will buy them. Here, privacy is paid for directly and transparently. Identity is a public key. Anyone may participate.
Existing private-messaging systems share three structural vulnerabilities:
Account state is centralized. Sign-ups require a phone number or an email, which links the messaging identity to a real-world identity at the platform's discretion. Account deletion is at the platform's discretion. Account suspension is at the platform's discretion. The user's right to communicate is rented from the provider.
Metadata is centralized. The provider knows who messaged whom, when, from what device, and at what frequency. Because the platform already holds the identity link from #1, it can attribute that metadata to a specific real-world person — it can point to exactly whose metadata this is, on demand. Subpoenas and silent compulsion target that metadata, not the content, because the platform can hand over both the data and the name attached to it.
Data monetization is structural. If a messaging platform is free, somebody is paying for its servers, its engineers, its bandwidth — and it isn't the platform. The payers are advertisers, data brokers, and governments buying aggregated behavioral data. The user isn't the customer; the user is the product. Even "privacy-first" free platforms are pressured by this structure — every quarterly revenue meeting asks the same question: how do we monetize our users?
KeyChat targets all three.
This produces a system with the following property: a third party who wishes to read a KeyChat conversation must compromise the private key of one of the participants. Observing the chain is insufficient. Compelling the operator is insufficient (there is no operator with privileged access). The only lawful path to a user's messages is to subpoena the user themselves — the key-holder. The platform has nothing to hand over; the user has the key. This is the security baseline.
KeyChat is a client application that runs entirely in the user's device. It performs three roles:
A KeyChat bridge is an SPV relay that mediates between the client and the BSV network. It accepts transactions for broadcast, indexes recent transactions for address-based queries, and serves UTXO data. The bridge holds no message content beyond what is already on the public chain.
┌──────────┐ opaque bytes ┌────────────┐ raw tx ┌──────────┐
│ Client │ ────────────────► │ Bridge │ ─────────────► │ BSV │
│ (browser)│ ◄──────────────── │ (relay) │ ◄───────────── │ network │
└──────────┘ UTXO / history └────────────┘ block / mempool └──────────┘
▲
│ opens message with user's private key (never sent anywhere)
▼
reader
The client is the only component with access to the message content.
Every message the user sends costs Compute. The default per-message cost is 0.25 Compute (125 satoshis), allocated approximately as:
| Cost | Sats | Purpose |
|---|---|---|
| Miner fee | ~100 | Pays for inclusion in a BSV block |
| Recipient dust | 5 | Delivers a UTXO the recipient can reference |
| Platform fee | ~20 | Operating revenue for KeyChat infrastructure |
Compute is currently purchased by depositing BSV to the user's wallet address (minimum 500 satoshis = 1 Compute). The wallet's splitter automatically packages deposits into 500-sat Compute UTXOs. A credit-card purchase path is planned for users who don't already hold or understand BSV. Users see their balance in Compute, not satoshis — for example, "3,973 Compute" rather than "1,986,500 sats."
A KeyChat message is a single BSV transaction with a 125-sat burn budget (one Compute sub-unit) and the following output shape:
| Output | Value | Purpose |
|---|---|---|
| 0 | 0 sats | OP_RETURN containing the SMSG payload |
| 1 | ~20 sats | Platform fee (covers KeyChat infrastructure) |
| 2 | 5 sats | Recipient dust (delivers a referenceable UTXO to the recipient) |
| 3 | 0, 125, 250, or 375 sats | Compute change back to sender (omitted when 0) |
The miner fee is implicit — it's the difference between the input sats consumed and the sum of the above outputs, collected by whichever miner includes the tx in a block. For a 600 character text message, the miner fee is ~100 sats.
Change depends on the size of the Compute UTXO the picker used as input: a 500-sat input leaves 375 change after the 125-sat burn; a 375-sat input leaves 250; and so on down to a 125-sat exact-match input which leaves zero change (the sub-unit is fully consumed).
The OP_RETURN payload is structured as a series of pushdata fields:
OP_FALSE OP_RETURN
PUSH "SMSG" ← protocol identifier
PUSH version (0x01)
PUSH sender_pubkey (33 bytes, compressed secp256k1)
PUSH recipient_pubkey (33 bytes)
PUSH private_blob (variable; private message body)
PUSH timestamp (4 bytes, Unix epoch)
Messages are sealed using BRC-78, a sealing scheme native to the BSV SDK. The sender derives a shared secret between derived child keys of the sender's private key and the recipient's public key, then seals the message bytes under that shared secret. The sealed envelope prepends a version tag, the sender's public key, the recipient's public key, a random 32-byte keyID, and a per-message IV + authentication tag. The total overhead per message is 150 bytes beyond the message byte length.
The envelope is asymmetric on open: the BRC-78 unseal function requires the caller to hold the private key matching the recipient pubkey in the envelope. A sender cannot open their own sent messages through the standard unseal path — the check refuses keys that don't match the stored recipient. Each message is openable only by whoever the sender chose as recipient.
To any third-party observer reading the BSV chain, the message body is opaque bytes. The two pubkeys are visible and identifiable as pubkeys — they reveal the channel of the conversation (which two keys are talking) — but the private blob past them appears as random-looking data. The pubkeys alone do not reveal the message content, the message intent, or the participants' real-world identities.
The on-chain data is a hash, not encryption. The two concepts are categorically distinct: a hash is a one-way-looking opaque byte string with no revealed structure; encryption is a regulated operation that carries legal baggage a hash does not. From any observer's perspective — including the one that matters legally — the user writes a hash to chain. The KeyChat client is just software running on the user's device; the BSV transaction is signed by the user's key and broadcast under the user's authority.
The two visible pubkeys are a design property, not a leak. KeyChat deliberately does not hide the sender or recipient. Stealth addressing, paymail-derived per-message recipient addresses, HD-derived signing keys, and ephemeral sender fields are all technically possible on BSV, and all rejected here. Hiding identity at the transport layer is what turns a messaging tool into an anonymous communication channel — a regulatory category KeyChat is intentionally not in. The chain showing two identified public keys exchanging hashes is the exact property that keeps the system on the lawful side of the line: the user is identifiable as the author of their own on-chain activity, and the content they authored is bytes they wrote — not ciphertext produced by a regulated operation.
This also means metadata — who talked to whom, when, how often — is visible on chain. Visibility is not the same as attribution. A pubkey is not a phone number or an email; it does not come with a name attached. Identifying who a specific pubkey belongs to requires chain analytics: correlating timing and spending patterns, aggregating known-pubkey disclosures from elsewhere, and eventually linking an on-chain identity to a real-world one. This is possible, but it is neither cheap nor fast. It demands substantial investment in analysis infrastructure and skilled analysts, and it succeeds unevenly depending on how careful the user has been about disclosure.
The cost structure is the point. A corporate messaging platform already holds the phone-to-metadata mapping; obtaining that data is a matter of issuing a subpoena to the provider. KeyChat has no such mapping to hand over; reconstructing it from the chain is a research project. That shifts the economics sharply: casual surveillance — advertisers, data brokers, passive observers — is priced out. Serious chain analytics are practical only for actors with real resources and a real reason — a law-enforcement investigation backed by specific evidence, for example — which is the correct economic equilibrium for a lawful messaging tool. Privacy is the default; identity attribution is available but expensive, and reserved for cases that justify the cost.
Users who want still stronger metadata privacy can do what Bitcoin users have always done: use more than one account. A KeyChat identity is a single secp256k1 keypair, generated on-device in seconds at zero cost. Separate identities for separate contexts — one for family, one for work, one for a pseudonymous persona — are not linked on chain unless the user links them. Each account has its own pubkey, its own address, its own conversation graph. The observer sees independent activity; only the user knows the connections. This is the same compartmentalization Bitcoin wallets have always offered, applied to messaging identity.
Files are encoded as a JSON envelope wrapped inside the same private-blob field:
{ "kind": "attachment", "mime": "image/jpeg", "name": "photo.jpg", "data": "<base64>" }
The envelope is sealed using the same BRC-78 path as a text message. From the chain's perspective, an attachment-bearing transaction is shape-identical to a text transaction — same output structure, same OP_RETURN format. Only the recipient (after opening) can determine that the private blob contains an attachment, what type it is, or what filename it carries.
Attachments may be of arbitrary size, capped only by the user's willingness to pay the corresponding Compute cost. The cost is shown to the user before send and approved twice for files over 500 MB.
Three reasons — philosophical, structural, and practical.
Philosophical: if you don't pay for your messages, someone else will buy them. Every free messaging platform monetizes users by selling their data. "Free" is the bait; the user is the product. The platform's real customers are advertisers and data brokers. KeyChat inverts that model: you pay a fraction-of-a-cent Compute cost per message, and in exchange you own your messages. No ad model, no behavioral graph sold, no operator profit motive to mine the metadata. Here, privacy is paid for directly and transparently. The price of your privacy is visible — paid to miners in Compute — and that's the whole point.
Structural: you are paying for storage. On every other messaging platform, your messages sit on servers owned by a corporation. That corporation decides who can read them, how long to keep them, and under what conditions to hand them over. With KeyChat, the Compute fee buys permanent storage on the BSV blockchain — a distributed ledger with no owner. The hash of the sealed message is what hides your content from observers, but the real privacy comes from where the data lives: not on any one party's server, where it can be compelled or monetized. You pay fractions of a cent, and the record of your message is held by no one and by everyone.
Practical: spam barrier. Free messaging has no cost barrier for spammers. Existing platforms rely on phone-number scarcity (one number per user, costly to acquire in bulk) as their soft anti-spam mechanism. Charging Compute per send attaches a real cost to each message. For honest users it's invisible. For a spammer it's unrecoverable expense per attack, with no observable benefit — messages cannot be read by recipients without prior consent (§5).
Every KeyChat user registers a username. The username is the only addressable handle in the system — there is no paste-by-pubkey path, no QR-scan path, no "add by address" shortcut. Registration is part of joining: without a username, a user has nothing for other users to search, and no one else can initiate a message to them. Every contact-add flow in the product routes through the same economic gate described in this section — there is no second rail.
Registration costs 0.25 Compute. The transaction produces four outputs: a zero-value OP_RETURN carrying [KCHAT_REG][version][owner_pubkey], a 1-sat output to a provably-unspendable anchor address derived from the username itself (see §5.2), a platform-fee output to KeyChat's operating address, and change back to the sender. The anchor output is what later lookups find; the 1 sat locked there is unrecoverable (the anchor has no known private key). Whatever is left of the 125-sat budget after the miner fee and the 1-sat anchor goes to the platform. Registration is first-write-wins at each anchor.
Once registered, the username string can be shared however the user wants — posted on a profile, printed on a business card, sent in conversation — and anyone who sees it can search that exact string inside KeyChat to reach the owner. Registration itself is the spam-gated front door: every new contact relationship begins with a paid intro (below), and the economics of that intro are what keep the system lawful and low-noise. (Recovery of a previously-registered username after a device wipe is covered in §5.4.)
Because usernames are public identifiers, they open the pubkey up to unsolicited contact. To deter spam against this surface, every message to a contact who has not yet accepted the intro carries an elevated cost of 2.5 Compute. Of that, 2.25 Compute is paid as a real Compute output to the recipient's address; the remaining 0.25 Compute covers miner + platform fees for the tx itself. An unsolicited message to a stranger therefore transfers real value to the recipient, inverting the economics of spam: a campaign that messages strangers funds them instead of costing them.
When the recipient taps Accept, their client auto-sends an acceptance transaction that refunds 2.25 Compute back to the sender — the same output shape as the intro, in reverse. The round-trip math: each side paid 2.5 Compute and received 2.25 Compute back, net 0.25 Compute per side — just one message's worth of miner and platform fees each way. The contact pair is now mutual, and subsequent messages between them revert to the standard 0.25 Compute rate.
If the recipient ignores or rejects, no refund is sent. The 2.25 Compute stays with the recipient, and the sender is out the full 2.5 Compute. Every future intro from the same sender costs another 2.5 Compute and again funds the recipient. The economics punish unwanted contact and compensate the target; there is no per-message refund path other than accepting.
Receiver-side enforcement. The economics only hold if the receiver's client refuses to surface non-compliant messages. For any incoming message from a pubkey that is not already a mutual contact, the client checks the on-chain outputs of the transaction. Unless the sender has included a P2PKH output of at least 2.25 Compute (1,125 satoshis) to the recipient's address, the message is silently dropped — no notification, no pending request, no chat thread. A rogue client that strips the intro fee and sends at the normal 0.25 Compute rate will get its message onto chain, but the recipient's client will never show it. The spam barrier is therefore protocol + client policy together: the chain permits the send, but the honest client only renders messages that paid the real cost.
The Compute whitepaper defines what Compute is — an output carrying the Compute inscription tag, where 500 satoshis is 1 Compute and the sat value scales linearly (1,000 sats = 2 Compute, 125 sats = 0.25 Compute). Wallets may break Compute into sub-unit amounts within their own ecosystem, and can technically send sub-unit amounts to other app wallets — but whole-Compute transfers (multiples of 500) are recommended between apps for easier accounting. KeyChat picks 125 satoshis as its internal sub-unit and implements two pieces: a splitter that converts non-conforming UTXOs into valid Compute denominations, and a picker that decides which Compute UTXOs to spend per send.
What counts as a valid Compute UTXO. A UTXO is a valid Compute denomination if both (a) its locking script begins with the DEP Compute inscription tag (OP_FALSE OP_IF "Compute" OP_ENDIF, 11 bytes) followed by a standard P2PKH, and (b) its value is any positive multiple of 125 satoshis. That includes values like 125, 250, 375, 500 (one whole Compute), 1,125 (the 2.25-Compute refund from an intro acceptance), 5,000, 100,000, and so on. Every Compute output KeyChat produces — from the splitter, the picker's change, refund handling, and the dust sweeper — carries the inscription tag, so the wallet's own output is immediately recognizable as Compute by any other DEP-aware wallet (per §3 of the Compute whitepaper). A wallet UTXO is only considered "fragmented" and targeted by the splitter if it's not a valid Compute — typically a raw BSV deposit from an external address, which arrives as a plain P2PKH with no inscription tag. Refund outputs, intro-fee outputs, change from prior sends, and anything else the KeyChat client produces are already conforming by construction and pass through the splitter untouched.
Splitter. Any wallet UTXO that isn't already a valid Compute denomination is a candidate for splitting. The splitter consumes it and produces:
splitCount = floor((input − 20) / 500)), then iterate downward until the miner fee covers the tx's byte cost at 100 sats/KB.The splitter refuses to run on a UTXO if the resulting miner fee would fall below the 100 sats/KB relay floor. In that case the UTXO is left alone until it can be consolidated with other inputs.
The splitter runs once per app load, looping until no more splittable UTXOs remain. A wallet with many stuck fragments clears in a single load.
Picker. When sending a message or attachment, KeyChat selects the largest available Compute UTXO first. The picker maintains two homogeneous pools — confirmed and unconfirmed — and prefers the confirmed pool. This grabs 500-sat pieces from older, already-mined txs rather than fresh unconfirmed change fragments, breaking the chain dependency that would otherwise make each message reliant on the previous one confirming. When a single UTXO doesn't cover the budget, the picker accumulates largest-first from the same pool until the sum is sufficient.
If the confirmed pool can't cover the cost (e.g., right after a burst of sends where all 500-sat pieces are pending), the picker falls back to the unconfirmed pool and builds the send from there. The two pools are never mixed within a single transaction — each send is either fully confirmed-input or fully unconfirmed-input. This preserves the chain-break property at the tx level: an unconfirmed send still rides on a coherent parent chain, and a confirmed send is never accidentally tied to a pending ancestor. If both pools fail to cover the cost, the user is out of Compute and the picker surfaces an insufficient-Compute error.
Change chain. A standard text message burns 125 sats. Spending a whole 500-sat Compute via successive messages produces the chain 500 → 375 → 250 → 125 → 0, draining the token over four sends. This is the origin of the "1 Compute = 4 messages" user-facing guarantee. The 125-sat per-message cost is set for today's miner fee rate (~100 sats/KB). As miner fees rise or fall, the KB-per-Compute a message buys varies accordingly, and the messages-per-Compute ratio moves with it — a message may consume more or less than 0.25 Compute depending on prevailing network fees. The current rate is always visible to users in the Fund area of the KeyChat app.
The 125-sat sub-unit is a KeyChat choice, not a Compute protocol constant. Compute's only protocol-level invariant is 1 Compute = 500 sats (§2 of the Compute whitepaper); each app picks its own sub-unit below that 500-sat interop boundary. KeyChat picked 125 because it matches the minimum viable message-tx cost under today's BSV economics. As Compute's economic strength shifts — miner rates drop, BSV price rises, message tx sizes shrink — KeyChat can adjust its sub-unit structure to offer more messages per Compute (500 / sub_unit = messages per Compute, e.g. dropping to 100 gives 5 messages per Compute, 50 gives 10). This is a KeyChat code change, not a Compute protocol change — other apps using Compute with different sub-units are unaffected, and existing Compute UTXOs continue to hold their 500-sat face value across the transition.
Dust sweeper. Received messages deposit a 5-sat recipient output to the user's address — the chain-level "you have mail" signal. Those 5-sat outputs aren't a valid Compute denomination, so the picker ignores them and they accumulate. The sweeper consumes all such non-Compute UTXOs in a single transaction and emits valid Compute pieces in their place. It auto-runs on app load at a 10 sats/KB fee rate (one-tenth the 100 sats/KB used for normal sends). Miners accept this low rate for multi-input consolidation transactions — confirmed on mainnet — because consolidation txs shrink the UTXO set, which is net-beneficial to miners. The sweeper no-ops when the total wouldn't produce at least one 125-sat piece after miner fee. At 10 sats/KB, approximately 36 received messages (36 × 5 = 180 sats of dust) must accumulate before a sweep becomes viable, recovering one 0.25 Compute piece.
A KeyChat user's underlying identity is a 33-byte secp256k1 compressed public key generated on device. The pubkey serves three technical roles:
OP_RETURN of the user's registered username (§4.2, §5.2). Lookups of that username resolve to this key.The user-facing identifier is the username, not the pubkey. Every KeyChat user registers a username as part of joining (§4.2); every contact-add flow in the product routes through a username search. Pubkeys are never copied, pasted, scanned, or typed by a user — they are the internal target a username resolves to, nothing more. The sections below cover how that disclosure happens at the application layer (§5.1), how a username resolves to its pubkey on chain (§5.2), how the sender's own claimed username rides inside the sealed body of every message (§5.3), and how the private identity key is kept in the user's sole custody (§5.4).
When a user registers a username, sends a message containing their real name, or links their pubkey to a public profile, they are creating the identity-to-pubkey link — not the platform. The platform observes that link only insofar as the public chain does. Disclosure is a user choice, made one decision at a time.
This architecture does not eliminate identity. It moves identity from a derived attribute (assigned by the platform at sign-up) to a disclosed attribute (chosen by the user at communication time). The legal and surveillance properties of the system follow from this distinction.
There is no central directory of KeyChat users and no search index. Discovery of usernames is explicitly external — the username string is shared through whatever channel the user chooses (a copied URL, a profile on another platform, a business card, a text message, in-person conversation). The receiving client then searches that exact string inside KeyChat to resolve it to a pubkey; there is no in-app browse, scan, or paste-by-key path. KeyChat does not enumerate its own users.
Username resolution. Each username deterministically owns an anchor address on chain:
hash = RIPEMD160(SHA256("kchat:reg:v2:" || username))
anchor = base58check(0x00 || hash)
The anchor is a valid P2PKH address with no known private key — the 20-byte hash is derived by hashing the username directly, not by hashing a pubkey. Producing a pubkey that would spend from this address would require a SHA256+RIPEMD160 preimage attack. Value sent to the anchor is provably unspendable; the registration transaction puts a single 1-sat marker there, which is enough for the address to show up in the bridge's history index. The rest of the 0.25-Compute registration budget is paid out elsewhere in the same transaction — miner fee plus a platform fee to KeyChat's operating address (§4.2).
Registration is a transaction that pays a minimal anchor output to the derived address and carries the owner's pubkey in OP_RETURN:
OP_FALSE OP_RETURN
PUSH "KCHAT_REG"
PUSH version (0x02)
PUSH owner_pubkey (33 bytes)
To resolve @alice, a client computes the anchor address for the string "alice", queries the chain for transactions at that address, parses each registration OP_RETURN, and returns the earliest confirmed one as the authoritative owner. First-write-wins; later registrations at the same anchor are ignored.
No beacon to scrape. Because each username's anchor is independent, there is no single address a scraper can read to enumerate the user base. Harvesting usernames reduces to guessing them: computing the anchor for candidate strings and probing each. Common English words or short handles can be dictionary-attacked; high-entropy usernames are practically unreachable by guessing.
Reverse lookup is infeasible by design. Given only a pubkey, there is no way to ask "what username does this key own?" — the scheme does not index registrations by pubkey, and the chain carries no reverse pointer. A human-readable label for a pubkey must be supplied by the other party, either in the sealed body of a message (§5.3) or out of band.
A user may register additional names over time. Registration is per-name, not per-key — the same pubkey can own several names. Each registration is permanent at its own anchor, so an older name continues to resolve to the original pubkey even after the user has moved on to a new one. Reverting to a previously-owned name is free: the client re-attaches to the existing anchor without broadcasting a new registration, since the anchor already resolves to the user's pubkey. Clients advertise whichever name the user most recently registered in outgoing messages (§5.3); recipients update their local contact records when a new verifiable claim from the same pubkey arrives.
Because reverse lookup (pubkey → username) is not part of the protocol (§5.2), the sealed message body itself also carries the sender's self-claimed username. The claim sits inside the sealed bytes, so no third-party observer on chain can read it. After BRC-78 unsealing, the body parses as a compact JSON envelope:
{ "kv": 1, "b": "<body>", "u": "<sender's claimed username or null>" }
Where b is the original text (or the attachment JSON of §3.2) and u is the username the sender had registered locally at send time. The receiver forward-verifies the claim before trusting it: the anchor for the claimed username (§5.2) must resolve to the sender's pubkey. Only verified names appear in the UI; an unverified or mismatched claim is discarded and the sender is displayed by truncated pubkey.
The private identity key is enforced as user-held on both ends of the identity lifecycle:
At setup. When a user creates a new identity, the Continue button remains disabled until they download their private key backup. KeyChat refuses to finalize the account until the user has a local copy of the key. Refreshing before downloading discards the unsaved key — no partial-setup state persists on chain or on the device.
At wipe. To remove an identity from a device, the user must upload or paste the private identity key as proof of ownership. The client will not erase local identity state without this verification.
Why:
This completes the no-central-authority loop: the user is the only party who can create, use, or abandon their identity.
On-chain recovery is one-way. After a wipe, a user restoring from their private key recovers every message they received from chain. On identity load the client scans the address's full history for SMSG transactions and unseals those where the recipient pubkey matches. The scan is independent of any UTXO state — the SMSG OP_RETURN payload lives on chain forever, so received messages remain recoverable indefinitely.
Username restoration on wipe + recovery. Own username does not auto-recover. Because reverse lookup is not available (§5.2), a client restored from the private key cannot determine what username the user previously registered — the recovery flow has no way to discover it from chain data alone. The user must type their username back in manually. Once they do, the client reads the anchor, confirms the on-chain pubkey still matches the restored identity key, and restores the username locally without broadcasting a new transaction or spending any Compute (see §4.2). The UI surfaces this as a "Username recovered!" message to make clear that the pre-existing registration was re-attached, not re-paid.
Contacts' usernames come back as messages are recovered: each unsealed body exposes the sender's claimed username (§5.3), and the receiver's live forward-verification (§5.3) populates pending-request rows and contact records from newly-verified claims.
Sent messages, by contrast, cannot be recovered from chain. The BRC-78 unseal function enforces that the caller's private key must match the recipient pubkey in the envelope; for a sent message the recipient is someone else, so the sender's own key cannot open it. Sent messages are restored from local chat backups (if the user downloaded them before wiping), not from chain.
This has a direct legal consequence: reconstructing a full conversation between two users requires subpoena authority over both participants. Subpoenaing only user A recovers messages B sent to A, but not the messages A sent to B — those exist on B's side of the chain (as B's received messages) and on A's local chat backups if any. A complete thread is only assembleable when both key holders produce what they have. One-sided subpoenas yield one-sided threads.
Chat backups. KeyChat lets the user export any chat as a sealed .keychat backup file. Each backup contains the full conversation (both sent and received messages) and is sealed (BRC-78) to the user's own identity public key — only the user's private key can open it. Post-wipe or on a new device, the user re-imports backups to restore the complete thread. While received messages can also be recovered from chain, sent messages cannot — so the backup is the only record of the user's sent-side history. Backups stay with the user — KeyChat never holds them, never stores them server-side, and has no way to open one even if handed a file.
Privacy is a human right. Anonymity is not. KeyChat's architecture protects the first without granting the second. A user's identity is a public key; their messages to that key are private by construction. But if a lawful authority needs to reach the human behind the key, the user is the identifiable subpoena target — with the key in hand, they are the only party who can open their own messages.
KeyChat today runs as client-side wallet software in the browser; messages are fetched by polling a bridge and only surface while the app is open. The next step is native iOS and Android shells (a thin wrapper around the same client) and opt-in push notifications so new messages can reach users while the app is closed.
Push delivery will go through each platform's native service — APNs on iOS, FCM on Android — via a separate push relay that watches the chain for new messages addressed to subscribed pubkeys and fires a generic "new message" wake-up. The payload carries no content, no sender identity, and no preview; the actual message is unsealed on-device using the user's private key, same as today.
The metadata cost of enabling push is explicit and bounded:
{pubkey → device token} mapping for subscribed users.What push does not leak. No content, no counterparties, no social graph. The things that actually matter — who a user talks to, what they said, when their relationships formed — stay fully sealed on chain, unreachable without the recipient's key. Push only reveals "the user got some message sometime" to the platform routing the notification.
This is consistent with KeyChat's broader stance (§5.4): KeyChat is a private messaging system, not an anonymous one. Common identifiers — a user's public key, their device — are acceptable data points to share with the systems routing their activity. What stays sealed is the content and the relationships; what is deliberately not hidden is the user's identifiable presence on the network.
Push is opt-in per user. A user who prefers zero device-identifier correlation simply doesn't register, and their messages still arrive on next app open via the same polling path available today.
KeyChat is deliberately small. It is client-side wallet software — running entirely on the user's device — that makes it easy for users to send their own messages from one BSV public key to another directly on chain, paid for in Compute, with the message body kept private from any observer who lacks the recipient's private key. KeyChat does not send, relay, or store messages; each user signs and broadcasts their own transactions, and the chain itself is the delivery medium. There is no server-side message store, no account model, and no privileged operator in the message path. The architecture follows Bitcoin's: a public ledger, asymmetric keys, no trusted third party.
If you don't pay for your messages, someone else will buy them. That is the premise KeyChat is built on. Every free messaging platform that has ever existed eventually asks the same question: how do we monetize the people using this? The answer is always the same — sell their data, sell their attention, sell access to their behavioral graph. KeyChat removes the question by removing the incentive. Users pay directly, in Compute, for the on-chain work their messages consume. There is nothing left to sell, no one to sell it to, and no operator with the access to do so even if they wanted to. Privacy is not a feature of KeyChat. It is the product.
The system's value is not in any single technical novelty. It is in the deletion of components: the database that holds your messages, the account that links you to your phone number, the operator who can be subpoenaed to reveal who you talked to. KeyChat's design choice is to not have those components at all, so that no future legal or technical event can repurpose them against the user.
daba7f9e…).9329512…).