Anatomy of a STARK, Part 4: The STARK Polynomial IOP(译)

source: https://aszepieniec.github.io/stark-anatomy/stark

本教程的这一部分涉及 STARK 证明系统的信息论主干,您可以将其称为 STARK 多项式 IOP。回想一下,SNARKs 的编译流水线涉及中间阶段,其中前两个是 算术约束系统(arithmetic constraint system)多项式 IOP(Polynomial IOP)。本教程确实描述了算术约束系统的属性。 然而,关于将初始计算转换为算术约束系统的 算术化 步骤的讨论超出了教程的范围。我们详细讨论了将这个算术约束系统转换为多项式 IOP 的插值步骤。最终的多项式 IOP 可以使用第 3 部分中描述的基于 FRI 的编译器编译成具体的证明系统。

Arithmetic Intermediate Representation (AIR)

The arithmetic intermediate representation (AIR) is a way of describing a computation in terms of an execution trace that satisfies a number of constraints induced by the correct evolution of the state. The term arithmetic refers to the fact that this execution trace consists of a list of finite field elements (or an array, if more than one register is involved), and that the constraints are expressible as low degree polynomials.

Let’s make this more concrete. Let \({\rm F}_p\) be the field of definition. Without loss of generality, the computation describes the evolution a state of \({\rm w}\) registers for \(T\) cycles. The algebraic execution trace (AET) is the table of \(T \times {\rm w}\) field elements where every row describes the state of the system at the given point in time, and every column tracks the value of the given register.

A state transition function

\[f: {\rm F}_p^{\rm w} \to {\rm F}_p^{\rm w} \]

determines the state at the next cycle as a function of the state at the previous cycle. Furthermore, a list of boundary conditions

\[\mathcal{B}:[{\rm Z}_T \times {\rm Z}_{\rm_w} \times {\rm F} ] \]

enforce the correct values of some or all registers at the first cycle, last cycle, or even at arbitrary cycles.

计算完整性声明由状态转换函数和边界条件组成。该声明的见证(witness)是代数执行轨迹(AET)。The claim is true if there is a witness \(W \in G^{T \times {\rm w}}\) such that:

  • 对于每一轮,状态都会正确演化(\(f\) 的输入为上一个状态,输出为下一个状态): \(\forall i \in \{ 0,..., T-1 \}. f(W_{[i,:]}) = f(W_{[i+1,:]})\)
  • 所有的边界条件都得到满足: \(\forall(i,w,e) \in \mathcal{B}. W_{[i,w]}=e\) (第\(i\)步,第\(w\)个寄存器,值为\(e\))

状态转换函数隐藏了很多复杂性。 出于 STARK 的目的,它需要被描述为 与循环无关(independent of the cycle) 的低次多项式。然而,这个多项式列表不需要从当前状态计算下一个状态; 它只需要区分正确的演变和不正确的演变。具体的,函数

\[f: {\rm F}_p^{\rm w} \to {\rm F}_p^{\rm w} \]

由多项式 \({\bf p}(X_0,...,X_{{\rm w}-1},Y_0,...,Y_{{\rm w}-1})\) 的列表表示,使得 \(f({\bf x}) = {\bf y}\) 当且仅当 \(\bf{p(x,y)}=0\) .假设有 \(r\) 个这样的状态转换 验证 多项式。 那么转换约束变为:

  • \(\forall i \in \{ 0,...,T-1 \}. \forall j \in \{0,...,r-1\}. p_j(W_{[i,0]},...,W_{[i,{\rm w}-1]}, W_{[i+1,0]},...,W_{[i+1,{\rm w}-1]})=0\)

This representation admits non-determinism, which has the capacity to reduce high degree state transition computation polynomials with low degree state transition verification polynomials. (state transition polynomials 和 state transition verification polynomials 可以不是一个东西,前者用于生成trace,后者用于验证trace)

Not all lists of \({\rm w}\) represent valid states. 例如,一些寄存器可能被限制为bits,因此只能从 \(\{0,1 \}\) 中获取值。The state transition function is what guarantees that the next state is well-formed if the current state is. 当转换为验证多项式时,这些一致性约束是仅变量的前半部分\((X_0,...,X_{{\rm w}-1})\)的多项式 ,因为它们适用于 AET 中的每一行,而不是每一对连续的行。为了简单起见,本教程将忽略一致性约束,并假装每个 \({\rm w}\) 元组的字段元素都代表一个有效状态。

