# 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)` |

**Analysis:**Let’s suppose for a moment that everyone is honestly following the rules, and that are truly isomorphic. Then you’ll

*always*

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` ` ` `...` `&lt;SNIP&gt;` ` ` `...` `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!

## Δεν υπάρχουν σχόλια:

## Δημοσίευση σχολίου