V神新论文|STARKs III: Into the Weeds(下)

image

前面咱们已经阅读了上周五,Vitalik 7月21日发表的论文《STARKSIII:Into the Weeds》上半部分。如下为该论文下半部分:html

STARKs III: Into the Weeds(下)python

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

提醒:如今多是审阅和重读本系列第2部分[1]的好时机。github

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

  • 一个咱们声称是低次多项式的值的值集合app

  • 单位根;被求值多项式的x坐标是该单位根的连续幂dom

  • 一个使得咱们证实多项式的次数严格小于N的值N函数

  • 模数学习

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

咱们首先将值放入默克尔树中,并使用默克尔根来选择伪随机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, x4)。若是P的次数小于N,那么P'(y) = Q(special_x, y)的次数将小于N / 4。因为咱们不想浪费精力以系数形式来实际计算Q(这须要一个相对难受且繁杂的FFT!),咱们改成使用另外一种技巧。对于任何给定的x4形式的值,它有4个对应的x值:x,模数 – x以及x乘以-1的两个模平方根。因此咱们已经有四个关乎Q(?, x4)的值,咱们能够用它来插值多项式R(x) = Q(x, x4),并从据此计算R(special_x) = Q(special_x, x4) = P'(x**4)。x4有N / 4个可能的值,这种方法使得咱们能够轻松计算全部这些值。

image

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

咱们的证实包含来自x4(使用该列的默克尔根做为种子)形式的值列表的有限次(好比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函数。在默认证实设置中,即便一个错误也会爆炸并致使验证失败)。

1

STARK

提醒:如今多是审阅和重读本系列第1部分[3]的好时机。

如今,咱们获得将全部这些部分组合在一块儿的实质成果: def mk_mimc_proof(inp, steps, round_constants)(代码见此[4]),它生成运行MIMC函数的执行结果的证实,其中给定的输入为步骤数。首先,是一些assert函数:

assert steps <= 2**32 // 扩展因子

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的连续幂,其中 g^2steps * 8 = 1(注意g^256 = g)——的多项式求值。

computational_trace_polynomial = inv_fft(computational_trace, modulus, subroot)

p_evaluations = fft(computational_trace_polynomial, modulus, root_of_unity)

image

黑色:‘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(g1x), K(x)) = P(g1x) – P(x)**3 – K(x)。咱们但愿,对于咱们正在放置计算轨迹的每一个x(除了最后一步,由于在最后一步“以后”没有步骤),轨迹中的下一个值等于轨迹中的前一个值的立方,再加上循环常量。与第1部分中的斐波那契示例不一样,在该例子中,若是一个计算步骤在坐标k处,则下一步是在坐标k + 1处。而在这里,咱们沿着低次单位根(g1)的连续幂放下计算轨迹。所以,若是一个计算步骤位于x = g1^i,则“下一步”位于g1^i+1 = g1^i * g1 = x * g1。所以,对于低阶单位根(g1)的每个幂(除了最后一个),咱们都但愿它知足P(xg1) = P(x)**3 + K(x), 或者 P(xg1) – 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(或gsteps-1)是对应于计算中最后一步的坐标)的值是很脆弱的,由于没有证实代表这些值与其他数据处在同一多项式上。因此咱们使用相似的多项式除法技巧:

#计算 ((1, input), (x_atlast_step, output))的插值

interpolant = f.lagrange_interp_2([1, last_step_position], [inp, 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)的倍数,咱们经过……提供商来实现这一点!

image

紫色:计算轨迹多项式(P)。绿色:插值(I)(注意插值是如何构4造的,其在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’) 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步,而P和B的次数小于步数,因此咱们实际上构建了P,P * x^steps,B,B^steps和D的随机线性组合,并检查该组合的次数小于2步。

如今,咱们对全部多项式进行抽查。咱们生成一些随机索引,并提供在这些索引处求值的多项式的默克尔分支:

#在伪随机坐标处对默克尔树进行抽查

excluding

# `扩展因子`的倍数

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

    # Check boundary constraints 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(g1x), K(x)) = Z(x) * D(x)和B(x) * Z2(x) + I(x) = P(x)(提醒:对于不在原始计算轨迹上的x, Z(x)不会为零,所以C(P(x),P(g1x), 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倍。

2

补充资料

文中说起的标注原文连接以下:

[1] https://mp.weixin.qq.com/s/KPjpaOJahIm9fH_Q3UhunQ

[2] https://github.com/ethereum/research/blob/master/mimc_stark/fri.py

[3]https://mp.weixin.qq.com/s/O-qGXp2Dlh1SHzx2dWUEIA

[4] https://github.com/ethereum/research/blob/master/mimc_stark/mimc_stark.py

内容来源: Unitimes

原文做者:Vitalik Buterin

翻译:喏贝尔

原文连接:

https://vitalik.ca/general/2018/07/21/starks_part_3.html

原文篇幅较长,分为上下两部分发布

线上课程推荐

线上课程:《8小时区块链智能合约开发实践》

培训讲师:《白话区块链》做者 蒋勇

课程原价:999元,现价 399元

更多福利:

  • @全部人,识别下图二维码转发课程邀请好友报名,便可得到报名费50%返利

  • @学员,报名学习课程并在规定时间内完成考试便可瓜分10000元奖金

image

相关文章
相关标签/搜索