Interpolation

上面描述的算术约束系统已经将计算完整性声明表示为一堆多项式; 每个这样的多项式对应一个约束。将此约束系统转换为多项式 IOP 需要将此表示形式以多项式的形式扩展到见证人(witness),并将有效见证人的概念扩展到 见证多项式(witness polynomials)。具体来说,我们需要根据多项式的相等关系来表示真正的计算完整性声明的条件。

从这里开始,\(D\)轨迹评估域(trace evaluation domain) 上的点列表。Typically, D is set to the span of a generator \(o\) of a subgroup of order \(2^k \ge T+1\). 所以暂时设置 \(D=\{ o^i,i \in {\rm Z} \}\) . 希腊字母 \(ο\)(“omicron”)表示轨迹评估域(trace evaluation domain)比 FRI 评估域(FRI evaluation domain)小一个因子,该因子恰好等于扩展因子。

\(t(X) \in ({\rm F}_p[X])^{\rm w}\)\({\rm w}\) 单变量多项式的列表,这些单变量多项式是 \(D\) 上对 \(W\) 的插值。具体来说,寄存器 \(w\)迹多项式(trace polynomial) \(t_w(X)\) 是最低次数的单变量多项式,它使得 \(\forall i \in \{ 0,...,T \}. t_w(o^i)=W[i,w]\) 。 迹多项式是用单变量多项式表示的代数执行迹(AET)。

将真实计算完整性声明的条件转换为迹多项式,可以得到:

  • 所有的边界约束都得到满足:\(\forall (i,w,e) \in \mathcal{B}. t_w(o^i)=e\)
  • for all cycles, all transition constraints are satisfied:
    \(\forall i \in \{ 0,...,T-1 \}. \forall j \in \{0,...,r-1\}. p_j(t_0(o^i),...,t_{{\rm w}-1}(o^i), t_0(o^{i+1}),...,t_{{\rm w}-1}(o^{i+1}))=0\)

最后一个表达式看起来很复杂。 但是,请注意等式的左侧对应于单变量多项式\(p_j(t_0(X),...,t_{{\rm w}-1}(X), t_0(o·X),...,t_{{\rm w}-1}(o·X))=0\) 。整个表达式简单地说,所有这些转移多项式在 \(\{ o^i,i \in {\rm Z_T} \}\) 中的计算结果为 0.

This observation gives rise to the following high-level Polynomial IOP:

  1. The prover commits to the trace polynomials \({\bf t}(X)\).

  2. The verifier checks that \(t_w(X)\) evaluates to \(e\) in \(o^i\) for all \((i,w,e) \in \mathcal{B}\) .

  3. The prover commits to the transition polynomials \({\bf c}(X)={\bf p}(t_0(X),...,t_{\rm w-1}(X),t_0(o·X),...,t_{\rm w-1}(o·X))\) .

  4. The verifier checks that \({\bf c}(X)\) and \({\bf t}(X)\) are correctly related by:

    • 从不包括元素 0 的域中选择一个均匀绘制的随机点 \(z\)
    • querying the values of \({\bf t}(X)\) in \(z\) and \(o·z\)
    • evaluating the transition verification polynomials \({\bf p}(X_0,...X_{{\rm w}-1},Y_0,...Y_{{\rm w}-1})\) in these 2w points
    • querying the values of \({\bf c}(X)\) in \(z\)
    • 检查在前两个步骤中获得的值是否匹配。
  5. The verifier checks that the transition polynomials \({\bf c}(X)\) evaluate to zero in \(\{ o^i,i \in \{0,...,T-1 \} \}\) .

事实上,转移多项式的承诺可以省略。相反,验证者使用 \({\bf t}(X)\)\(z\)\(o·z\) 上的评估来计算 \({\bf c}(X)\) 在所需要的点处的值,该点被用来验证 \({\bf c}(X)\)\(\{ o^i,i \in \{ 0,...,T-1 \} \}\) 上评估为 0。

还有另一层冗余,但只有在展开评估检查后才会显现出来。 FRI 编译器通过以下方式模拟评估检查:a) 减去 y 坐标,b) 除zerofier,以及 c) 证明所得商具有有界度。对于 STARK 多项式,此过程发生了两次——第一次:应用于迹多项式以显示满足边界约束,第二次:应用于转移多项式以表明满足转移约束。我们将得到的商多项式列表分别称为 边界商(boundary quotients)转移商(transition quotients)

