KKleo

Desktop · macOS, Linux, Windows · MIT

Encrypt to a name,
not to a key.

Kleo brings back the best part of GnuPG's Kleopatra — the Notepad, where you sign, verify, encrypt and decrypt text inline — and builds the rest around it: messages, files, a wallet and channels, all addressed by human-readable BitNames on a Bitcoin sidechain, with every check run against your own node.

No keyringA name is the key
No server to trustVerified on-chain
No accountsA BitName is the barrier
Kleo — Notepad
Sign as alice · key bn-svk1q8x3f0…f3d2
-----BEGIN BITNAMES SIGNED MESSAGE----- The release notes for 0.17.2 are final. -----BEGIN BITNAMES SIGNATURE----- Signer: alice Key: bn-svk1q8x3f0alice7m2kd9w0p5v6r4t3y8u2i1o0p9l8k7j6h5g4f3d2 0x7b41f0c2a98e5d31b6740fe2c18a93db5f0e2a77c4b9016d 83f5a1e6d029b7c4188ae35f90d2c6b71a4e0f83d5629cab -----END BITNAMES SIGNATURE-----
Signed with the key alice publishesThe signature covers exactly the text above — no headers — so any BitNames tool can check it.

What it is

A workbench, not a wallet

Kleo is a desktop client for the plain-bitnames sidechain. It never reimplements the chain: your node holds the keys and does the cryptography, and Kleo is the surface that makes it usable by hand.

Notepad

Sign, verify, encrypt, decrypt

One text pane addressed by name. Verifying checks the signature and that the key is the one that name publishes right now. Decrypt also checks any signature sealed inside.

⌘/Ctrl+Enter runs the suggested action · Undo for every transformation

Your names

Hold an identity

Create or restore a wallet, register a BitName through its two-block commit → reveal, and replace your signing or encryption keys later without losing the name.

12 words to write down, then a three-word check

Contacts

Keys pinned on sight

Save a name and Kleo remembers the keys it published. If they change, the contact is flagged, encrypting to it is blocked until you review, and verified signatures say so.

XChaCha20-Poly1305, key in the OS keychain

Profiles

A phone book that can't lie

Publish a display name, bio, links and ways to be paid. Kleo re-hashes whatever a server returns and compares it with the commitment on-chain. A host can withhold a profile; it can't alter one.

RFC 8785 canonical JSON · BLAKE3 · bitname_commit

Files

Detached signatures and sealed files

Sign a file into a small .bnsig that travels beside it, encrypt one to a name, or write BLAKE3 checksums for a whole folder.

.bnsig · .bnenc · .b3sums · up to 256 MB

Chat

Messages, computer to computer

Write to a name and the message goes straight to their machine over an encrypted peer-to-peer link — signed by you, sealed to them. When they're away, it waits at a hub, still sealed.

iroh QUIC · forward secret · files up to 150 KB · blocking

Channels

A channel is a BitName

Which makes its name unique and costly, and puts its rules — broadcast, private group, or open community — under the same on-chain commitment as everything else. Groups seal a key to each member; a hub holds ciphertext.

published moderators and bans · following is local

Wallet

Coins, and what they were for

Receive, pay a name or an address, and move coins to and from Bitcoin. Every payment is quoted before it is signed, and the history says what each transaction was.

deposits and withdrawals through the BIP300 enforcer

Backup

Your side, on another machine

A recovery phrase brings back the wallet and its names. It brings back nothing Kleo keeps privately — contacts, pinned keys, blocks, invitations, conversations. One file carries those, encrypted to a passphrase rather than to this machine's keychain.

Argon2id · restoring only ever adds, never overwrites

Paid messages

A price to be written to

Publish what a stranger pays to reach you, and their first message arrives as the payment — the message sealed inside its memo. Contacts and anyone you've replied to are never charged.

upstream paymail · on-chain price · allow list

What it checks

Nothing is trusted because it arrived

A message, a profile and a chat line are all judged the same way: re-derive the keys from the chain as your own node reports it, then check the signature. Where something doesn't line up, Kleo says so in words rather than showing a padlock. What it proves, what it only claims, and what it leaks is written down in full — including the gaps — in its threat model.

The four verdicts

