KKleo

Kleo roadmap

The plan from PROJECT_BRIEF.md §8, revised with decisions made since. The brief remains the product vision; this file is the current plan.

Principles

off a human-readable name that is verified on-chain.

hosted. Where that isn't enough, reaching someone costs a small payment.

clients verify against the chain. They can withhold data, but they can't read or forge it.

run over peer-to-peer links plus replaceable relays.

- Only BitNames' own cryptography; no GPG/OpenPGP. - No Lightning Network. Payments use BitNames sidechain sats (plus Bitcoin and Paymail addresses as published payment methods).

Status

PhaseWhatState
1Plumbing: node connection, settings, statusDone (392037a)
2Notepad: sign / verify / encrypt / decrypt by BitNameDone (392037a)
3Identity: wallet seed, registration, key rotation, contactsDone (392037a)
4Public profiles (linktree / phone book), kleo-resolverDone (282cfdd)
5Files: signatures, encryption, checksumsDone (0a4dac5)
6Direct messages, peer to peer (iroh)Done (5af452e)
7Offline delivery: kleo-hub mailboxes and endpoint directoryDone
8Everyday chat: notifications, tray, blocking, attachments, search, export, second deviceDone
9The wallet: receive, pay, deposit, withdraw, historyDone
10Payments and paid messages: paymail, prices, allow listDone
11Channels: broadcast, private groups, communitiesDone
12Release engineering: builds, audit, threat model, onboardingPartly done
13The BitNames design: five sections, the directory, the two-pane workbench, the activity logDone
5OpenPGP backendDropped (BitNames crypto only)

Last shipped

Phase 13: the BitNames design

The five mockups in bitnames-kleo-spec.md, built. The nav leads with Coins, Lookup, My PayMail, Messaging, Activity and keeps the rest of Kleo below them.

the selected name's keys, holding address, away message, links and PayMail fee, plus a right-click menu and an inline composer that quotes the price first.

direction toggle and a live transform. From, To, Sign message and Password are in the notepad rather than behind a tab. Several recipients get a block each; Password encrypts to nobody at all.

sets the price.

name in Lookup.

one) and the words behind a hash (only blake3(name) is published). Both are shown as what they are — a sequence id and a hash — rather than guessed at. See §11 of the spec.

set is compared with the last, and what differs — a rotated key, a new profile, a changed price or address — is recorded locally with the time it was seen. The first reading is a baseline, and the interface says how far back the record goes.

found; nothing polls the name set, which is the heaviest call Kleo makes.

(KDE Plasma) and Adwaita (GNOME/GTK4). Each is Kleo's own tokens in another desktop's palette, with light and dark sets so they follow the machine.

and sampling while Kleo is closed — the record only grows when it is open.

Phase 6: direct messages (online, peer to peer)

1:1 chat between BitNames, over iroh QUIC.

under chat.endpoint, so it is covered by their on-chain commitment and no server can substitute another one. Kleo publishes your own with one profile update.

public relay that only ever carries ciphertext. That is separate from the store-and-forward relays in phase 7, which hold messages for people who are offline.

the recipient's, through the node, exactly as in the workbench. The transport is never trusted: what the signature says beats what the envelope claims, and a message from a device the sender doesn't publish is shown as such.

only to authenticate the session. Worth doing once relays (phase 7) make sessions long-lived.

fixes.

Seed nodes and hubs (decided)

Chat today reaches people through [iroh]'s default infrastructure: Number Zero's relay servers, and their pkarr/DNS service at iroh.link, where each app publishes signed records of the addresses it can currently be reached at. That works, but it is the only part of Kleo that depends on somebody else's servers, it isn't configurable, and a BitName publishes only an endpoint id — so if that lookup is unreachable there is no other way to find the machine behind it.

Kleo will ship its own seed nodes, called hubs, and run some of them itself — but a hub is infrastructure, not an authority. Everything it serves is self-certifying:

What a hub holdsWho signs itWhat a hub can doWhat it can't
Profile documentsthe name's on-chain key, checked against the commitmentwithhold, serve a stale copyalter a field, invent a profile
Endpoint records (which addresses a machine is on)the endpoint key the record describeswithhold, expirepoint you at a different machine
Mailbox envelopes for offline peoplethe sender's BitName, sealed to the recipientwithhold, delay, dropread, alter, or forge a sender

So the worst a hub can do is fail to help, which is why several are configured at once and why any of them can be replaced.

A hub is itself a BitName. Its descriptor — roles, relay URL, retention, limits, operator — is published as that name's profile, so a hub is looked up and pinned exactly like a person, and Kleo can say "this is the hub you trusted last time" when its keys change.

Four roles, one small binary (kleo-resolver grows into kleo-hub, each role switchable):

  1. Resolver — hosts owner-signed profiles and answers bitname_commit. Shipped.
  2. Endpoint directory — signed address records keyed by endpoint id, so finding a

