Pedersen commitments 允许我们用单个椭圆曲线点对任意大小的向量进行编码,同时可以选择隐藏有关该向量的任何信息。
它允许我们在不泄露向量本身的情况下对向量做出声明。
动机
当我们讨论 Bulletproof 零知识证明时,它们通常会采用这样的形式:“我有两个向量,它们的内积是 ”。这可能看起来很基础,但实际上你可以使用这种机制来证明非常不平凡(non-trivial)的声明。我们稍后会讲到这一点。
但为了让这样的证明起作用,这些向量不能仅仅存在于证明者的脑海中——否则证明者可以随意更改它们。它们必须是现实世界中的数学实体。通常,证明者不想直接将这两个向量传递给验证者,但他们仍然需要“传递一些东西”给验证者,以表示他们已经选择了一对向量且无法更改。
这就是 Pedersen commitments 发挥作用的地方。
在内积论证(inner product argument)中,证明者提供两个向量的两个 commitments(承诺),然后提供一个证明,证明承诺的向量具有特定的内积。
前置知识
我们假设读者已经熟悉椭圆曲线点加法和标量乘法,以及一个点“在曲线上”的含义。
在符号方面,大写字母表示椭圆曲线点,小写字母表示有限域元素。
我们用 表示椭圆曲线(EC)点, 表示有限域元素, 是有限域元素 和 EC 点 之间的点乘法。表达式 表示椭圆曲线点加法。
传统的 commitments
当我们在智能合约中设计 commit-reveal 函数时,它们通常具有以下形式:
其中 是一个随机值,用于防止攻击者暴力猜测 。
例如,如果我们正在对一次投票进行 commitment,由于选项数量有限,攻击者可以通过尝试所有投票选项并查看哪个哈希值匹配来猜出投票的选择。
在 Pedersen commitments 中,变量 salt 的学术术语是 blinding factor(致盲因子)。因为它是随机的,攻击者会被“致盲”,从而无法猜出所承诺的值。
因为对手无法猜出这个“commitment”值,所以我们说这种 commitment 方案是 hiding(隐藏的)。
在 reveal 阶段,承诺者揭示该值和 salt,以便另一方(或智能合约)可以验证它与原始 commitment 是否匹配。不可能获得另一个会导致相同 commitment 的 对,因此我们说这种方案是 binding(绑定的)——承诺者在事后不能更改(即被绑定于)其承诺的值。
能够得出该哈希值的 对被称为 opening。说某人“知道 commitment 的 opening”意味着他们知道 (value, salt)。reveal(揭示) 意味着 open(打开)这个 commitment。
在讨论 Pedersen commitments 时,knowing(知道)opening 和 opening(打开)commitment 是有区别的。我们通常想要证明我们 know 这个 opening,但不一定要去 open 它。
术语总结
- hiding(隐藏性)承诺不允许对手知道承诺者选择了什么值。这通常是通过引入一个攻击者无法猜测的随机项来实现的。
- blinding(致盲)项是使承诺无法被猜测的随机数。
- opening(打开/揭示值)是将计算出承诺的值。
- binding(绑定性)承诺不允许承诺者用不同的值计算出同一个哈希。也就是说,他们找不到两个哈希出相同值的 (value, salt) 对。
Pedersen Commitments
Pedersen commitments 的行为与前面描述的 commit-reveal 方案非常相似,只不过它们使用椭圆曲线群而不是加密哈希函数。
在离散对数假设下,给定椭圆曲线点 和 ,我们无法计算出满足 = 的 。也就是说,我们不知道它们的离散对数关系,即不知道 需要与自身相加多少次才能得到 。
即使我们无法计算它,我们仍将 称为 的离散对数,因为我们知道它是存在的。所有(加密的)椭圆曲线点都有一个离散对数,即使它们无法被计算出来。
从这个意义上讲,椭圆曲线点乘法的表现就像一个哈希函数。只要我们仅允许在曲线阶(curve order)内进行 opening,它们就具有绑定性(binding)。
然而,如果离散对数的范围很小,并且受到应用程序上下文(例如投票选项)的限制,那么离散对数可能会变得可猜测。
我们可以通过以下方式使 Pedersen commitment 具备隐藏性(hiding):
其中 是我们要承诺的值, 是 salt(或 blinding factor), 是另一个椭圆曲线点,且承诺者不知道 和 之间的离散对数关系。
我们应该强调,尽管离散对数是未知的,但点 和 是公开的,并且对验证者和承诺者都是已知的。
为什么承诺者不能知道 和 之间的离散对数关系
假设承诺者知道 ,使得 。
在这种情况下,他们可以将 commitment
open 到一个不同于他们最初承诺值的 。
如果承诺者知道 是 的离散对数,以下是他们作弊的方法。
承诺者可以重写 commitment 等式:
承诺者选取一个新值 并计算 :
然后,证明者将 作为伪造的 opening 呈现。
这之所以有效,是因为
因此,承诺者绝对不能知道他们正在使用的椭圆曲线点之间的离散对数关系。
实现这一点的一种方法是让验证者为承诺者提供椭圆曲线点。然而,一种更简单的方法是以随机和透明的方式挑选椭圆曲线点,比如伪随机地选择椭圆曲线点。给定一个随机的椭圆曲线点,我们是无法知道其离散对数的。
例如,我们可以从生成元点开始,对 和 值进行哈希,然后将其作为种子,用于对下一个点进行伪随机但具有确定性的搜索。
为什么 Pedersen Commitments 很有用?
看起来 Pedersen Commitments 只是具有不同哈希函数的普通 commit-reveal,那么它的意义何在呢?
这种方案有几个优点。
Pedersen commitments 具有加法同态性
给定一个点 ,我们可以将两个 commitments 加在一起 = 。
如果我们引入随机的 blinding 项,我们仍然可以通过将 blinding 项加在一起并将其提供给验证者来进行有效的 opening。假设 和 是 commitments。现在考虑当我们把 加在一起时会发生什么:
或者,验证者可以检查
常规哈希(如 SHA-256)不能以这种方式加在一起。
给定两个使用相同椭圆曲线点进行承诺的 Pedersen commitments,我们可以将这些 commitments 加在一起,并仍然能够为它们得出一个有效的 opening。
Pedersen commitments 允许证明者对承诺值的总和做出声明。
我们可以在单个点中编码任意数量的点
我们使用 和 的示例也可以被认为是缺乏 blinding 项的 2D 向量 commitment。但我们可以添加任意数量的椭圆曲线点 并且承诺任意数量的标量。(这里的 、 等表示同一群中的不同点,而不是不同群的生成元)。
Pedersen Vector Commitments
我们可以将上述方案推进一步,对一组值进行承诺,而不是仅对一个值和一个 blinding 项进行承诺。
向量 commitment 方案
假设我们有一组随机的椭圆曲线点 (我们不知道其离散对数),并且我们执行以下操作:
这让我们能够将 个值承诺到 中,并用 将其隐藏。
由于承诺者不知道任何 的离散对数,所以他们也不知道 的离散对数。因此,这种方案是 binding 的:他们稍后只能揭示 来生成 ,而不能生成另一个向量。
向量 commitments 可以被组合
我们可以将两个 Pedersen Vector Commitments 相加,得到针对两个向量的一个 commitment。这仍然只允许承诺者 open 出原始向量。重要的实现细节是,我们必须使用另一组不同的椭圆曲线点来进行承诺。
通过将 和 加在一起,我们在功能上承诺了一个大小为 的更大向量。
在这里, 和 是 blinding 项。即使承诺者承诺了零向量,该 commitment 看起来仍然会是一个随机点。
承诺者稍后将揭示原始向量 和 以及 blinding 项 。这具有绑定性:他们无法揭示另一对不同的向量和 blinding 项。
我们对一个向量使用 而对另一个向量使用 这一事实,并不意味着 点之间存在特殊关系,或者 点之间存在特殊关系。所有的点都需要被伪随机地选择。这仅仅是一种符号上的便利,用于表示“这个椭圆曲线点向量与这个域元素向量搭配使用,而另一个 EC 点向量与另一个域元素向量搭配使用”。
我们可以承诺的向量数量没有实际的上限。
**留给读者的练习:**如果我们在相加之前对两个向量使用相同的 ,承诺者如何能为 open 出两个不同的向量?给出一个例子。使用另一组不同的点 是如何防止这种情况发生的?
**留给读者的练习:**如果承诺者试图交换向量内部的相同元素会发生什么?
例如,他们承诺了:
但是用交换了前两个元素的向量进行 open:
也就是说,他们交换了前两个元素,而保持其他一切不变。假设向量 是未经过置换的。
透明地生成随机点
我们如何生成这些随机的椭圆曲线点?一个明显的解决方案是使用可信设置(trusted setup),但这并非必需。承诺者可以通过以透明的方式随机选择点,来设置这些点,从而无法知道它们的离散对数。
他们可以选择生成元点,混入一个公开选择的随机数,并将该结果进行哈希(然后对有限域的模数取模)以获得另一个值。如果这产生了一个落在椭圆曲线上的 值,就使用它作为下一个生成元,并再次对 对进行哈希。否则,如果 值没有落在曲线上,就递增 直到它落在曲线上为止。由于承诺者并非直接生成这些点,因此他们不知道这些点的离散对数。
**练习:**调整以下代码以生成 n 个具有未知离散对数的点:
from py_ecc.bn128 import is_on_curve, FQ
from py_ecc.fields import field_properties
field_mod = field_properties["bn128"]["field_modulus"]
from hashlib import sha256
from libnum import has_sqrtmod_prime_power, sqrtmod_prime_power
b = 3 # for bn128, y^2 = x^3 + 3
seed = "RareSkills"
x = int(sha256(seed.encode('ascii')).hexdigest(), 16) % field_mod
entropy = 0
vector_basis = []
# modify the code below to generate n points
while not has_sqrtmod_prime_power((x**3 + b) % field_mod, field_mod, 1):
# increment x, so hopefully we are on the curve
x = (x + 1) % field_mod
entropy = entropy + 1
# pick the upper or lower point depending on if entropy is even or odd
y = list(sqrtmod_prime_power((x**3 + b) % field_mod, field_mod, 1))[entropy & 1 == 0]
point = (FQ(x), FQ(y))
assert is_on_curve(point, b), "sanity check"
vector_basis.append(point)
# new x value
x = int(sha256(str(x).encode('ascii')).hexdigest(), 16) % field_mod
print(vector_basis)
在任何时候,你都不应该通过选取一个标量并将其与生成元相乘来生成一个点,因为这会导致离散对数变得已知。你需要通过哈希函数伪随机地选择曲线点的 值,并计算它是否在曲线上。
从生成元(其已知离散对数为 1)开始并生成其他点是没问题的。
**留给读者的练习:**假设我们将一个值向量承诺到点 和 上。 的离散对数是已知的,但 的离散对数是未知的。我们暂时忽略 blinding 项。承诺者可以 open 到两个不同的向量吗?为什么能或为什么不能?
进一步了解 RareSkills
如果你想学习零知识证明,请查看我们的 ZK bootcamp。