Ex1: Prove Knowledge of Preimage

What is a Preimage?

ZKP Four Elements

ElementIn This Exercise
Witnesssecret — the private value only the prover knows
Public InputC = g^secret mod p — visible to the verifier
Relationg^w mod p == C — knowledge of discrete log
What Verifier Checksproof corresponds to C, without learning secret

Concepts Learned

1. Pedersen Commitment

commit(v, r) = g^v * h^r mod p

2. Sigma Protocol (Interactive)

Three-move protocol, two roles:

Prover                          Verifier
  |                                |
  |  1. R = g^k mod p              |
  |  ------- R -------->           |
  |                                |
  |  2.       <---- e --------     |  (random challenge)
  |                                |
  |  3. z = k + e * secret         |
  |  ------- z -------->           |
  |                                |
  |      check: g^z == R * C^e     |

Why it works (expand the verification equation):

g^z = g^(k + e * secret)
    = g^k * g^(e * secret)
    = g^k * (g^secret)^e
    = R * C^e  ✓

Why it’s secure:

3. Interactive vs Non-Interactive Challenge

Interactive (Sigma Protocol)

Verifier picks a random e and sends it to the prover.

ProsCons
Simple to reason about securityRequires real-time communication between prover and verifier
Verifier controls randomness — prover cannot manipulate eCannot produce a proof offline or share it with multiple verifiers
Well-studied, strong theoretical foundationsEach verification requires a new interaction

Non-Interactive (Fiat-Shamir Transform)

Replace verifier’s random challenge with a hash:

e = hash(g, C, R) mod p
ProsCons
Proof can be generated offline and verified by anyoneSecurity relies on hash being a “random oracle” — a modeling assumption
No back-and-forth needed — single message from proverVulnerable if hash function is weak or inputs are not properly domain-separated
Proof is portable — can be posted on-chain, shared, storedSlightly more complex to implement correctly (need careful hash input construction)

4. Hash Concatenation Detail

hasher.update(g_bytes);
hasher.update(c_bytes);
hasher.update(r_bytes);

Equivalent to hash(g_bytes || c_bytes || r_bytes), since SHA256 is a streaming compression function.

Caveat: raw byte concatenation has no boundary info — different-length inputs can collide:

g=[01,02] c=[03]     → [01,02,03]
g=[01]    c=[02,03]  → [01,02,03]  ← same hash!

Proper fix: add a length prefix before each segment. Not an issue in our exercise since p is fixed and byte lengths are nearly identical.

Rust Lessons