bitcoin++
§ Project file

Shroud Chat

The permanent record for this hackathon project: write-up, links, team, tags, and prizes.
Submitted Hackathon
Stop texting “check your wallet” over WhatsApp. Keep it private end to end. Google Pay-style chat for Bitcoin silent payments, with no server and no phone numbers.
The write-up

The problem.

Silent payments (BIP-352) make Bitcoin payments private, but they are awkward to use. The receiver can‘t see a payment until it’s mined and an indexer scans it. Unconfirmed payments are invisible to the indexer. Senders have no way to tell the receiver “I paid you”, so people paste txids into a “track payment” screen. There‘s no contact or conversation layer like Google Pay or Paytm or Venmo’s “I paid you”.

What I built.

I added end-to-end encrypted chat to Shroud, an open-source React Native silent-payments wallet. It runs on Marmot (MLS over Nostr) through marmot-ts. Three design choices make it work:

  • Your silent payment address is your chat address. The Nostr key is derived from your scan key, so every saved contact is already reachable with no lookup server or sign-up.
  • Instant detection. After a payment is broadcast, the sender‘s app sends the receiver an encrypted notice with the txid, the BIP-352 scan tweak, the raw transaction and a note. The receiver verifies the transaction by hash, scans with that tweak, and finds the output in the mempool. It shows as a pending coin right away and is reconciled when the block arrives.
  • Payments live in the conversation. Payment cards move Sent → Received by Bob ✓ → Confirmed on the sender’s side and Verifying → Received · pending → Confirmed on the receiver‘s. The receiver’s app sends the receipt back automatically, and the amount shown is always what the receiver‘s own scan matched, never what the message claims. Payment requests (“Request 21,000 sats”) give the payer a Pay button that opens send prefilled, after checking the address really belongs to the sender.

Marmot’s crypto needs WebCrypto, which React Native‘s Hermes engine lacks. I wrote an @noble-backed crypto.subtle shim and verified it against Node’s WebCrypto.

What works.

I tested on an iOS simulator and an Android emulator over real public Nostr relays on signet:

  • Two-way chat, with history surviving app restarts.

  • A real signet payment shown as pending on the receiver about 3 seconds after broadcast, before any block. After the next block both cards went Confirmed, with no duplicate output in the wallet.

  • Payment requests, message requests from non-contacts, and a Shroud Chat settings screen with a toggle and relay list.

  • It‘s covered by unit tests, including an end-to-end test of two chat clients on an in-memory relay. The type check and lint pass.

    Limits.

    A first message to a new contact takes about 5–7 seconds while the encrypted group is created. Chat identity is linkable to the address by design, and the settings screen says so.