Last time, we saw a specific zero-knowledge proof for graph isomorphism. This introduced us to the concept of an interactive proof, where you have a prover and a verifier sending messages back and forth, and the proveris trying to prove a specificclaim to the verifier.
A zero-knowledge proof is a special kind of interactive proof in whichthe prover has some secret piece of knowledgethat makes it very easy toverify a disputed claim is true. The prover’s goal, then, is to convince the verifier (a polynomial-time algorithm) that the claim is true without revealing any knowledgeat all about the secret.
In this post we’ll see that, using a bit of cryptography, zero-knowledge proofs capture a much wider class of problems than graph isomorphism. Basically, if you believe that cryptography exists, every problem whose answers can be easily verified have zero-knowledge proofs (i.e., all of the class NP).Here are a bunch of examples. For each I’ll phrase the problem as a question, and then say what sort of data the prover’s secret could be.
Given a boolean formula, is there an assignment of variables making it true? Secret: a satisfying assignment to the variables. Given a set of integers, is there a subset whose sum is zero? Secret: such a subset. Given a graph, does it have a 3-coloring? Secret: a valid 3-coloring. Given a boolean circuit, canit produce a specific output? Secret: a choice of inputs that produces the output.The common link among all of these problems is that they are NP-hard (graph isomorphism isn’t known to be NP-hard).For us thismeans two things: (1) we think these problems are actually hard, so the verifier can’tsolve them, and (2)if you show that one of them has a zero-knowledge proof, then they all have zero-knowledge proofs.
We’re going to describe and implementa zero-knowledge proof for graph 3-colorability, and in the next postwe’lldive into the theoretical definitions and talk about the proof that the scheme we present iszero-knowledge. As usual, all of the code used in making this post is available in a repository on this blog’s Github page .
One-way permutations In a recentprogram gallery post we introduced the Blum-Blum-Shub pseudorandom generator . A pseudorandom generator is simply an algorithm that takes as input a short random string of length and produces as output a longer string, say, of length
. This output stringshouldnot be random, but rather“indistinguishable” from random in a sense we’ll make clear next time.The underlying function for this generator is the “modular squaring” function

, for some cleverly chosen

. The

is chosen in such a way that makes this mapping a permutation.So this function is more than just a pseudorandom generator, it’s a one-way permutation .
If you have a primality-checking algorithm on hand (we do), then preparing the Blum-Blum-Shub algorithm is onlyabout 15 lines of code.
def goodPrime(p): return p % 4 == 3 and probablyPrime(p, accuracy=100) def findGoodPrime(numBits=512): candidate = 1 while not goodPrime(candidate): candidate = random.getrandbits(numBits) return candidate def makeModulus(numBits=512): return findGoodPrime(numBits) * findGoodPrime(numBits) def blum_blum_shub(modulusLength=512): modulus = makeModulus(numBits=modulusLength) def f(inputInt): return pow(inputInt, 2, modulus) return fThe interested reader should check out theproof gallery postfor more details about this generator. For us, having a one-way permutation is the important part (and we’re going to defer the formal definition of “one-way” until next time, just think “hard to get inputs from outputs”).
The other concept we need, which is related to a one-way permutation, is the notion of a hardcore predicate. Let
beaone-way permutation, and let

bea function that produces a single bit from a string. We say that

is a hardcore predicate for

if you can’t reliably compute

when given only

. Hardcore predicatesareimportant because there are many one-way functions for which, when given the output, you can guess part of the input very reliably, but not the rest (e.g., if is a one-way function,

is also one-way, but the part is trivially guessable). So a hardcore predicate formally measures, when given the output of a one-way function, what information derived from the inputishard to compute.
In the case of Blum-Blum-Shub, onehardcore predicate is simply the parity of the input bits.
def parity(n): return sum(int(x) for x in bin(n)[2:]) % 2 Bit Commitment SchemesAcore idea that will makes zero-knowledge proofs work for NP is the ability for the proverto publicly “commit” to a choice, and later reveal that choice in a way that makes it infeasible to fake their commitment. This will involve not just thecommitment to a single bit of information, but also the transmission of auxiliary data that is provably infeasible to fake.
Our pair of one-way permutation
and hardcore predicate

comes in very handy. Let’s say Iwant to commit to a bit

. Let’s fix a security parameter that will measure how hard it is to change my commitment post-hoc, say

. My process for committing is to draw a random string of length , and send you the pair

, where

is the XOR operator on two bits. The guarantee of a one-way permutation with a hardcore predicate is that if you only see

, you can’t guess

with any reasonable edge overrandomguessing. Moreover, ifyou fix a bit , and take an unpredictably randombit , the XOR

isalso unpredictably random. In other words, if

is hardcore, then so is

for afixed bit . Finally, to reveal mycommitment, I just send the string and let youindependently compute

. Since

is a permutation, that is the only that could have produced the commitment I sent you earlier.
Here’s a python implementation of this scheme. We start with a generic base class for a commitment scheme.
class CommitmentScheme(object): def __init__(self, oneWayPermutation, hardcorePredicate, securityParameter): ''' oneWayPermutation: int -> int hardcorePredicate: int -> {0, 1} ''' self.oneWayPermutation =