以太坊創始人Vitalik Buterin 7月21日在其我的網站上發佈了論文STARKs III: Into the Weeds。此論文爲其系列文章的第三篇。html
如下爲其中文譯文:python
STARKs III: Into the Weeds(上)git
特別感謝Eli ben Sasson一如既往地提供幫助;也特別感謝Chih-Cheng Liang和Justin Drake的審閱。github
前方高能:如下內容涉及大量數學與pythonweb
爲本系列的第1部分[1]和第2部分[2]的後續內容,本文將介紹在實際中實現STARK的途徑與效果,並使用python語言進行實現。STARKs(「可擴展的透明知識參數」是一種用於構建關於f(x)=y 的證實的技術。其中,f 可能須要很長時間來計算,但該證實能夠很是快速地獲得驗證。STARK是「雙重可擴展的」:對於帶有 t 步的計算,其須要大約O(t * log(t))步來生成證實,這多是最優的;而且其須要 ~O(log2(t))步來進行驗證。在這種方式中,哪怕 t 的值很大,只要在必定範圍內,那麼計算過程也比原始計算要快得多。STARK一樣擁有可進行隱私保護「零知識」屬性。儘管咱們將這一屬性應用到本用例中,可是建立可驗證的延遲函數並不須要這個屬性,因此咱們不須要擔憂。算法
首先,如下是關於本文的免責聲明:編程
此代碼未通過全面審覈,生產用例的可靠性沒法保證數組
這些代碼遠非最理想的實現(它們都是用Python編寫的,你還想怎麼樣)app
考慮到特定應用的效率緣由,STARK「在現實生活中」(即在Eli和co的生產實現中實現)傾向於使用二進制域而不是素域。然而,他們確實在他們的着做中強調,基於素域的方法對於本文描述的STARK也是合理而且可行的。ide
不存在實現STARK的「惟一正確道路」。它是一種普遍的密碼學和數學結構,針對不一樣應用具備不一樣的最佳設置。此外,關於減小證實者和驗證者複雜度並提升可靠性的研究仍在繼續。
本文絕對但願你已經瞭解模運算和素域的工做原理,並熟悉多項式、插值和求值的概念。不然的話,請回到本系列的第2部分[3],以及以前關於二次方程式算術的編程的文章[4]。
如今,咱們進入正題。
這是咱們將要實現的STARK函數:
def mimc(inp, steps, round_constants): start_time = time.time() for i in range(steps-1): inp = (inp**3 + round_constants[i % len(round_constants)]) % modulus print(「MIMC computed in %.4f sec」 % (time.time() – start_time)) return inp
咱們之因此選擇MIMC(參見論文[5])做爲例子,是由於它既(i)易於理解,同時(ii)足夠有趣,而且在現實生活中實在的用處。該函數可被看做下圖的形式:
注意:在許多關於MIMC的討論中,你一般會看到人們使用的是XOR而不是+。這是由於MIMC一般在二進制域上完成,而二進制域的加法就是XOR。在這裏,咱們主要圍繞素域進行。
在本例中,循環常量是一個相對較小的列表(例如只包含64項),列表中的數據不斷循環(也就是說,在k[64]以後循環回到k[1])。
正如咱們在這裏所作的那樣,具備很是多輪次的MIMC做爲可驗證的延遲函數是很是有用的——這是一種難以計算的函數,尤爲是沒法並行計算,但驗證過程相對容易。MIMC自己在某種程度上實現了「零知識」屬性,由於MIMC能夠「向後」計算(從其相應的「輸出」中恢復「輸入」),但向後計算須要的計算時間比向前計算多100倍(而且兩種方向的計算時間都沒法經過並行化來顯著加快)。所以,你能夠將向後計算函數視爲「計算」不可並行化工做量證實的行爲,並將前向計算函數計算爲「驗證」它的過程。
咱們能夠由x -> x^(2p-1)/3 得出x -> x^3的倒數。根據費馬的小定理[6],這是正確的。費馬小定理儘管「小」,但毫無疑問,它對數學的重要性要大於更着名的「費馬最後定理」。
咱們在這裏嘗試實現的是,經過使用STARK使驗證更有效——相對於驗證者必須本身在前向運行MIMC,證實者在完成「後向」計算後,將計算「前向」計算的STARK,而且驗證者只需簡單地驗證STARK。咱們但願計算STARK的開銷可以小於前向運行MIMC的速相對於後向的速度差別,所以證實者的時間仍將由最初的「後向」計算而不是(高度可並行化的)STARK計算主導。不管原始計算的耗時多長,STARK的驗證均可以相對較快(在咱們的python實現中,約爲0.05到0.3秒)。
全部計算均以2^256 – 351 * 2^32 + 1爲模。咱們之因此使用這個素域模數是由於它是2^256之內最大的素數,它的乘法組包含一個2^32階亞組(也就是說,存在數字g,使得g的連續冪模這個素數以後可以在2^32個循環之後回到1) ,其形式爲6k + 5。第一個屬性是必要的,它確保咱們的FFT和FRI算法的有效版本能夠發揮做用。第二個屬性確保MIMC實際上能夠「向後」計算(參見上述關於x -> x^(2p-1)/3的使用)。
咱們首先構建一個可進行素域運算以及在素域上進行多項式運算的方便的類。其代碼在此[7]。初始的細節以下:
class PrimeField(): def __init__(self, modulus): # 快速素性檢驗 assert pow(2, modulus, modulus) == 2 self.modulus = modulus def add(self, x, y): return (x+y) % self.modulus def sub(self, x, y): return (x-y) % self.modulus def mul(self, x, y): return (x*y) % self.modulus
以及用於計算模逆的擴展歐幾里德算法[8](至關於在素域中計算1 / x):
#使用擴展的歐幾里德算法進行模逆計算 def inv(self, a): if a == 0: return 0 lm, hm = 1, 0 low, high = a % self.modulus, self.modulus while low > 1: r = high//low nm, new = hm-lm*r, high-low*r lm, low, hm, high = nm, new, lm, low return lm % self.modulus
上述算法的開銷相對較大。所幸的是,在須要進行衆多模逆計算的特殊狀況中,有一個簡單的數學技巧能夠幫助咱們計算多個逆,咱們稱之爲蒙哥馬利批量求逆[9]:
使用蒙哥馬利批量求逆來計算模逆,其輸入爲紫色,輸出爲綠色,乘法門爲黑色,紅色方塊是_惟一的_模逆。
下述代碼實現了這個算法,並附有一些略微醜陋的特殊狀況邏輯。若是咱們正在求逆的集合中包含零,那麼它會將這些零的逆設置爲0並繼續前進。
def multi_inv(self, values): partials = [1] for i in range(len(values)): partials.append(self.mul(partials[-1], values[i] or 1)) inv = self.inv(partials[-1]) outputs = [0] * len(values) for i in range(len(values), 0, -1): outputs[i-1] = self.mul(partials[i-1], inv) if values[i-1] else 0 inv = self.mul(inv, values[i-1] or 1) return outputs
當咱們開始處理多項式的求值集合劃分時,這種批量求逆算法將會很是重要。
如今咱們繼續進行多項式運算。咱們將多項式視爲一個數組,其中元素 i 是第 i 次項(例如, x^3 + 2x + 1變爲[1,2,0,1])。如下是對某一點上的多項式求值的運算:
#對某一點上的多項式求值 def eval_poly_at(self, p, x): y = 0 power_of_x = 1 for i, p_coeff in enumerate(p): y += power_of_x * p_coeff power_of_x = (power_of_x * x) % self.modulus return y % self.modulus
思考題:
若是模數爲31,那麼f.eval_poly_at([4, 5, 6], 2) 的輸出是多少?
答案是:
6 * 2^2 + 5 * 2 + 4 = 38,38 mod 31 = 7。
還有對多項式進行加、減、乘、除的代碼;教科書上通常冗長地稱之爲加法/減法/乘法/除法。有一個很重要的內容是拉格朗日插值,它將一組x和y座標做爲輸入,並返回經過全部這些點的最小多項式(你能夠將其視爲多項式求值的逆):
#構建一個在全部指定x座標處返回0的多項式 def zpoly(self, xs): root = [1] for x in xs: root.insert(0, 0) for j in range(len(root)-1): root[j] -= root[j+1] * x return [x % self.modulus for x in root] def lagrange_interp(self, xs, ys): #生成主分子多項式,例如(x – x1) * (x – x2) * … * (x – xn) root = self.zpoly(xs) #生成每一個值對應的分子多項式,例如,當x = x2時, #經過用主分子多項式除以對應的x座標 # 獲得(x – x1) * (x – x3) * … * (x – xn) nums = [self.div_polys(root, [-x, 1]) for x in xs] #經過求出在每一個x處的分子多項式來生成分母 denoms = [self.eval_poly_at(nums[i], xs[i]) for i in range(len(xs))] invdenoms = self.multi_inv(denoms) #生成輸出多項式,即每一個值對應的分子的總和 # 多項式從新調整爲具備正確的y值 b = [0 for y in ys] for i in range(len(xs)): yslice = self.mul(ys[i], invdenoms[i]) for j in range(len(ys)): if nums[i][j] and ys[i]: b[j] += nums[i][j] * yslice return [x % self.modulus for x in b]
相關數學說明請參閱本文關於「M-of-N」[10]的部分。須要注意的是,咱們還有特殊狀況方法lagrange_interp_4和lagrange_interp_2來加速次數小於2的拉格朗日插值和次數小於4的多項式運算。
若是你仔細閱讀上述算法,你可能會注意到拉格朗日插值和多點求值(即求在N個點處次數小於N的多項式的值)都須要執行耗費二次時間。舉個例子,一千個點的拉格朗日插值須要數百萬步才能執行,一百萬個點的拉格朗日插值則須要幾萬億步。這種超低效率的情況是不可接受的。所以,咱們將使用更有效的算法,即快速傅立葉變換。
FFT僅須要O(n * log(n))時間(即1000個點須要約10000步,100萬個點須要約2000萬步),但它的範圍更受限制:其x座標必須是知足N = 2^k階的單位根[11]的完整集合。也就是說,若是有N個點,則x座標必須是某個p的連續冪1,p,p^2,p^3 …,其中,p^N = 1。該算法只須要一個小參數調整就能夠使人驚訝地用於多點求值或插值運算。
思考題:
找出模337爲1的16次單位根,且該單位根的8次冪模337不爲1。
答案是:
59,146,30,297,278,191,307,40
你能夠經過進行諸如[print(x) for x in range(337) if pow(x, 16, 337) == 1 and pow(x, 8, 337) != 1]的操做來獲得上述答案列表。固然,也有適用於更大模數的更智能的方法:首先,經過查找知足pow(x, 336 // 2, 337) != 1(這些答案很容易找到,其中一個答案是5)的值x來識別單個模337爲1的原始根(不是完美的正方形) ,而後取它的(336 / 16)次冪。
如下是算法實現(該實現略微簡化,更優化內容請參閱此處的代碼[12]):
def fft(vals, modulus, root_of_unity): if len(vals) == 1: return vals L = fft(vals[::2], modulus, pow(root_of_unity, 2, modulus)) R = fft(vals[1::2], modulus, pow(root_of_unity, 2, modulus)) o = [0 for i in vals] for i, (x, y) in enumerate(zip(L, R)): y_times_root = y*pow(root_of_unity, i, modulus) o[i] = (x+y_times_root) % modulus o[i+len(L)] = (x-y_times_root) % modulus return o def inv_fft(vals, modulus, root_of_unity): f = PrimeField(modulus) # Inverse FFT invlen = f.inv(len(vals)) return [(x*invlen) % modulus for x in fft(vals, modulus, f.inv(root_of_unity))]
你能夠嘗試鍵入幾個輸入,並檢查它是否會在你使用 eval_poly_at時,給出你指望獲得的答案。例如:
>>> fft.fft([3,1,4,1,5,9,2,6], 337, 85, inv=True) [46, 169, 29, 149, 126, 262, 140, 93] > f = poly_utils.PrimeField(337) >>> [f.eval_poly_at([46, 169, 29, 149, 126, 262, 140, 93], f.exp(85, i)) for i in range(8)] [3, 1, 4, 1, 5, 9, 2, 6]
傅立葉變換將[x[0] …. x[n-1]]做爲輸入,其目標是輸出 x[0] + x[1] + … + x[n-1]做爲第一個元素, x[0] + x[1] * 2 + … + x[n-1] * w**(n-1) 做爲第二個元素等等。快速傅里葉變換經過將數據分紅兩半,並在這兩半數據上進行FFT,而後將結果粘合在一塊兒的方式來實現。
這是信息在FFT計算中的路徑圖表。注意FFT如何基於數據的兩半內容進行兩次FFT複製,並進行「粘合」步驟,而後依此類推直到你獲得一個元素。
通常而言,想要更直觀地瞭解FFT工做原理以及FFT以及多項式數學,我推薦這篇文章[13];關於DFT與FFT的一些更具體的細節,我推薦以爲這篇文章[14]的思路還不錯。可是請注意,大多數關於傅里葉變換的文獻都只談到關於實數和複數的傅里葉變換,並無涉及素域。若是你發現這部份內容實在太難了,而且也不想去理解它,那就把它當成某種詭異的巫術就好了——它之因此有用是由於你運行了幾回代碼並證實這玩意兒確實有用——這樣你內心就舒服多了。
本論文中說起的標註原文連接以下:
[1] https://vitalik.ca/general/2017/11/09/starks_part_1.html
[2] https://vitalik.ca/general/2017/11/22/starks_part_2.html
[3] https://vitalik.ca/general/2017/11/22/starks_part_2.html
[4] https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649
[5] https://eprint.iacr.org/2016/492.pdf
[6] https://en.wikipedia.org/wiki/Fermat's_little_theorem
[7] https://github.com/ethereum/research/blob/master/mimc_stark/poly_utils.py
[8] https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
[9] https://books.google.com/books?id=kGu4lTznRdgC&pg=PA54&lpg=PA54&dq=montgomery+batch+inversion&source=bl&ots=tPJcPPOrCe&sig=Z3p_6YYwYloRU-f1K-nnv2D8lGw&hl=en&sa=X&ved=0ahUKEwjO8sumgJjcAhUDd6wKHWGNA9cQ6AEIRDAE#v=onepage&q=montgomery batch inversion&f=false [10] https://blog.ethereum.org/2014/08/16/secret-sharing-erasure-coding-guide-aspiring-dropbox-decentralizer/
[11] https://en.wikipedia.org/wiki/Root_of_unity
[12] https://github.com/ethereum/research/blob/master/mimc_stark/fft.py
[13]web.cecs.pdx.edu/~maier/cs584/Lectures/lect07b-11-MG.pdf
[14]https://dsp.stackexchange.com/questions/41558
內容來源: Unitimes
原文做者:Vitalik Buterin
翻譯:喏貝爾
原文連接:
https://vitalik.ca/general/2018/07/21/starks_part_3.html
原文篇幅較長,分爲上下兩部分發布
線上課程推薦
線上課程:《8小時區塊鏈智能合約開發實踐》
培訓講師:《白話區塊鏈》做者 蔣勇
課程原價:999元,現價 399元
更多福利: