Three secrets every payment leaks
Imagine you buy a coffee, and the receipt is not slipped into your pocket but etched into a glass plaque in the town square — forever. It names you, names the café, and prints the exact amount. Anyone walking by, today or in fifty years, can read it. That is what a payment on a transparent chain like Bitcoin or Ethereum really is. In the first guide of this rung you saw that addresses are only pseudonyms that chain analysis can unmask; in the second you saw that mixers and CoinJoin can blur the link between coins. Privacy coins go further: they attack the leak at the protocol level.
Any payment exposes three distinct facts, and a private system must conceal all of them: the sender (which coins were spent, and by whom), the receiver (who got paid), and the amount. Mixers from the last guide mostly tackle the link between sender and receiver; they leave amounts in plain sight, which is often enough to re-stitch the trail. To get real privacy you need a design that hides all three at once.
Two designs lead the field, and they embody opposite philosophies. Monero makes privacy mandatory and always-on: every transaction hides all three facts, so there is no transparent crowd to stand out from. Zcash keeps a transparent chain but adds an optional shielded pool you can opt into, where a single zk-SNARK proves a payment is valid while revealing nothing. The rest of this guide opens up both machines.
Monero #1 — hiding the receiver with stealth addresses
Start with the receiver. On Bitcoin, if you publish one address and people pay you there, every payment lands in the same visible bucket — your whole income is one cluster. Monero fixes this with a stealth address: the recipient publishes one long-lived address, but the sender derives a fresh, unlinkable one-time public key for every single payment. No two payments to you share an on-chain address, and only you can recognise which outputs are yours.
The trick is a shared secret built from elliptic-curve keys. Your wallet address is really two public keys: a view key `A = a·G` and a spend key `B = b·G`. The sender picks a random scalar `r`, publishes `R = r·G` with the transaction, and computes the one-time output key from a hash of the shared secret `r·A`. Because `a·R = a·(r·G) = r·(a·G) = r·A`, you — holding the private view key `a` — can recompute the very same secret and spot the payment. Nobody else can.
# Monero one-time (stealth) address — sender pays recipient (A, B) # A = a*G public view key a = private view key (recipient) # B = b*G public spend key b = private spend key (recipient) r = random_scalar() # fresh per output, chosen by the SENDER R = r*G # published with the transaction P = Hs(r*A)*G + B # the ONE-TIME output public key written on chain # Recipient scans every output, recomputing with the private view key a: # P' = Hs(a*R)*G + B since a*R = a*r*G = r*(a*G) = r*A -> P' == P # Only someone who knows a can recognise the payment as theirs. # To spend it, the recipient forms the one-time private key: # x = Hs(a*R) + b and P = x*G holds, so x signs for P # key image I = x * Hp(P) is published to stop double-spends (see next section)
Monero #2 — hiding the sender (rings) and the amount (RingCT)
Now the sender. A normal signature says *"the owner of this exact coin authorised this spend."* A ring signature says something weaker and far more private: *"the owner of one of these N coins authorised it — but I won't tell you which."* Monero builds each input as a ring of the real coin plus decoys (decoy outputs pulled from past transactions). Since 2020 every ring is a fixed size 16 (1 real + 15 decoys), so all transactions look alike. The signer proves membership without revealing position.
How do you stop someone spending the same coin twice if you can't even see which coin it is? Each ring signature publishes a key image `I = x·Hp(P)` — a value uniquely and deterministically derived from the real coin's private key. Spend the same coin again and the same key image reappears, so the network rejects it. The key image links a coin to itself across double-spends without revealing which ring member it came from.
Finally the amount. Monero replaces visible numbers with RingCT (ring confidential transactions). Each amount `b` is hidden inside a Pedersen commitment `C = a·G + b·H`, where `a` is a random blinding factor and `H` is a second generator whose discrete-log relation to `G` is unknown. Commitments are homomorphic: they add up. A transaction is valid only if the input commitments minus the output commitments minus the fee cancel to zero — proving no coins were minted from nothing, while every individual amount stays sealed.
# Monero RingCT — hide each amount in a Pedersen commitment # G, H : two curve points with UNKNOWN discrete-log relation (H = hash_to_point(G)) # a : blinding factor (random, secret) # b : the amount (secret) C = a*G + b*H # the commitment reveals nothing about b # Homomorphic balance check — a transaction is accepted only if: # sum(C_in) - sum(C_out) - fee*H == 0 # i.e. inputs and outputs (plus fee) cancel: no inflation, yet amounts stay hidden. # But a hidden b could be NEGATIVE -> mint money. So each output also carries a # range proof showing 0 <= b < 2^64 without revealing b. # Monero's Bulletproofs (Oct 2018) shrank these proofs ~80%, cutting typical fees ~95%.
Zcash — one shielded pool, one zero-knowledge spend
Zcash takes a completely different route. Instead of mixing visible coins with decoys, it keeps a single encrypted shielded pool. Your money lives there as a note — a secret tuple `(value, recipient, randomness)`. When a note is created, only a short commitment to it is appended to a global Merkle tree; the note's contents are encrypted to the recipient. The chain stores commitments, never plaintext notes.
To spend, you publish a zk-SNARK — a tiny proof (a few hundred bytes) that you can verify in milliseconds but that reveals nothing beyond *"this spend is valid."* The proof attests to a whole bundle of facts at once. Crucially it reveals exactly one new public value: a nullifier, a deterministic tag derived from the spent note. Publishing the nullifier lets the network reject a second spend of the same note, without revealing which note in the tree it was. That single move hides sender, receiver, and amount together.
// Zcash shielded SPEND — what the zk-SNARK proves (Sapling, simplified) // // PUBLIC inputs (on chain, everyone sees): // rt : current root of the note-commitment Merkle tree // nf : nullifier of the note being spent // cv : a Pedersen value commitment that hides the amount // // PRIVATE witness (never revealed): // note : (value v, recipient pk, randomness rcm) // path : Merkle path proving cm = NoteCommit(note) sits under root rt // ask : spend-authority key that owns the note // // The circuit checks ALL of, simultaneously: // 1. cm == NoteCommit(v, pk, rcm) // the note is well formed // 2. MerkleVerify(path, cm) == rt // it really exists in the pool // 3. nf == PRF(ask, note_position) // nullifier is the unique right one // 4. cv == v*G + rcv*H // value commitment matches v // 5. ask authorises spending pk // you actually own it // // Reveal only rt, nf, cv. Across a transaction, sum of input cv minus output cv // must commit to zero -> value balances -> no inflation, all amounts hidden.
Because the proof reveals nothing about which note, your sender anonymity set is the entire shielded pool — potentially every shielded note ever created, not a ring of 16. Zcash addresses come in two flavours: transparent t-addresses (just like Bitcoin) and shielded z-addresses. The proving system matured fast: the 2018 Sapling upgrade swapped the slow original circuits for Groth16 proofs, dropping proving from tens of seconds and gigabytes of RAM to roughly a second on a phone; the 2022 Orchard upgrade (Halo 2) went further, which we return to next.
Trusted setup, toxic waste, and a real counterfeiting scare
Zcash's earliest SNARKs needed a trusted setup: a one-time ceremony that generates public proving parameters from secret random values. Those secrets — nicknamed toxic waste — must be destroyed. If even one participant in the multi-party ceremony secretly keeps their share, here is the precise danger: they could forge proofs and counterfeit coins out of thin air. What they could not do is break anyone's privacy. So the worst case of a botched setup is silent inflation, not deanonymization.
This is not hypothetical. In 2018 the cryptographer Ariel Gabizon found a genuine flaw — not in the ceremony, but in the *BCTV14* proving system Zcash originally used — that could have allowed unlimited, undetectable counterfeiting of shielded ZEC. It was fixed quietly by the Sapling upgrade and disclosed in February 2019; the team found no evidence it was ever exploited, and importantly the bug never threatened the privacy of shielded users. It is a sobering lesson: novel cryptography carries novel, sometimes invisible, risks.
Two responses to the trusted-setup problem are worth knowing. First, Zcash's 2022 Orchard upgrade adopted Halo 2, a recursive proof system that needs no trusted setup at all — eliminating the toxic-waste risk for new shielded value. Second, Monero never needed a trusted setup in the first place: its Pedersen commitments and range proofs (modern Monero uses Bulletproofs) rely only on standard discrete-log assumptions. That is a genuine architectural advantage for Monero — paid for, as we saw, with a much smaller anonymity set per spend.
Two philosophies, two trade-offs
Step back and the choice is really about mandatory versus optional privacy, and it cuts both ways.
- Anonymity set. Monero's set is bounded by the ring (16 today) but is uniform — everyone is private, so there is no transparent crowd to filter out. Zcash's shielded set can be astronomically larger, but only if people actually use it.
- The optional-privacy trap. Because Zcash privacy is opt-in, for years most ZEC sat in transparent t-addresses, leaving the practical shielded set far smaller than the crypto allows — and a transfer that hops from a t-address into the pool and back can leak its endpoints. Mandatory privacy avoids this 'who bothered to shield?' fingerprint entirely.
- Fungibility. Mandatory privacy gives every coin the same blank history, so no coin can be 'tainted' or blacklisted — a property cash has and transparent crypto lacks. Optional privacy means transparent coins can still be tainted, weakening fungibility for the chain as a whole.
- Compliance & disclosure. Zcash's opt-in design pairs naturally with viewing keys: you can hand an auditor or exchange a key that decrypts your shielded activity, proving compliance selectively without going public. Monero's always-on model offers view keys too, but its all-or-nothing stance makes exchange listings harder. This compliance tension is the whole subject of the final guide in this rung.
- Usability & cost. Monero transactions are larger (a ring plus range proofs) but need no special hardware. Zcash shielded proofs are tiny to verify but historically heavy to create; Sapling made shielding practical on a phone. Both ask more of the user than a transparent chain.
You have now seen privacy applied to payments. The next guide pushes the same zero-knowledge machinery further — into programmable privacy, where smart-contract state and balances stay hidden while the rules are still provably enforced. The nullifier idea you just met in Zcash is the seed of that whole construction.