内容简介:感谢上帝,今天是FRI日(即“快速里德-所罗门码接近性交互预言证明(Fast Reed-Solomon Interactive Oracle Proofs of Proximity)”)提醒:现在可能是审阅和重读本系列现在,我们来探讨创建
感谢上帝,今天是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个可能的值,这种方法使得我们可以轻松计算所有这些值。
我们的证明包含来自 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) 的倍数,我们通过……提供商数来实现这一点!
思考题:
假设你还想要证明在第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') for
pval, 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) % modulus
for 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
翻译:喏贝尔
本文首发于公众号 Unitimes·独角时代 (ID:Uni-times),EthFans 受权转载。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- STARKs,Part-3:攻坚(下)
- 曙光公司:攻坚突破关键领域核心技术
- iOS概念攻坚之路(一):RunLoop
- iOS概念攻坚之路(二):Runtime
- iOS概念攻坚之路(三):内存管理
- iOS概念攻坚之路(七):block
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Developer's Guide to Social Programming
Mark D. Hawker / Addison-Wesley Professional / 2010-8-25 / USD 39.99
In The Developer's Guide to Social Programming, Mark Hawker shows developers how to build applications that integrate with the major social networking sites. Unlike competitive books that focus on a s......一起来看看 《Developer's Guide to Social Programming》 这本书的介绍吧!