STARKs 系列:

干货 | STARKs, Part I: 多项式证明
干货 | STARKs, Part II
干货 | STARKs,Part-3:攻坚(上)


感谢上帝,今天是 FRI 日(即“快速里德-所罗门码接近性交互预言证明(Fast Reed-Solomon Interactive Oracle Proofs of Proximity)”)

提醒:现在可能是审阅和重读本系列第 2 部分的好时机。

现在,我们来探讨创建低次证明的代码。首先回顾一下,低次证明是一个概率性证明,即给定值集合中占足够高百分比(例如80%)的部分表示某一特定多项式的值,其中,该多项式的次数远低于给定值的数量 。直观上,我们只需将其视为一个“我们声称代表多项式的某个默克尔根确实代表了某个多项式,当然,其中可能会有一些误差”的证明。作为输入,我们有:

  • 一个我们声称是低次多项式的值的值集合
  • 单位根;被求值多项式的x坐标是该单位根的连续幂
  • 一个使得我们证明多项式的次数严格小于N的值N
  • 模数

我们采用递归的方法,有两种情况。首先,如果次数足够低,我们只需提供完整的值列表作为证明,这是“基本情况”。对基本情况的验证十分简单:进行 FFT 或拉格朗日插值或其它对表示这些值的多项式插值,并验证其次数小于 N 的方法。否则,如果次数高于某个设定的最小值,我们将进行第 2 部分最后介绍的垂线 –对角线技巧。

我们首先将值放入默克尔树中,并使用默克尔根来选择伪随机x坐标( special_x )。然后我们计算“列”:

\# 计算x坐标的集合
xs = get_power_cycle(root_of_unity, modulus)column = []
for i in range(len(xs)//4):x_poly = f.lagrange_interp_4([xs[i+len(xs)*j//4] for j in range(4)],[values[i+len(values)*j//4] for j in range(4)],)column.append(f.eval_poly_at(x_poly, special_x))
)

这短短几行代码包含了很多内容。其宽泛的想法是将多项式 P(x) 重新演绎为多项式 Q(x, y),其中 P(x) = Q(x, x**4) 。如果 P 的次数小于 N,那么 P'(y) = Q(special_x, y) 的次数将小于 N / 4。由于我们不想浪费精力以系数形式来实际计算 Q (这需要一个相对难受且繁杂的 FFT!),我们改为使用另一种技巧。对于任何给定的 x^4 形式的值,它有 4 个对应的 x 值: x , 模数 – x 以及 x 乘以 -1 的两个模平方根。所以我们已经有四个关乎 Q(?, x**4) 的值,我们可以用它来插值多项式 R(x) = Q(x, x**4) ,并从据此计算 R(special_x) = Q(special_x, x**4) = P'(x**4) 。x^4 有N / 4个可能的值,这种方法使得我们可以轻松计算所有这些值。

-这个图表来自本系列第2部分。记住这张图表对于理解本文很有帮助。-

我们的证明包含来自 x^4(使用该列的默克尔根作为种子)形式的值列表的有限次(比如 40)随机查询。对于每个查询,我们提供 Q(?, x**4) 的五个值的默克尔分支:

