← Back to list

3. Inside Groth16: How Blinding, Simulators, and Four Secret Numbers Make zkSNARKs Zero-Knowledge

Part 3 of a 4-part series. In Part 2, we built the machinery of zkSNARKs — circuits, R1CS, QAP, and pairings. We got to succinct proofs…

Mahdi Darabi · 2026-04-19 13:18 · 0 claps · 14.9 min read
#cryptography #zero-knowledge-proofs #zksnark #blockchain #ethereum
Open on Medium ↗
Wiki topics: CRY · Crypto & Web3 🔒 · Cybersecurity 📐 · Mathematics

3. Inside Groth16: How Blinding, Simulators, and Four Secret Numbers Make zkSNARKs Zero-Knowledge

Part 3 of a 4-part series. In Part 2, we built the machinery of zkSNARKs — circuits, R1CS, QAP, and pairings. We got to succinct proofs: 200 bytes, 1 millisecond to verify. But we haven’t yet delivered the zero-knowledge part. That’s what this post is about.

A note on notation

To avoid the confusing clash between “the witness” and “the W polynomial in the QAP identity,” this post uses:

  • a = (a₀, a₁, …, aₘ) — the witness vector. Entries aⱼ are the values of each circuit variable. This matches Groth’s original 2016 paper.
  • uⱼ(X), vⱼ(X), wⱼ(X) — the per-variable QAP polynomials (lowercase, with subscript j). Each circuit variable has three of these.
  • U(X), V(X), W(X) — the aggregated QAP polynomials (uppercase). These are weighted sums of the per-variable polynomials by the witness:
U(X) = Σⱼ aⱼ · uⱼ(X)
V(X) = Σⱼ aⱼ · vⱼ(X)
W(X) = Σⱼ aⱼ · wⱼ(X)

The QAP identity is then U(X) · V(X) − W(X) = h(X) · t(X).

Throughout this post, whenever a “w” appears, it is the lowercase per-variable polynomial wⱼ — never a witness. Witnesses are always aⱼ or a.

1. The problem: succinct ≠ zero-knowledge

Suppose the prover commits to U(τ), V(τ), W(τ), h(τ) and the verifier checks the pairing equation. The proof is small and fast. It is also deterministic: same witness a in, same proof out.

That’s a leak. A verifier who guesses the witness a can run the prover themselves and compare proof bytes. Match → guess confirmed. The proof’s exact bit-pattern fingerprints the witness.

To fix this, two things must happen:

  1. Every proof must look uniformly random among all valid proofs for the same statement, so the bit-pattern carries no information about which a produced it.
  2. There must be a way to construct a valid-looking proof without any witness at all — because if a witness-free algorithm produces the same distribution, then real proofs cannot be leaking witness information.

These are the two ingredients: blinding (achieves 1) and the simulator argument (proves 2).

2. Ingredient 1: Blinding

2.1 The blinded prover formulas

The prover samples two fresh random scalars r, s from the scalar field (different every proof) and computes:

A = α + U(τ) + r·δ
B = β + V(τ) + s·δ
         Σ_{j∈priv} aⱼ·(β·uⱼ(τ) + α·vⱼ(τ) + wⱼ(τ)) + h(τ)·t(τ)
C  =  ─────────────────────────────────────────────────────  +  A·s + B·r − r·s·δ
                              δ

Recall: U(τ) = Σⱼ aⱼ·uⱼ(τ) and V(τ) = Σⱼ aⱼ·vⱼ(τ), where aⱼ are the witness entries.

The C-numerator sums only over private witness indices j ∈ priv. Public-input contributions are routed through γ instead of δ — see Section 5.

The proof is the triple π = ([A]₁, [B]₂, [C]₁).

2.2 Why A looks uniformly random

Since r is uniform over the scalar field 𝔽_q and δ ≠ 0 is a fixed unit, the map r ↦ r·δ is a bijection of 𝔽_q onto itself. So r·δ is uniform on 𝔽_q. Adding a fixed constant α + U(τ) keeps it uniform. Therefore A is uniform on 𝔽_q, and [A]₁ is uniform on 𝔾₁.

This is perfect indistinguishability — not “statistical.” Two real proofs of the same statement, or a real proof and a simulator’s proof, draw A from exactly the same distribution. Same story for B.

(The original Groth16 paper, Theorem 1, calls this perfect zero-knowledge for this reason.)

