Skip to main content

CoinJoin Implementation

Trezor Suite integrates CoinJoin, a privacy-enhancing protocol that makes Bitcoin transactions more difficult to trace by mixing coins from multiple users.

What is CoinJoin?

CoinJoin is a trustless method for combining multiple Bitcoin payments into a single transaction:

Privacy Enhancement

Breaks deterministic links between inputs and outputs

Trustless

No third party can steal your funds

Collaborative

Multiple users participate in each mixing round

Trezor Native

Fully integrated with Trezor hardware wallet

How It Works

1

Coordinator Selection

Suite connects to CoinJoin coordinator backend via Tor
2

Round Discovery

Client discovers available mixing rounds with required parameters
3

Input Registration

User’s UTXOs registered for mixing (requires Trezor confirmation)
4

Output Registration

Anonymous outputs registered through fresh Tor identity
5

Transaction Signing

Trezor signs the collaborative transaction
6

Broadcast

Coordinator broadcasts completed transaction to Bitcoin network

CoinjoinClient

Core client library for CoinJoin operations:
import { CoinjoinClient } from '@trezor/coinjoin';

// Initialize client
const client = new CoinjoinClient({
  network: 'mainnet', // or 'testnet', 'regtest'
  coordinatorUrl: 'https://coordinator.example.com',
  torIdentityFactory: createTorIdentity,
  logger: console,
});

// Enable CoinJoin
const status = await client.enable();

// Listen for status updates
client.on('status', (event) => {
  console.log('Rounds:', event.rounds);
  console.log('Changed:', event.changed);
  console.log('Max mining fee:', event.maxMiningFee);
  console.log('Coordinator fee:', event.coordinatorFeeRate);
});

// Disable when done
await client.disable();

Status Events

Client emits status updates periodically:
interface CoinjoinStatus {
  // Current mixing rounds
  rounds: Round[];
  
  // Rounds that changed since last update
  changed: Round[];
  
  // Maximum mining fee from recommended rates
  maxMiningFee: number;
  
  // Coordinator fee structure
  coordinatorFeeRate: CoordinationFeeRate;
  
  // Allowed input amount range
  allowedInputAmounts: AllowedRange;
}

CoinJoin Accounts

Dedicated account type for CoinJoin:

Account Creation

// CoinJoin accounts use special derivation path
// m/10025'/coin_type'/account'

const coinjoinAccount = {
  accountType: 'coinjoin',
  symbol: 'btc',
  path: "m/10025'/0'/0'",
  // ... other account properties
};

Account Features

CoinJoin accounts maintain separate UTXOs from regular accounts to avoid mixing private and mixed coins

Development Setup

For local development and testing:

Prerequisites

VPN required for communication with affiliate server

Running Local Backend

# Start local CoinJoin backend (Regtest only)
./docker/docker-coinjoin-backend.sh

# Backend control panel accessible at:
# http://localhost:8080/

Suite Configuration

1

Enable Debug Mode

Go to Settings and click “Settings” header 5 times
2

Enable Bitcoin Regtest

Settings → Crypto → Enable Bitcoin Regtest
3

Set Custom Backend

Set custom backend to http://localhost:19121/ (default)
4

Optional: Disable Other Coins

For cleaner testing environment
5

Access CoinJoin Account

Navigate to CoinJoin account in Suite
CoinJoin accounts use the local backend, but regular accounts need the same bitcoind instance. Keep all Regtest accounts synchronized with the same backend.

Tor Integration

CoinJoin requires Tor for privacy:

Identity Management

Each operation uses fresh Tor identity:
// Tor identity factory
const createTorIdentity = async () => {
  // Request new Tor circuit
  const identity = await torController.getIdentity();
  
  return {
    // Unique identifier for this circuit
    id: identity.id,
    
    // SOCKS proxy settings
    proxy: {
      host: '127.0.0.1',
      port: 9050,
      auth: identity.credentials,
    },
  };
};

Circuit Isolation

Different phases use different identities:
  • Status polling: Rotated periodically
  • Input registration: Fresh identity
  • Output registration: Different fresh identity
  • Transaction signing: Another fresh identity
This prevents coordinator from linking user’s actions.

Round Phases

Phase 1: Input Registration

Users register UTXOs to mix:
interface InputRegistration {
  // UTXOs to mix
  inputs: {
    txid: string;
    vout: number;
    amount: number;
    path: string;
  }[];
  
  // Proof of ownership (from Trezor)
  ownershipProofs: Buffer[];
  
  // Desired anonymity set
  targetAnonymity: number;
}

