Zero Knowledge Proofs — A Primer
In this post we’ll get a strong taste for zero knowledge proofs by
exploring the graph isomorphism problem in detail. In the next post,
we’ll see how this relates to cryptography and the bigger picture. The
goal of this post is to get a strong understanding of the terms
“prover,” “verifier,” and “simulator,” and “zero knowledge” in the
context of a specific zero-knowledge proof. Then next time we’ll see how the same concepts (though not the same proof) generalizes to a cryptographically interesting setting.
Graph isomorphism
Let’s start with an extended example. We are given two graphs , and we’d like to know whether they’re isomorphic, meaning they’re the same graph, but “drawn” different ways.The problem of telling if two graphs are isomorphic seems hard. The
pictures above, which are all different drawings of the same graph (or
are they?), should give you pause if you thought it was easy.
To add a tiny bit of formalism, a graph is a list of edges, and each edge is a pair of integers between 1 and the total number of vertices of the graph, say . Using this representation, an isomorphism between and is a permutation of the numbers with the property that is an edge in if and only if is an edge of . You swap around the labels on the vertices, and that’s how you get from one graph to another isomorphic one.
Given two arbitrary graphs as input on a large number of vertices , nobody knows of an efficient—i.e., polynomial time in —algorithm
that can always decide whether the input graphs are isomorphic. Even if
you promise me that the inputs are isomorphic, nobody knows of an
algorithm that could construct an isomorphism. (If you think about it,
such an algorithm could be used to solve the decision problem!)
A game
Now let’s play a game. In this game, we’re given two enormous graphson a billion nodes. I claim they’re isomorphic, and I want to prove it
to you. However, my life’s fortune is locked behind these particular
graphs (somehow), and if you actually had an isomorphism between these
two graphs you could use it to steal all my money. But I still want to
convince you that I do, in fact, own all of this money, because
we’re about to start a business and you need to know I’m not broke.
Is there a way for me to convince you beyond a reasonable doubt that
these two graphs are indeed isomorphic? And moreover, could I do so
without you gaining access to my secret isomorphism? It would be even
better if I could guarantee you learn nothing about my isomorphism or any isomorphism, because even the slightest chance that you can steal my money is out of the question.
Zero knowledge proofs have exactly those properties, and here’s a zero knowledge proof for graph isomorphism. For the record, and
are public knowledge, (common inputs to our protocol for the sake of
tracking runtime), and the protocol itself is common knowledge. However,
I have an isomorphism that you don’t know.
Step 1: I will start by picking one of my two graphs, say , mixing up the vertices, and sending you the resulting graph. In other words, I send you a graph which is chosen uniformly at random from all isomorphic copies of . I will save the permutation that I used to generate for later use.
Step 2: You receive a graph which you save for later, and then you randomly pick an integer which is either 1 or 2, with equal probability on each. The number corresponds to your challenge for me to prove is isomorphic to or . You send me back , with the expectation that I will provide you with an isomorphism between and .
Step 3: Indeed, I faithfully provide you such an isomorphism. If I you send me , I’ll give you back , and otherwise I’ll give you back .
Because composing a fixed permutation with a uniformly random
permutation is again a uniformly random permutation, in either case I’m
sending you a uniformly random permutation.
Step 4: You receive a permutation , and you can use it to verify that is isomorphic to . If the permutation I sent you doesn’t work, you’ll reject my claim, and if it does, you’ll accept my claim.
Before we analyze, here’s some Python code that implements the above scheme. You can find the full, working example in a repository on this blog’s Github page.
First, a few helper functions for generating random permutations (and
turning their list-of-zero-based-indices form into a
function-of-positive-integers form)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import random def randomPermutation(n): L = list ( range (n)) random.shuffle(L) return L def makePermutationFunction(L): return lambda i: L[i - 1 ] + 1 def makeInversePermutationFunction(L): return lambda i: 1 + L.index(i - 1 ) def applyIsomorphism(G, f): return [(f(i), f(j)) for (i, j) in G] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | class Prover( object ): def __init__( self , G1, G2, isomorphism): ''' isomomorphism is a list of integers representing an isomoprhism from G1 to G2. ''' self .G1 = G1 self .G2 = G2 self .n = numVertices(G1) assert self .n = = numVertices(G2) self .isomorphism = isomorphism self .state = None def sendIsomorphicCopy( self ): isomorphism = randomPermutation( self .n) pi = makePermutationFunction(isomorphism) H = applyIsomorphism( self .G1, pi) self .state = isomorphism return H def proveIsomorphicTo( self , graphChoice): randomIsomorphism = self .state piInverse = makeInversePermutationFunction(randomIsomorphism) if graphChoice = = 1 : return piInverse else : f = makePermutationFunction( self .isomorphism) return lambda i: f(piInverse(i)) |
And here’s the corresponding class for the verifier
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Verifier( object ): def __init__( self , G1, G2): self .G1 = G1 self .G2 = G2 self .n = numVertices(G1) assert self .n = = numVertices(G2) def chooseGraph( self , H): choice = random.choice([ 1 , 2 ]) self .state = H, choice return choice def accepts( self , isomorphism): ''' Return True if and only if the given isomorphism is a valid isomorphism between the randomly chosen graph in the first step, and the H presented by the Prover. ''' H, choice = self .state graphToCheck = [ self .G1, self .G2][choice - 1 ] f = isomorphism isValidIsomorphism = (graphToCheck = = applyIsomorphism(H, f)) return isValidIsomorphism |
1 2 3 4 5 6 7 8 9 | def runProtocol(G1, G2, isomorphism): p = Prover(G1, G2, isomorphism) v = Verifier(G1, G2) H = p.sendIsomorphicCopy() choice = v.chooseGraph(H) witnessIsomorphism = p.proveIsomorphicTo(choice) return v.accepts(witnessIsomorphism) |
accept my claim, because I can always provide you with an isomorphism.
Now let’s suppose that, actually I’m lying, the two graphs aren’t
isomorphic, and I’m trying to fool you into thinking they are. What’s
the probability that you’ll rightfully reject my claim?
Well, regardless of what I do, I’m sending you a graph and you get to make a random choice of that I can’t control. If is only actually isomorphic to either or
but not both, then so long as you make your choice uniformly at random,
half of the time I won’t be able to produce a valid isomorphism and
you’ll reject. And unless you can actually tell which graph is isomorphic to—an open problem, but let’s say you can’t—then probability 1/2 is the best you can do.
Maybe the probability 1/2 is a bit unsatisfying, but remember that we
can amplify this probability by repeating the protocol over and over
again. So if you want to be sure I didn’t cheat and get lucky to within a
probability of one-in-one-trillion, you only need to repeat the
protocol 30 times. To be surer than the chance of picking a specific
atom at random from all atoms in the universe, only about 400 times.
If you want to feel small, think of the number of atoms in the universe. If you want to feel big, think of its logarithm.
Here’s the code that repeats the protocol for assurance.
1 2 3 4 5 6 7 8 | def convinceBeyondDoubt(G1, G2, isomorphism, errorTolerance = 1e - 20 ): probabilityFooled = 1 while probabilityFooled > errorTolerance: result = runProtocol(G1, G2, isomorphism) assert result probabilityFooled * = 0.5 print (probabilityFooled) |
1 2 3 4 5 6 7 8 9 10 11 | $ python graph-isomorphism.py 0.5 0.25 0.125 0.0625 0.03125 ... <SNIP> ... 1.3552527156068805e-20 6.776263578034403e-21 |
But how can we be sure that there’s no leakage of knowledge in the
protocol? What does “leakage” even mean? That’s where this topic is the
most difficult to nail down rigorously, in part because there are at
least three a priori different definitions! The idea we want to
capture is that anything that you can efficiently compute after the
protocol finishes (i.e., you have the content of the messages sent to
you by the prover) you could have computed efficiently given only the two graphs , and the claim that they are isomorphic.
Another way to say it is that you may go through the verification
process and feel happy and confident that the two graphs are isomorphic.
But because it’s a zero-knowledge proof, you can’t do anything
with that information more than you could have done if you just
took the assertion on blind faith. I’m confident there’s a joke about
religion lurking here somewhere, but I’ll just trust it’s funny and move
on.
In the next post we’ll expand on this “leakage” notion, but before we
get there it should be clear that the graph isomorphism protocol will
have the strongest possible “no-leakage” property we can come up with.
Indeed, in the first round the prover sends a uniform random isomorphic
copy of
to the verifier, but the verifier can compute such an
isomorphism already without the help of the prover. The verifier can’t
necessarily find the isomorphism that the prover used in retrospect, because the verifier can’t solve graph isomorphism. Instead, the point is that the probability space of “ paired with an made by the prover” and the probability space of “ paired with as made by the verifier” are equal. No information was leaked by the prover.
For the second round, again the permutation used by the prover to generate is
uniformly random. Since composing a fixed permutation with a uniform
random permutation also results in a uniform random permutation, the
second message sent by the prover is uniformly random, and so again the
verifier could have constructed a similarly random permutation alone.
Let’s make this explicit with a small program. We have the honest
protocol from before, but now I’m returning the set of messages sent by
the prover, which the verifier can use for additional computation.
1 2 3 4 5 6 7 8 9 | def messagesFromProtocol(G1, G2, isomorphism): p = Prover(G1, G2, isomorphism) v = Verifier(G1, G2) H = p.sendIsomorphicCopy() choice = v.chooseGraph(H) witnessIsomorphism = p.proveIsomorphicTo(choice) return [H, choice, witnessIsomorphism] |
colloquial) is to say that anything that the verifier could compute,
given as input the return value of this function along with and the claim that they’re isomorphic, the verifier could also compute given only and the claim that are isomorphic.
It’s easy to prove this, and we’ll do so with a python function called
simulateProtocol.
1 2 3 4 5 6 7 8 9 10 11 12 | def simulateProtocol(G1, G2): # Construct data drawn from the same distribution as what is # returned by messagesFromProtocol choice = random.choice([ 1 , 2 ]) G = [G1, G2][choice - 1 ] n = numVertices(G) isomorphism = randomPermutation(n) pi = makePermutationFunction(isomorphism) H = applyIsomorphism(G, pi) return H, choice, pi |
messagesFromProtocol
and simulateProtocol
are equal. But simulateProtocol
will work regardless of whether are isomorphic. Of course, it’s not convincing to the verifier because
the simulating function made the choices in the wrong order, choosing
the graph index before making . But the distribution that results is the same either way.
So if you were to use the actual Prover/Verifier protocol outputs as
input to another algorithm (say, one which tries to
compute an isomorphism of ), you might as well use the output of your simulator instead. You’d have no information beyond hard-coding the assumption that are isomorphic into your program. Which, as I mentioned earlier, is no help at all.
In this post we covered one detailed example of a zero-knowledge proof. Next time
we’ll broaden our view and see the more general power of zero-knowledge
(that it captures all of NP), and see some specific cryptographic
applications. Keep in mind the preceding discussion, because we’re going
to re-use the terms “prover,” “verifier,” and “simulator” to mean
roughly the same things as the classes
Prover, Verifier
and the function simulateProtocol
.Until then!
Δεν υπάρχουν σχόλια:
Δημοσίευση σχολίου