冗余是因为trace polynomials和两个商都相关。因此,可以通过合并它们所涉及的方程来消除它。下图说明了在 STARK 多项式 IOP 工作流程中的这种消除。绿色框表示多项式已通过评估和 Merkle 根过程完成了提交(commit),并作为 FRI 的输入提供。

该图顶部的红色是与算术约束系统相关联的对象,约束以小写字体表示,以表明它们对验证者是已知的。证明者对执行轨迹进行插值以获得轨迹多项式,但不必提交(commit)这些多项式。相反,证明者对边界点进行插值,并从迹多项式中减去得到的插值。此过程产生 稠密迹多项式(dense trace polynomials)。为了从稠密迹多项式中获得边界商,证明者除以zerofier。请注意,边界商和迹多项式在以下意义上是等价的:如果验证者知道一个给定点的值,他可以仅使用公共信息计算另一个的匹配值。

为了获得转移多项式,证明者在迹多项式中象征性地评估转移约束(回想一下,这些是作为多元多项式给出的)。要从转移多项式中获得转移商,请除zerofier。 暂时假设验证者能够有效地评估这个zerofier。请注意,转移商和迹多项式不等价——验证者不一定要撤消符号评估。但是,这种不等价并不重要。 验证者需要验证的是边界商和转移商是联系在一起的。从边界商移动到转移商,并沿途执行指示的算术,建立起了这个联系。整个计算完整性声明的其余部分是商多项式的有界度,这正是 FRI 已经解决的问题。

右手边的复数形式的使用有点误导。在通过将 Merkle 根发送给验证者来确定边界商之后,证明者从验证者那里获得随机权重,用这些权重将转移约束压缩为单个线性组合。作为这种压缩的结果,只存在一个转换约束、一个转换多项式和一个转换商。然而,这种压缩可以省略而不影响安全性; 它只是需要证明者和验证者双方都做更多的工作。

总而言之,此工作流生成两个配方:一个用于证明者,一个用于验证者。 它们在这里以抽象的术语和交互的形式呈现。

Prover:

  • 对执行轨迹进行插值以获得轨迹多项式。
  • Interpolate the boundary points to obtain the boundary interpolants, and compute the boundary zerofiers along the way.
  • 从迹多项式中减去边界插值,得到密集迹多项式。
  • Divide out the boundary zerofiers from the dense trace polynomials.
  • Commit to the dense trace polynomials.
  • Get \(r\) random coefficients from the verifier.
  • \(r\) 个转换约束压缩为一个主约束,即加权和。
  • Symbolically evaluate the master constraint in the trace polynomials, thus generating the transition polynomial.
  • Divide out the transition zerofier to get the transition quotient.
  • Commit to the transition zerofier.
  • Run FRI on all committed polynomials.
  • Supply the Merkle leafs and authentication paths that are requested by the verifier.

Verifier:

  • 读取对边界商(boundary quotients)的承诺。
  • 为主转换约束提供随机系数。
  • 读取对转换商(transition quotient)的承诺。
  • Run the FRI verifier.
  • Verify the link between boundary quotients and transition quotient. To do this:
    • For all points of the transition quotient codeword that were queried in the first round of FRI do:
      • 设点为 \((x,y)\) .
      • 查询边界商码字上的匹配点。请注意,其中有两个,为 \(x\)\(o·x\),表示“相隔一个周期”的点。
      • 将这些点的 y 坐标乘以 \(x\)\(o·x\) 处zerofiers的值。
      • Add the boundary interpolants’ values.
      • Evaluate the master transition constraint in this point.
      • Divide by the value of the transition zerofier in \(x\) .
      • Verify that the resulting value equals \(y\) .

Generalized AIR Constraints

到目前为止的描述清楚地区分了过渡约束和边界约束。然而,有一个统一的观点将两者都描述为多项式的干净划分比率。更准确地说,如果计算是完整的,则分母将分子整除; 否则,有一个非零余数。

这种广义 AIR 约束由两个多项式给出。

  • The numerator determines which equations between elements of the algebraic execution trace hold, in a manner that is independent of the cycle. 对于转移约束,分子正好是转移约束多项式。对于边界约束,分子为 \(t_i(X)-y\), 其中 \(y\)\(t_i(X)\) 在给定边界处的值。
  • The denominator is a zerofier that determines where the equality is supposed to hold, by vanishing (i.e., evaluating to zero) in those points. 对于转移约束,除最后一个点之外,该zerofier在跟踪评估域的所有点上消失(即为0)。 对于边界约束,这个zerofier只在边界上消失。