m2 = merkelize(column)# 伪随机选择y索引用于采样
# (m2[1]是该列的默克尔根)
ys = get_pseudorandom_indices(m2[1], len(column), 40)# 为多项式和列中的值计算默克尔分支
branches = []
for y in ys:branches.append([mk_branch(m2, y)] +[mk_branch(m, y + (len(xs) // 4) * j) for j in range(4)])

验证者的工作是验证这五个值实际上是否位于小于4的相同次数多项式上。据此,我们递归并在列上执行FRI,验证该列的次数是否小于 N / 4。这就是 FRI 的全部内容。

作为一项思考题练习,你可以尝试创建拥有错误的多项式求值的低次证明,并看看有多少错误可以被忽略并得到验证者的通过(提示,你需要修改 prove_low_degree 函数。在默认证明设置中,即使一个错误也会爆炸并导致验证失败)。

STARK

提醒:现在可能是审阅和重读本系列第 1 部分的好时机。

现在,我们得到将所有这些部分组合在一起的实质成果: def mk_mimc_proof(inp, steps, round_constants) (代码见此处),它生成运行 MIMC 函数的执行结果的证明,其中给定的输入为步骤数。首先,是一些 assert 函数:

assert steps <= 2**32 // extension_factor
assert is_a_power_of_2(steps) and is_a_power_of_2(len(round_constants))
assert len(round_constants) < steps

扩展因子是我们将“拉伸”计算轨迹(执行 MIMC 函数的“中间值”的集合)的程度。我们需要步数乘以扩展因子最多为 2^32,因为当 k > 32 时,我们没有 2^k 次的单位根。

我们的第一个计算是生成计算轨迹,即计算的所有中间值,从输入一直到输出。

# 生成计算轨迹
computational_trace = [inp]
for i in range(steps-1):computational_trace.append((computational_trace[-1]**3 + round_constants[i % len(round_constants)]) % modulus)
output = computational_trace[-1]

然后,我们将计算轨迹转换为多项式,在单位根 g (其中,g^steps = 1)的连续幂的轨迹上“放下”连续值,然后我们对更大的集合——即单位根 g2 的连续幂,其中 g2^steps * 8 = 1(注意g2^8 = g)——的多项式求值。

computational_trace_polynomial = inv_fft(computational_trace, modulus, subroot)
p_evaluations = fft(computational_trace_polynomial, modulus, root_of_unity)

-黑色: g1 的幂。紫色: g2 的幂。橙色:1。你可以将连续的单位根看作一个按这种方式排列的圆圈。我们沿着 g1的幂“放置”计算轨迹,然后扩展它来计算在中间值处(即 g2 的幂)的相同多项式的值。-

我们可以将 MIMC 的循环常量转换为多项式。因为这些循环常量循环的周期非常短(在我们的测试中,大约为 64 步),结果证明它们形成了一个 64 次多项式,我们可以相当容易地计算它的表达式及其扩展:

skips2 = steps // len(round_constants)
constants_mini_polynomial = fft(round_constants, modulus, f.exp(subroot, skips2), inv=True)
constants_polynomial = [0 if i % skips2 else constants_mini_polynomial[i//skips2] for i in range(steps)]
constants_mini_extension = fft(constants_mini_polynomial, modulus, f.exp(root_of_unity, skips2))

假设有 8192 个执行步骤和 64 个循环常量。以下是我们正在做的事情:我们正在进行 FFT 将循环常量作为 g1^128 的函数来计算。然后我们在常量之间添加零使其成为g1本身的函数。因为 g1^128 每 64 步循环一次,我们也知道 g1 的函数。我们只需计算 512 个扩展步骤,因为我们知道扩展也是每 512 步重复。

我们现在——正如在本系列第 1 部分的斐波那契例子中那样——计算 C(P(x)) ,但这一次是 C(P(x), P(g1*x), K(x)) :

# 创建组合多项式使得
# C(P(x), P(g1*x), K(x)) = P(g1*x) - P(x)**3 - K(x)
c_of_p_evaluations = [(p_evaluations[(i+extension_factor)%precision] -f.exp(p_evaluations[i], 3) -constants_mini_extension[i % len(constants_mini_extension)])% modulus for i in range(precision)]
print('Computed C(P, K) polynomial')

请注意,这里我们不再使用系数形式的多项式,我们根据高次单位根的连续幂来对多项式进行求值。

c_of_p 要满足 Q(x) = C(P(x), P(g1*x), K(x)) = P(g1*x) – P(x)**3 – K(x) 。我们希望,对于我们正在放置计算轨迹的每个 x (除了最后一步,因为在最后一步“之后”没有步骤),轨迹中的下一个值等于轨迹中的前一个值的立方,再加上循环常量。与第1部分中的斐波那契示例不同,在该例子中,如果一个计算步骤在坐标k处,则下一步是在坐标k + 1处。而在这里,我们沿着低次单位根( g1 )的连续幂放下计算轨迹。因此,如果一个计算步骤位于 x = g1^i ,则“下一步”位于 g1^i+1 = g1^i * g1 = x * g1 。因此,对于低阶单位根( g1 )的每一个幂(除了最后一个),我们都希望它满足 P(x*g1) = P(x)**3 + K(x) ,或者 P(x*g1) – P(x)**3 – K(x) = Q(x) = 0 。因此, Q(x) 将在低次单位根 g 的所有(除了最后一个)连续幂上等于零。

有一个代数定理证明:如果 Q(x) 在所有这些x坐标处都等于零,那么它是在所有这些x坐标上等于零的最小多项式的倍数: Z(x) = (x – x_1) * (x – x_2) * … * (x – x_n) 。由于证明 Q(x) 在我们想要检查的每个坐标上都等于零十分困难(因为验证这样的证明比运行原始计算需要耗费更长的时间!),因此,我们使用间接方法来(概率地)证明 Q(x) 是 Z(x) 的倍数。我们该怎么做?当然是通过提供商 D(x) = Q(x) / Z(x) 并使用 FRI 来证明它是一个实际的多项式而不是一个分数。

我们选择低次单位根和高次单位根的特定排列(而不是沿着高次单位根的前几个幂放置计算轨迹),因为事实证明,计算 Z(x) (在除了最后一个点之外的计算轨迹上的所有点处值为零的多项式)。并且除以 Z(x) 十分简单:Z 的表达式是两项的一部分。

# 计算 D(x) = Q(x) / Z(x)
# Z(x) = (x^steps - 1) / (x - x_atlast_step)
z_num_evaluations = [xs[(i * steps) % precision] - 1 for i in range(precision)]
z_num_inv = f.multi_inv(z_num_evaluations)
z_den_evaluations = [xs[i] - last_step_position for i in range(precision)]
d_evaluations = [cp * zd * zni % modulus for cp, zd, zni in zip(c_of_p_evaluations, z_den_evaluations, z_num_inv)]
print('Computed D polynomial')

请注意,我们直接以“求值形式”计算Z的分子和分母,然后用批量模逆的方法将除以Z转换为乘法 (* zd * zni),随后通过 Z(X) 的逆来逐点乘以 Q(x) 的值。请注意,对于低次单位根的幂,除了最后一个(即沿着作为原始计算轨迹的一部分的低次扩展部分),有 Z(x) = 0 。所以这个包含它的逆的计算会中断。虽然我们能通过简单地修改随机检查和FRI算法使其不在那些点上采样的方式来堵塞这些漏洞,但这仍然是一件十分不幸的事情。因此,我们计算错误的事实永远不重要。

因为 Z(x) 可以如此简洁地表达,我们得到另一个好处:验证者可以非常快速地计算任何特定 x 的 Z(x) ,而无需任何预计算。我们可以接受证明者必须处理大小等于步数的多项式,但我们不想让验证者做同样的事情,因为我们希望验证过程足够简洁(即超快速,同时证明尽可能小)。

在几个随机选择的点上概率地检查 D(x) * Z(x) = Q(x) 允许我们验证转换约束——即每个计算步骤是前一步的有效结果。但我们也想验证边界约束——即计算的输入和输出与证明者所说的相同。只要求证明者提供 P(1) , D(1) , P(last_step) 和 D(last_step) (其中, last_step (或 g^(steps-1) )是对应于计算中最后一步的坐标)的值是很脆弱的,因为没有证明表明这些值与其余数据处在同一多项式上。所以我们使用类似的多项式除法技巧:

#计算 ((1, input), (x_atlast_step, output))的插值
i_evaluations = [f.eval_poly_at(interpolant, x) for x in xs]zeropoly2 = f.mul_polys([-1, 1], [-last_step_position, 1])
inv_z2_evaluations = f.multi_inv([f.eval_poly_at(quotient, x) for x in xs])# B = (P - I) / Z2
b_evaluations = [((p - i) * invq) % modulus for p, i, invq in zip(p_evaluations, i_evaluations, inv_z2_evaluations)]
print('Computed B polynomial')

论证如下。证明者想要证明 P(1) == 输入 以及 P(last_step) ==输出 。如果我们将 I(x) 作为插值—— I(x) 是穿过点 (1, input) 和 (last_step, output) 的线,则 P(x) – I(x) 在这两点处将等于零。因此,这足以证明 P(x) – I(x) 是 (x – 1) * (x – last_step) 的倍数,我们通过……提供商数来实现这一点!

-紫色:计算轨迹多项式 (P) 。绿色:插值 (I)(注意插值是如何构造的,其在 x = 1 处等于输入(应该是计算轨迹的第一步),在 x=g^(steps-1) 处等于输出(应该是计算轨迹的最后一步)。红色:P-I。黄色:在x = 1和 x=g^(steps-1)(即 Z2)处等于 0 的最小多项式。粉红色:(P – I) / Z2。-

思考题:

假设你还想要证明在第703步之后计算轨迹中的值等于8018284612598740,你该如何修改上述算法来执行此操作?

答案是:

将 I(x) 设置为 (1, input) ,(g ** 703, 8018284612598740),(last_step, output) 的插值,并通过提供商数 B(x) = (P(x) – I(x)) / ((x – 1) * (x – g ** 703) * (x – last_step)) 来创建证明。

现在,我们将P,D和B的默克尔根组合在一起。

#计算它们的默克尔根
mtree = merkelize([pval.to_bytes(32, 'big') +dval.to_bytes(32, 'big') +bval.to_bytes(32, 'big') forpval, dval, bval in zip(p_evaluations, d_evaluations, b_evaluations)])
print('Computed hash root')

现在,我们需要证明 P,D 和 B 实际上都是多项式,并且多项式的次数都是正确的最大次数。但是 FRI 证明很大且成本高昂,我们不希望有三个 FRI 证明。因此,我们计算 P,D 和 B 的伪随机线性组合(使用 P,D 和 B 的默克尔根作为种子),并对此进行 FRI 证明:

k1 = int.from_bytes(blake(mtree[1] + b'\x01'), 'big')
k2 = int.from_bytes(blake(mtree[1] + b'\x02'), 'big')
k3 = int.from_bytes(blake(mtree[1] + b'\x03'), 'big')
k4 = int.from_bytes(blake(mtree[1] + b'\x04'), 'big')# 计算线性组合。我们甚至不打算对它进行计算。
# 以系数形式,我们只是计算估值。
root_of_unity_to_the_steps = f.exp(root_of_unity, steps)
powers = [1]
for i in range(1, precision):powers.append(powers[-1] * root_of_unity_to_the_steps % modulus)l_evaluations = [(d_evaluations[i] +p_evaluations[i] * k1 + p_evaluations[i] * k2 * powers[i] +b_evaluations[i] * k3 + b_evaluations[i] * powers[i] * k4) % modulusfor i in range(precision)]

除非三个多项式都具有正确的低次数,否则它们的随机选择线性组合几乎不可能具有正确的低次(你必须非常幸运地消去这些项),所以这是充分的。

我们想证明 D 的次数小于 2 * steps ,而 P 和 B 的次数小于 steps ,所以我们实际上构建了P,P * x^steps,B,B^steps 和 D 的随机线性组合,并检查该组合的次数小于 2*steps 。

现在,我们对所有多项式进行抽查。我们生成一些随机索引,并提供在这些索引处求值的多项式的默克尔分支:

# 在伪随机坐标处对默克尔树进行抽查
# `扩展因子` 的倍数
branches = []
samples = spot_check_security_factor
positions = get_pseudorandom_indices(l_mtree[1], precision, samples,exclude_multiples_of=extension_factor)
for pos in positions:branches.append(mk_branch(mtree, pos))branches.append(mk_branch(mtree, (pos + skips) % precision))branches.append(mk_branch(l_mtree, pos))
print('Computed %d spot checks' % samples)

get_pseudorandom_indices 函数返回 [0…precision-1] 范围内的一些随机索引, exclude_multiples_of 参数告诉它不要给出特定参数(此处为 扩展因子 )的倍数的值。这可以确保我们不会沿着原始计算轨迹进行采样,否则的话,我们可能会得到错误的答案。

证明(约 25 万到 50 万字节)由一组默克尔根、经过抽查的分支以及随机线性组合的低次证明组成:

o = [mtree[1],l_mtree[1],branches,prove_low_degree(l_evaluations, root_of_unity, steps * 2, modulus, exclude_multiples_of=extension_factor)]

在实践中,证明的最大部分是默克尔分支和 FRI 证明(它可能包含更多分支)组成。这是验证者的实质成果:

for i, pos in enumerate(positions):x = f.exp(G2, pos)x_to_the_steps = f.exp(x, steps)mbranch1 =  verify_branch(m_root, pos, branches[i*3])mbranch2 =  verify_branch(m_root, (pos+skips)%precision, branches[i*3+1])l_of_x = verify_branch(l_root, pos, branches[i*3 + 2], output_as_int=True)p_of_x = int.from_bytes(mbranch1[:32], 'big')p_of_g1x = int.from_bytes(mbranch2[:32], 'big')d_of_x = int.from_bytes(mbranch1[32:64], 'big')b_of_x = int.from_bytes(mbranch1[64:], 'big')zvalue = f.div(f.exp(x, steps) - 1,x - last_step_position)k_of_x = f.eval_poly_at(constants_mini_polynomial, f.exp(x, skips2))# 检查转换约束Q(x) = Z(x) * D(x)assert (p_of_g1x - p_of_x ** 3 - k_of_x - zvalue * d_of_x) % modulus == 0# j检查转换约束 B(x) * Z2(x) + I(x) = P(x)interpolant = f.lagrange_interp_2([1, last_step_position], [inp, output])zeropoly2 = f.mul_polys([-1, 1], [-last_step_position, 1])assert (p_of_x - b_of_x * f.eval_poly_at(zeropoly2, x) -f.eval_poly_at(interpolant, x)) % modulus == 0# 检查线性组合的正确性assert (l_of_x - d_of_x -k1 * p_of_x - k2 * p_of_x * x_to_the_steps -k3 * b_of_x - k4 * b_of_x * x_to_the_steps) % modulus == 0

在证明者提供默克尔证明的每个位置,验证者检查默克尔证明,并检查 C(P(x), P(g1*x), K(x)) = Z(x) * D(x) 和 B(x) * Z2(x) + I(x) = P(x) (提醒:对于不在原始计算轨迹上的 x, Z(x) 不会为零,因此 C(P(x),P(g1*x), K(x)) 可能不会为零)。验证者还检查线性组合是否正确,并调用 verify_low_degree_proof(l_root, root_of_unity, fri_proof, steps * 2, modulus, exclude_multiples_of=extension_factor) 来验证 FRI 证明。我们完成了!

好吧,我们没有全部完成。证明对跨多项式检查和 FRI 所需的抽查次数的可靠性分析是非常棘手的。但这就是代码的全部内容,至少如果你不打算进行更疯狂的优化的话。当我运行上述代码时,我们得到一个大约 300 到 400 倍的 STARK 证明“开销”(例如,一个需要 0.2 秒的 MIMC 计算需要 60 秒来证明)。这表明使用一台 4 核机器计算前向 MIMC 计算上的 STARK 实际上可以比后向计算 MIMC 更快。也就是说,这些都是在 python 中相对低效的实现,并且在适当优化的实现中,证明与运行时间比可能是不同的。此外,值得指出的是,MIMC 的 STARK 证明开销非常低,因为 MIMC 几乎完全是“可算术化的” ——它的数学形式非常简单。对于包含较少算术明晰运算(例如,检查数字是否大于或小于另一个数字)的“平均”计算而言,其开销可能会更高,大约为 10000 到 50000 倍。


原文链接: https://vitalik.ca/general/2018/07/21/starks_part_3.html
作者: Vitalik

区块链零知识证明:STARKs,Part-3:攻坚(下)相关推荐

  1. 区块链零知识证明能否破解《三体》的黑暗森林法?

    摘要:在刘慈欣<三体>书中的黑暗森林法则中,区块链零知识证明能否在一片黑暗中,在没有共同故事的文明之间提供一种信任方法? 在刘慈欣<三体>宇宙社会学中有一条重要法则--黑暗森林 ...

  2. 区块链零知识证明:zkSNARKs与zcash

    什么是zk-SNARKs? Zcash是zk-SNARKs的第一个广泛应用,它是一种零知识密码学的新形式.Zcash的隐私保证源于可以屏蔽交易,但仍可通过zk-SNARK证明在网络共识规则下验证为有效 ...

  3. 深入浅出区块链——零知识证明

    引子 隐私保护在区块链领域越来越受到重视,在隐私保护的种种手段中,零知识证明脱颖而出.那么零知识证明到底是什么东西呢? 首先,我们抛出一个问题:如何在不说出秘密的情况下,让对方知道你已经知道了这个秘密 ...

  4. 区块链零知识证明:STARKs, Part II

    本系列的上一篇文章: 干货 | STARKs, Part I: 多项式证明 在本系列的上一篇文章中,我们谈到了,如何能够做出一些非常有意思且简洁的计算证明,比如通过利用多项式复合和除法技术,证明你算出 ...

  5. 兄弟连区块链入门教程分享区块链POW证明代码实现demo

    区块链入门教程分享区块链POW证明代码实现demo 这里强调一下区块链的协议分层 应用层 合约层 激励机制 共识层 网络层 数据层 上 一篇主要实现了区块链的 数据层,数据层主要使用的技术 ...

  6. 区块链存储证明和时空证明

    proof of storage email:1650727278@qq.com 画笔 科学的本质是哲学.哲学的追求有一个极:永恒.有两个底:变和不变.这两个哲学思想,就好比九阳神功和太极剑.学会之后 ...

  7. 第一张区块链完税证明在深圳开出,区块链政务应用再获突破

    近日,深圳市至远投资有限公司办税员吴博通过微信小程序成功开具了车船税电子完税证明,这是深圳市开具的首张区块链完税证明. 以往纳税人通过保险公司或者海事局代缴车船税后,当场仅能获得保单或者电子缴款凭证, ...

  8. 【区块链基础知识系列】 第8课 区块链之零知识证明

    所谓零知识证明,指的是示证者在证明自己身份时不泄露任何信息,验证者得不到示证者的任何私有信息,但又能有效证明对方身份的一种方法. 从本质上讲,零知识证明是一种协议.所谓协议(Protocol),就是两 ...

  9. 当区块链使用证明之后

    以太坊因为PoW链在性能上的限制,在很早期就开始研究扩容方案.在L2的众多案例里,基于"证明"技术的两类方案脱颖而出,其也被以太坊奉为主要的扩容方案.未来,即使PoS共识运行以及分 ...

最新文章

  1. 0.项目运行环境和项目经理
  2. gcc undefined reference to 问题解决方法(使用库)
  3. Leetcode-Median of Two Sorted Arrays
  4. 那些上海滩的金融传奇,或许都开始于一份PPT
  5. DEV控件中GridView中的复选框与CheckBox实现联动的全选功能
  6. python苦逼_自学Python编程的第六天(最后代码有更好的请告诉我)----------来自苦逼的转行人...
  7. 女人的安全感到底是什么?
  8. 神奇的幻方(洛谷P2615题题解,Java语言描述)
  9. 【项目调研+论文阅读】基于医学文献的实体抽取(NER)方法研究 day5
  10. python文件行数运行结果_python统计文件行数
  11. YYF的一些简单配置 2016-10-13
  12. MYSQL BENCHMARK()函数
  13. Autorun.inf病毒和qhbpriAppInit_DLLs专杀
  14. 【网管知识】狼牙抓鸡器中毒后的解决办法
  15. 软件使用经验合集(操作系统、常用软件)
  16. python制作日历_用Python制作一份你专属的爱豆日历
  17. 【HLA】初识HLA/RTI
  18. C# Winform窗体切换与关闭
  19. 人工智能和5G在无人机技术中的作用
  20. python云计算1_python云计算1

热门文章

  1. c++数据结构:散列表(哈希)
  2. 如何根据event\marker将EEG数据的测试阶段和实验阶段分开
  3. linux 多线程ppn,Linux下如何查看CPU信息, 包括位数和多核信息
  4. python学习小记--取整/取余
  5. UI自动化中的分层设计
  6. 八个常用的网络命令ping、Telnet等详细方法介绍
  7. UE4项目运行后出现得灰色小球如何取消显示
  8. flask---》Flask-WTF
  9. flask-wtf表单验证--修改邮箱密码
  10. SQL Server 2014 官方培训课件