Phase 2: Connection Confirmation

Confirm continued participation:
  • Prevents users from registering and disappearing
  • Small time window to respond
  • Failure causes round restart

Phase 3: Output Registration

Register receiving addresses anonymously:
interface OutputRegistration {
  // Fresh addresses from Trezor
  outputs: {
    address: string;
    amount: number;
  }[];
  
  // Issued via different Tor identity
  // Coordinator cannot link to input registration
}

Phase 4: Transaction Signing

Sign the collaborative transaction:
// All participants sign the transaction
const signature = await TrezorConnect.signTransaction({
  inputs: coinjoinInputs,
  outputs: coinjoinOutputs,
  // Trezor verifies CoinJoin transaction structure
  coinjoin: true,
});

Phase 5: Broadcast

Coordinator broadcasts when all signatures received.

Fees

Mining Fees

Network transaction fees:
  • Dynamically calculated based on network conditions
  • User can set mining fee rate limit
  • Higher fees for faster confirmation
  • Distributed among participants based on input size

Coordinator Fees

Service fee structure:
interface CoordinatorFeeRate {
  // Percentage fee (e.g., 0.003 = 0.3%)
  rate: number;
  
  // Threshold below which no fee charged
  plebsDontPayThreshold: number;
  
  // Minimum fee amount
  minFee: number;
}
Small amounts below plebsDontPayThreshold don’t pay coordinator fees, making CoinJoin accessible for smaller users.

Anonymity Levels

UTXOs and addresses tagged with anonymity scores:
LevelDescriptionColor
1Never mixed, no privacyRed
2-9Limited mixing roundsOrange
10-49Moderate privacyYellow
50+Strong privacyGreen

Anonymity Set

The anonymity set represents how many participants’ outputs are indistinguishable:
// Larger anonymity set = better privacy
const anonymitySet = round.participants.length * round.outputsPerParticipant;

State Management

Redux Integration

interface CoinjoinState {
  // Client instances per account
  clients: {
    [accountKey: string]: CoinjoinClient;
  };
  
  // Current rounds
  rounds: Round[];
  
  // Account CoinJoin settings
  accounts: {
    [accountKey: string]: {
      enabled: boolean;
      targetAnonymity: number;
      skipRounds: string[];
    };
  };
  
  // Session statistics
  sessions: CoinjoinSession[];
}

CoinJoin Actions

// Enable CoinJoin for account
dispatch(enableCoinjoin({ accountKey }));

// Disable CoinJoin
dispatch(disableCoinjoin({ accountKey }));

// Update settings
dispatch(updateCoinjoinSettings({
  accountKey,
  targetAnonymity: 50,
}));

// Start mixing session
dispatch(startCoinjoinSession({ accountKey }));

// Stop session
dispatch(stopCoinjoinSession({ accountKey }));

Privacy Considerations

Never send mixed coins to the same address as non-mixed coins. This defeats the privacy benefit.
CoinJoin rounds take time. More rounds = better privacy but longer wait.
Always use Tor with CoinJoin. Direct connections reveal your IP to coordinator.
Avoid consolidating many small UTXOs immediately after mixing. Space out spending.
Never reuse addresses. CoinJoin accounts generate fresh addresses automatically.

Error Handling

Common Errors

  • Not enough participants
  • Someone disconnected
  • Automatically retry next round

Retry Logic

// Automatic retry with exponential backoff
const retryCoinjoin = async (
  attempt = 0,
  maxAttempts = 5
) => {
  try {
    await client.enable();
  } catch (error) {
    if (attempt < maxAttempts) {
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
      await sleep(delay);
      return retryCoinjoin(attempt + 1, maxAttempts);
    }
    throw error;
  }
};

Best Practices

For Users

  • Use dedicated CoinJoin accounts
  • Mix before spending for privacy
  • Don’t rush - more rounds = better privacy
  • Always verify on Trezor device
  • Keep Tor enabled throughout session

For Developers

  • Always use Tor for coordinator communication
  • Rotate identities between phases
  • Handle round failures gracefully
  • Test on Regtest first
  • Monitor coordinator status

Implementation Files

// CoinJoin package
packages/coinjoin/
  src/
    client/      // CoinjoinClient implementation
    coordinator/ // Coordinator API
    types/       // TypeScript types
  tests/         // Unit tests

// Suite integration
packages/suite/src/
  actions/wallet/coinjoin/
  reducers/wallet/coinjoinReducer.ts
  middlewares/wallet/coinjoinMiddleware.ts

Resources

Build docs developers (and LLMs) love