We're not going to explain ZK in five minutes here. We're not even going to touch it. But there are a handful of math topics you have to understand before anything in this series makes sense.
Are you scared of those nasty math symbols? Does opening an arxiv paper make you want to hide under your bed? Good. Same here. So let's get comfortable with it slowly, one concept at a time, with zero greek letters and some things you can click on.
1. Modular arithmetic
Think of a clock. A normal clock has 12 positions (0 through 11 if you're a programmer). If it's 10 o'clock and you add 5 hours, you don't get 15. You get 3. The number wraps around.
That's modular arithmetic. The mod operator gives you the remainder after division:
10 + 5 = 15 → 15 mod 12 = 3
7 + 7 = 14 → 14 mod 12 = 2
In crypto, the modulus isn't 12. It's a prime number so large it has 77 digits. But the principle is identical: every result wraps back into a fixed range, and that wrapping destroys information about the original inputs.
Try it yourself. The clock below uses a small modulus so you can watch values wrap around:
Interactive: Modular arithmetic clock visualization — try it on luk3.tech
Formal explanation: Modular arithmetic on Wikipedia
2. Finite fields
A finite field (also called a Galois field) is a set of numbers where you can add, subtract, multiply, and divide, and you never leave the set. Every operation wraps back into the same pool of values.
More precisely, a finite field GF(p) is the set of integers {0, 1, 2, ..., p-1} where p is prime, and all arithmetic is done mod p. Three properties matter:
- Closed: No operation produces a result outside the set.
-
Every element has an inverse: For any number
ain the field, there exists somebsuch thata * b = 1 (mod p). Division always works. - No escape: You can chain as many operations as you want. You're still in the field.
Why does crypto care? Because finite fields let you do complex algebra on huge numbers while guaranteeing that every intermediate result stays within a fixed, predictable range. No overflow, no floating point issues, no surprises. And the modular wrapping makes it extremely hard to reverse-engineer what inputs produced a given output.
Formal explanation: Finite field on Wikipedia
3. The discrete logarithm problem
Here's the core asymmetry that makes public-key cryptography possible.
The easy direction: Given a base g, an exponent k, and a modulus p, computing g^k mod p is fast. Computers are good at exponentiation.
g = 3, k = 5, p = 7
3^5 = 243 → 243 mod 7 = 5
The hard direction: Given g, p, and the result 5, find k. With small numbers you can just try them all. With numbers that have 77 digits? No known algorithm can do it efficiently. You'd be guessing until the heat death of the universe.
This is the discrete logarithm problem (DLP). "Discrete" because you're working in a finite field (discrete values, not continuous). "Logarithm" because you're trying to find the exponent.
When this same problem is framed on an elliptic curve (finding how many times a point was "multiplied" to produce another point), it's called the Elliptic Curve Discrete Logarithm Problem (ECDLP). Same idea, different algebraic structure, even harder to break.
Important distinction: "hard" here means computationally infeasible, not mathematically impossible. The answer exists. Nobody forbids you from finding it. It's just that the fastest known algorithms would take longer than the age of the universe on current hardware.
Formal explanation: Discrete logarithm on Wikipedia
4. Hash functions
A hash function takes any input (a single character, a novel, a video file) and produces a fixed-size output. SHA-256 always gives you 256 bits (64 hex characters). SHA-3 gives you the same. Doesn't matter if your input is four letters or four gigabytes.
But how? How does the word math turn into a0885e289f3e77a14e06e6887a1fc93b5ed2e14cfe7f7052805fe92e1a0e0e38?
What actually happens inside a hash
Let's walk through the rough steps. Different algorithms (SHA-2, SHA-3, BLAKE) vary in the details, but the skeleton is the same:
Step 1: Convert to binary. Your input becomes raw bytes. The string math becomes four ASCII values: 109 97 116 104, which in binary is 01101101 01100001 01110100 01101000.
Step 2: Pad the message. The algorithm needs the input to be a specific length (a multiple of the block size). So it appends a 1 bit, then enough 0 bits, then the original message length. Now you have a neat, fixed-size block to work with.
Step 3: Initialize state. The algorithm starts with a set of fixed constants as its internal state. These aren't secret. They're defined in the spec (for SHA-256 they come from the fractional parts of the square roots of the first eight primes, because why not).
Step 4: Compress, round after round. This is where the magic happens. The padded message gets split into chunks, and each chunk gets fed through a compression function that mixes it into the internal state. SHA-256 runs 64 rounds. SHA-3 runs 24. Each round does a combination of:
- Bitwise operations: XOR, AND, NOT, rotations. These scramble the bits in non-linear ways.
- Modular addition: Adding values mod 2^32, which causes carries to cascade unpredictably.
- Mixing: Bits from different positions influence each other, so information spreads across the entire state.
The visualization below walks through the full SHA-256 computation for the word "math", step by step. You can see the padding, the message schedule, and how each round scrambles the working variables until the final hash emerges.
Interactive: SHA-256 step-by-step computation walkthrough — try it on luk3.tech
Step 5: Output. The final internal state (or a portion of it) becomes your hash.
The result is deterministic (same input, same output, every time) but practically irreversible. You can't reconstruct math from the hash any more than you can reconstruct an egg from an omelette. This makes hash functions a kind of trapdoor: easy to compute forward, impossible to reverse. You'll see this same pattern everywhere in cryptography. Your public key is derived from your private key through a trapdoor (the discrete log). Digital signatures use one. Hash functions are the simplest example: one-way, no way back.
Formal explanation: Cryptographic hash function on Wikipedia · Trapdoor function on Wikipedia
5. Constraints: how ZK thinks about computation
Here's where things start to feel unfamiliar.
Normally, when you want to prove you ran a computation correctly, you just re-run it and check the answer. ZK proofs take a completely different approach. Instead of re-running the program, you express the entire computation as a set of equations (called constraints) that must all be satisfied simultaneously.
A simple example. Say you want to prove you know two numbers that multiply to 35. Instead of revealing "5 and 7", you write a constraint:
a * b = 35
If you can provide values for a and b that satisfy this equation, you've proven you know them. The constraint system doesn't care how you found the values. It only cares that they satisfy the equations.
Real ZK systems express entire programs this way. A program that checks a password, verifies a merkle proof, or validates a transaction gets compiled into thousands (or millions) of constraints. The most common format for this is called R1CS (Rank-1 Constraint System), where every constraint has the form:
(left) * (right) = (output)
Each of left, right, and output is a linear combination of variables. The entire program becomes a system of these equations.
The key insight: "I know values that satisfy all these constraints" is a statement you can prove without revealing the values themselves.
Formal explanation: R1CS on Wikipedia
6. The idea behind zero-knowledge proofs
Now you have all the pieces. ZK proofs combine the concepts above into a single protocol.
The setup: there's a prover (who knows a secret) and a verifier (who wants to be convinced the prover knows it, without learning the secret).
A zero-knowledge proof has three properties:
- Completeness: If the prover actually knows the secret, they can always convince the verifier. Honest provers always succeed.
- Soundness: If the prover doesn't know the secret, they can't trick the verifier (except with negligible probability). Liars get caught.
- Zero-knowledge: The verifier learns nothing beyond the fact that the statement is true. No information about the secret leaks.
Put those together with constraint systems and you get the full picture: the prover takes a program, compiles it into constraints, plugs in their secret values, and generates a proof that all constraints are satisfied. The verifier checks the proof without ever seeing the secret values.
The math that makes this actually work (polynomial commitments, elliptic curve pairings, Fiat-Shamir transforms) is deep. Future posts will go there. For now, the mental model is enough: constraints define what "correct" means, and ZK proofs let you demonstrate correctness without revealing your inputs.
Formal explanation: Zero-knowledge proof on Wikipedia
Top comments (0)