Skip to main content
Ctrl+K
blockchainkit logo blockchainkit logo
  • Subpackages
  • History
  • Examples
  • API
  • GitHub
  • Charles Poli
  • Subpackages
  • History
  • Examples
  • API
  • GitHub
  • Charles Poli

Section Navigation

  • blockchainkit.crypto
    • Blind signatures
    • Commitments
    • Elliptic curves
    • Discrete logarithms
    • Hash-based signatures
    • Hashing
    • One-time pad
    • Public-key exchange
    • Secret sharing
    • Proofs and signatures
  • blockchainkit.structures
    • Bloom filters
    • Chains and payments
    • Hash chains
    • Ledgers
    • Merkle trees
    • Light clients
    • State commitments
    • Timestamping
    • Transaction identity
    • Unspent outputs
  • blockchainkit.consensus
    • Agreement protocols
    • Attacks and their probabilities
    • Nakamoto consensus
    • Proof of stake
    • Proof of work
  • blockchainkit.network
    • Simulated and logical time
    • Gossip and broadcast
    • Peer graphs
    • Overlays and their attacks
    • Block and transaction relay
    • Replication under partitions
  • blockchainkit.vm
    • Contracts and verification
    • Ethereum
    • Foundations of stack machines
    • Replicated and atomic execution
    • Bitcoin Script
  • blockchainkit.contracts
    • Foundations
    • Governance
    • Paying out
    • Token standards
    • Upgrades and addresses
    • Verification
    • Multisig wallets
  • blockchainkit.economics
    • Auctions and market makers
    • Coordination and oracles
    • Decentralized finance
    • Fee markets
    • Transaction ordering
    • Block rewards
  • blockchainkit.channels
    • Erasure codes and data availability
    • Interoperability
    • Payment and state channels
    • Channel networks
    • Scaling the chain
  • blockchainkit.proofs
    • Polynomials and interactive proofs
    • Probabilistically checkable proofs and arguments
    • Private payments
    • Pairing-based SNARKs
    • Transparent proofs
  • blockchainkit.fraud
    • Fabricated figures
    • Market manipulation
    • Phishing
    • Scam contracts
    • Ponzi schemes
    • Custodians and solvency
  • blockchainkit.privacy
    • Hidden coins and amounts
    • Measuring anonymity
    • Mixing coins
    • Anonymous networks
    • Ring signatures and stealth addresses
  • blockchainkit.wallets
    • Address encodings
    • Error-detecting codes
    • Shared control
    • Key derivation
    • Key stretching
  • Examples
  • Examples
  • Mixing coins
  • TumbleBit: an untrusted tumbler that cannot link payments (2017)

Note

Go to the end to download the full example code or to run this example in your browser via JupyterLite.

TumbleBit: an untrusted tumbler that cannot link payments (2017)#

A mixing service, a tumbler, takes coins from payers and pays payees. Heilman, Alshenibr, Baldimtsi, Scafuro and Goldberg made one that can neither steal nor tell who paid whom. The tumbler promises each payee a token locked under an RSA puzzle \(z = \epsilon^e \bmod N\), which only it can solve. The payee blinds the puzzle with a random \(\rho\),

\[z' = z \rho^{e} \bmod N, \qquad (z')^{d} = \epsilon \rho ,\]

and a payer pays the tumbler to solve \(z'\). Dividing the solution by \(\rho\) gives \(\epsilon\), which opens the token. The tumbler saw \(z\) with the payee and \(z'\) with the payer: unrelated numbers, as in Chaum’s blind signatures.

import matplotlib.pyplot as plt

import blockchainkit as bk

Twenty payments through one tumbler#

pairs = [(f"payer{i}", f"payee{i}") for i in range(20)]
blinded = bk.privacy.run_tumbler(pairs, seed=1)
plain = bk.privacy.run_tumbler(pairs, blind=False, seed=1)
assert all(blinded.paid) and all(plain.paid)
print("links without blinding:", len(bk.privacy.tumbler_links(plain)))
print("links with blinding:   ", len(bk.privacy.tumbler_links(blinded)))
assert bk.privacy.tumbler_links(blinded) == ()
links without blinding: 20
links with blinding:    0

The tumbler’s two logs#

Without blinding the same puzzle appears in both; with blinding, never.

n = bk.privacy.TUMBLER_KEY.n
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
for ax, run, title in ((ax1, plain, "unblinded"), (ax2, blinded, "blinded")):
    ax.scatter(
        [z / n for _, z in run.promised],
        [z / n for _, z in run.solved],
        color="#2563eb",
    )
    ax.plot([0, 1], [0, 1], color="#64748b", linestyle=":")
    ax.set(xlabel="puzzle promised to payee i / N", title=f"{title} puzzles")
ax1.set_ylabel("puzzle solved for payer i / N")
fig.tight_layout()

plt.show()
unblinded puzzles, blinded puzzles

Exercise#

Every payment in a TumbleBit epoch has the same size, and all are made in the same window. Why do both matter for unlinkability, even though the puzzles are blinded? A worked solution is in Exercises: privacy.

Total running time of the script: (0 minutes 0.085 seconds)

Launch JupyterLite

Download Jupyter notebook: plot_02_tumblebit.ipynb

Download Python source code: plot_02_tumblebit.py

Download zipped: plot_02_tumblebit.zip

Gallery generated by Sphinx-Gallery

previous

CoinJoin: many payers, one transaction (2013)

next

Tornado Cash: a mixer contract with zero-knowledge withdrawals (2019)

On this page
  • Twenty payments through one tumbler
  • The tumbler’s two logs
  • Exercise
Show Source

© Copyright 2026, Charles Poli.

Created using Sphinx 9.1.1.

Built with the PyData Sphinx Theme 0.23.0.