前序博客有:
相关代码见:
以Rescue-Prime hash STARK为例。
Rescue-Prime STARK 针对的场景为:
其中 H ( ∗ ) H(*) H(∗) 为Rescue-Prime hash函数。
Rescue-Prime hash运算中涉及:
根据Alan Szepieniec等人2020年论文《Rescue-Prime: a Standard Specification (SoK)》可知,Rescue-Prime哈希函数主要参数有 ( p , m , c p , s ) (p,m,c_p,s) (p,m,cp,s):
此外,还有如下参数:
Rescue-Prime hash函数本质是将任意长的field elements序列 映射为
r
p
r_p
rp个field elements:
f
R
′
:
F
p
∗
→
F
p
r
p
f_{R'}:\mathbb{F}_p^*\rightarrow \mathbb{F}_p^{r_p}
fR′:Fp∗→Fprp
其在sponge构造中使用了Rescue-XLIX permutation。最终sponge函数会将任意长的序列 映射为 任意长的field elements序列:
f
R
′
−
s
p
o
n
g
e
:
F
p
∗
→
F
⋆
f_{R'-sponge}:\mathbb{F}_p^*\rightarrow \mathbb{F}^\star
fR′−sponge:Fp∗→F⋆
然后对该sponge函数裁剪,仅获取其前
r
p
r_p
rp个elements作为结果。
在sponge函数的evaluation过程中,包含了:
3.1)absorbing阶段:会重复以下流程,直到所有的input elements都吸收完毕:
3.2)squeezing阶段:state的前 r p r_p rp个elements即为输出。理论上,可对state递归调用 f R X L I X f_{R^{XLIX}} fRXLIX获得任意长的output elements序列,不过本文限制为output elements数量最多为 r p r_p rp个。
以input elements长度为
2
∗
r
p
2*r_p
2∗rp为例,相应的absorbing阶段具有2次迭代,示意为:
填充Padding:当inputs为任意长度时,sponge函数需要进行填充操作——在inputs之后附加一个
1
∈
F
p
1\in\mathbb{F}_p
1∈Fp以及一堆
0
∈
F
p
0\in\mathbb{F}_p
0∈Fp,使得填充后的inputs长度为
r
p
r_p
rp的整数倍。
相应的SageMath算法示例为:
Rescue-XLIX Permutation f R X L I X f_{R^{XLIX}} fRXLIX中包含了 N N N次Rescue-XLIX round函数迭代,单个round中包含了以下6步:
以
m
=
3
m=3
m=3为例,Rescue-XLIX permutation中第
i
i
i轮示意为:
相应的SageMath代码示例为:
令
g
g
g为
F
p
\mathbb{F}_p
Fp的smallest primitive element,通常取
g
=
2
g=2
g=2,基于此找到order为
p
−
1
p-1
p−1的最小
g
g
g。详细的生成算法参见SageMath代码:
round constants
{
C
i
}
i
=
0
2
m
N
−
1
\{C_i\}_{i=0}^{2mN-1}
{Ci}i=02mN−1选择,见SageMath算法:
α
\alpha
α为the smallest integer that is coprime with
p
−
1
p-1
p−1,
α
−
1
\alpha^{-1}
α−1为其multiplicative inverse in the ring
Z
/
<
p
−
1
>
\mathbb{Z}/
当
gcd
(
p
−
1
,
3
)
=
1
\gcd(p-1,3)=1
gcd(p−1,3)=1时,有
α
=
3
,
α
−
1
=
2
p
−
1
3
\alpha=3,\alpha^{-1}=\frac{2p-1}{3}
α=3,α−1=32p−1。
详细的SageMath算法为:
当
∣
p
∣
≥
32
|p|\geq 32
∣p∣≥32且
80
≤
s
≤
512
80\leq s\leq 512
80≤s≤512时,Gr¨obner basis攻击效率最高,要基于该攻击来选择round数。
详细SageMath算法见:
Rescue-Prime哈希SageMath算法见:
以https://github.com/aszepieniec/stark-anatomy/blob/master/code/rescue_prime.py中的Rescue-Prime哈希函数实现为例,其设置input length固定为1, r b = 1 , m = 1 , c p = 1 , N = 27 , p = 407 ∗ ( 1 < < 119 ) + 1 r_b=1,m=1,c_p=1,N=27,p=407 * (1 << 119) + 1 rb=1,m=1,cp=1,N=27,p=407∗(1<<119)+1 等:【本例只需要一次absorb】
class RescuePrime:
def __init__( self ):
self.p = 407 * (1 << 119) + 1
self.field = Field(self.p)
self.m = 2
self.rate = 1
self.capacity = 1
self.N = 27
self.alpha = 3
self.alphainv = 180331931428153586757283157844700080811
self.MDS = [[FieldElement(v, self.field) for v in [270497897142230380135924736767050121214, 4]],
[FieldElement(v, self.field) for v in [270497897142230380135924736767050121205, 13]]]
self.MDSinv = [[FieldElement(v, self.field) for v in [210387253332845851216830350818816760948, 60110643809384528919094385948233360270]],
[FieldElement(v, self.field) for v in [90165965714076793378641578922350040407, 180331931428153586757283157844700080811]]]
self.round_constants = [FieldElement(v, self.field) for v in [174420698556543096520990950387834928928,
109797589356993153279775383318666383471,
228209559001143551442223248324541026000,
268065703411175077628483247596226793933,
250145786294793103303712876509736552288,
154077925986488943960463842753819802236,
204351119916823989032262966063401835731,
57645879694647124999765652767459586992,
102595110702094480597072290517349480965,
8547439040206095323896524760274454544,
50572190394727023982626065566525285390,
87212354645973284136664042673979287772,
64194686442324278631544434661927384193,
23568247650578792137833165499572533289,
264007385962234849237916966106429729444,
227358300354534643391164539784212796168,
179708233992972292788270914486717436725,
102544935062767739638603684272741145148,
65916940568893052493361867756647855734,
144640159807528060664543800548526463356,
58854991566939066418297427463486407598,
144030533171309201969715569323510469388,
264508722432906572066373216583268225708,
22822825100935314666408731317941213728,
33847779135505989201180138242500409760,
146019284593100673590036640208621384175,
51518045467620803302456472369449375741,
73980612169525564135758195254813968438,
31385101081646507577789564023348734881,
270440021758749482599657914695597186347,
185230877992845332344172234234093900282,
210581925261995303483700331833844461519,
233206235520000865382510460029939548462,
178264060478215643105832556466392228683,
69838834175855952450551936238929375468,
75130152423898813192534713014890860884,
59548275327570508231574439445023390415,
43940979610564284967906719248029560342,
95698099945510403318638730212513975543,
77477281413246683919638580088082585351,
206782304337497407273753387483545866988,
141354674678885463410629926929791411677,
19199940390616847185791261689448703536,
177613618019817222931832611307175416361,
267907751104005095811361156810067173120,
33296937002574626161968730356414562829,
63869971087730263431297345514089710163,
200481282361858638356211874793723910968,
69328322389827264175963301685224506573,
239701591437699235962505536113880102063,
17960711445525398132996203513667829940,
219475635972825920849300179026969104558,
230038611061931950901316413728344422823,
149446814906994196814403811767389273580,
25535582028106779796087284957910475912,
93289417880348777872263904150910422367,
4779480286211196984451238384230810357,
208762241641328369347598009494500117007,
34228805619823025763071411313049761059,
158261639460060679368122984607245246072,
65048656051037025727800046057154042857,
134082885477766198947293095565706395050,
23967684755547703714152865513907888630,
8509910504689758897218307536423349149,
232305018091414643115319608123377855094,
170072389454430682177687789261779760420,
62135161769871915508973643543011377095,
15206455074148527786017895403501783555,
201789266626211748844060539344508876901,
179184798347291033565902633932801007181,
9615415305648972863990712807943643216,
95833504353120759807903032286346974132,
181975981662825791627439958531194157276,
267590267548392311337348990085222348350,
49899900194200760923895805362651210299,
89154519171560176870922732825690870368,
265649728290587561988835145059696796797,
140583850659111280842212115981043548773,
266613908274746297875734026718148328473,
236645120614796645424209995934912005038,
265994065390091692951198742962775551587,
59082836245981276360468435361137847418,
26520064393601763202002257967586372271,
108781692876845940775123575518154991932,
138658034947980464912436420092172339656,
45127926643030464660360100330441456786,
210648707238405606524318597107528368459,
42375307814689058540930810881506327698,
237653383836912953043082350232373669114,
236638771475482562810484106048928039069,
168366677297979943348866069441526047857,
195301262267610361172900534545341678525,
2123819604855435621395010720102555908,
96986567016099155020743003059932893278,
248057324456138589201107100302767574618,
198550227406618432920989444844179399959,
177812676254201468976352471992022853250,
211374136170376198628213577084029234846,
105785712445518775732830634260671010540,
122179368175793934687780753063673096166,
126848216361173160497844444214866193172,
22264167580742653700039698161547403113,
234275908658634858929918842923795514466,
189409811294589697028796856023159619258,
75017033107075630953974011872571911999,
144945344860351075586575129489570116296,
261991152616933455169437121254310265934,
18450316039330448878816627264054416127]]
def hash( self, input_element ):
# absorb
state = [input_element] + [self.field.zero()] * (self.m - 1)
# permutation
for r in range(self.N):
# forward half-round
# S-box
for i in range(self.m):
state[i] = state[i]^self.alpha
# matrix
temp = [self.field.zero() for i in range(self.m)]
for i in range(self.m):
for j in range(self.m):
temp[i] = temp[i] + self.MDS[i][j] * state[j]
# constants
state = [temp[i] + self.round_constants[2*r*self.m+i] for i in range(self.m)]
# backward half-round
# S-box
for i in range(self.m):
state[i] = state[i]^self.alphainv
# matrix
temp = [self.field.zero() for i in range(self.m)]
for i in range(self.m):
for j in range(self.m):
temp[i] = temp[i] + self.MDS[i][j] * state[j]
# constants
state = [temp[i] + self.round_constants[2*r*self.m+self.m+i] for i in range(self.m)]
# squeeze
return state[0]
上述哈希运算,对应的execution trace表示为:【将absorb之后的state、以及Rescue-XLIX Permutation
f
R
X
L
I
X
f_{R^{XLIX}}
fRXLIX中每轮round函数执行后的state,记录在trace中。即trace中有
m
m
m个field elements】
【以下execution trace表示法,共有
N
+
1
N+1
N+1条记录。】
def trace( self, input_element ):
trace = []
# absorb
state = [input_element] + [self.field.zero()] * (self.m - 1)
# explicit copy to record state into trace
trace += [[s for s in state]]
# permutation
for r in range(self.N):
# forward half-round
# S-box
for i in range(self.m):
state[i] = state[i]^self.alpha
# matrix
temp = [self.field.zero() for i in range(self.m)]
for i in range(self.m):
for j in range(self.m):
temp[i] = temp[i] + self.MDS[i][j] * state[j]
# constants
state = [temp[i] + self.round_constants[2*r*self.m+i] for i in range(self.m)]
# backward half-round
# S-box
for i in range(self.m):
state[i] = state[i]^self.alphainv
# matrix
temp = [self.field.zero() for i in range(self.m)]
for i in range(self.m):
for j in range(self.m):
temp[i] = temp[i] + self.MDS[i][j] * state[j]
# constants
state = [temp[i] + self.round_constants[2*r*self.m+self.m+i] for i in range(self.m)]
# record state at this point, with explicit copy
trace += [[s for s in state]]
# squeeze
# output = state[0]
return trace
以上execution trace表示法,共有
N
+
1
N+1
N+1条记录,即总步数
T
=
N
+
1
T=N+1
T=N+1。
边界约束条件为:
def boundary_constraints( self, output_element ):
constraints = []
# at start, capacity is zero
constraints += [(0, 1, self.field.zero())]
# at end, rate part is the given output element
constraints += [(self.N, 0, output_element)]
return constraints
以上execution trace表示法,共有
N
+
1
N+1
N+1条记录,即总步数cycles
T
=
N
+
1
T=N+1
T=N+1。
令
D
t
r
a
c
e
D_{trace}
Dtrace为trace evaluation domain。
D
t
r
a
c
e
D_{trace}
Dtrace的generator为
ο
\omicron
ο,其order为
2
k
≥
T
+
1
2^k \geq T+1
2k≥T+1。
令
D
t
r
a
c
e
=
{
ο
i
∣
i
∈
Z
}
D_{trace} = \lbrace \omicron^i \vert i \in \mathbb{Z}\rbrace
Dtrace={οi∣i∈Z}。
本例中, T = 27 + 1 = 28 T=27+1=28 T=27+1=28,设置了co-linearity check等参数为:
expansion_factor = 4
num_colinearity_checks = 2
security_level = 2
为实现zero-knowledge STARK,引入的randomizer数为:
self.num_randomizers = 4*num_colinearity_checks
transition_constraints_degree
默认为2。
相应的trace evaluation domain 和 FRI evaluation domain参数为:
randomized_trace_length = self.original_trace_length + self.num_randomizers # 28+8=36
omicron_domain_length = 1 << len(bin(randomized_trace_length * transition_constraints_degree)[2:]) # 离(36*2)最近的2^k,从而结果为2^7=128。
fri_domain_length = omicron_domain_length * expansion_factor
transition constraints约束表达的基本流程为:
def round_constants_polynomials( self, omicron ):
first_step_constants = []
for i in range(self.m):
domain = [omicron^r for r in range(0, self.N)]
values = [self.round_constants[2*r*self.m+i] for r in range(0, self.N)]
univariate = Polynomial.interpolate_domain(domain, values)
multivariate = MPolynomial.lift(univariate, 0)
first_step_constants += [multivariate]
second_step_constants = []
for i in range(self.m):
domain = [omicron^r for r in range(0, self.N)]
values = [self.field.zero()] * self.N
#for r in range(self.N):
# print("len(round_constants):", len(self.round_constants), " but grabbing index:", 2*r*self.m+self.m+i, "for r=", r, "for m=", self.m, "for i=", i)
# values[r] = self.round_constants[2*r*self.m + self.m + i]
values = [self.round_constants[2*r*self.m+self.m+i] for r in range(self.N)]
univariate = Polynomial.interpolate_domain(domain, values)
multivariate = MPolynomial.lift(univariate, 0)
second_step_constants += [multivariate]
air = []
for i in range(self.m):
# compute left hand side symbolically
# lhs = sum(MPolynomial.constant(self.MDS[i][k]) * (previous_state[k]^self.alpha) for k in range(self.m)) + first_step_constants[i]
lhs = MPolynomial.constant(self.field.zero())
for k in range(self.m):
lhs = lhs + MPolynomial.constant(self.MDS[i][k]) * (previous_state[k]^self.alpha)
lhs = lhs + first_step_constants[i]
# compute right hand side symbolically
# rhs = sum(MPolynomial.constant(self.MDSinv[i][k]) * (next_state[k] - second_step_constants[k]) for k in range(self.m))^self.alpha
rhs = MPolynomial.constant(self.field.zero())
for k in range(self.m):
rhs = rhs + MPolynomial.constant(self.MDSinv[i][k]) * (next_state[k] - second_step_constants[k])
rhs = rhs^self.alpha
# equate left and right hand sides
air += [lhs-rhs]
基于execution trace、boundary constraints、transition constraints(AIR),基于FRI evaluation domain,调用stark.prove
生成STARK证明。
# prove
trace = rp.trace(input_element)
air = rp.transition_constraints(stark.omicron)
boundary = rp.boundary_constraints(output_element)
proof = stark.prove(trace, air, boundary)
num_registers
实际就是Rescue-Prime的state width,本例中为2。
num_randomizers = 4*num_colinearity_checks
生成STARK proof的基本流程为:
num_randomiers
行随机值,以实现zero-knowledge: # 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_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)]
boundary = rp.boundary_constraints(output_element)
# -------------------
def boundary_constraints( self, output_element ):
constraints = []
# at start, capacity is zero
constraints += [(0, 1, self.field.zero())]
# at end, rate part is the given output element
constraints += [(self.N, 0, output_element)]
return constraints
def boundary_interpolants( self, boundary ):
interpolants = []
for s in range(self.num_registers):
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
def zerofier_domain( domain ):
field = domain[0].field
x = Polynomial([field.zero(), field.one()])
acc = Polynomial([field.one()])
for d in domain:
acc = acc * (x - Polynomial([d]))
return acc
# 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]
fri_domain_length = omicron_domain_length * expansion_factor
self.omega = self.field.primitive_nth_root(fri_domain_length)
self.generator = self.field.generator()
self.fri = Fri(self.generator, self.omega, fri_domain_length, self.expansion_factor, self.num_colinearity_checks)
fri_domain = self.fri.eval_domain()
# --------------
def eval_domain( self ):
return [self.offset * (self.omega^i) for i in range(self.domain_length)]
# commit to boundary quotients
boundary_quotient_codewords = []
boundary_quotient_Merkle_roots = []
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)
point = [Polynomial([self.field.zero(), self.field.one()])] + trace_polynomials + [tp.scale(self.omicron) for tp in trace_polynomials]
# 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]
self.original_trace_length = num_cycles
def transition_zerofier( self ):
domain = self.omicron_domain[0:(self.original_trace_length-1)]
return Polynomial.zerofier_domain(domain)
# divide out zerofier
transition_quotients = [tp / self.transition_zerofier() for tp in transition_polynomials]
self.max_degree(transition_constraints)
的随机多项式randomizer_polynomial(来对transition polynomials进行hide),并对其进行commit: # commit to randomizer polynomial
randomizer_polynomial = Polynomial([self.field.sample(os.urandom(17)) for i in range(self.max_degree(transition_constraints)+1)])
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())
def sample_weights( self, number, randomness ):
return [self.field.sample(blake2b(randomness + bytes(i)).digest()) for i in range(0, number)]
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()])
max_degree = self.max_degree(transition_constraints)
terms = []
terms += [randomizer_polynomial]
for i in range(len(transition_quotients)):
terms += [transition_quotients[i]]
shift = max_degree - 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 = max_degree - 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
combined_codeword = combination.evaluate_domain(fri_domain)
# prove low degree of combination polynomial, and collect indices
indices = self.fri.prove(combined_codeword, proof_stream)
# process indices
duplicated_indices = [i for i in indices] + [(i + self.expansion_factor) % self.fri.domain_length for i in indices]
quadrupled_indices = [i for i in duplicated_indices] + [(i + (self.fri.domain_length // 2)) % self.fri.domain_length for i in duplicated_indices]
quadrupled_indices.sort()
# open indicated positions in the boundary quotient codewords
for bqc in boundary_quotient_codewords:
for i in quadrupled_indices:
proof_stream.push(bqc[i])
path = Merkle.open(i, bqc)
proof_stream.push(path)
# ... as well as in the randomizer
for i in quadrupled_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()
Rescue-Prime hash运算中涉及:
因此,验证STARK证明的输入有:
# verify
verdict = stark.verify(proof, air, boundary)
# -----
def verify( self, proof, transition_constraints, boundary, proof_stream=None ):
。。。。。。
验证STARK证明的基本流程为:
# 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]
duplicated_indices.sort()
leafs = []
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
randomizer = dict()
for i in duplicated_indices:
randomizer[i] = proof_stream.pull()
path = proof_stream.pull()
verifier_accepts = verifier_accepts and Merkle.verify(randomizer_root, i, path, randomizer[i])
if not verifier_accepts:
return False
self.fri.verify
返回的values值是否匹配: # 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]
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)
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
verifier_accepts = verifier_accepts and (combination == values[i])
if not verifier_accepts:
return False