EOS安全開(kāi)發(fā)智能合約的終極指南

EOS智能合約安全終極指南。當(dāng)世界上最大的ICO,EOS于2018年6月推出時(shí),加密社區(qū)變得持懷疑態(tài)度,并且由于軟件錯(cuò)誤而被凍結(jié)了2天。但快進(jìn)4個(gè)月,EOS今天占了以太網(wǎng)今天所做交易的兩倍以上。通過(guò)免費(fèi)和更快速交易的承諾,EOS最頂級(jí)的Dapp擁有大約13,000個(gè)每日活躍用戶(hù),而以太網(wǎng)的最頂級(jí)Dapp只有2,000個(gè)。

一些常見(jiàn)的智能合約漏洞幾乎適用于所有平臺(tái)。與以太坊一樣,在EOS上編寫(xiě)的智能合約需要在主網(wǎng)上上線(xiàn)之前進(jìn)行審核。合約中的致命錯(cuò)誤可以在合約沒(méi)有經(jīng)過(guò)足夠的測(cè)試時(shí)被利用。在本指南中,我們將幫助你避免在EOS上制作下一個(gè)殺手dApp的過(guò)程中常見(jiàn)的陷阱。

在閱讀本指南之前,了解有關(guān)EOS開(kāi)發(fā)的一些先決條件信息非常重要,這些信息在你閱讀本指南時(shí)會(huì)很方便。了解C++是必須的。開(kāi)始智能合約開(kāi)發(fā)的最佳位置是EOSIO自己的文檔。

ABI調(diào)用處理

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調(diào)用程序的示例代碼。如下所示的更簡(jiǎn)單的ABI調(diào)用程序用于簡(jiǎn)化合約的操作處理。

EOSIO_ABI( class_name, (action_1)(action_n) );

ABI調(diào)用程序/交易處理程序允許合約收聽(tīng)傳入的eosio.token交易時(shí)間,以及與智能合約的正常交互。為了避免異常和非法調(diào)用,綁定每個(gè)鍵操作和代碼以滿(mǎn)足要求是很重要的。

一個(gè)例子是由于他們的ABI轉(zhuǎn)發(fā)源代碼中的錯(cuò)誤而發(fā)生在dApp EOSBet Casino上的黑客攻擊 。

if( code == self || code == N(eosio.token) ) {
TYPE thiscontract( self );
switch( action ) {
EOSIO_API( TYPE, MEMBERS )
}
}

上面檢查ABI轉(zhuǎn)發(fā)源代碼的apply動(dòng)作處理程序允許攻擊者完全繞過(guò)eosio.token::transfer()函數(shù),并在放置之前直接調(diào)用contract::transfer()函數(shù)而不將EOS轉(zhuǎn)移到合約中。打賭。對(duì)于損失,他沒(méi)有得到任何報(bào)酬,但一無(wú)所獲。然而,對(duì)于勝利,他從合約中支付了真正的EOS。

他們通過(guò)在傳入操作請(qǐng)求合約之前添加eosio.token合約轉(zhuǎn)移操作檢查來(lái)修復(fù)上述錯(cuò)誤。

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 )
}
}

使用語(yǔ)句require_auth(account)非常重要;進(jìn)入只需要授權(quán)帳戶(hù)才能執(zhí)行的操作。require_auth(_self);用于僅授權(quán)合約的所有者簽署交易。

操作中的授權(quán)

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 );
}

上面的示例代碼允許任何人調(diào)用該操作。要解決它,請(qǐng)使用require_auth(from),聲明授權(quán)付款人調(diào)用該行動(dòng)??。

盡量避免修改eosio.token合約