OutcomeWhat Kleo saysWhat it means
Verified Good signature from alice The key is the one that name publishes on-chain, and the text hasn't changed since it was signed.
Key mismatch Claims to be from alice, but it isn't signed with alice's key Either they rotated keys after signing, or someone is impersonating them. Never shown as trusted.
Unknown name Claims to be from alice, but no such BitName exists The signature is intact, but nobody has registered that name — so it proves nothing about who wrote it.
Bad signature Bad signature. Do not trust this message. The text was edited after signing, or the armor is damaged. Ask for it again.

What a server can and can't do

WhoCanCan't
A profile host
kleo-resolver
Refuse to serve a profile, or serve an old one it kept. Change a field, publish for a name it doesn't hold a signature from, or make Kleo show anything unverified.
Anyone, later
holding a stolen BitName key
Read old messages that were encrypted to that key — the Notepad, files, paid messages. Open a forward-secret conversation: those message keys were used once and thrown away.
A hub
mail, addresses, channels
Withhold, delay or lose mail; see who collects it and when. Read an envelope, forge a sender, invent a channel post, or point you at a machine of its choosing.
A chat relay
iroh
See that two endpoints are talking, and drop packets. Read a message, alter one, or make a message look like it came from someone else.
A node you don't run Lie about the chain, which is why Kleo defaults to loopback. Be silently trusted: point Kleo at your own node and every verdict is yours to check.

Manual

From nothing to a signed message

Fourteen steps, in order. The first three are once per machine; after that you're working, and the rest can be read as you need them. Every command here is real — the regtest stack in step 2 builds a complete local chain, so you can practise without touching mainchain coins.

Build and run Kleo

Tagging a release builds installers for macOS, Linux and Windows, but they are not signed yet — so until they are, build it yourself and know what you're running. That takes Rust ≥ 1.85 (edition 2024), Node.js ≥ 20, and Tauri's platform packages (Xcode Command Line Tools on macOS; WebView2 on Windows; webkit2gtk-4.1, libayatana-appindicator3, librsvg2 and libdbus-1-dev on Linux).

npm install
npm run tauri dev        # run it with hot reload
npm run tauri build      # bundle for this platform (dmg/app, msi, deb/AppImage)
Just looking npm run dev opens the interface in a plain browser against a simulated node — no Rust, no chain. Add ?offline or ?noseed to see those states.

Point it at a node

Kleo talks to plain-bitnames over JSON-RPC and defaults to http://127.0.0.1:6002. Open Settings → BitNames node, enter a URL, press Test connection, then Save. The sidebar then shows block height, balance and peers, refreshed every five seconds.

Don't have a chain yet? One script downloads, builds and runs a full regtest stack — Bitcoin Core, the BIP300 enforcer and plain-bitnames:

scripts/regtest.sh install      # once: ~2 min, mostly the enforcer build
scripts/regtest.sh start        # funds the enforcer, activates BitNames, deposits 10 BTC
scripts/regtest.sh demo-names   # register alice, bob, carol, dave + publish two profiles
scripts/regtest.sh automine 10  # a block every 10 s, so registrations confirm
If it won't connect plain-bitnames refuses to start its RPC without a healthy enforcer and bitcoind. If your node splits public and private RPC, point Kleo at the private address — wallet and crypto methods live there. The RPC has no authentication and exposes wallet secrets, so keep it on loopback; for a remote node, put it behind an HTTPS proxy and use Kleo's basic or bearer auth.

Set up your wallet

Open Your names. If the node's wallet has no seed, Kleo offers to create one: twelve words to write down, then a three-word check before it continues. Restoring an existing wallet takes the same phrase.

Check your backup compares a phrase you've written down against the wallet without changing anything — worth doing the day after, when the handwriting is cold.

There is no recovery The phrase is the wallet. Lose it and the names registered to it are gone, because nobody else holds the keys. A node's seed can only be set once.

Register a BitName

In Your names → Register, type the name you want. Kleo checks whether it is free, then runs the two-block dance: a reservation that commits to the name without revealing it, and a registration in a later block that reveals it and publishes your keys. Progress is shown as it goes, and it resumes if you close the app.

  • The name itself never appears on-chain — only blake3(name).
  • Registration publishes your signing key and encryption key with the name, so others can find them.
  • On regtest, mine or wait for blocks (scripts/regtest.sh mine 2) for it to complete.

Sign something, and verify someone

Open Notepad, type your text, pick your name under Sign as and press Sign. You get an armored block that survives email and chat.

Going the other way, paste anything into the Notepad and press Verify. Kleo resolves the claimed signer on-chain, compares the key, checks the signature over the exact text, and returns one of the four verdicts above — in a sentence, not a symbol.

