EOS智慧合約安全終極指南
EOS智慧合約安全終極指南。當世界上最大的ICO,EOS於2018年6月推出時,加密社群變得持懷疑態度,並且由於軟體錯誤而被凍結了2天。但快進4個月,EOS今天佔了乙太網今天所做交易的兩倍以上。通過免費和更快速交易的承諾,EOS最頂級的Dapp擁有大約13,000個每日活躍使用者,而乙太網的最頂級Dapp只有2,000個。
一些常見的智慧合約漏洞幾乎適用於所有平臺。與以太坊一樣,在EOS上編寫的智慧合約需要在主網上上線之前進行稽核。合約中的致命錯誤可以在合約沒有經過足夠的測試時被利用。在本指南中,我們將幫助你避免在EOS上製作下一個殺手dApp的過程中常見的陷阱。
在閱讀本指南之前,瞭解有關EOS開發的一些先決條件資訊非常重要,這些資訊在你閱讀本指南時會很方便。瞭解C++是必須的。開始智慧合約開發的最佳位置是EOSIO自己的文件。
ABI呼叫處理
extern "C" { void apply(uint64_t receiver, uint64_t code, uint64_t action) { class_name thiscontract(receiver); if ((code == N(eosio.token)) && (action == N(transfer))) { execute_action(&thiscontract, &class_name::transfer); return; } if (code != receiver) return; switch (action) {EOSIO_API(class_name, (action_1)(action_n))}; eosio_exit(0); } }
上面是修改後的ABI呼叫程式的示例程式碼。如下所示的更簡單的ABI呼叫程式用於簡化合約的操作處理。
EOSIO_ABI( class_name, (action_1)(action_n) );
ABI呼叫程式/交易處理程式允許合約收聽傳入的eosio.token
交易時間,以及與智慧合約的正常互動。為了避免異常和非法呼叫,繫結每個鍵操作和程式碼以滿足要求是很重要的。
一個例子是由於他們的ABI轉發原始碼中的錯誤而發生在dApp EOSBet Casino上的黑客攻擊 。
if( code == self || code == N(eosio.token) ) { TYPE thiscontract( self ); switch( action ) { EOSIO_API( TYPE, MEMBERS ) } }
上面檢查ABI轉發原始碼的apply動作處理程式允許攻擊者完全繞過eosio.token::transfer()
函式,並在放置之前直接呼叫contract::transfer()
函式而不將EOS轉移到合約中。打賭。對於損失,他沒有得到任何報酬,但一無所獲。然而,對於勝利,他從合約中支付了真正的EOS。
他們通過在傳入操作請求合約之前新增eosio.token
合約轉移操作檢查來修復上述錯誤。
if( code == self || code == N(eosio.token) ) { if( action == N(transfer) ){ eosio_assert( code == N(eosio.token), "Must transfer EOS"); } TYPE thiscontract( self ); switch( action ) { EOSIO_API( TYPE, MEMBERS ) } }
使用語句require_auth(account)
非常重要;進入只需要授權帳戶才能執行的操作。require_auth(_self)
;用於僅授權合約的所有者簽署交易。
操作中的授權
void token::transfer( account_name from, account_name to, asset quantity) { auto sym = quantity.symbol.name(); require_recipient( from ); require_recipient( to ); auto payer = has_auth( to ) ? to : from; sub_balance( from, quantity ); add_balance( to, quantity, payer ); }
上面的示例程式碼允許任何人呼叫該操作。要解決它,請使用require_auth(from)
,宣告授權付款人呼叫該行動。
儘量避免修改eosio.token合約
最近一位白帽黑客因其eosio.token合約中的方法呼叫問題而設法獲得了10億美元的dapp代幣。Dapp Se7ens(現在處於非活動狀態)在eosio.token合約中聲明瞭一種新方法,用於將其代幣空投到使用者帳戶中。合約沒有反映eosio.token
合約的問題或轉移操作的變化,因此資金神奇地出現在使用者的帳戶上。其次,他們忘記在轉移之前驗證方法中的金額,這允許黑客在此過程中索取10億個代幣。
除了更改最大供應和代幣符號之外,建議避免為自定義函式修改它,因為eosio.token合約中的錯誤可能是致命的。為了便於安全地進行空投,將空投代幣轉移到一個單獨的帳戶並從那裡分發。
修改多索引表屬性
EOS當前將資料儲存在共享記憶體資料庫中,以便跨操作共享。
struct [[eosio::table]] person { account_name key; std::string first_name; std::string last_name; std::string street; std::string city; std::string state; uint64_t primary_key() const { return key; } }; typedef eosio::multi_index<N(people), person> address_index;
上面的示例程式碼建立了一個名為people
的multi_index
表 ,該表基於使用struct person
的該表的單行的資料結構。部署後,EOS目前不允許修改表屬性。eosio_assert_message
斷言失敗將是將被丟擲的錯誤。因此,在部署表之前需要完全考慮屬性。否則,需要建立具有不同名稱的新表,並且在從舊錶遷移到新表時需要特別小心。如果不這樣做可能會導致資料丟失。
數值外溢檢查
在進行算術運算時,如果沒有足夠負責地檢查邊界條件,則值可能會溢位,從而導致使用者資產丟失。
void transfer(symbol_name symbol, account_name from, account_names to, uint64_t balance) { require_auth(from); account fromaccount; eosio_assert(is_balance_within_range(balance), "invalid balance"); eosio_assert(balance > 0, "must transfer positive balance");uint64_t amount = balance * 4; //Multiplication overflow }
在上面的示例程式碼中,使用uint64_t
表示使用者餘額可能會在值乘以時導致溢位。因此,儘量避免使用uint64_t
來表示餘額並對其執行算術運算。使用eosiolib
中定義 的asset結構進行操作,而不是處理溢位條件的精確餘額。
考慮合約中的假設
在執行合約時會有假設需要斷言。如果斷言失敗,使用eosio_assert
將事先處理條件並停止執行特定操作。舉個例子:
void assert_roll_under(const uint8_t& roll_under) { eosio_assert(roll_under >= 2 && roll_under <= 96, "roll under overflow, must be greater than 2 and less than 96"); }
上面的斷言語句假設roll_under
整數大於2且小於96.但如果不是,則丟擲上述訊息並停止執行。沒有發現像上面這樣的區域性問題可能會成為規則制定的全域性災難。
生成正確隨機數
如果沒有準確完成,在EOS區塊鏈上生成正確的隨機數仍然存在風險。如果沒有正確地做到這一點,將導致對手預測結果,在整個過程中對整個系統進行遊戲。像Oracalize.it
這樣的服務可以提供來自外部源的隨機數,但它們很昂貴並且可能出現單點故障。人們過去曾使用Blockchain的上下文變數(塊編號,塊時間戳等)來生成以太坊智慧合約中的隨機數,這只是在遊戲中使用。為了正確地進行生成,程式必須提供一種單一方無法單獨控制的組合隨機性。目前最好的方法之一是Dan Larimar自己生成隨機數時建議的方法。
string sha256_to_hex(const checksum256& sha256) { return to_hex((char*)sha256.hash, sizeof(sha256.hash)); } string sha1_to_hex(const checksum160& sha1) { return to_hex((char*)sha1.hash, sizeof(sha1.hash)); } template <class T> Inline void hash_combine(std::size_t& seed, const T& v) { std::hash<T> hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); }
上面的示例程式碼給出了1到100之間的優化隨機數生成.seed1
是種子,seed2
是上面的使用者種子。作為參考,Dappub
和EOSBetCasino
開放了他們的完整合約,隨機數發生器實現了玩家和開發商之間的公平骰子游戲。
uint8_t compute_random_roll(const checksum256& seed1, const checksum160& seed2) { size_t hash = 0; hash_combine(hash, sha256_to_hex(seed1)); hash_combine(hash, sha1_to_hex(seed2)); return hash % 100 + 1; }
EOSBet最近再次遭到65,000EOS的攻擊,當一個對手欺騙他們的eosio.token
合約時,只要他在自己的錢包之間進行交易,就將EOS送到他的錢包。eosio.token
合約程式碼 通知EOS代幣的傳送者和接收者有傳入代幣。為了模仿這種行為並促進黑客攻擊,對手建立了兩個帳戶,讓我們假設A和B。A與智慧合約的操作有宣告require_recipient(N(eosbetdice11))
。當A通過操作呼叫促進了從A到B的交易時,它通知合約中的轉移功能,就好像該呼叫來自eosio.token
合約一樣。由於EOS沒有真正轉移到合約中,每當黑客輸掉賭注時,他什麼都沒有丟失,但是在贏得賭注時他獲得了獎勵。因此,僅檢查合約名稱和操作名稱是不夠的。
檢查合約中的通知
為了緩解這個問題,該函式應檢查合約是否確實是代幣的接收者。
eosio_assert(transfer_data.from == _self || transfer_data.to == _self, "Must be incoming or outgoing transfer");
在EOS上制定智慧合約時應遵循的最佳做法是什麼?
錯誤是任何軟體不可避免的一部分。它的後果在去中心化的環境中被放大,特別是如果它涉及價值交易。除了上面討論的EOS特定保護措施之外,以下是新智慧合約開發人員應該記住的一些一般預防措施和最佳實踐:
- 在主網上釋出之前,始終獨立於第三方智慧合約審計公司審計合約。
- 在釋出到testnet之前,進行必要的Caveman除錯(當前除錯合約的唯一方法)。EOSIO文件有一個很好的指南。
- 設定提款限額轉移率,避免主網啟動初期出現過多損失。有白帽黑客負責任披露的bug賞金計劃。
- 當檢測到錯誤時,有一個killswitch來凍結合約。
為了實現它,我們在multi_index
表中保留一個標誌。我們使用只能由合約所有者呼叫的操作來設定標誌。然後我們檢查每個公共行動是否將標誌設定為凍結。下面給出了該函式的示例實現。
struct st_frozen { uint64_t frozen; }; typedef singleton<N(freeze), st_frozen> tb_frozen; tb_frozen _frozen; uint64_t getFreezeFlag() { st_frozen frozen_st{.frozen = 0}; return _frozen.get_or_create(_self, frozen_st); } void setFreezeFlag(const uint64_t& pFrozen) { st_frozen frozen_st = getFreezeFlag(); frozen_st.frozen = pFrozen; _frozen.set(frozen_st, _self); } // public Action void freeze() { require_auth(_self); setFreezeFlag(1); } // public Action void unfreeze() { require_auth(_self); setFreezeFlag(0); } // any public action void action(...) { eosio_assert(getFreezeFlag().frozen == 1, "Contract is frozen!"); ... }
隨時瞭解庫中的安全性增強或平臺上的漏洞披露。必要時立即更新庫。至少開源合約程式碼,以便在專案中保持公平性,獨立開發人員可以更快地發現錯誤。
EOS智慧合約安全:結論
自EOS推出僅僅5個月,它已經超出了預期。它所取得的DPOS,可變智慧合約,21個採礦節點等,當然受到權力下放極端主義者的嚴厲批評。然而,考慮到平臺今天提供的可擴充套件性,它並沒有阻止基於以太坊的dApp轉向EOS。無論是EOS還是以太坊贏得戰爭還有待確定,但EOS肯定贏得了這場戰鬥。它將保持不變,直到以太坊設法達到執行“世界計算機”所需的世界所需的可擴充套件性。
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- ofollow,noindex">tendermint區塊鏈開發詳解 ,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裡是原文EOS智慧合約安全終極指南