Zero-Knowledge Proofs for Encrypted Data: Proving What’s Inside a Ciphertext

Homomorphic encryption has a dark side, and it’s the same as its bright side: nobody can see what’s inside a ciphertext. That’s wonderful when a survey adds up encrypted salaries without seeing them. It’s less wonderful when someone submits an encrypted salary of a billion dollars to skew the average, an encrypted rating of 1,000 stars, or a ballot that counts as a hundred votes. The server can’t tell, because the server can’t look. Homomorphic encryption hides honest values and dishonest values equally well.

Flat lay shot of ingredients for making a banana raisin cake, showcasing vibrant colors by pexels

The fix is a zero-knowledge proof: the encryptor attaches a proof that the ciphertext satisfies some rule—it’s one of the allowed values, it’s within a range, the encryptor knows what’s inside—and anyone can verify the proof without learning the value itself. Think of a “Where’s Waldo?” page: you can prove you found Waldo by covering the page with a large sheet that has a small hole right over him. Your friend sees Waldo, but learns nothing about where on the page he is.


🙋‍♂️ You may consider to enroll my top-rated cryptography course on Udemy

Public Key Cryptography From Scratch

This is the first post of a three-part series on zero-knowledge proofs for homomorphic encryption, with LightPHE in Python, Go, Java and TypeScript. Here, we prove what’s inside a ciphertext. Next, we’ll prove that a decryption is honest. Finally, we’ll look at verifiable encryption.

What Makes a Proof Zero-Knowledge

A zero-knowledge proof has three properties:

  • Completeness: An honest prover always convinces the verifier.
  • Soundness: A cheating prover cannot convince the verifier of a false statement, except with a negligible probability.
  • Zero-knowledge: The verifier learns nothing beyond the fact that the statement is true.

The proofs in LightPHE are built the same way. The prover commits to some fresh randomness, receives a random challenge, and answers with a response that combines the challenge, the randomness and its secret. An honest prover can answer any challenge, while a cheater could only answer a challenge it guessed in advance. To avoid a back-and-forth conversation, the challenge is computed as a hash of everything the verifier sees, as proposed by Fiat and Shamir in How to Prove Yourself (CRYPTO 1986). That turns the proof into a single message that anybody can verify, any time.

That hash also includes the ciphertext and a context, typically the identity of the prover, such as a voter ID. So a proof is welded to its ciphertext and its owner: copy it to another ciphertext, or submit it under another name, and it no longer verifies.

Proof of Knowledge: I Know What I Encrypted

With homomorphic encryption, anyone can take somebody else’s ciphertext and transform it without knowing what’s inside: add 1, multiply by 2, or re-randomize it so it looks brand new. That’s the whole point of homomorphic encryption, but in a sealed-bid auction, it’s a disaster. Mallory sees Alice’s encrypted bid, adds 1 to it, and submits it as her own. She outbids Alice by the smallest possible margin without ever knowing the amount.

A proof of knowledge shows that the encryptor knows the secret randomness behind the ciphertext, and with it what’s inside. Mallory doesn’t know either for her modified ciphertext, so she cannot produce such a proof, and Alice’s proof doesn’t fit the modified ciphertext.

Both of Mallory’s tricks fail: the +1 version and the re-randomized copy. To win, she has to bid with her own money, like everybody else. A proof of knowledge is cheap: about 1 KB and 30 milliseconds with a 1024-bit Paillier key on my laptop. Java snippets use assert for brevity, so run them with -ea to enable assertions.

Proof of Allowed Values: It’s One of These

Sometimes we need more than “I know what’s inside”. A product rating must be 1 to 5 stars, a ballot must be 0 or 1, and a vote for a party must be one of the encoded parties. A proof of allowed values shows that a ciphertext encrypts one of a list of values, without revealing which one.

Under the hood, it is an OR-proof, as introduced by Cramer, Damgård and Schoenmakers in Proofs of Partial Knowledge and Simplified Design of Witness Hiding Protocols (CRYPTO 1994). The prover proves “the ciphertext encrypts 1, or 2, or 3, or 4, or 5”. For the true value, it runs an honest proof. For every other value, it simulates a proof, which it can do because it is allowed to choose those challenges itself, as long as all challenges add up to the one fixed by the hash. The verifier sees five proofs that all look exactly alike, and cannot tell which one was real.

The troll’s trick was to pair a 1,000-star ciphertext with a perfectly valid proof of a 5-star rating, and it failed: a proof is bound to the ciphertext it was made for. Note that the troll can’t even create a proof for 1,000 stars, as LightPHE refuses with Plaintext must be one of the allowed values. This example uses Elliptic Curve ElGamal, whose short keys suit mobile apps, and the same proofs power the ballots of our verifiable referendum.

Proof of Range: It’s Between Low and High