Interoperable on purpose The signature covers only the message text — line endings normalized to LF, trailing whitespace removed — so any other BitNames tool can check it with verify_signature(sig, key, "arbitrary", text). The Signer: header is only a hint; Kleo re-checks it against the chain.

Encrypt to a name

Type a recipient's BitName under Encrypt to. Kleo looks up the encryption key that name publishes and shows it to you before anything happens. Sign & Encrypt puts your signature inside the envelope, so only the recipient learns who wrote it; their Decrypt checks that inner signature automatically.

  • A name with no published encryption key can't be written to, and Kleo says which.
  • If a saved contact's keys have changed, encrypting is blocked until you review the change.

Work with files

Files takes the same four operations to disk. Drop files in, or choose them.

  • Sign writes a detached <file>.bnsig. It covers the file's BLAKE3 hash, so renaming doesn't break it; verifying reports both whether the signature is good and whether the contents still match.
  • Encrypt writes <file>.bnenc. Each file gets a one-off key and only that key is sealed to the recipient, which keeps large files fast. The name, size and any signature travel inside the encrypted part.
  • Decrypt restores the original name and never overwrites an existing file.
  • Checksums writes a BLAKE3 .b3sums file in the usual <hash>  <name> layout; checking one reports every file as unchanged, changed or missing.

Publish a profile

A profile is how a name becomes a phone-book entry: display name, bio, website, links and ways to be paid. The owner commits on-chain to the BLAKE3 hash of the profile's canonical JSON; the document itself is served by any host. Kleo re-hashes what it receives and compares.

Publish from Your names → Public profile…, to a hosted resolver or to your own:

kleo-resolver --node http://127.0.0.1:6002 \
  --listen 0.0.0.0:7777 --public-addr 203.0.113.7:7777

# or push a document to someone else's resolver
kleo-resolver publish --node http://127.0.0.1:6002 \
  --to http://127.0.0.1:7777 --name alice alice.json
Looking someone up is private Web links open in your browser, http/https only, and remote images are never loaded — so reading a profile can't be tracked. Addresses on-chain are chosen by their owner, so Kleo won't contact loopback or private-network addresses unless you allow it in Settings.

Start a conversation

Chat needs one thing first: your device has to be findable. At the bottom of the Chat pane, press Publish it for <your name>. That writes this machine's endpoint into your profile under chat.endpoint, covered by your on-chain commitment, and takes one block like any other profile change.

Then type any BitName into Write to a BitName… and send. Kleo reads their endpoint from their profile, opens an encrypted link straight to their machine, and delivers a message signed by you and sealed to them.

  • A message is judged by its signature, never by the link it came in on. An envelope claiming to be someone it isn't signed by is shown as unverified.
  • A message from a device the sender doesn't publish still counts as theirs if the signature holds — Kleo just tells you where it came from.
  • History is stored locally in the same encrypted vault as your contacts, and a conversation can be deleted outright.
  • Forward secrecy switches itself on when both names publish a prekey — which yours does, the moment you publish your endpoint. Each message then gets a key of its own, used once and dropped, so a BitName key obtained later opens nothing that was already said. The header says forward secret when a conversation has it.
When they're not online Add a hub under Settings → Chat hubs and the message waits there, still sealed, until they collect it. A hub can withhold or delay; it can't read, alter or forge. Kleo ships with none configured, so nothing is used until you choose it.

Run a hub, or use one

A hub is the small server that holds what a direct link can't: profiles, mail for people who are offline, where a machine can currently be reached, and channel posts. One binary, four roles, each switchable.

kleo-hub --node http://127.0.0.1:6002 --listen 0.0.0.0:7777 \
  --public-addr 203.0.113.7:7777 --hub-name myhub --retention-days 30
  • Leaving mail is signed by the sender's BitName — that is the whole spam barrier, so there are no accounts.
  • Collecting it is signed by the recipient's, over a statement carrying the time, so a captured request goes stale.
  • A hub is itself a BitName, so it can be looked up and pinned like a person.
Why this is safe to share Everything a hub serves is signed by the key it describes — profiles by the name's on-chain key, address records by the endpoint key itself, envelopes by the sender. The worst a hub can do is fail to help, which is why Kleo can ship a default list and still let anyone swap it out.

Fund the wallet, and pay by name

