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

相關文章
相關標籤/搜索