将边界约束和转换约束视为广义 AIR 约束的子类,可以得到更简单的流程图。现在,证明者提交原始迹多项式,但这些多项式不输入到 FRI 子协议。 相反,它们仅用于验证 FRI 的第一个 Merkle 树的叶子是否被正确计算。

本教程的实现遵循早期的工作流程,它将边界约束与转换约束分开,而不是由 AIR 约束的广义概念所告知的工作流程。

Adding Zero-Knowledge

Formally, an interactive proof system is zero-knowledge if the distribution of transcripts arising from authentic executions of the protocol is independent of the witness and can be sampled efficiently with public information only. 在实践中,这意味着证明者使用同样保密的随机性来随机化数据结构和证明算法。The transcript is independent of the witness because any transcript can be explained by the right choice of randomizers.

关于 STARK 证明系统的随机化,值得将机制分成两部分并分别随机化它们。

  1. The FRI bounded degree proof. 该组件通过向非线性组合添加随机化码字来随机化。这个随机化码字对应于一个最大次数的多项式,其系数是随机均匀绘制的。
  2. The linking part that establishes that the boundary quotients are linked to the transition quotient(s). 为了使这一点随机化,每个寄存器的执行轨迹都用 \(4s\) 个均匀随机的有限域元素进行扩展。数字 \(4s\) 来自 FRI 协议中的共线性检查次数 \(s\):每次共线性检查都会在初始码字中引发两个查询。转换商码字的两个值需要与边界商码字的两个四值相关联。

It is important to guarantee that none of the x-coordinates that are queried as part of FRI correspond to x-coordinates used for interpolating the execution trace. 这就是 coset-FRI 派上用场的原因之一。Nevertheless, other solutions can address this problem.

最后,如果该域不够大(特别是,如果它的基数明显小于安全级别 \(2^{\lambda}\) 所需的 \(\lambda\)),那么在构建 Merkle 树时需要将盐(salts)附加到叶子上。具体来说,每个叶子都需要 \(\lambda\) 位的随机性,如果它不是来自域的元素,那么它必须来自一个明确的附录。

如果没有叶子加盐,Merkle 树及其路径对于给定的代码字是确定的。This codeword is still somewhat random, because the polynomial that generates it has randomizers......This observation leads to a distinguisher between authentic and simulated transcript, which in turn undermines zero-knowledge.

此处提供的代码省略了对叶子加盐,因为该域足够大。

Implementation

Like the FRI module, the STARK module starts with an initializer function that sets the class’s fields to the initialization arguments or values inferred from them.

from functools import reduce
import os

class Stark:
    def __init__( self, field, expansion_factor, num_colinearity_checks, security_level, num_registers, num_cycles, transition_constraints_degree=2 ):
        assert(len(bin(field.p)) - 2 >= security_level), "p must have at least as many bits as security level"
        assert(expansion_factor & (expansion_factor - 1) == 0), "expansion factor must be a power of 2"
        assert(expansion_factor >= 4), "expansion factor must be 4 or greater"
        assert(num_colinearity_checks * 2 >= security_level), "number of colinearity checks must be at least half of security level"

        self.field = field
        self.expansion_factor = expansion_factor
        self.num_colinearity_checks = num_colinearity_checks
        self.security_level = security_level

        self.num_randomizers = 4*num_colinearity_checks

        self.num_registers = num_registers
        self.original_trace_length = num_cycles
        
        # 两个不同的trace_length
        randomized_trace_length = self.original_trace_length + self.num_randomizers
        omicron_domain_length = 1 << len(bin(randomized_trace_length * transition_constraints_degree)[2:])
        # 两个不同的domain_length
        fri_domain_length = omicron_domain_length * expansion_factor

        self.generator = self.field.generator()
        self.omega = self.field.primitive_nth_root(fri_domain_length)
        self.omicron = self.field.primitive_nth_root(omicron_domain_length)
        self.omicron_domain = [self.omicron^i for i in range(omicron_domain_length)]

        self.fri = Fri(self.generator, self.omega, fri_domain_length, self.expansion_factor, self.num_colinearity_checks)