BitNames is a sidechain: coins move in by paying a special address from a Bitcoin wallet, and out through a withdrawal bundle Bitcoin settles later. Wallet does both, and pays either an address or a BitName — Kleo looks up where that name takes payments, from its profile or from the chain.

Every payment is quoted first BitNames addresses are base58 with no checksum, so a mistyped character is still a valid-looking address. Kleo shows the address and the total back — amount plus fee — and sends only what you confirmed. A withdrawal is reported as started, never sent.

Keep a backup

Your recovery phrase restores the wallet and every name it holds. It restores none of what Kleo keeps privately: who you saved and the keys you pinned for them, who you blocked or invited, the channels you follow, and every conversation. Those live only on this machine until you write them somewhere.

Settings → Backup puts the lot in one file, encrypted with Argon2id to a passphrase you choose — not to this machine's keychain, which is the point: that key never leaves the machine that made it.

  • Restoring only ever adds: contacts you already hold keep the keys you pinned here, and messages are matched by id, so importing twice changes nothing the second time.
  • A wrong passphrase and an altered file are reported as the same thing, because authenticated encryption cannot tell them apart.
Write the passphrase down There is no way to open a backup without it, and no way to ask anyone for help: nobody else has a copy of anything in that file.

Start a channel

A channel is a BitName. In Channels, turn one of your names into one and choose who may post: only you and the authors you list, members of a private group, or anyone with a BitName.

  • Broadcast posts are public and signed, so any hub can hold them and every reader checks them against the chain.
  • Private groups seal a group key to each member's BitName and encrypt posts under it. Changing the membership makes a new key, so whoever was removed reads nothing written afterwards.
  • Communities are open to any BitName holder, with published moderator and ban lists — a ban is the channel's decision, applied by every hub and reader alike.
Following is nobody's business Following a channel is local: nothing is published, so a channel never learns who reads it.

Reference

Formats and where things live

Everything Kleo writes is plain text or a small binary file next to the original. Nothing is locked in a database you can't read.

ArtifactShapeNotes
Signed message-----BEGIN BITNAMES SIGNED MESSAGE-----Text, then a signature block with Signer and Key headers.
Encrypted message-----BEGIN BITNAMES ENCRYPTED MESSAGE-----ECIES over X25519 with AES-256-GCM, done by the node.
File signature<file>.bnsigDetached; covers the file's BLAKE3 hash, not its name.
Encrypted file<file>.bnencOne-off key per file, sealed to the recipient's BitName.
Checksums<name>.b3sumsBLAKE3, in the conventional hash  name layout.
Contacts & chat historyXChaCha20-Poly1305 vaultKey in the OS keychain; nothing readable on disk.
Profile documentJSON + on-chain commitmentRFC 8785 canonical JSON hashed with BLAKE3; served by any host.

Good to know

  • Double-click a name anywhere — Contacts, a Notepad field, a verification result — to open its card.
  • Signing keys are bn-svk1…, encryption keys bn-enc1…, both bech32m.
  • A BitName on-chain is blake3(name); the plaintext name never touches the chain.
  • Rotating keys is a BitNameUpdate transaction — plain-bitnames has no RPC for it, so Kleo builds it and has the node's wallet sign and submit.

Run a node

Three ways to have a chain

Kleo is a client. It needs something to ask — pick whichever suits what you're doing.

Real

A regtest stack

Bitcoin Core 31.1 (checksum-verified), bip300301_enforcer built from source, and plain-bitnames 0.17.2 — all in a gitignored .regtest/.

scripts/regtest.sh install | start | demo-names | automine 10 | status | stop | reset

Fast

kleo-devnode

An in-memory stand-in serving the RPC subset Kleo uses, with the same wire formats, the same cryptography and the same error texts. Four demo names: alice and bob are yours, carol isn't, dave has no encryption key.

cargo run -p kleo-devnode

Yours

A real node

Any plain-bitnames node with its mainchain stack running. Point Kleo at its private RPC address and keep that address on loopback.

http://127.0.0.1:6002 by default

Running a resolver for other people

kleo-resolver hosts profiles for many names at once. Uploads must be signed by the name's current on-chain signing key, which the resolver checks with its own node — so only BitName holders can store anything, and it needs no accounts, quotas or moderation. It also answers bitname_commit by commitment and exposes a small HTTP API:

GET /v1/names/{name}        # keys and verified profile
GET /v1/names/{name}/pay    # payment methods
GET /v1/commitments/{hex}   # a document by its hash

Roadmap

What's done, what's next