最近一位白帽黑客因其eosio.token合約中的方法調(diào)用問(wèn)題而設(shè)法獲得了10億美元的dapp代幣。Dapp Se7ens(現(xiàn)在處于非活動(dòng)狀態(tài))在eosio.token合約中聲明了一種新方法,用于將其代幣空投到用戶(hù)帳戶(hù)中。合約沒(méi)有反映eosio.token合約的問(wèn)題或轉(zhuǎn)移操作的變化,因此資金神奇地出現(xiàn)在用戶(hù)的帳戶(hù)上。其次,他們忘記在轉(zhuǎn)移之前驗(yàn)證方法中的金額,這允許黑客在此過(guò)程中索取10億個(gè)代幣。

除了更改最大供應(yīng)和代幣符號(hào)之外,建議避免為自定義函數(shù)修改它,因?yàn)閑osio.token合約中的錯(cuò)誤可能是致命的。為了便于安全地進(jìn)行空投,將空投代幣轉(zhuǎn)移到一個(gè)單獨(dú)的帳戶(hù)并從那里分發(fā)。

修改多索引表屬性

EOS當(dāng)前將數(shù)據(jù)存儲(chǔ)在共享內(nèi)存數(shù)據(jù)庫(kù)中,以便跨操作共享。

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;

上面的示例代碼創(chuàng)建了一個(gè)名為peoplemulti_index表 ,該表基于使用struct person的該表的單行的數(shù)據(jù)結(jié)構(gòu)。部署后,EOS目前不允許修改表屬性。eosio_assert_message斷言失敗將是將被拋出的錯(cuò)誤。因此,在部署表之前需要完全考慮屬性。否則,需要?jiǎng)?chuàng)建具有不同名稱(chēng)的新表,并且在從舊表遷移到新表時(shí)需要特別小心。如果不這樣做可能會(huì)導(dǎo)致數(shù)據(jù)丟失。

數(shù)值外溢檢查

在進(jìn)行算術(shù)運(yùn)算時(shí),如果沒(méi)有足夠負(fù)責(zé)地檢查邊界條件,則值可能會(huì)溢出,從而導(dǎo)致用戶(hù)資產(chǎn)丟失。

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表示用戶(hù)余額可能會(huì)在值乘以時(shí)導(dǎo)致溢出。因此,盡量避免使用uint64_t來(lái)表示余額并對(duì)其執(zhí)行算術(shù)運(yùn)算。使用eosiolib中定義 的asset結(jié)構(gòu)進(jìn)行操作,而不是處理溢出條件的精確余額。

考慮合約中的假設(shè)

在執(zhí)行合約時(shí)會(huì)有假設(shè)需要斷言。如果斷言失敗,使用eosio_assert將事先處理?xiàng)l件并停止執(zhí)行特定操作。舉個(gè)例子:

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");
  }

上面的斷言語(yǔ)句假設(shè)roll_under整數(shù)大于2且小于96.但如果不是,則拋出上述消息并停止執(zhí)行。沒(méi)有發(fā)現(xiàn)像上面這樣的局部問(wèn)題可能會(huì)成為規(guī)則制定的全局災(zāi)難。

生成正確隨機(jī)數(shù)