The code makes a distinction between the original trace length, which is one greater than the number of cycles, and the randomized trace length which the previous variable with \(4s\) randomizers extra. A third related variable is the omicron_domain, which is the list of points in the subgroup of order \(2^k\), \(k\) 是让 \(2^k\) 大于等于 randomized_trace_length * transition_constraints_degree 的最小整数。

Next up are the helper functions. First are the degree bounds calculators for a) transition polynomials; b) transition quotient polynomials; and c) the nonlinear random combination of polynomials that goes into FRI. This last number is one less than the next power of two.

# transition polynomials 的度
def transition_degree_bounds( self, transition_constraints ):
    point_degrees = [1] + [self.original_trace_length+self.num_randomizers-1] * 2*self.num_regisers
    return [max( sum(r*l for r, l in zip(point_degrees, k)) for k, v in a.dictionary.items()) for a in transition_constraints]

# transition quotient polynomials 的度
def transition_quotient_degree_bounds( self, transition_constraints ):
    return [d - (self.original_trace_length-1) for d in self.transition_degree_bounds(transition_constraints)]

# 组合多项式的度, 等于 omicron_domain - 1
def max_degree( self, transition_constraints ):
    md = max(self.transition_quotient_degree_bounds(transition_constraints))
    return (1 << (len(bin(md)[2:]))) - 1

请注意,此代码并未将许多转换约束压缩为一个。 作为结果,存在多个转移多项式和多个转移商。

Up next are zerofier polynomials, which come in two categories: boundary zerofiers and transition zerofiers.

def transition_zerofier( self ):
    domain = self.omicron_domain[0:(self.original_trace_length-1)]
    return Polynomial.zerofier_domain(domain)

def boundary_zerofiers( self, boundary ):
    zerofiers = []
    for s in range(self.num_regisers):
        points = [self.omicron^c for c, r, v in boundary if r == s]
        zerofiers = zerofiers + [Polynomial.zerofier_domain(points)]
    return zerofiers

下一个函数通过边界条件的 (location,value) 对进行插值来计算得到多项式。This function also enables a boundary counterpart to the transition quotient degree bounds calculator.

# 对边界点插值得到多项式
def boundary_interpolants( self, boundary ):
    interpolants = []
    for s in range(self.num_regisers):
        points = [(c,v) for c, r, v in boundary if r == s]
        domain = [self.omicron^c for c,v in points]
        values = [v for c,v in points]
        interpolants = interpolants + [Polynomial.interpolate_domain(domain, values)]
    return interpolants

# 边界商 度的上界: randomized_trace_degree - boundary_zerofiers_degree
def boundary_quotient_degree_bounds( self, randomized_trace_length, boundary ):
    randomized_trace_degree = randomized_trace_length - 1
    return [randomized_trace_degree - bz.degree() for bz in self.boundary_zerofiers(boundary)]

The last helper function is used by prover and verifier when they want to transform a seed, obtained from the Fiat-Shamir transform, into a list of field elements. The resulting field elements are used as weights in the nonlinear combination of polynomials before starting FRI.

# 根据 randomness 生成 number 个随机数
def sample_weights( self, number, randomness ):
    return [self.field.sample(blake2b(randomness + bytes(i)).digest()) for i in range(0, number)]

Prove

接下来是prover。与上述解释的最大区别在于,没有将转换约束压缩为一个主约束。 这个任务留给读者作为练习。

另一个区别是转换约束有 \(\rm{2w}+1\) 个变量而不是 \(\rm{2w}\) 个。The extra variable takes the value of the evaluation domain over which the execution trace is interpolated. This feature anticipates constraints that depend on the cycle, for instance to evaluate a hash function that uses round constants that are different in each round.

