Kimchi
Share the date, not your data.
Kimchi is a private group tab for the moments you would rather not publish. A group of friends opens an encrypted group, one person shares the receipt, the group splits the bill, and everyone settles in Cashu ecash.
The chat is Marmot: MLS group messaging over Nostr relays. The money is Cashu: NUT-11 P2PK-locked ecash requested with NUT-18 payment requests. There is no phone number, no email, no account server, and no plaintext bill on a relay.
Why this exists
Splitting a bill leaks a surprising amount of social information: who was together, who paid, how much, where, and when. Most apps fix that by asking you to trust a server. Kimchi removes the server from the conversation model:
- The group is an MLS group whose keys live on the members‘ devices.
- Relays only carry MLS ciphertext and routing metadata.
- The bill is an application message inside MLS, not a public event.
- The payment is ecash locked to a fresh key per share, not an invoice that names the group.
- The tab can close and the local tab secrets can be deleted, while the chat remains.
The result is a demo where the relay operator can see that a group is active but not what the group is saying, and the mint can see amounts but not who was at dinner.
Architecture
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Guest device │ │ Host device │
│ │ │ │
│ Cashu wallet (proofs) │ │ Cashu wallet (proofs) │
│ Marmot group state (MLS) │ │ Marmot group state (MLS) │
│ Kimchi tab state (rumors) │ │ Kimchi tab state (rumors) │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
│ kind 445 ciphertext │ kind 445 ciphertext
▼ ▼
┌─────────────────────────────────────────────────────┐
│ Nostr relays │
│ see: group id, sender pubkey, timing, sizes │
│ cannot see: plaintext, tab amounts, payer identity │
└─────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Cashu mint (Minibits by default) │
│ sees: amounts, timestamps, distinct locks │
│ cannot see: group, names, who paid whom │
└──────────────────────────────────────────────┘
Optional encrypted media is uploaded to a Blossom server as ciphertext (MIP-04). The server sees opaque blobs and hashes, not the receipt.
The three layers
1. Marmot / MLS — the room
Marmot specifies efficient end-to-end encrypted group messaging using Nostr’s decentralized identity and relay network combined with the Messaging Layer Security (MLS) protocol.
Kimchi uses Marmot as the core messaging substrate, not as a bolt-on:
- Group creation is an MLS group with a public Nostr group id and a set of relays.
- Membership is MLS group state. Adding or removing a member is an MLS commit, and every member converges on the same epoch.
- KeyPackages (
kind 30443) advertise a device to potential inviters. - Invites are MLS Welcomes, delivered as NIP-59 gift-wrapped events to the recipient‘s inbox relays.
- Chat is an MLS application message. The relay sees a
kind 445ciphertext event, not the text. - Forward secrecy and post-compromise security come from MLS’s epoch-based key schedule, not from a server promise.
- No central server holds the group. Members store the MLS state locally and use relays as dumb transport.
Kimchi adds an application protocol inside the group for tabs: kind 20001 rumors with kimchi.tab, kimchi.payment, kimchi.paid, and kimchi.closing payloads. The relay cannot distinguish a tab message from a chat message; both are MLS ciphertext.
This is the Marmot track story: the bill is not a separate product with a group-chat feature. It is a Marmot-native application where the group, the invite, the chat, the media, and the settlement all ride the same encrypted group state.
2. Nostr — identity, discovery, transport
Nostr supplies the decentralized identity and relay layer:
- Identity: a secp256k1 keypair. The public key is the
npub. No phone number, no email, no signup server. - Transport: relays. Marmot group messages are
kind 445events; the relay routes ciphertext and knows the group id, author pubkey, timestamp, and size. - Discovery: NIP-65 outbox relay lists (
kind 10002) and Marmot invite-inbox relays (kind 10050). NIP-05 is optional. - Invites: KeyPackages are
kind 30443addressable events; Welcomes and join requests are NIP-59 gift wraps (kind 1059) whose inner rumor is encrypted to the recipient. - Join links: Kimchi mints a capability token for a group and shares a URL. A guest opens it, publishes a fresh KeyPackage, and sends a NIP-17-style gift-wrapped join request to the host. The host validates the token and adds them. The guest auto-accepts the Welcome.
- Invite cards: a shareable npub + KeyPackage card, with QR scanning, as the fallback when relay discovery is slow.
Nostr does not provide confidentiality by itself. Marmot supplies the group encryption; Nostr supplies identity, routing, and censorship-resistant transport.
3. Cashu — the money
Cashu is Chaumian ecash. A mint issues blind-signed proofs; wallets hold and swap them. Kimchi uses the parts that make a private group tab possible:
- NUT-10 / NUT-11 P2PK: the host derives a fresh keypair per guest share and builds a payment request locked to that share‘s public key.
- NUT-18 payment requests: the request is a
creqA…string containing the amount, mint, description, and P2PK lock. The guest pays the request from their wallet. - NUT-07 proof states: before redeeming, the host checks that the proofs are unspent and not pending.
- NUT-04 / NUT-05 / NUT-03: mint, melt, and swap operations used by the wallet for funding, receiving, and paying.
- Minibits (
https://mint.minibits.cash/Bitcoin) is the default. Live verification: it advertises NUT-10, NUT-11, and NUT-07. NUT-18 is a wallet-level request format, not a mint capability; the mint only needs to honor the P2PK lock and process the swap/redeem.
Payment flow
- The host computes the total in sats and splits it equally.
- For each guest share, the host generates a fresh P2PK keypair.
- The host publishes a
kimchi.tabmessage inside MLS containing onecreqA…per guest, each locked to that share’s public key. - The guest‘s wallet pays the request. The resulting proofs are locked to the host’s share key.
- The guest sends the NUT-18 payload back inside a
kimchi.paymentMLS message. - The host verifies the request, checks NUT-07 states, and redeems with the share private key.
- The host publishes
kimchi.paidso every member sees the tab fill.
Because each share uses a fresh key, the mint sees distinct locks rather than one reusable address. It still sees amounts and timing; the privacy model is explicit about that.
Status and roadmap
Working
- Marmot group chat with replies, reactions, and encrypted media.
- Join links with gift-wrapped join requests and automatic acceptance.
- Invite cards, QR scanning, and an expiring contacts book.
- Cashu wallet with Lightning top-up, receive/send, and account-scoped proofs.
- Kimchi tabs with per-share P2PK requests, auto-redeem, and inline tab cards in chat.
- Group deletion with an unpaid-tab warning.
Next
kimchi.tab.v1as a Marmot app component so White Noise and other clients render tabs natively.- An agent host that opens and redeems tabs for a group.
- Multi-tab trip sessions and a Lightning fallback for guests without ecash.
- NIP-40 expiration and relay-side deletion for closed tabs.
- Seed-based wallet recovery, multi-mint support, and optional Tor transport.
- Push notifications without leaking message metadata.
Credits
Forked from marmot-protocol/marmots-web-chat, the marmot-ts reference client. Built with @internet-privacy/marmot-ts, @cashu/cashu-ts, and the Marmot protocol. Thanks to the Marmot/White Noise, Cashu, and Minibits teams.