A Hands-On Tutorial for Zero-Knowledge Proofs: Part I

Zero Knowledge Proofs

Zero Knowledge (AKA ZK) proofs are stories of the following type: side A states a claim and proves it to side B, after some deliberation between them, such that:

  1. B is sure that the claim is true with 99.99999% certainty.
  2. B has learnt nothing new in the process, other than that the claim is true.

This Wikipedia article contains an excellent explanation of the idea, with some concrete examples.

In this series I’ll deal with ZK arguments of knowledge, which are not exactly the same as proofs, but they’re close enough. In short: a ZK proof can be trusted completely, even if the side who’s trying to prove their claim (usually referred to as “the prover”) has unlimited computational power. ZK argument-of-knowledge can be trusted under the assumption that if the prover indeed tries to cheat, it is polynomialy bounded (if you use credit cards on the internet, then you already assume that, btw).

In the world of ZK proofs, the other side of the exchange is often called “the verifier”. I’ll stick to this terminology here.

The Partition Problem

Given a sequence of numbers a0,a1,…,a**n−1a0,a1,…,an−1, can one partition this sequence into two subsets that sum up to the same number?

If the sequence in question is 1,9,8,0,2,21,9,8,0,2,2, then the answer is clearly yes since 2+9=8+1+2+02+9=8+1+2+0.

However if the sequence is 2,3,4,5,6,72,3,4,5,6,7, then the answer is clearly no, since the sum is odd, and therefore there cannot be two subsets summing exactly to half of it each (the numbers are all integers).
While these are simple enough instances, in general this problem is NP-complete (though it has a pseudo-polynomial algorithm).

在这里插入图片描述

Let’s Start Proving!

Suppose we have a python list ll of numbers, that defines our Partition Problem instance. We’ll say that another list mm is a satisfying assignment if:

  1. len(m) == len(l).
  2. All of the elements in mm are either 11 or −1−1.
  3. The dot-product of ll and mm is 0.

Note that this is equivalent to the statement of the partition problem, if we think of a ‘1’ in mm as assigning its corresponding number in ll to the left side of the equation, and ‘-1’ as assigning it to the right side.

Given ll, a proof for its satisfiability can be given by revealing mm, but that would violate the ZK requirement.

Let’s rewrite ll as the partial sum list of its dot product with mm.

Mathematically speaking, let p**i:=∑0≤k<i**l[k]⋅m[k]pi:=∑0≤k<il[k]⋅m[k].

So if l=[4,11,8,1]l=[4,11,8,1], and m=[1,−1,1,−1]m=[1,−1,1,−1], then pp will be one element longer: p=[0,4,−7,1,0]p=[0,4,−7,1,0].

Note that pp now has two interesting properties, if mm is indeed a satisfying assignment:

  • (property 1 of p) It starts and ends with 0.
  • (property 2 of p) For every 0≤i<n0≤i<n, we have |l[i]|=|p[i+1]−p[i]||l[i]|=|p[i+1]−p[i]|.

So here’s a first draft for a ZK protocol:

The verifier chooses a random 0≤in0≤i≤n.

If i=ni=n, the verifier asks the prover to provide p[0]p[0] and p[n]p[n] and checks that they are both 0.

Otherwise, the verifier asks the prover to provide p[i]p[i] and p[i+1]p[i+1] and checks that indeed |l[i]|=|p[i+1]−p[i]||l[i]|=|p[i+1]−p[i]| (recall that ll is known to the verifier, as part of the claim made by the prover).

What if the prover is lying???

The above contains an implicit assumption that when the verifier asks the prover to provide some data, the prover will indeed provide it honestly. We don’t want to assume that, but we postpone dealing with this issue to the next post. For now, let’s assume everything is kosher.

This doesn’t prove anything!

An observant reader will probably point out that asking about a single element doesn’t mean much. And that’s true, we’d like to ask many queries, and after enough of them - we’ll be certain that the claim is true. We’ll quantify this more accurately in the third (and last) post.

This is not Zero-Knowledge!

Each query reveals something about mm, and so it is not zero-knowledge. Consequently, after enough queries - mm can be completely revealed.

That’s terrible! Let’s fix it.

Manufacturing Zero-Knowledge

Mathematically speaking, we usually say that something provides no new information, if it appears random, or more precisely - if it is uniformly distributed over some appropriately chosen domain. Without getting into the exact definition, this means that to make something ZK, we mix it with randomness. So here’s how we do it here.

  1. Instead of mm as it was given to us, we flip a coin. If it shows heads, we leave mm as it is, if it shows tails, we multiply all of mm’s elements by −1−1. Note that since its elmenets were initially −1−1 and 11, and its dot product with ll was 0, this does not change its dot product with ll at all.
  2. We choose a random number rr and add it to all the elements of pp. This does not effect the second property of pp, but it changes the first property such that the first and last elements of pp now may not be zero. However, they must still be identical to one another.

Now suppose that before each query - we recompute this randomness (i.e. - flip the coin and change mm, and choose a random number rr and add it to the elements of pp).

If we choose rr carefully, then indeed, every two consecutive elements of pp will differ (in absolute value) by the corresponding element in ll but look otherwise random.

So, here’s the first piece of code we’ll need, something that takes a problem (i.e. ll) and a satisfying assignment (i.e. mm) and constructs a witness (i.e. pp) that will attest to the satisfiability of the problem instance:

import random

def get_witness(problem, assignment):
    """
    Given an instance of a partition problem via a list of numbers (the problem) and a list of
    (-1, 1), we say that the assignment satisfies the problem if their dot product is 0.
    """
    sum = 0
    mx = 0    
    side_obfuscator = 1 - 2 * random.randint(0, 1)
    witness = [sum]
    assert len(problem) == len(assignment)
    for num, side in zip(problem, assignment):
        assert side == 1 or side == -1
        sum += side * num * side_obfuscator
        witness += [sum]
        mx = max(mx, num)
    # make sure that it is a satisfying assignment
    assert sum == 0
    shift = random.randint(0, mx)
    witness = [x + shift for x in witness]
    return witness

This post didn’t have nearly enough images as a blog post should have. Her’e one to make up for it:

Part II

Part III


原文:https://www.shirpeled.com/2018/09/a-hands-on-tutorial-for-zero-knowledge.html

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值