DEV Community

Alexander
Alexander

Posted on

Monero Ring Signatures: Deep Dive for Researchers

Privacy-Preserving Cryptocurrency Protocols

Overview

Privacy in cryptocurrency is a harder problem than most people assume. Bitcoin transactions are pseudonymous, not anonymous — every transaction is permanently recorded on a public ledger, and chain analysis firms like Chainalysis have gotten very good at clustering addresses and tracing fund flows. When you send BTC to an exchange that does KYC, your entire transaction history becomes linkable to your identity.

Three main technical approaches have emerged to address this: zero-knowledge proofs, coin mixing, and protocol-level fungibility guarantees. Each makes different tradeoffs between privacy, scalability, and trust assumptions.


1. Zero-Knowledge Proofs

A zero-knowledge proof lets you convince a verifier that a statement is true without revealing why it's true. In cryptocurrency, the classic application is proving you have enough balance to cover a transaction without revealing your balance or transaction history.

The math behind this goes back to a 1985 paper by Goldwasser, Micali, and Rackoff. The modern implementation most people encounter is zk-SNARKs (Succinct Non-interactive Arguments of Knowledge).

How a zk-SNARK works at a high level:

  1. You have a computation — say, "I know a secret value x such that H(x) = y" where H is a hash function and y is public.
  2. The computation gets encoded as an arithmetic circuit — a graph of addition and multiplication gates over a finite field.
  3. The prover produces a proof that they executed the circuit correctly, without revealing the inputs.
  4. The verifier checks the proof in milliseconds, even if the original computation was expensive.

Zcash uses a specific zk-SNARK construction called Groth16. A Zcash shielded transaction proves three things simultaneously:

  • The input notes exist in the commitment tree (Merkle proof)
  • The spending keys are valid (discrete log relation)
  • The sum of inputs equals the sum of outputs (balance equation)

All without revealing which notes are being spent, who the sender is, or the amounts involved.

Simplified illustration of what a Zcash shielded transaction commits to

(not actual Zcash code, but reflects the structure)

import hashlib
import secrets

def pedersen_commit(value: int, randomness: int, G: int, H: int, p: int) -> int:
"""
Pedersen commitment: C = value*G + randomness*H (mod p)
Hiding: randomness keeps value secret
Binding: computationally infeasible to find two (v,r) pairs for same C
"""
return (value * G + randomness * H) % p

def note_commitment(recipient_pubkey: bytes, value: int, rho: bytes) -> bytes:
"""
A Zcash note commits to (recipient, value, unique nullifier seed)
The nullifier prevents double-spending without revealing which note was spent
"""
note_data = recipient_pubkey + value.to_bytes(8, 'big') + rho
return hashlib.blake2b(note_data, digest_size=32).digest()

def compute_nullifier(spending_key: bytes, rho: bytes) -> bytes:
"""
Nullifier is published when a note is spent.
Derived from the spending key + note randomness.
Reveals that a note was spent, but not which one.
"""
return hashlib.blake2b(spending_key + rho, digest_size=32).digest()

Example flow

spending_key = secrets.token_bytes(32)
rho = secrets.token_bytes(32) # per-note randomness
recipient = secrets.token_bytes(32) # recipient's diversified payment address

commitment = note_commitment(recipient, 100, rho) # 100 ZEC
nullifier = compute_nullifier(spending_key, rho)

print(f"Note commitment: {commitment.hex()}")
print(f"Nullifier: {nullifier.hex()}")

These two values reveal nothing about each other to an outside observer

Key numbers for Zcash (Sapling, 2018 upgrade):

  • Proof generation time: ~2.3 seconds on modern hardware
  • Proof

Research References

The following archives provide additional research context:

Open Source Tools:

Top comments (0)