2.3 Why the verifier’s equation still holds

This is the part most expositions hand-wave. Let me prove it.

The verifier’s equation, lifted from the pairing groups down to the field, is:

A · B  =  α·β  +  Σ_{j∈pub} aⱼ·(β·uⱼ(τ) + α·vⱼ(τ) + wⱼ(τ))  +  C·δ        (V)
                  └──────────── public part ────────────┘

(The γ that appears in the actual pairing equation cancels here — see Section 5 for what γ is doing.)

Expand the left side, using the shorthand U = U(τ), V = V(τ):

A · B = (α + U + r·δ)(β + V + s·δ)
      = α·β + α·V + α·s·δ + β·U + U·V + U·s·δ + r·δ·β + r·δ·V + r·s·δ²

That’s nine terms. Now expand the right side. Using A = α + U + r·δ and B = β + V + s·δ:

A·s·δ = (α + U + r·δ)·s·δ  =  α·s·δ + U·s·δ + r·s·δ²
B·r·δ = (β + V + s·δ)·r·δ  =  β·r·δ + V·r·δ + r·s·δ²

So C·δ expands to:

C·δ  =  Σ_{priv} aⱼ·(β·uⱼ + α·vⱼ + wⱼ(τ)) + h(τ)·t(τ)        ← witness part
        + α·s·δ + U·s·δ + r·s·δ²                              ← from A·s·δ
        + β·r·δ + V·r·δ + r·s·δ²                              ← from B·r·δ
        − r·s·δ²                                              ← cancellation term

The two copies of r·s·δ² minus the −r·s·δ² leave exactly one r·s·δ². Adding α·β and the public part to the right side of (V), and combining the witness-part-plus-public-part:

Σ_{priv} aⱼ·(β·uⱼ + α·vⱼ + wⱼ(τ))  +  Σ_{pub} aⱼ·(β·uⱼ + α·vⱼ + wⱼ(τ))
                                  =
                  Σⱼ aⱼ·(β·uⱼ + α·vⱼ + wⱼ(τ))
                                  =
                  β·U(τ) + α·V(τ) + W(τ)

That last expression contains the aggregated QAP polynomial W(τ) — computed from the per-variable wⱼ and the witness a. So the right side of (V) becomes:

α·β + β·U + α·V + W(τ) + h(τ)·t(τ) + α·s·δ + U·s·δ + β·r·δ + V·r·δ + r·s·δ²

Compare to the LHS expansion. Every term matches except that LHS has U·V where RHS has W(τ) + h(τ)·t(τ). But that’s exactly the QAP identity: by construction of h,

U(τ) · V(τ) − W(τ) = h(τ) · t(τ)

Substitute and the equation is satisfied identically — for any r and s. The randomness scrambles the public proof but cancels in the verifier’s check.

I confirmed this symbolically in a computer-algebra system; the difference LHS − RHS collapses to exactly zero after substituting the QAP identity.

2.4 Why δ specifically?

The blinding terms in A and B are r·δ and s·δ, not just r and s. Why?

Because C is paired against [δ]₂ in the verifier’s equation: e([C]₁, [δ]₂). That pairing multiplies δ into C’s exponent. So if C contains the cancellation terms A·s, B·r, −r·s·δ, then C·δ contains A·s·δ, B·r·δ, −r·s·δ² — and these are exactly the cross-terms that show up when you expand (α + U + r·δ)(β + V + s·δ).

If you blinded with r alone (no δ factor), the cross-terms in A·B would have a single δ factor (from α·δ·r etc.), not matching what C·δ can produce. The cancellation would fail.

So the choice of δ as the blinding factor isn’t arbitrary — it’s forced by the structure of the verification equation.

3. A complete numerical example over a tiny prime field

Let’s actually run Groth16 end-to-end on a toy statement and see every term concretely. We’ll skip the elliptic-curve / pairing layer and just work in a prime field 𝔽_p with p = 101. (Real Groth16 uses 254-bit primes; the algebra is identical.)

3.1 Statement

“I know x such that x · (x + 2) = 15.”

Witness: x = 3 (since 3 · 5 = 15).

3.2 R1CS

One constraint: L · R = O where L = x, R = x + 2, O = 15.

Circuit variables (entries of the witness vector a):

  • a₀ = 1 (the constant — always present)
  • a₁ = 15 (public output)
  • a₂ = x = 3 (private input)