Each phase ended in a runnable, tested build, checked against a real plain-bitnames 0.17.2 regtest. All twelve are in; what remains needs a certificate or a decision, not a design.

01–03Node connection, Notepad, identitySettings and status, the four text operations, wallet, registration, key rotation, contactsShipped
04Public profiles and the resolverThe linktree / phone book, verified against the chainShipped
05FilesDetached signatures, hybrid encryption, checksumsShipped
06Direct messages, peer to peeriroh QUIC, endpoints published in profiles, encrypted local historyShipped
07HubsMail for people who are offline, an endpoint directory, and a relay — none of it trustedShipped
08Everyday chatNotifications, tray, blocking, attachments, search, export, more than one deviceShipped
09The walletReceive, pay, deposit, withdraw, and a history that says what each transaction wasShipped
10Paid messagesOn-chain prices, paymail, an allow list — a stranger's first message is the paymentShipped
11ChannelsBroadcast, private groups, and communities with published moderationShipped
12ReleaseBuilds for all three platforms on a tag, a dependency audit, a written threat model, first-run onboardingUnsigned
+SinceVault backup, and forward secrecy: a Double Ratchet between names that publish a prekeyShipped
nextWhat's leftSigning and notarization, an update channel, reproducible builds — and a lock for the app itself, fees to post in busy channels, and rotating prekeys on a schedulePlanned

Questions

Reasonable doubts

Is this PGP with extra steps?

No — there is no OpenPGP anywhere in Kleo. The hard part of PGP was never the algebra, it was the keyring: finding a key, believing it, noticing when it changed. A BitName answers all three, because the key is published by the name on a chain anyone can read.

Do I have to run my own node?

You should. The node holds your wallet and does the cryptography, and its answers are what every verdict rests on. Kleo can point at a remote node behind an HTTPS proxy with auth, but then you are trusting whoever runs it to tell the truth about the chain.

What happens if someone steals a name's keys?

The owner publishes new ones. Everyone who has that name saved sees the contact flagged, encrypting to it is blocked until they review, and old signatures start reading “isn't signed with the key they publish now” — which is the warning you want, rather than silence.

Can the people hosting profiles read them?

Profiles are public documents, so yes — that's their job. What a host can't do is change one: Kleo hashes what it receives and compares with the on-chain commitment, and shows nothing that doesn't match. Messages and files are a different matter; those are encrypted end to end and no host ever sees them.

Does chat work when my friend is asleep?

Yes, if either of you has a hub configured. The message waits there — signed by you, sealed to them — until they collect it. A hub sees that someone sent them something, when, and how big; never what it said, and it cannot forge one. With no hub, both of you have to be online at once.

What stops strangers flooding me?

Writing to you at all costs a BitName, which has to be registered on a chain. On top of that you can publish a price for people you've never spoken to: their first message arrives as a payment carrying the message, and anyone you reply to writes free from then on. Blocking is separate, local, and never announced.

Can I run the infrastructure myself?

All of it. kleo-hub is one binary that hosts profiles, holds mail, answers where machines are, and keeps channel posts; point Kleo at yours and it never touches anyone else's. The defaults exist so a new install works, not because they are required.

If my key is stolen, can someone read old messages?

In a conversation where both names publish a prekey, no: each message has its own key, derived, used once and dropped, so the BitName key opens none of it — before or after. Everywhere else — the Notepad, files, paid messages, channel posts — a message is encrypted to a long-lived key, and someone who obtains that key and kept the ciphertext can read it. Kleo says which kind of conversation you are in. The ratchet is Kleo's own implementation of a well-known design, not an audited library, and the threat model says so.

What happens when I get a new laptop?

The recovery phrase brings back the wallet and its names. Everything else Kleo holds — contacts, the keys you pinned, blocks, invitations, followed channels, conversations — comes from a backup file you made, encrypted to a passphrase. Without one, the names come back and the history does not.

Which cryptography is actually used?

For anything addressed to a BitName, whatever plain-bitnames uses, done by your node: Ed25519 signatures with the arbitrary domain separator, ECIES over X25519 with AES-256-GCM, and BLAKE3 for hashing. Kleo adds three things its node cannot do for it: XChaCha20-Poly1305 for the local vault, Argon2id for a backup's passphrase, and the message ratchet — X25519, HKDF-SHA256 and XChaCha20-Poly1305 — which exists precisely so that the node's long-lived keys stop being able to open old messages.