There are tons of free ZK resources out there. The hard part is knowing what to learn, and in what order.
So I built a curated ZK learning roadmap:
Math → Cryptography → SNARKs/STARKs → Prover Engineering → zkVMs
https://t.co/wbPMce0dCN
Q. Why isn't KZG's basic commitment automatically "hiding," and what's the concrete fix?
A basic KZG commitment, C = g^(p(s)), is binding but not automatically hiding. Binding means that a prover cannot easily change the committed polynomial, while hiding means that the commitment does not reveal information about the polynomial itself. Although the commitment does not directly expose the coefficients, additional information, such as enough evaluation proofs at different points, could allow someone to reconstruct the polynomial through interpolation. The concrete fix is to add random blinding. In polynomial-commitment schemes, this can involve adding a random multiple of a vanishing polynomial, such as p'(x) = p(x) + r(x)Z(x). Because Z(x) is zero on the intended evaluation domain, this modification does not change the polynomial's values at those points, but it introduces randomness that helps hide the original polynomial. The exact blinding method depends on the KZG variant and the security property required.
Q. Why is KZG's commitment C = g^(p(s)) computationally binding under the discrete log assumption?
KZG commitments are binding because, under the usual discrete logarithm and knowledge-of-exponent assumptions, it is computationally infeasible to find two different polynomials that produce the same commitment. The commitment is C = g^(p(s)), where s is a secret value chosen during setup. If a prover creates two different polynomials, p and p', with the same commitment, then their difference d(x) = p(x) - p'(x) must satisfy d(s) = 0. In other words, the secret s would have to be a root of a nonzero polynomial deliberately constructed by the attacker. Since s is hidden and randomly chosen, the probability of this happening is very small, roughly bounded by the degree of the polynomial divided by the field size. However, one important correction is that this does not directly reduce to solving discrete logarithms. KZG binding is normally justified using a stronger or more specialized assumption called the knowledge of exponent assumption, together with the security of the trusted setup.
Q. Why does PLONK's universal setup still need to be updatable, beyond simply being "universal"?
Universal and updatable describe two different properties of a PLONK setup. Universal means that the same setup can support many different circuits up to a certain size, so a new ceremony is not needed for every application. However, if the original ceremony’s secret randomness called toxic waste were compromised, an attacker could potentially create invalid proofs for all circuits using that setup. Updatability reduces this long-term trust risk: new participants can contribute fresh randomness later, re-randomizing the setup without invalidating existing circuits. As long as at least one participant in the entire contribution history honestly generates and destroys their secret, the setup remains secure. Therefore, universality provides reusability, while updatability provides ongoing trust improvement and recovery from reliance on the original ceremony.
Q. What specific inefficiency does a lookup argument (PLOOKUP-style) fix that arithmetic constraints alone cannot solve cheaply?
A lookup argument makes it much cheaper to prove that a value belongs to a specific set of allowed values. Imagine you are building a zkVM and need to prove that a particular value is a valid byte. A byte must be between 0 and 255. If you use only ordinary arithmetic constraints, you have to build extra logic to prove that the value falls within this range. You might break the number into bits and then prove that each bit is either 0 or 1. This requires additional constraints and increases the size of your circuit. Now imagine you have a predefined table containing all 256 possible byte values. With a lookup argument, you can simply say, "The value I am using must appear in this table." The lookup protocol then uses a polynomial-based technique to verify that your claimed values belong to the allowed table, without requiring you to build a large collection of arithmetic constraints for every possible value. The same idea works for operations such as XOR: instead of constructing a complicated arithmetic circuit to calculate XOR, you can create a table of valid input-output combinations and check that your result matches one of the entries. In simple terms, arithmetic constraints prove that a mathematical equation is correct, while lookup arguments efficiently prove that a value is present in a predefined list of valid values. This is especially useful in zkVMs, where operations such as byte manipulation, range checks, and bitwise logic occur frequently.
There are tons of free ZK resources out there. The hard part is knowing what to learn, and in what order.
So I built a curated ZK learning roadmap:
Math → Cryptography → SNARKs/STARKs → Prover Engineering → zkVMs
https://t.co/wbPMce0dCN
Q. How does PLONK's "custom gate" mechanism differ structurally from R1CS's fixed A*B=C gate shape?
The key difference is that R1CS has a fixed constraint shape, while PLONK allows more flexibility in what each row of the circuit can calculate. In R1CS, each constraint must follow the form (A · z) × (B · z) = (C · z), which means you can multiply two linear expressions and set the result equal to another linear expression. If you want to perform a more complicated operation, you often need extra intermediate wires and additional constraints. In PLONK, each row has three wire values, usually called a, b, and c, and a set of selector values that tell the system how to combine them. For example, the equation q_L·a + q_R·b + q_M·a·b + q_O·c + q_C = 0 can express addition, multiplication, or a combination of these operations, depending on the selector values. Custom gates go further by allowing the circuit designer to define additional algebraic relationships tailored to a specific operation, such as a step in a hash function. This can reduce the number of rows and intermediate wires needed for certain computations. However, custom gates do not make arbitrary computations fit into one row; the operation must still be expressible through the gate's allowed algebraic structure, and additional rows may be needed. In simple terms, R1CS uses a standard building block for multiplication, while PLONK lets you design more specialized building blocks for particular calculations.
I see all this discussion around Formal Verification focusing so much on bugs, but what a lot of people doesn't seem to realize is that bugs only makes sense with respect to some specification.
If you don't have a specification of your software, then it can't even have bugs to begin with!
Q. Explain PLONK's permutation argument: what's actually inside the "grand product" polynomial Z(x)?
In PLONK, the permutation argument is a way to prove that different wires in a circuit contain the same values whenever they are supposed to be connected. Imagine a circuit where the output of one gate must be connected to the input of another gate. Instead of checking every connection separately, PLONK uses a clever trick called the grand product polynomial. First, PLONK assigns a position to every wire and creates a permutation that tells us which wire positions should contain equal values. Then, it chooses two random numbers, β and γ, and constructs a running product. At each step, it multiplies the current product by a fraction: the numerator uses the wire's value plus β times its position plus γ, while the denominator uses the same value plus β times the position of the wire it is supposed to match. The polynomial Z(x) stores this running product across the circuit's evaluation domain. If the wiring is correct, the products of the numerators and denominators contain the same terms in a different order, so they cancel out and the final product returns to 1. If the wiring is incorrect, the products generally do not match, and the final value is unlikely to be 1 because β and γ were chosen randomly. PLONK can therefore check many wire connections through a single polynomial identity instead of checking every connection individually. In simple terms, Z(x) is like a running scorekeeper: it multiplies fractions that track whether the values assigned to connected wires are consistent, and if everything is correct, the score returns to 1 at the end.
Q. Why does Groth16 need two independent random blinding factors, not one, to achieve zero-knowledge?
Groth16 uses two separate random values, usually called r and s, to hide information about the secret witness in two different proof elements, A and B. Think of A and B as two separate envelopes containing information about the same secret calculation. If the prover used only one random value to hide both envelopes, the relationship between their contents might reveal information about the witness. Using two independently chosen random values makes it harder for the verifier to learn anything from the individual elements or their relationship. However, simply using two random numbers is not enough on its own; the blinding must be designed according to the Groth16 protocol. The prover combines these random values with the witness-related terms in A, B, and C in a carefully calculated way. The verifier then uses a pairing equation to check the proof, and the extra blinding terms cancel out mathematically, so the proof remains valid. In simple terms, two random values help hide the secret information in two different parts of the proof, while C is constructed to ensure that this extra hiding does not break verification.
Q. What do the three group elements A, B, C in a Groth16 proof actually encode?
In Groth16, a proof consists of three group elements called A, B, and C. Together, they provide evidence that the prover knows a valid witness satisfying the circuit's constraints, without revealing that witness to the verifier. Think of these three elements as cryptographic summaries of the computation. A contains information about the witness-weighted polynomials from the QAP, combined with secret setup values and a random blinding factor. B contains similar information, but it is represented in a different elliptic-curve group, allowing the verifier to use a pairing to check the proof. C contains information related to the QAP quotient polynomial, which represents how the computation's polynomial identity is satisfied, along with additional witness-related and blinding terms. The values α and β come from the trusted setup, while the random values used for blinding are freshly chosen for each proof to help preserve zero-knowledge. The verifier then uses a special mathematical operation called a pairing to check a relationship between A, B, C, and the public inputs. If the relationship holds, the verifier accepts the proof. In simple terms, A, B, and C are three cryptographic pieces of evidence that encode information about the hidden computation. The verifier combines them through pairings to check that the computation is valid, without directly seeing the secret witness.