peer doesn't depend on a third party's DNS.

  1. Mailbox — store-and-forward envelopes for people who are offline.
  2. Relay — an iroh-relay on the same host, advertised in the descriptor, so hole

punching and the fallback path don't depend on a third party either.

Defaults and choice. Kleo ships a short list of hubs (the ones published on the Kleo site among them), a name publishes its own preferred hubs in its profile, and both lists are editable. Running your own is one command, and a name that lists only its own hub never touches ours.

The honest cost. A hub sees IP addresses, which names collect mail, and the size and timing of envelopes — never content, never who wrote what. Direct peer-to-peer delivery stays the default and the mailbox is the fallback, so the metadata a hub sees is the traffic that couldn't be delivered directly.

[iroh]: https://iroh.computer

Phase 7: offline delivery — kleo-hub

Both sides no longer need to be online at the same time.

be signed by a BitName (that is the spam barrier), fetching proves you hold the name by signing a challenge with its on-chain key, and envelopes expire on a published schedule.

entirely at infrastructure its users run.

its profile, automatic fallback from a direct link to the mailbox, and a status line per conversation saying how the last message actually travelled.

recipient's key, retention and per-name caps), the endpoint directory (records signed by the endpoint key, replay-proof, expiring), the hub descriptor, hub settings in the app, automatic fallback from a direct link to mail, and a background round that collects mail and republishes where this machine is.

from its recovery phrase collects its own mail without anyone retyping a name.

Phase 8: everyday chat

The things that make chat usable daily, none of which need new cryptography.

icon, and the option to keep receiving while the window is closed. Without this, a peer-to-peer message only arrives if the app happens to be open.

pricing in phase 10.

signed, verifiable text.

and history) can be exported and restored, so a new laptop doesn't mean starting over.

text stays in the app), a tray icon and close-to-tray so messages keep arriving, blocking (local, never announced, dropped after decryption so the signature decides), attachments up to 150 KB sealed like .bnenc files and written into Kleo's own folder, search across conversations, plain-text export, and several devices per name (chat.endpoints, tried in turn).

invitations, followed channels, your own names and every conversation, encrypted with Argon2id to a passphrase rather than to this machine's keychain. Importing only ever adds: contacts you already hold keep the keys you pinned, messages are matched by id, and restoring the same file twice changes nothing the second time.

it verified rather than the armor.

Phase 9: the wallet

Kleo can read a balance but cannot move a coin, and everything in phase 10 needs funds.

being spent.

the BIP300 enforcer, which is how a wallet gets funded in the first place.

before anything is signed (addresses have no checksum, so the address is shown back), deposits via format_deposit_address, withdrawals reported as started rather than sent, and a history assembled from the wallet's own outputs — upstream has no list-my-transactions call, so it is built from UTXOs, spent outputs and one get_transaction_info per transaction.

Phase 10: payments and paid messages

Bitcoin or Paymail address), and send BitNames sats with create_transfer.

- A paymail is a payment with a memo, sent to the address that holds the recipient's BitName. - The payment must be at least the recipient's on-chain paymail_fee_sats. - Kleo encrypts the memo to the recipient's key, because memos are public. - Its own inbox scanner replaces the broken get_paymail RPC.

- Each person sets their price: the on-chain paymail_fee_sats (price 0 means anyone may write). - Contacts, and people you've already talked to, are always free. - An allow list (decided): let specific BitNames through for free without saving them as contacts, so you can invite someone before they write. Entries can be one-off (their next message only) or permanent, and the list lives in the encrypted vault. - Charged once per stranger, not per message (decided). - A stranger's first message must carry a payment of at least that price. It arrives in a Requests folder, and the recipient's client checks the payment on its own node before showing it. Unpaid messages are dropped. - Accepting (replying, or saving the sender as a contact) opens a normal free conversation. - An optional one-click refund turns the fee into a deposit ("I'll refund you if it wasn't spam").

| Case | Who pays | Mechanism | |---|---|---| | First DM to a stranger | sender | the recipient's price (paymail_fee_sats), before the first message | | Someone you invited | nobody | on your allow list, so they skip the price | | Joining a private channel | new member | the channel's join fee | | Posting in a busy community channel | poster | the channel's per-post fee |

name), paid messages as upstream paymail — a payment whose memo carries the message, signed and sealed, hex on-chain — an inbox scanned out of the wallet because get_paymail returns nothing usable, prices published on-chain per name, an allow list with one-off and permanent invitations, and the gate itself: a stranger writing to a name that charges is refused with the price rather than dropped, and a free message from a stranger is dropped at the other end.

carries the whole message rather than a pointer to it. Kleo caps it at 4 KB.

conversation it belongs to, marked with what was paid.

Phase 11: channels

A channel is itself a BitName. That makes channel names unique, human-readable and spam-proof in the same way people's names are. The channel's public profile (Phase 4) declares what kind of channel it is, its hubs, moderators and fees.