def prove( self, trace, transition_constraints, boundary, proof_stream=None ):
    # create proof stream object if necessary
    if proof_stream == None:
        proof_stream = ProofStream()
    
    # concatenate randomizers(为了零知识性)
    for k in range(self.num_randomizers):
        trace = trace + [[self.field.sample(os.urandom(17)) for s in range(self.num_registers)]]

    # interpolate, 计算trace_polynomials
    trace_domain = [self.omicron^i for i in range(len(trace))]
    trace_polynomials = []
    for s in range(self.num_registers):
        single_trace = [trace[c][s] for c in range(len(trace))]
        trace_polynomials = trace_polynomials + [Polynomial.interpolate_domain(trace_domain, single_trace)]

    # subtract boundary interpolants and divide out boundary zerofiers
    boundary_quotients = []
    for s in range(self.num_registers):
        interpolant = self.boundary_interpolants(boundary)[s]
        zerofier = self.boundary_zerofiers(boundary)[s]
        quotient = (trace_polynomials[s] - interpolant) / zerofier
        boundary_quotients += [quotient]

    # commit to boundary quotients
    fri_domain = self.fri.eval_domain()
    boundary_quotient_codewords = []
    boundary_quotient_Merkle_roots = []
    # 边界商多项式在fri_domain上求值
    for s in range(self.num_registers):
        boundary_quotient_codewords = boundary_quotient_codewords + [boundary_quotients[s].evaluate_domain(fri_domain)]
        merkle_root = Merkle.commit(boundary_quotient_codewords[s])
        proof_stream.push(merkle_root)

    # symbolically evaluate transition constraints
    point = [Polynomial([self.field.zero(), self.field.one()])] + trace_polynomials + [tp.scale(self.omicron) for tp in trace_polynomials]
    transition_polynomials = [a.evaluate_symbolic(point) for a in transition_constraints]

    # divide out zerofier
    transition_quotients = [tp / self.transition_zerofier() for tp in transition_polynomials]

    # commit to randomizer polynomial
    randomizer_polynomial = Polynomial([self.field.sample(os.urandom(17)) for i in range(self.max_degree(transition_constraints)+1)])
    # randomizer polynomial 在fri_domain上求值
    randomizer_codeword = randomizer_polynomial.evaluate_domain(fri_domain) 
    randomizer_root = Merkle.commit(randomizer_codeword)
    proof_stream.push(randomizer_root)

    # get weights for nonlinear combination
    #  - 1 randomizer
    #  - 2 for every transition quotient
    #  - 2 for every boundary quotient
    weights = self.sample_weights(1 + 2*len(transition_quotients) + 2*len(boundary_quotients), proof_stream.prover_fiat_shamir())

    assert([tq.degree() for tq in transition_quotients] == self.transition_quotient_degree_bounds(transition_constraints)), "transition quotient degrees do not match with expectation"

    # compute terms of nonlinear combination polynomial
    x = Polynomial([self.field.zero(), self.field.one()])
    terms = []
    terms += [randomizer_polynomial]
    for i in range(len(transition_quotients)):
        terms += [transition_quotients[i]]
        shift = self.max_degree(transition_constraints) - self.transition_quotient_degree_bounds(transition_constraints)[i]
        terms += [(x^shift) * transition_quotients[i]]
    for i in range(self.num_registers):
        terms += [boundary_quotients[i]]
        shift = self.max_degree(transition_constraints) - self.boundary_quotient_degree_bounds(len(trace), boundary)[i]
        terms += [(x^shift) * boundary_quotients[i]]

    # take weighted sum
    # combination = sum(weights[i] * terms[i] for all i)
    combination = reduce(lambda a, b : a+b, [Polynomial([weights[i]]) * terms[i] for i in range(len(terms))], Polynomial([]))

    # compute matching codeword
    # randomizer_polynomial,transition_quotients,boundary_quotients三个的组合多项式在fri_domain上求值
    combined_codeword = combination.evaluate_domain(fri_domain)

    # prove low degree of combination polynomial
    indices = self.fri.prove(combined_codeword, proof_stream)
    indices.sort()
    duplicated_indices = [i for i in indices] + [(i + self.expansion_factor) % self.fri.domain_length for i in indices]

    # open indicated positions in the boundary quotient codewords
    for bqc in boundary_quotient_codewords:
        for i in duplicated_indices:
            proof_stream.push(bqc[i])
            path = Merkle.open(i, bqc)
            proof_stream.push(path)

    # ... as well as in the randomizer
    for i in indices:
        proof_stream.push(randomizer_codeword[i])
        path = Merkle.open(i, randomizer_codeword)
        proof_stream.push(path)

    # the final proof is just the serialized stream
    return proof_stream.serialize()

Verify

最后是验证者。 它带有相同的警告和练习。

