1. 程式人生 > >STARKs,Part-3:攻堅(下)

STARKs,Part-3:攻堅(下)

在這裡插入圖片描述感謝上帝,今天是 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, x4) 。如果 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, x4) ,並從據此計算 R(special_x) = Q(special_x, x4) = P’(x**4) 。x^4 有N / 4個可能的值,這種方法使得我們可以輕鬆計算所有這些值。

在這裡插入圖片描述

-這個圖表來自本系列第2部分。記住這張圖表對於理解本文很有幫助。-

我們的證明包含來自 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(g1x), K(x)) = P(g1x) - 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 (或 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) 的倍數,我們通過……提供商數來實現這一點!

在這裡插入圖片描述

-紫色:計算軌跡多項式 § 。綠色:插值 (I)(注意插值是如何構造的,其在 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 * steps ,而 P 和 B 的次數小於 steps ,所以我們實際上構建了P,P * xsteps,B,Bsteps 和 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[i3])
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(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 倍。

原文連結: https://vitalik.ca/general/2018/07/21/starks_part_3.html
作者: Vitalik
翻譯: 喏貝爾
稿源:公眾號 Unitimes·獨角時代(ID:Uni-times)