Order (decided): 11a → 11b → 11c.

- The owner (and any authors they list) sign posts; followers fetch them from hubs or peers and verify every post against the channel's on-chain keys. - Public and cacheable anywhere, so they scale like profiles.

- Members are a signed list of BitNames. A group key is sealed to each member's BitNames encryption key. - Removing a member rotates the key. - Capped in size (for example 256 members): this is for teams and friends, not crowds.

- Any BitName holder can post, signed with their own key, so every post has a verifiable, costly-to-create author. - Moderators publish signed removal and ban lists, which hubs and clients honour. - Optional fees to post or join throttle abuse without accounts or CAPTCHAs.

Still out of scope: voice and video, media pipelines, and giant public groups that need server-side moderation.

what it is and who may post is covered by the on-chain commitment. Posts are signed over a statement naming the channel, author and time; the hub checks the author against the channel's rules before storing, and every reader checks the signature against the chain before believing it. Private groups seal a group key to each member's BitName and encrypt posts under it — changing the membership makes a new key, so a removed member reads nothing written afterwards — and communities carry published moderator and ban lists, so a ban is the channel's decision rather than a hub's.

are fetched when a channel is opened rather than pushed.

Phase 12: release

channel that can be rolled back.

and no node. It should walk out of that.

it leaks — the document that makes the promises on the site checkable.

domain we actually hold. The app name is still open.

on a tag and drafts a release, a dependency audit in CI, the threat model (docs/THREAT_MODEL.md), and in-app onboarding that says which of node, wallet, name and hub is still missing.

certificates), an update channel with rollback, reproducible builds, and the final app name. The bundle id is settled: xyz.kleoapp.desktop.

The one that changes the shape: a light client

Today Kleo has no keys. Signing, decryption and the seed live in the node's wallet, and Kleo drives it over RPC. That is why everyone needs a node of their own: a remote node means that node's wallet is your identity, and two people pointed at the same node are one wallet with two windows.

The alternative is for Kleo to hold its own seed, derive its BitName keys, and do the signing and encryption itself — using a node only as a view of the chain. Then a remote node has to be honest, not trusted with secrets, and one public node could serve everybody who wants to look something up.

What it takes:

wallet can move between the two.

paymail. Kleo already builds BitNameUpdate transactions and only asks the node to sign them, so the shape is known; what is missing is the signing and the UTXO selection.

Worth doing before asking strangers to install anything, because "run three daemons first" is the current answer to "how do I try it", and it is not a good one.

Cross-cutting, not scheduled

Work that belongs to no single phase and should land alongside whichever one touches it.

and are added by hand with connect_peer. Kleo only lists peers, so a fresh node has nobody to talk to. The Node page should be able to add, forget and suggest peers.

conversations between two names that have one run a Double Ratchet — a key per message, discarded after use, with the BitName signature still inside deciding who wrote it. A conversation upgrades silently the moment the other side publishes a prekey, and falls back when they haven't. Rotation shipped too: replacing a prekey is an ordinary profile update, the replaced key is kept so mail already sealed against it still opens, and a message that opens a session Kleo doesn't have starts one. Still to do: extending the same treatment to files and channel posts.

it out of the keychain, with a lock-now button and an idle timer. Locking clears contacts, history and chat keys from memory. Still to do: locking on sleep, and a shorter grace period than the idle timer for a screen someone walks away from.

extracted for translation.

whether names can expire, is unanswered upstream and needs a decision before channels.

How spam is kept out

SurfaceBarrier
Hosting a profile on a resolverMust be signed by the name's on-chain key
DM from a contactNone (you chose them)
DM from a strangerTheir BitName plus your price (paymail_fee_sats)
Broadcast channelOnly the owner and listed authors can post
Private groupMembers only, end-to-end encrypted
Community channelA BitName to post, optional post fee, moderator ban lists
Hubs (mailbox)Accept only envelopes signed by a BitName; apply per-name limits

Open decisions

Answers are needed before the phase in brackets:

  1. Chat history [6–7]: kept on your computer, encrypted, with no expiry by

default, plus a per-conversation "delete" and "keep for N days". Mailboxes (phase 7) stay optional and off unless you name a hub. Revisit once chat is usable.

  1. Refunds for paid messages [10]: whether the fee can be refunded with one click.

Proposed: yes, since it turns a toll into a deposit.

  1. Paymail memo limits — answered: memos are hex, and a real regtest node

accepted 16 KB. Kleo puts the whole message in, capped at 4 KB.

  1. Mailbox retention [7]: how long a hub holds an undelivered envelope, and what it

does when a mailbox fills up. Proposed: 30 days and a per-name size cap, both published in the hub's descriptor so a client can see the terms before using it.

Decided: transport is iroh, peer-to-peer first; Kleo runs its own hubs as seed nodes but never trusts them; paid messages are charged once per stranger, with an allow list for people you invite; channels ship in the order 11a → 11b → 11c.