如果沒(méi)有準(zhǔn)確完成,在EOS區(qū)塊鏈上生成正確的隨機(jī)數(shù)仍然存在風(fēng)險(xiǎn)。如果沒(méi)有正確地做到這一點(diǎn),將導(dǎo)致對(duì)手預(yù)測(cè)結(jié)果,在整個(gè)過(guò)程中對(duì)整個(gè)系統(tǒng)進(jìn)行游戲。像Oracalize.it這樣的服務(wù)可以提供來(lái)自外部源的隨機(jī)數(shù),但它們很昂貴并且可能出現(xiàn)單點(diǎn)故障。人們過(guò)去曾使用Blockchain的上下文變量(塊編號(hào),塊時(shí)間戳等)來(lái)生成以太坊智能合約中的隨機(jī)數(shù),這只是在游戲中使用。為了正確地進(jìn)行生成,程序必須提供一種單一方無(wú)法單獨(dú)控制的組合隨機(jī)性。目前最好的方法之一是Dan Larimar自己生成隨機(jī)數(shù)時(shí)建議的方法。

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之間的優(yōu)化隨機(jī)數(shù)生成.seed1是種子,seed2是上面的用戶(hù)種子。作為參考,DappubEOSBetCasino開(kāi)放了他們的完整合約,隨機(jī)數(shù)發(fā)生器實(shí)現(xiàn)了玩家和開(kāi)發(fā)商之間的公平骰子游戲。

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的攻擊,當(dāng)一個(gè)對(duì)手欺騙他們的eosio.token合約時(shí),只要他在自己的錢(qián)包之間進(jìn)行交易,就將EOS送到他的錢(qián)包。eosio.token合約代碼 通知EOS代幣的發(fā)送者和接收者有傳入代幣。為了模仿這種行為并促進(jìn)黑客攻擊,對(duì)手創(chuàng)建了兩個(gè)帳戶(hù),讓我們假設(shè)A和B。A與智能合約的操作有聲明require_recipient(N(eosbetdice11))。當(dāng)A通過(guò)操作調(diào)用促進(jìn)了從A到B的交易時(shí),它通知合約中的轉(zhuǎn)移功能,就好像該呼叫來(lái)自eosio.token合約一樣。由于EOS沒(méi)有真正轉(zhuǎn)移到合約中,每當(dāng)黑客輸?shù)糍€注時(shí),他什么都沒(méi)有丟失,但是在贏得賭注時(shí)他獲得了獎(jiǎng)勵(lì)。因此,僅檢查合約名稱(chēng)和操作名稱(chēng)是不夠的。

檢查合約中的通知

為了緩解這個(gè)問(wèn)題,該函數(shù)應(yīng)檢查合約是否確實(shí)是代幣的接收者。

eosio_assert(transfer_data.from == _self || transfer_data.to == _self, "Must be incoming or outgoing transfer");

在EOS上制定智能合約時(shí)應(yīng)遵循的最佳做法是什么?

錯(cuò)誤是任何軟件不可避免的一部分。它的后果在去中心化的環(huán)境中被放大,特別是如果它涉及價(jià)值交易。除了上面討論的EOS特定保護(hù)措施之外,以下是新智能合約開(kāi)發(fā)人員應(yīng)該記住的一些一般預(yù)防措施和最佳實(shí)踐:

  • 在主網(wǎng)上發(fā)布之前,始終獨(dú)立于第三方智能合約審計(jì)公司審計(jì)合約。
  • 在發(fā)布到testnet之前,進(jìn)行必要的Caveman調(diào)試(當(dāng)前調(diào)試合約的唯一方法)。EOSIO文檔有一個(gè)很好的指南。
  • 設(shè)置提款限額轉(zhuǎn)移率,避免主網(wǎng)啟動(dòng)初期出現(xiàn)過(guò)多損失。有白帽黑客負(fù)責(zé)任披露的bug賞金計(jì)劃。
  • 當(dāng)檢測(cè)到錯(cuò)誤時(shí),有一個(gè)killswitch來(lái)凍結(jié)合約。

為了實(shí)現(xiàn)它,我們?cè)?code>multi_index表中保留一個(gè)標(biāo)志。我們使用只能由合約所有者調(diào)用的操作來(lái)設(shè)置標(biāo)志。然后我們檢查每個(gè)公共行動(dòng)是否將標(biāo)志設(shè)置為凍結(jié)。下面給出了該函數(shù)的示例實(shí)現(xiàn)。

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!");
   ...
}

隨時(shí)了解庫(kù)中的安全性增強(qiáng)或平臺(tái)上的漏洞披露。必要時(shí)立即更新庫(kù)。至少開(kāi)源合約代碼,以便在項(xiàng)目中保持公平性,獨(dú)立開(kāi)發(fā)人員可以更快地發(fā)現(xiàn)錯(cuò)誤。

EOS智能合約安全:結(jié)論

