以太坊的每筆交易Transaction會被轉換成一個Message物件,傳入EVM中執行,隨後EVM將Message物件轉換成Contract物件。如果是一筆普通轉賬交易,那麼直接修改 StateDB 中對應的賬戶餘額即可。如果是智慧合約的建立或者呼叫,則透過 EVM 中的直譯器載入和執行位元組碼,執行過程中可能會查詢或者修改 StateDB。從圖中可以看出,Contract物件會根據合約的地址,從資料庫中載入相應的合約程式碼,然後送入直譯器中進行執行。
EVM直譯器是一個基於棧式的機器,它有自己的PC、堆疊、記憶體和Gas池。一份合約程式碼會被解釋為一條條OPCode,然後執行。在執行OPCode之前會檢查該命令所需要的Gas和當前所剩Gas,如果Gas不足,則會返回ErrOutOfGas錯誤。因為EVM是基於棧的虛擬機器,他沒有暫存器之類的中間儲存,所有的操作都要透過一個棧來進行維護,所以它的執行效率比較低,完成一個複雜操作可能需要較長的時間。更復雜的操作可能無法在有效時間執行完畢。具體可參見。
2、隱私資產
區塊鏈是公開的分散式交易賬本,鏈上的資料都是公開可見的,雖然一筆交易的傳送方和接收方無法和現實生活中買賣雙方進行關聯,但是可以透過對鏈上的資料進行地址聚簇分析,從而得出一些地址和身份的關聯資訊。且交易的金額在鏈上也是公開的,可見儘管資料的公開透明保證了賬本真實和不可篡改的特點,但是這也使得很多需要隱私的場景無法在區塊鏈上進行運用。
在此背景下,隱私資產的概念被提出。透過使用密碼學等技術手段將交易的傳送方、接收方和交易金額進行隱藏,而礦工(驗證交易者)可以在不需要知道具體資料的情況對一筆交易的合法性進行驗證。常用的隱私資產實現的方法有Mimble-Wimble、ZK-SNARK等。
由於合約模式的廣泛使用,一些專案方想到可以透過使用智慧合約來實現隱私交易,如Nightfall、Zether、AZTEC等,透過部署和隱私交易有關的智慧合約來達到在鏈上發行隱私資產的目的。
預編譯合約
1、預編譯合約的概念
因為EVM是基於棧的虛擬機器,它根據操作的內容來計算gas,所以如果牽涉到十分複雜的計算,把運算過程放在EVM中執行就可能十分地低效,同時消耗非常多的gas。比如在zk-snark中,需要進行橢圓曲線的加減和配對運算,這個過程十分複雜,放在EVM中執行是不現實的。這就是以太坊提出預編譯合約的初衷。
預編譯合約是EVM中為了提供一些不適合寫成opcode的較為複雜的庫函式(多用於加密、雜湊等複雜運算)而採用的一種折中方案,適用於合約邏輯簡單但呼叫頻繁,或者合約邏輯固定而計算量大的場景。預編譯合約通常是在客戶端用客戶端程式碼實現,由於不需要使用EVM,所以執行速度快。對於開發者來說比直接使用執行在EVM上的函式消耗更低。
現在以太坊已經實現的預編譯合約如下:
從程式碼層面來看,所謂的地址其實就是合約陣列的下標,一個下標標識了一個預編譯合約。其中和隱私演算法有關的三個預編譯合約是bn256Add()、bn256ScalarMul()、bn256Pairing()。
2、預編譯合約的實現
在evm.go檔案中,封裝著evm的操作邏輯,裡面有4個函式用於呼叫智慧合約,Call()、CallCode()、DelegateCall()、StaticCall()。這四個函式做的工作都是生成一個contract物件,但是具體的細節如引數等會有一些差異。contract例項化之後,都是呼叫evm.go中的run函式來執行智慧合約。該函式對預編譯合約和非預編譯合約呼叫兩種情況均有考慮。下面的程式碼中,第一個分支是當該合約是一個預編譯合約的時候,透過指定precompiles這個陣列變數的下標來指定某一個預編譯合約,從而例項化p引數。此處陣列的下標其實就對應了預編譯合約陣列宣告時地址的概念。之後呼叫RunPrecompiledContract函式來執行預編譯合約。而如果是非預編譯合約,從程式碼中可以看到是呼叫了evm的直譯器進行執行。
go-ethereum/core/vm/evm.go
在RunPrecompiledContract函式中,可以看到p變數實現接收器進行bn256曲線加的操作,然後將結果進行返回。可以很明顯地看到這部分操作是在客戶端語言的執行過程中進行計算的。具體可參見[2]。
go-ethereum/core/vm/contracts.go
go-ethereum/core/vm/contracts.go
3、預編譯合約的使用
在智慧合約程式碼中,預編譯合約也像普通的合約一樣,可以直接在合約檔案中進行呼叫,但呼叫方式有一些不同。透過在.sol檔案中註明一個assembly程式碼塊進行預編譯合約的呼叫。呼叫規範和呼叫的引數如下所示。
一個實現橢圓曲線加法的例項如下
預編譯合約在隱私資產中的應用
1、橢圓曲線的預編譯合約
以太坊現在處理隱私的解決方案是使用zk-snark[6][8],但是zk-snark是一個及其複雜的數學過程,裡面牽涉到很多橢圓曲線的計算。經過前面的分析,這個過程放在evm裡面執行是十分不現實的,所以為了支援zk-snark的相關運算,以太坊分別在EIP196[3]和EIP197[4]裡增加了三個與zk-snark運算有關的預編譯合約,可以供開發者呼叫。
EIP-196增加了在alt_bn128曲線上的ECADD()和ECMUL()兩個預編譯合約,其中ECADD()消耗500gas,ECMUL()消耗40000gas。
EIP-197增加了在alt_bn128曲線上的配對Pairing函式,消耗gas為80000*k+100000(k和點對個數有關)。
橢圓曲線上的加和乘比較好理解,pairing的出現是因為zk-snark中有KCA(Knowledge of Coefficient Test and Assumption)認證過程[6],需要用到雙線性對映來進行證明,具體可參見V神medium[7]。Pairing就是證明過程中會用到的公式。Pairing函式的輸入是一個不定長的列表,因為不同的zk-snark演算法可能資料量是不同的,並且pairing函式所消耗的gas也與輸入的點對個數有關。
現在許多利用預編譯合約實現隱私的演算法都使用到了pairing過程,例如EYBlockchain、AZTEC等。pairing是ZK-snark所必須要求的一個步驟,耗費的gas巨大,當然官方也在做最佳化。實現pairing的過程一般要在pairing-friendly曲線上來進行,這是一類具有特殊屬性的曲線,主要表現在計算pairing速度很快。所以如果要用pairing這個過程,那麼常用的secp256k1這樣的曲線顯然是不適合的,就需要增加對pairing-friendly曲線的支援。
現在主流的pairing-friendly曲線有Barreto-Naehrig(BN)系列和Barreto-Lynn-Scott(BLS)系列[9],以太坊使用的就是BN系列,ZCASH使用的是BLS系列,以太坊後續也會加上對BLS曲線的支援。BLS的曲線綜合表現會好很多,計算量也相對較小。但是不管怎麼樣,選擇曲線都有安全和效率之間進行平衡的一個取捨。
值得一提的是,如果不需要用到pairing過程,也就不需要pairing-friendly曲線。另外一種方案是增加對secp256k1曲線的預編譯合約的支援。例如PGC團隊雖然使用了bn256ADD和bn256MUL兩個預編譯合約,即使用了bn系列的曲線,但是他們的演算法是不需要pairing的,如果換成對secp256k1的預編譯合約支援會對演算法效率有提高。
針對於C++版本實現的問題,現在主要有兩個外部庫對這些操作進行了封裝和實現。首先是Libff,這也是現在以太坊正在 使用的庫,原始碼中LibSnark.cpp呼叫了libff庫的相關計算函式。還有一個是MCL庫,這是EIP-1108所推薦的庫。
2、隱私資產專案
現在在業內比較流行的四個隱私解決方案是EYBlockchain、PGC、Zether、AZTEC。
EYBlockchain是基於以太坊zk-snark零知識證明實現的隱私資產。它透過移植以太坊推薦的ZoKrates工具包進行線下的零知識證明的生成。演算法需要用bn256曲線的add、multiply、pairing過程。一筆轉賬Gas消耗在2.7M左右。
Zether是一個以太坊上的匿名支付協議,以智慧合約 Zether Smart Contract(ZSC)的形式部署在以太坊上,並且具有稱為 Zether 令牌(ZTH)的代幣,其可作為 ElGamal 公鑰的 Zether 賬戶之間傳輸的載體,並支援匿名的智慧合約互動。演算法需要用到bn256曲線的add、multiply演算法。
PGC是改進版的Zether演算法,PGC使用原版的Elgamal和原版的bulletproof零知識證明演算法。PGC使用了bn256曲線的add、multiply演算法,但是,如果secp256k1橢圓曲線的預編譯合約可以實現,那麼PGC演算法可以不使用bn系列的曲線。
AZTEC結合同態證明和range proof提供零知識證明,以在以太坊上提供隱私資產。AZTEC需要使用bn256曲線上的add、multiply、pairing操作,具體操作量為(3n+m-1)add+(2n+2m-2)multiply+1pairing(n和m分別是交易票據的數量)。
3、gas問題
EIP-196和EIP-197的提出,使得很多零知識證明的演算法可以在以太坊上執行。但還是有一個問題,儘管把這些複雜的數學過程透過預編譯合約的方式來實現,它所消耗的gas還是十分巨大,不誇張地說,在某些場景下,轉賬一筆隱私資產所消耗的Gas可能比轉賬的金額還要高。以太坊每個block的最多gas消耗為8M,這就使得很多隱私的專案無法真正地落地。
為了降低預編譯合約的gas,AZTEC的員工提出了EIP-1108[5]的改進。
EIP-1108是對add、mul、pairing這三個預編譯合約所做的一個最佳化,透過對呼叫庫的底層演算法進行最佳化,提高了程式碼的執行效率,從而降低了gas。Golang版本的預編譯合約名也變成了bn256。bn256就是alt_bn128,只是更換了一個說法。256是指公式中p的長度,而128是指曲線的安全等級。這是一條曲線的兩個不同的屬性描述。所以EIP-1108並不是更換了曲線降低了gas,而是對實現的演算法進行了最佳化。更新後的gas對比圖如下。EIP-1108目前處於draft的狀態,程式碼中演算法部分已經進行改進,但是gas的值還沒有進行更新。
若使用EIP-1108的改進方法,許多隱私資產的演算法可以得到大大的最佳化。Zether一筆轉賬交易gas消耗可從7188000減少到1700000。PGC一筆轉賬交易gas消耗可從6563000減少到1100000。AZTEC則從121000n+41000m+219000降低到12200n+6200m+85930。
如果要部署預編譯合約,如何確定預編譯合約的Gas值是一個嚴峻的問題。預編譯合約gas的設定是和操作的計算量有關的,例如EIP-1108中介紹了Pairing gas的計算方法。它是根據ecrecover的效率和既定gas來決定的。一次ecrecover呼叫需要花費116ms,它的gas被設定成了3000,這樣得到了1ms執行花費25.86gas的事實。因為pairing計算花費的時間分為兩個部分,一個是基時間base_tim,可以理解為運算的啟動時間,還有一個是浮動時間per_pair_time,它和輸入的計算量有關。計算1個pairing需要耗費3037ms,計算10個pairing耗費14663ms。得出base_time和per_pair_time,乘以既定好的25.86gas,得出pairing過程的gas消耗。如此,則可以根據具體的環境做相關的benchmark以規範gas的設定。
Qtum與隱私資產
Qtum每個block的最多gas消耗為40M,每個transaction的最多gas消耗為20M,因此隱私資產在Qtum上執行基本不會受到gas的限制。但是使用EVM去執行一些計算量大的隱私演算法是非常低效的。所以,未來將會把一些基礎的、通用的隱私演算法做成預編譯合約的形式,讓隱私資產能夠更加高效地執行在Qtum上,也節省了合約的開發工作。
對於橢圓曲線而言,下一步可以考慮增加BLS和secp256k1橢圓曲線的預編譯合約。BLS的效能和安全性都優於bn256,且是pairing-friendly,所以可以用於代替bn256。並且如果要部署pairing預編譯合約,也可以找一個已經定好gas的預編譯合約進行參照,分別benchmark,進行類似的gas設定。secp256k1雖然不是pairing-friendly,但其效能更好,且通用性更強,廣泛用於區塊鏈的簽名、加密演算法中。
對於零知識證明而言,未來可以考慮增加Bulletproof演算法作為預編譯合約。Bulletproof是目前區塊鏈中廣泛使用的範圍證明演算法,主要用於證明MimbleWimble中隱藏的交易金額是一個正數。Bulletproof已經在Grin和Beam專案中實現並穩定執行。Bulletproof的驗證過程計算量大,因此使用預編譯合約實現是更為合適的選擇。有了Bulletproof預編譯合約之後,MimbleWimble就能以合約的方式高效地執行於Qtum上。
參考文獻
[1]https://www.chainnews.com/articles/693606750348.htm
[2]https://blog.csdn.net/hello2mao/article/details/87254340
[3]https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md
[4]https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md
[5]https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1108.md
[6] https://www.jianshu.com/p/7b772e5cdaef
[7]https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627
[8]https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6
[9]https://blog.csdn.net/mutourend/article/details/92784689