blindjoin

blindjoin is a standalone CoinJoin coordinator and client for Bitcoin signet. It uses RSA blind signatures (RFC 9474) so the coordinator cryptographically cannot link transaction inputs to outputs — by construction, not by policy.

Why I Built This

CoinJoin is a well-understood technique for improving Bitcoin transaction privacy: multiple participants combine inputs and outputs into a single transaction, breaking the chain-analysis assumption that inputs and outputs belong to the same owner. The problem is the coordinator — the entity that assembles the transaction can see which input registered which output, and that is a surveillance bottleneck. The privacy guarantee is only as good as your trust in the coordinator, which is not a privacy guarantee at all.

RSA blind signatures (RFC 9474) solve this cryptographically. Participants submit their output addresses in blinded form: the coordinator signs the blinded token without seeing the underlying address, and the participant unblinds the signature to prove they have coordinator authorization when registering the output. The coordinator issues the credential and later verifies it, but the two events are mathematically unlinkable. It cannot determine which input produced which output even if it tries.

All production traffic runs over Tor hidden services using arti-client. Coordinator discovery happens through the PKARR DHT — no hardcoded addresses, no central registry. The round uses per-phase Tor circuit isolation: input registration goes through one circuit, output registration through a separate one, so the coordinator cannot correlate phases by Tor circuit either.

The goal was to implement the full blind-signature CoinJoin protocol correctly, in Rust, with no company and no fees — just the cryptography.

How It Works

A blindjoin round follows a five-step protocol. The coordinator announces a fixed-denomination round (default: 0.01 BTC on signet). Participants register their inputs with BIP-322 ownership proofs and receive blind-signed tokens. They then switch to a fresh Tor circuit and register their output addresses using the unblinded tokens. The coordinator builds a PSBT, participants verify and sign their own inputs. The coordinator broadcasts the completed CoinJoin transaction.

Round Protocol:

Participants → register inputs (BIP-322 proof) → coordinator issues blind-signed tokens
                                                               ↓
                        fresh Tor circuit (alice → bob)
                                                               ↓
Participants → register outputs (unblinded token) → coordinator builds PSBT
        ↓
Participants verify + sign their own inputs
        ↓
Coordinator broadcasts CoinJoin transaction
        ↓
Ephemeral RSA keys destroyed; all round state zeroed from memory

The security model makes explicit what the coordinator can and cannot do. It cannot link inputs to outputs — RSA blind signatures, RFC 9474. It cannot steal funds — participants sign their own inputs. It cannot reconstruct round data after completion — all state is zeroed from memory via the zeroize crate after broadcast. It cannot correlate input and output registration by Tor circuit because the client uses isolated circuits for each phase.

What the coordinator can do: refuse to complete rounds, register sybil inputs (diluted by the minimum participant count), and see which UTXOs registered (which is observable on-chain anyway). Non-signers are detected, banned, and the round restarts with remaining participants.

Coordinator discovery uses PKARR — the same DHT-based key-material transport that cipherpost and cclink use for their own protocols. A coordinator publishes its Tor .onion address as a PKARR SignedPacket; clients resolve by coordinator public key. No hardcoded addresses.

What I Learned

The theoretical hardness of blind signatures is one thing; getting the implementation right under concurrency pressure is another. Async RPC calls to Bitcoin Core needed to run before acquiring the write lock so a slow node could not serialize all participants waiting on a single lock. RSA keys are parsed once per round rather than per request. Blinded tokens are size-bounded to the RSA modulus to prevent padding oracle attacks.

The PKARR DHT for coordinator discovery turned out to be a natural fit for a tool that otherwise has no central infrastructure. A coordinator publishes its Tor .onion address as a PKARR SignedPacket; clients resolve by coordinator public key. The same discovery mechanism cipherpost and cclink use for key-material transport works here for service advertisement — building on the same primitives across projects clarified what those primitives are actually good for.

Building to signet rather than mainnet was the right call for a v1.0 tool at this stage. The fixed-denomination round (0.01 BTC) and minimum participant count (three) are appropriate constraints for a protocol that requires careful review before handling real funds. The full Docker Compose stack — bitcoind signet, coordinator, liquidity bot — makes it easy to run a complete round locally for development and testing, which matters a lot when the system requires multiple concurrent participants to function at all.


The coordinator cannot link what it was never allowed to see.