自EOS推出僅僅5個(gè)月,它已經(jīng)超出了預(yù)期。它所取得的DPOS,可變智能合約,21個(gè)采礦節(jié)點(diǎn)等,當(dāng)然受到權(quán)力下放極端主義者的嚴(yán)厲批評(píng)。然而,考慮到平臺(tái)今天提供的可擴(kuò)展性,它并沒(méi)有阻止基于以太坊的dApp轉(zhuǎn)向EOS。無(wú)論是EOS還是以太坊贏得戰(zhàn)爭(zhēng)還有待確定,但EOS肯定贏得了這場(chǎng)戰(zhàn)斗。它將保持不變,直到以太坊設(shè)法達(dá)到運(yùn)行“世界計(jì)算機(jī)”所需的世界所需的可擴(kuò)展性。

======================================================================

分享一些以太坊、EOS、比特幣等區(qū)塊鏈相關(guān)的交互式在線(xiàn)編程實(shí)戰(zhàn)教程:

  • EOS教程,本課程幫助你快速入門(mén)EOS區(qū)塊鏈去中心化應(yīng)用的開(kāi)發(fā),內(nèi)容涵蓋EOS工具鏈、賬戶(hù)與錢(qián)包、發(fā)行代幣、智能合約開(kāi)發(fā)與部署、使用代碼與智能合約交互等核心知識(shí)點(diǎn),最后綜合運(yùn)用各知識(shí)點(diǎn)完成一個(gè)便簽DApp的開(kāi)發(fā)。
  • java以太坊開(kāi)發(fā)教程,主要是針對(duì)java和android程序員進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的web3j詳解。
  • python以太坊,主要是針對(duì)python工程師使用web3.py進(jìn)行區(qū)塊鏈以太坊開(kāi)發(fā)的詳解。
  • php以太坊,主要是介紹使用php進(jìn)行智能合約開(kāi)發(fā)交互,進(jìn)行賬號(hào)創(chuàng)建、交易、轉(zhuǎn)賬、代幣開(kāi)發(fā)以及過(guò)濾器和交易等內(nèi)容。
  • 以太坊入門(mén)教程,主要介紹智能合約與dapp應(yīng)用開(kāi)發(fā),適合入門(mén)。
  • 以太坊開(kāi)發(fā)進(jìn)階教程,主要是介紹使用node.js、mongodb、區(qū)塊鏈、ipfs實(shí)現(xiàn)去中心化電商DApp實(shí)戰(zhàn),適合進(jìn)階。
  • C#以太坊,主要講解如何使用C#開(kāi)發(fā)基于.Net的以太坊應(yīng)用,包括賬戶(hù)管理、狀態(tài)與交易、智能合約開(kāi)發(fā)與交互、過(guò)濾器和交易等。
  • java比特幣開(kāi)發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Java代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢(qián)包、構(gòu)造裸交易等,是Java工程師不可多得的比特幣開(kāi)發(fā)學(xué)習(xí)課程。
  • php比特幣開(kāi)發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Php代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢(qián)包、構(gòu)造裸交易等,是Php工程師不可多得的比特幣開(kāi)發(fā)學(xué)習(xí)課程。
  • tendermint區(qū)塊鏈開(kāi)發(fā)詳解,本課程適合希望使用tendermint進(jìn)行區(qū)塊鏈開(kāi)發(fā)的工程師,課程內(nèi)容即包括tendermint應(yīng)用開(kāi)發(fā)模型中的核心概念,例如ABCI接口、默克爾樹(shù)、多版本狀態(tài)庫(kù)等,也包括代幣發(fā)行等豐富的實(shí)操代碼,是go語(yǔ)言工程師快速入門(mén)區(qū)塊鏈開(kāi)發(fā)的最佳選擇。

匯智網(wǎng)原創(chuàng)翻譯,轉(zhuǎn)載請(qǐng)標(biāo)明出處。這里是原文EOS智能合約安全終極指南

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容