def verify( self, proof, transition_constraints, boundary, proof_stream=None ):
    H = blake2b

    # infer trace length from boundary conditions
    original_trace_length = 1 + max(c for c, r, v in boundary)
    randomized_trace_length = original_trace_length + self.num_randomizers

    # deserialize with right proof stream
    if proof_stream == None:
        proof_stream = ProofStream()
    proof_stream = proof_stream.deserialize(proof)

    # get Merkle roots of boundary quotient codewords
    boundary_quotient_roots = []
    for s in range(self.num_registers):
        boundary_quotient_roots = boundary_quotient_roots + [proof_stream.pull()]

    # get Merkle root of randomizer polynomial
    randomizer_root = proof_stream.pull()

    # get weights for nonlinear combination
    weights = self.sample_weights(1 + 2*len(transition_constraints) + 2*len(self.boundary_interpolants(boundary)), proof_stream.verifier_fiat_shamir())

    # verify low degree of combination polynomial
    polynomial_values = []
    verifier_accepts = self.fri.verify(proof_stream, polynomial_values)
    polynomial_values.sort(key=lambda iv : iv[0])
    if not verifier_accepts:
        return False

    indices = [i for i,v in polynomial_values]
    values = [v for i,v in polynomial_values]

    # read and verify leafs, which are elements of boundary quotient codewords
    duplicated_indices = [i for i in indices] + [(i + self.expansion_factor) % self.fri.domain_length for i in indices]
    leafs = []
    # 验证merkle path
    for r in range(len(boundary_quotient_roots)):
        leafs = leafs + [dict()]
        for i in duplicated_indices:
            leafs[r][i] = proof_stream.pull()
            path = proof_stream.pull()
            verifier_accepts = verifier_accepts and Merkle.verify(boundary_quotient_roots[r], i, path, leafs[r][i])
            if not verifier_accepts:
                return False

    # read and verify randomizer leafs
    # 验证merkle path
    randomizer = dict()
    for i in indices:
        randomizer[i] = proof_stream.pull()
        path = proof_stream.pull()
        verifier_accepts = verifier_accepts and Merkle.verify(randomizer_root, i, path, randomizer[i])

    # verify leafs of combination polynomial
    for i in range(len(indices)):
        current_index = indices[i] # do need i

        # get trace values by applying a correction to the boundary quotient values (which are the leafs)
        domain_current_index = self.generator * (self.omega^current_index)
        next_index = (current_index + self.expansion_factor) % self.fri.domain_length
        domain_next_index = self.generator * (self.omega^next_index)
        current_trace = [self.field.zero() for s in range(self.num_registers)]
        next_trace = [self.field.zero() for s in range(self.num_registers)]
        for s in range(self.num_registers):
            zerofier = self.boundary_zerofiers(boundary)[s]
            interpolant = self.boundary_interpolants(boundary)[s]
            
            # 通过边界商 反求 trace值
            current_trace[s] = leafs[s][current_index] * zerofier.evaluate(domain_current_index) + interpolant.evaluate(domain_current_index)
            next_trace[s] = leafs[s][next_index] * zerofier.evaluate(domain_next_index) + interpolant.evaluate(domain_next_index)
        
        # x值, 当前状态, 下一步状态
        point = [domain_current_index] + current_trace + next_trace
        transition_constraints_values = [transition_constraints[s].evaluate(point) for s in range(len(transition_constraints))]

        # compute nonlinear combination
        counter = 0
        terms = []
        terms += [randomizer[current_index]]

        # 转移商选定点处的值
        for s in range(len(transition_constraints_values)):
            tcv = transition_constraints_values[s]
            quotient = tcv / self.transition_zerofier().evaluate(domain_current_index)
            terms += [quotient]
            shift = self.max_degree(transition_constraints) - self.transition_quotient_degree_bounds(transition_constraints)[s]
            terms += [quotient * (domain_current_index^shift)]
        # 边界商选定点处的值
        for s in range(self.num_registers):
            bqv = leafs[s][current_index] # boundary quotient value
            terms += [bqv]
            shift = self.max_degree(transition_constraints) - self.boundary_quotient_degree_bounds(randomized_trace_length, boundary)[s]
            terms += [bqv * (domain_current_index^shift)]
        # 组合多项式选定点处的值
        combination = reduce(lambda a, b : a+b, [terms[j] * weights[j] for j in range(len(terms))], self.field.zero())

        # verify against combination polynomial value
        # 验证 自己求出来的组合多项式的值 和 fri第一层的merkle树上的值 是否相等
        verifier_accepts = verifier_accepts and (combination == values[i])
        if not verifier_accepts:
            return False

    return verifier_accepts
posted @ 2022-02-13 23:43  elimsc  阅读(145)  评论(0编辑  收藏  举报