So m = 2, ℓ = 1 (public indices are {0, 1}), and the private index is {2}.

R1CS coefficient vectors:

a₀   a₁   a₂
  L:    0    0    1
  R:    2    0    1
  O:    0    1    0

Check: L · a = 3, R · a = 5, O · a = 15, and 3 · 5 = 15. ✓

3.3 QAP at evaluation point X = 1

With one constraint, the per-variable polynomials uⱼ(X), vⱼ(X), wⱼ(X) are constants equal to the L, R, O column entries:

uⱼ = Lⱼ,   vⱼ = Rⱼ,   wⱼ = Oⱼ

The vanishing polynomial is t(X) = X − 1.

The aggregated QAP polynomials, evaluated at τ (which doesn’t matter here since the per-variable polynomials are constant):

U(τ) = Σⱼ aⱼ·uⱼ = 1·0 + 15·0 + 3·1 = 3
V(τ) = Σⱼ aⱼ·vⱼ = 1·2 + 15·0 + 3·1 = 5
W(τ) = Σⱼ aⱼ·wⱼ = 1·0 + 15·1 + 3·0 = 15

QAP identity check: U(τ)·V(τ) − W(τ) = 15 − 15 = 0. So h(X) = 0 (the trivial case for one constraint), and h(τ)·t(τ) = 0.

3.4 Setup: pick toxic-waste secrets

For reproducibility I’ll fix them, but in reality these are uniformly random:

α = 7,   β = 11,   γ = 13,   δ = 17,   τ = 23

(All in 𝔽₁₀₁. Note δ = 17 is invertible mod 101: its inverse is 6 because 17 · 6 = 102 ≡ 1.)

3.5 Prover picks blinding randomness

r = 19,   s = 29

3.6 Compute A

A = α + U(τ) + r·δ = 7 + 3 + 19·17 = 7 + 3 + 323 = 333 ≡ 30  (mod 101)

3.7 Compute B

B = β + V(τ) + s·δ = 11 + 5 + 29·17 = 11 + 5 + 493 = 509 ≡ 4  (mod 101)

3.8 Compute C

The “private witness part” of the C numerator (sum over j ∈ {2} only — that’s the private witness entry a₂ = 3):

priv_part = a₂·(β·u₂ + α·v₂ + w₂) = 3·(11·1 + 7·1 + 0) = 3·18 = 54

(Reminder: u₂, v₂, w₂ here are the per-variable QAP polynomials for variable index 2, which evaluate to the L, R, O coefficients 1, 1, 0 respectively.)

Now:

C  =  (54 + 0)/17  +  A·s  +  B·r  −  r·s·δ
   =  54·6  +  30·29  +  4·19  −  19·29·17     (mod 101)

Term by term:

54·6      = 324  ≡ 21    (since 324 = 3·101 + 21)
  30·29     = 870  ≡ 62    (since 870 = 8·101 + 62)
  4·19      = 76
  19·29·17  = 9367 ≡ 75    (since 9367 = 92·101 + 75)
C ≡ 21 + 62 + 76 − 75 ≡ 84  (mod 101)

3.9 The proof

π = (A, B, C) = (30, 4, 84)  ∈  𝔽₁₀₁³

In real Groth16 these would be group elements [30]₁, [4]₂, [84]₁ on a 254-bit elliptic curve.

3.10 Verifier’s check

LHS = A · B = 30 · 4 = 120 ≡ 19  (mod 101)

The “public part” the verifier computes from the public witness entries a₀ = 1, a₁ = 15:

pub_part = Σ_{j∈{0,1}} aⱼ·(β·uⱼ + α·vⱼ + wⱼ)
         = 1·(11·0 + 7·2 + 0) + 15·(11·0 + 7·0 + 1)
         = 14 + 15
         = 29
RHS = α·β + pub_part + C·δ
    = 7·11 + 29 + 84·17
    = 77 + 29 + 1428
    = 1534
    ≡ 19  (mod 101)

LHS = RHS = 19. Verifier accepts.

3.11 Notice what just happened

  • r and s never appeared in the verifier’s computation. They scrambled A, B, C but disappeared in the check.
  • A different draw of r, s would produce a different (A, B, C) triple that still passes verification. Each proof bit-pattern is a fresh random sample.
  • We never revealed a₂ = x = 3. The verifier only saw A = 30, B = 4, C = 84 and the public output a₁ = 15.

