How it works¶
joinstr builds a coinjoin from one input and one output per peer. Peers find each other and exchange messages through a nostr relay. Nobody runs a coordinator. The peer who creates a pool only announces it and hands out its key.
The protocol rests on one idea from the post Coinjoin with less steps using ALL|ANYONECANPAY. If outputs are registered first, each peer can sign their own input with SIGHASH_ALL|ANYONECANPAY as soon as all outputs are known. That signature commits to every output but to no other input, so the signed inputs can simply be merged into one valid transaction.
1. Announce¶
The creator picks a denomination, a number of peers, a relay and a transport. The client makes a new nostr key pair for the pool and publishes a kind 2022 event signed with it. The event carries the pool id, public key, denomination, number of peers, expiry time, relay, fee rate and transport. A pool can also require an aut-ct proof.
2. Join and credentials¶
A peer who wants to join creates a new nostr key and sends an encrypted join_pool request to the pool's public key. The creator answers with the credentials, which repeat the pool terms and include the pool's private key. The joiner accepts them only if that private key matches the announced public key and the terms match the announcement.
From now on every pool message is encrypted to the pool key and signed with it. All peers can read all messages, but the relay and outsiders cannot.
3. Output registration¶
Each peer takes a fresh address from their wallet and publishes it as an output message. With Tor, each message goes out on a new circuit, so the relay cannot tie the output to the peer's earlier messages by IP address.
Registration ends when the pool has as many outputs as peers.
4. Input registration¶
Each peer now knows every output. They build a transaction with all outputs in BIP69 order and their own single input, then sign that input with SIGHASH_ALL|ANYONECANPAY. The signed PSBT is published as an input message.
Before signing, a peer checks what the round costs them. They refuse if the output is below the dust limit, if the cost is above 15000 sats or if the PSBT could be broadcast on its own.
5. Combine and check¶
Every peer collects all inputs, merges them into one transaction and checks it.
- All outputs are equal and there is one output per peer.
- Their own output is present.
- No address is used twice and no input address appears as an output.
- The fee is between 100 and 10000 sats per peer.
6. Broadcast¶
Every peer broadcasts the same transaction through their own wallet backend. One copy is enough for the coinjoin to reach the network, so it goes through even if some peers go offline after signing.
Recovery when peers drop out¶
A peer can register an output and then never sign an input. Without a fix, the round would stall and the honest peers would have revealed their outputs for nothing.
When input registration times out with some inputs signed, the plugin starts a recovery.
- The peers who signed re-register their output addresses in
reregistermessages. Each one carries a linkable ring signature over the public keys of all signed inputs and the messageSHA256(address + pool id). It proves the address belongs to one of the signers without saying which one. The key image stops a peer from registering twice. - If every signer re-registers, each peer builds a smaller transaction with only the signed inputs and the re-registered outputs and signs it with plain
SIGHASH_ALLin aresignmessage. - The smaller transaction is combined, checked and broadcast as usual.
Each recovery phase waits 120 seconds. If not every signer re-registers, the round is abandoned.
Timing¶
| Step | Time |
|---|---|
| Pool open for joins | 10 minutes from the Electrum GUI, set per pool elsewhere |
| Waiting for outputs or inputs | up to 30 minutes (360 polls every 5 seconds) |
| Each recovery phase | 120 seconds |