Listing every allowed value works for 5 stars, but not for a salary between 0 and 500,000. For that, we need a range proof: the ciphertext encrypts a value in [low, high]. LightPHE builds it from the proofs we already have. It subtracts low, and decomposes what’s left into bits with carefully chosen weights, so that every value of the range, and nothing outside it, can be written as a weighted sum of bits. Each bit is encrypted with a proof that it is 0 or 1. For a range of width 190, for instance:

weights for width 190:   1, 2, 4, 8, 16, 32, 64, 63         (the last one is 190 - 127)

largest sum:             1 + 2 + 4 + 8 + 16 + 32 + 64 + 63 = 190, so nothing above the range
127 is written as:       64 + 63, so bits are 0, 0, 0, 0, 0, 0, 1, 1

ciphertext = E(low) + 1 * E(b_1) + 2 * E(b_2) + ... + 63 * E(b_8)

The last line is the key: the verifier rebuilds the ciphertext from the encrypted bits itself, with homomorphic addition and scalar multiplication. So the proof is about exactly the ciphertext that was submitted, and every bit is 0 or 1, so the value cannot fall outside the range. The price is size: the proof grows with the number of bits of the range.

Oscar’s billion and Trent’s negative salary are both rejected, so the average of 78,500 is computed from honest submissions only. For [0, 500000], a proof consists of 20 parts, the encrypted low and 19 encrypted bits, and takes about 38 KB, 0.8 seconds to create and 0.6 seconds to verify with a 1024-bit Paillier key on my laptop. Narrow your ranges: a range of [0, 255] needs only 8 bits.





Required Cryptographic Features

1. Randomized encryption with provable structure. Every proof above is ultimately about the randomness used in encryption: the prover shows it knows the randomness, or that the ciphertext minus a candidate value is an encryption of zero. That requires a group where such statements can be proven: a group of known prime order for the ElGamal family, which is why ElGamal and Exponential ElGamal keys must be generated with prime_order = True, and the structure of n-th powers for Paillier and Damgard-Jurik.

2. Additive homomorphism. Proofs of allowed values shift the ciphertext by each candidate value, and range proofs rebuild the ciphertext from its encrypted bits. Both need homomorphic addition and scalar multiplication.

3. A hash for Fiat-Shamir, binding every proof to its ciphertext, its statement and its context.

Which Algorithms Can Be Used, and Why

AlgorithmProof of knowledgeProof of allowed valuesProof of rangeNotes
Exponential ElGamal✅✅✅Keys need prime_order = True
Elliptic Curve ElGamal✅✅✅Works with default keys, and suits mobile apps with its short keys
Paillier, Damgard-Jurik✅✅✅Large plaintext space, e.g. for salaries
ElGamal✅❌❌Multiplicatively homomorphic, so it cannot shift or rebuild ciphertexts by addition. LightPHE suggests Use Exponential-ElGamal instead
RSA, Okamoto-Uchiyama and others❌❌❌LightPHE refuses to create these proofs for them. RSA encryption isn’t even randomized, so there is no randomness to prove anything about

More Scenarios

  • E-voting: Each voter proves that the ballot is 0 or 1, or a vote for exactly one party, so nobody can cast a vote of 100 in the homomorphic tally without revealing how they voted.
  • Age verification: A website requires visitors to be adults, and a visitor proves that the encrypted age is between 18 and 120 without revealing the exact age or birth date.
  • Credit checks: A lender accepts an encrypted credit score with a proof that it is between 650 and 850, so the applicant shows eligibility without revealing the exact score.
  • Participatory budgeting: Each citizen allocates up to 1,000 to public projects in encrypted form, so allocations are proven to be within the cap while individual preferences stay private.
  • Crowdfunding: A backer cannot copy a large encrypted pledge of another backer and scale it to look generous, because each pledge must come with a proof that its owner knows the amount.

Caveats

  • Valid is not the same as true: A range proof shows that a salary is between 0 and 500,000, not that it is the employee’s real salary. Proofs keep the data well-formed; they can’t keep people honest about facts the system cannot check.
  • The context must be authenticated: A proof bound to “alice” stops others from reusing Alice’s submission, but only if the system checks that a submission for “alice” really comes from Alice.
  • Prove at encryption time: The proofs need the randomness used in encryption, so they are created together with the ciphertext. You can’t prove something about an old ciphertext afterwards.
  • Range proofs have a size: About 2 KB per bit of the range with a 1024-bit Paillier key. Keep ranges as narrow as your data allows.

Conclusion

Homomorphic encryption hides everything, including cheating, and zero-knowledge proofs bring the accountability back without giving up the privacy. A proof of knowledge stops people from tampering with someone else’s ciphertext, a proof of allowed values keeps ratings and ballots within their options, and a range proof keeps numbers within sensible bounds. All of them are made non-interactive with the Fiat-Shamir trick, bound to their ciphertext and their owner, and verifiable by anyone with the public key.

So far, we’ve kept the encryptors honest. In the next post of this series, we’ll turn to the other side of the table and make sure that whoever decrypts the result can’t lie about it either.


Support this blog financially if you do like!

Buy me a coffee      Buy me a coffee


Leave a Reply