4. Ingredient 2: The simulator argument

Blinding makes proofs look random. The simulator argument is the formal proof that they carry no information.

4.1 What a simulator is

A simulator is a hypothetical algorithm that produces valid-looking proofs for true statements, without knowing any witness a. To pull this off, it gets a privilege the real prover doesn’t have: access to the trapdoor (α, β, γ, δ, τ) from setup.

The argument: if the simulator’s output distribution is identical to the real prover’s, then any information the real proof “contains” about the witness is also producible without the witness — so it can’t be witness-specific.

This is how zero-knowledge is formally proved, not just asserted.

4.2 The Groth16 simulator

Step 1. Pick uniformly random a_sim, b_sim ∈ 𝔽_q. (Note: these are not witness entries — they’re just two random scalars, named a and b to match Groth’s paper convention. They become the proof’s A and B values.)

Step 2. Set A_sim = a_sim and B_sim = b_sim.

Step 3. Solve the verifier’s equation for C_sim:

a_sim · b_sim = α·β + pub_part + C_sim·δ
         a_sim · b_sim − α·β − pub_part
C_sim = ────────────────────────────────
                       δ

The simulator can compute pub_part because the public inputs (the public entries of a) are part of the statement, and it knows α, β, δ from the trapdoor.

Step 4. Output π_sim = ([a_sim]₁, [b_sim]₂, [C_sim]₁).

Note: the simulator does not need γ at all, and it doesn’t need to know any witness polynomials, the private witness entries, or h(X).

4.3 Run the simulator on our example

Same setup as before: α = 7, β = 11, δ = 17, public witness entries a₀ = 1, a₁ = 15, so pub_part = 29 (computed exactly as the verifier does).

Simulator picks a_sim = 41 and b_sim = 67 uniformly at random.

a_sim · b_sim = 41·67 = 2747 = 27·101 + 20 ≡ 20  (mod 101)
C_sim = 17⁻¹·(20 − 77 − 29)  mod 101
      = 6·(−86)
      = −516
      ≡ 90    (since −516 + 6·101 = 90)

Verifier check on the simulated proof:

LHS = a_sim · b_sim = 20
RHS = α·β + pub_part + C_sim·δ
    = 77 + 29 + 90·17
    = 77 + 29 + 1530
    = 1636
    ≡ 20   (mod 101)

Verifier accepts the simulated proof. The private witness entry a₂ = 3 was never used.

4.4 Why the distributions are identical

Real prover output (A, B, C):

  • A = α + U(τ) + r·δ. Since r is uniform on 𝔽_q and *δ ∈ 𝔽_q*, the term r·δ is uniform; adding a fixed value keeps it uniform. So A* is uniform on 𝔽_q.
  • B is uniform on 𝔽_q by the same argument.
  • A and B are independent because r and s are independent.
  • Conditional on (A, B) and the statement, C is forced to the unique field element satisfying the verifier’s equation — there’s no freedom left.

Simulator output (A_sim, B_sim, C_sim):

  • A_sim = a_sim, uniform by construction.
  • B_sim = b_sim, uniform by construction, independent of a_sim.
  • C_sim is computed as the unique field element satisfying the same verifier equation.

The marginals match. The independence structure matches. The conditional distribution of C given (A, B) matches (it’s a delta function on the same value in both cases). Therefore the joint distributions are identical — not just close, identical.

This is why Groth16 has perfect zero-knowledge: the indistinguishability is information-theoretic, not just computational. An attacker with unlimited compute cannot tell a real proof from a simulated one, because there is no statistical signal to detect.

4.5 The philosophical punchline

A real proof contains, in some abstract sense, the witness a — the prover used it to construct the proof. But that information is inaccessible because the same probability distribution over proofs can be produced without a. There’s no test, no algorithm, no statistical procedure that can distinguish “this proof came from a real witness” from “this proof was fabricated from the trapdoor” — because there is literally no distributional difference.

Information that’s distributionally equivalent to information you could have generated yourself is, by definition, no information at all.

5. The four secrets: what each one defends against

The trapdoor (α, β, γ, δ, τ) has five values. τ is the “evaluation point” needed to commit to polynomials. The other four — α, β, γ, δ — each block a specific attack.

5.1 α and β: forcing the prover to use the real QAP polynomials

The attack. Without α, β, a cheating prover could pick any polynomials U′, V′, W′, h′ satisfying U′·V′ − W′ = h′·t — for example, all zeros, which trivially satisfies it. The pairing check would pass even though no real witness a was used.

The fix. The setup publishes, for each variable index j, both [uⱼ(τ)]₁ and the linked element [β·uⱼ(τ) + α·vⱼ(τ) + wⱼ(τ)]₁ / δ (or for public j). These are coupled: knowing one doesn’t let you forge the other.

By the Knowledge of Exponent assumption (in the generic group model used in Groth’s proof), the only way the prover can produce a valid pairing-check answer is to honestly take a linear combination of the published uⱼ(τ) values weighted by some witness vector a. The α·β-term in the verifier check effectively certifies that the same a is used for the u, v, and w polynomial families simultaneously — you can’t mix witnesses.

Mental model: α and β are stamps that say “this commitment was assembled from the real circuit polynomials, with a single coherent witness vector a.”

5.2 γ: isolating public from private

The attack. The witness vector a = (a₀, a₁, …, aₗ, aₗ₊₁, …, aₘ) mixes public entries (indices ≤ ℓ) with private ones. If everything were committed under one denominator, a cheater could shift contributions between the two halves: claim publicly that a₁ = 35 while the proof actually used a₁ = 36 internally.

The fix. Setup splits the Lⱼ commitments by denominator:

Public  j:  [Lⱼ]₁ = [(β·uⱼ(τ) + α·vⱼ(τ) + wⱼ(τ)) / γ]₁
Private j:  [Lⱼ]₁ = [(β·uⱼ(τ) + α·vⱼ(τ) + wⱼ(τ)) / δ]₁

The verifier itself assembles the public part from the public witness entries it knows:

[PublicSum]₁ = Σ_{j∈pub} aⱼ · [Lⱼ]₁

This is then paired against [γ]₂, multiplying γ back in and producing the public part in the field.

If the prover tries to pretend a different public value (say uses a₁ = 36 in its own computation while the verifier was told a₁ = 35), the verifier’s PublicSum is built from a₁ = 35 — and the equation no longer balances. Different denominators (γ vs δ) make the public and private commitments live in non-interchangeable slots.

Mental model: γ is a cryptographic firewall between public statement and private witness. They cannot leak across.

5.3 δ: protecting private inputs and absorbing the blinding

δ has two jobs:

Job 1. Same firewall as γ, but for the private side. Private contributions are gated by δ, public by γ. Since γ ≠ δ and both are secret, you cannot move terms between sides.

Job 2. Absorb the blinding randomness. The terms r·δ in A, s·δ in B, and the A·s + B·r − r·s·δ structure of C all rely on δ being the denominator that the pairing e([C]₁, [δ]₂) multiplies back in. Without δ in those exact positions, the cross-terms wouldn’t cancel and the verifier’s check would fail.

Mental model: δ is the shield protecting both the private witness data and the blinding randomness.

5.4 τ: the evaluation point

Not really a “defensive” secret — τ is the point at which all polynomials get evaluated to produce a single field element commitment per polynomial. It must be secret so that nobody can craft polynomials that “happen to” pass the check at τ without genuinely satisfying the QAP everywhere.

6. The full Groth16 protocol on one page

Proof

π = ([A]₁, [B]₂, [C]₁)

Three group elements. ~192 bytes on BN254. Constant size regardless of circuit complexity.

Verification key

VK = ( [α]₁, [β]₂, [γ]₂, [δ]₂, { [Lⱼ(τ)/γ]₁ }_{j=0..ℓ} )

where Lⱼ = β·uⱼ + α·vⱼ + wⱼ (combining the per-variable QAP polynomials). Small — typically under 1 KB. Hardcoded into smart contracts.

Verification equation

ℓ
e([A]₁, [B]₂)  =  e([α]₁, [β]₂)  ·  e( Σ aⱼ · [Lⱼ(τ)/γ]₁ , [γ]₂ )  ·  e([C]₁, [δ]₂)
                                              j=0

Three pairing terms on the right. The verifier evaluates the sum over public witness entries a₀, …, aₗ first, then runs three pairings. If the equation holds in the target group 𝔾_T, accept.

Proving key

For circuit with n constraints and m variables:

{ [τⁱ]₁ }_{i=0..n−1}   and   { [τⁱ]₂ }_{i=0..n−1}
[α]₁, [β]₁, [β]₂, [δ]₁, [δ]₂
{ [uⱼ(τ)]₁ }_{j=0..m},   { [vⱼ(τ)]₁ }_{j=0..m},   { [vⱼ(τ)]₂ }_{j=0..m}
                          (vⱼ in BOTH groups because C uses B·r in 𝔾₁)
{ [Lⱼ(τ)/δ]₁ }_{j=ℓ+1..m}        ← for PRIVATE j
{ [τⁱ · t(τ) / δ]₁ }_{i=0..n−2}  ← for committing to h(X)·t(X)

Linear in circuit size. For a 1M-constraint circuit, the proving key is hundreds of MB.

Setup destroys α, β, γ, δ, τ

Anyone with all five can forge. Hence the multi-party trusted setup ceremony: many participants each contribute randomness, and the parameters are safe as long as at least one participant honestly destroyed their share. The Ethereum KZG ceremony (2023) had over 140,000 participants.

7. Why this matters in practice

System    Proof size    Verify time    Trusted setup    Quantum-safe
───────   ──────────    ───────────    ─────────────    ────────────
Groth16     ~200 B        ~1 ms        Per-circuit          No
PLONK       ~500 B        ~5 ms        Universal            No
Halo2       ~5 KB        ~50 ms        None                 No
STARKs     ~100 KB       ~10 ms        None                 YES

Groth16 wins on raw size and speed — perfect when every byte costs gas — at the cost of a per-circuit ceremony. PLONK trades some efficiency for one universal ceremony. Halo2 and STARKs eliminate trusted setup entirely.

Real numbers:

  • Zcash Sapling: ~100k constraints, 192 B proof, ~10 ms to prove, ~2 ms to verify.
  • TornadoCash: ~28k constraints, ~2 sec to prove on a phone, ~1.5 ms to verify on Ethereum (~380k gas).
  • Scroll zkEVM: ~10M constraints, KB-range proof, minutes to prove, ms to verify.

The constant: verifier cost does not scale with computation.

8. The deep insight, one more time

zkSNARKs work by translating computation across multiple layers:

  1. Code → circuit (gates and wires)
  2. Circuit → constraints (R1CS), with witness vector a
  3. Constraints → polynomial identity (QAP): U·V − W = h·t where U, V, W aggregate the per-variable polynomials uⱼ, vⱼ, wⱼ weighted by a
  4. Polynomial identity → committed group elements (SRS)
  5. Group elements + pairings → succinct verification
  6. Blinding + simulator → zero-knowledge

Each step is lossless in what it preserves about correctness, but each step compresses what’s revealed about the witness. By the time you reach the proof, all that’s left is 192 bytes that any verifier on Earth can check in a millisecond — and the simulator argument certifies that those 192 bytes carry literally zero information about how they were produced.

The math doesn’t ask you to trust it. You can run the example in Section 3 with a pencil and verify every digit.

9. Homework

Before Part 4 (re-implementing TornadoCash), do this:

  1. Pick your own toxic-waste values (α, β, γ, δ, τ) in 𝔽₁₀₁.
  2. Pick your own r, s.
  3. Recompute A, B, C for the same statement (x·(x+2) = 15, witness entry a₂ = 3).
  4. Verify the equation balances.
  5. Now change one bit: try witness entry a₂ = 4. Show that the equation no longer balances when the verifier uses public output a₁ = 15.

Then do it once more with the simulator: pick a_sim, b_sim at random, solve for C_sim, and watch the verifier accept a proof you constructed without ever knowing a₂.

When the numbers cancel under your own pen, Groth16 stops being a black box and becomes engineering.

Coming in Part 4: building TornadoCash from these primitives — how a Merkle commitment and a nullifier turn this machinery into a working privacy mixer.


메타데이터
post_id
f438d35de06d
slug
inside-groth16-how-blinding-simulators-and-four-secret-numbers-make-zksnarks-zero-knowledge-f438d35de06d
url
https://medium.com/@mahdidarabi/inside-groth16-how-blinding-simulators-and-four-secret-numbers-make-zksnarks-zero-knowledge-f438d35de06d
canonical_url
https://medium.com/@mahdidarabi/inside-groth16-how-blinding-simulators-and-four-secret-numbers-make-zksnarks-zero-knowledge-f438d35de06d
author_url
https://medium.com/@mahdidarabi
status
ok
fetched_at
2026-06-13 16:00:06