Guida definitiva alla sicurezza dei contratti intelligenti EOS

Guida definitiva alla sicurezza dei contratti intelligenti EOS. La comunità crittografica è diventata scettica quando il più grande ICO del mondo, EOS è stato lanciato nel giugno 2018 ed è stato bloccato per 2 giorni a causa di un bug del software. Ma avanti veloce di 4 mesi e EOS oggi rappresenta più di raddoppiare le transazioni che Ethereum fa oggi. Attraverso la promessa di transazioni gratuite e più veloci, la Dapp più alta di EOS ha circa 13.000 utenti attivi giornalieri rispetto a solo 2.000 della Dapp più alta di Ethereum.

EOS Smart Contract Security

Di Rohan Agarwal

Alcune vulnerabilità generali degli smart contract sono applicabili a quasi tutte le piattaforme. Come Ethereum, i contratti intelligenti scritti su EOS devono essere verificati prima di essere pubblicati sulla mainnet. Bug fatali nel contratto possono essere sfruttati quando i contratti non sono sufficientemente testati. In questa guida, ti aiuteremo a evitare le insidie ​​comuni sulla tua strada per creare la prossima dApp killer su EOS.

Prima di leggere la guida, è importante conoscere alcune informazioni sui prerequisiti relativi allo sviluppo di EOS che torneranno utili durante la lettura della guida. La conoscenza del C ++ è un must. Il posto migliore per iniziare con lo sviluppo di contratti intelligenti è proprio EOSIO documentazione

Trattare con l’ABI Dispatcher

esterno "C" {

void apply (ricevitore uint64_t, codice uint64_t, azione uint64_t) {

class_name thiscontract (destinatario);

if ((code == N (eosio.token)) && (azione == N (trasferimento))) {

execute_action (&questo contratto, &nome_classe :: trasferimento);

ritorno;

}

se (codice! = destinatario) ritorna;

switch (action) {EOSIO_API (class_name, (action_1) (action_n))};

eosio_exit (0);

}

}

L’immagine sopra è un codice di esempio di un dispatcher ABI modificato. Un dispatcher ABI più semplice come mostrato di seguito viene utilizzato per una gestione più semplice dell’azione del contratto.

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

Il dispatcher / forwarder ABI consente al contratto di ascoltare gli eventi di trasferimento eosio.token in arrivo, nonché le normali interazioni con lo smart contract. È importante associare ogni azione chiave e codice per soddisfare i requisiti, al fine di evitare chiamate anomale e illegali.

Un esempio potrebbe essere l’hack avvenuto a dApp EOSBet Casino a causa di un bug nel codice sorgente di inoltro ABI.

if (code == self || code == N (eosio.token)) {

Digita questo contratto (autonomo);

switch (action) {

EOSIO_API (TIPO, MEMBRI)

}

}

Il controllo precedente nel gestore dell’azione di applicazione del codice sorgente di inoltro ABI ha consentito a un utente malintenzionato di bypassare completamente la funzione eosio.token :: transfer () e chiamare direttamente la funzione contract :: transfer () senza trasferire EOS al contratto prima di inserire il scommessa. Per le perdite, non è stato pagato nulla, ma non ha perso nulla. Tuttavia, per le vittorie è stato pagato EOS reale dal contratto.

Hanno risolto il bug precedente aggiungendo un controllo dell’azione di trasferimento del contratto eosio.token prima che l’azione in entrata richiedesse al contratto.

if (code == self || code == N (eosio.token)) {

if (action == N (transfer)) {

eosio_assert (codice == N (eosio.token), "È necessario trasferire EOS");

}

Digita questo contratto (autonomo);

switch (action) {

EOSIO_API (TIPO, MEMBRI)

}

}

È importante utilizzare l’istruzione require_auth (account); in azioni che desideri vengano eseguite solo dall’account autorizzato. require_auth (_self); viene utilizzato per autorizzare solo il titolare del contratto a firmare la transazione

Autorizzazione nelle azioni

void token :: transfer (account_name from, account_name to, asset quantity)

{

auto sym = quantity.symbol.name ();

require_recipient (da);

require_recipient (a);

pagatore automatico = has_auth (a)? a da;

sub_balance (da, quantità);

add_balance (a, quantità, pagatore);

}

Il codice di esempio precedente consente a chiunque di chiamare l’azione. Per risolverlo, usa require_auth (from); dichiarazione per autorizzare il pagatore a chiamare l’azione.

Cerca di evitare di modificare il contratto eosio.token

Un recente hacker cappello bianco è riuscito a rivendica 1 miliardo di gettoni di un dapp a causa di una chiamata al metodo mal testata nel loro contratto eosio.token. Il Dapp Se7ens (ora inattivo) ha dichiarato un nuovo metodo all’interno del contratto eosio.token per trasferire i propri token negli account utente. Il contratto non chiamava l’emissione o l’azione di trasferimento del contratto eosio.token per riflettere le modifiche e quindi i fondi apparivano magicamente sui conti degli utenti. In secondo luogo, si sono dimenticati di verificare l’importo nel metodo prima del trasferimento che ha consentito all’hacker di rivendicare 1 miliardo di token nel processo.

Oltre a cambiare la fornitura massima e il simbolo del token, si consiglia di evitare di modificarlo per funzioni personalizzate in quanto i bug nel contratto eosio.token possono essere fatali. Per facilitare un airdrop in sicurezza, trasferisci i token airdrop su un account separato e distribuiscili da lì.

Modifica delle proprietà della tabella multiindice

EOS attualmente memorizza i dati su un database di memoria condivisa per la condivisione tra le azioni.

struct [[eosio :: table]] person {

account_name chiave;

std :: string first_name;

std :: string last_name;

std :: string street;

std :: string city;

std :: string state;

uint64_t primary_key () const {chiave di ritorno; }

};

typedef eosio :: multi_index<N (persone), persona> address_index;

Il codice di esempio sopra crea una tabella multi_index denominata persone che si basa sulla struttura dei dati di una singola riga di quella tabella utilizzando la struttura persona. EOS attualmente non consente la modifica delle proprietà della tabella una volta che viene distribuita. Il fallimento dell’asserzione eosio_assert_message sarà l’errore che verrà lanciato. Pertanto le proprietà devono essere completamente pensate prima della distribuzione della tabella. Altrimenti, è necessario creare una nuova tabella con un nome diverso e prestare la massima attenzione durante la migrazione dalla vecchia tabella a quella nuova. In caso contrario, potrebbe verificarsi la perdita di dati.

Controllo numerico dell’overflow

Quando si eseguono operazioni aritmetiche, i valori possono traboccare se le condizioni al contorno non vengono controllate in modo sufficientemente responsabile, causando la perdita delle risorse dell’utente.

void transfer (symbol_name symbol, account_name from, account_names to, uint64_t balance) {

require_auth (da);

account fromaccount;

eosio_assert (is_balance_within_range (balance), "saldo non valido");

eosio_assert (balance > 0, "deve trasferire saldo positivo"); uint64_t importo = saldo * 4; // Overflow della moltiplicazione

}

Nel codice di esempio precedente, using uint64_t per denotare il saldo dell’utente può causare un overflow quando il valore viene moltiplicato. Quindi evita di usare uint64_t per denotare saldi ed eseguire operazioni aritmetiche su di esso per quanto possibile. Utilizzare la struttura patrimoniale definita in eosiolib per le operazioni piuttosto che il saldo esatto che si prende cura delle condizioni di overflow.

Prendersi cura delle ipotesi del contratto

Ci saranno ipotesi che richiederanno asserzioni durante l’esecuzione del contratto. L’uso di eosio_assert si prenderà cura delle condizioni in anticipo e interromperà l’esecuzione dell’azione specifica se le asserzioni falliscono. Come esempio –

void assert_roll_under (const uint8_t& roll_under) {

eosio_assert (roll_under >= 2 && roll_under <= 96,

"roll under overflow, deve essere maggiore di 2 e minore di 96");

}

L’affermazione di cui sopra presuppone che roll_under intero sia maggiore di 2 & inferiore a 96. Ma se non lo fa, lancia il messaggio sopra e interrompi l’esecuzione. Non riuscire a scoprire casi d’angolo come quello sopra potrebbe diventare catastrofico per la casa che stabilisce le regole.

Generazione di numeri casuali reali

La generazione di numeri True Random sulla Blockchain EOS è ancora un rischio se non eseguita in modo accurato. Se non lo si fa correttamente, l’avversario predice i risultati, mettendo in gioco l’intero sistema nel processo. Servizi come Oracalize.it esiste per fornire numeri casuali da una fonte esterna ma sono costosi e rappresentano un unico punto di errore. Le persone hanno utilizzato le variabili contestuali della Blockchain (numero di blocco, timbro di blocco ecc.) In passato per generare il numero casuale nel contratto intelligente di Ethereum, ma è stato giocato prima. Per eseguire correttamente la generazione, il programma deve fornire una sorta di casualità combinata che nessuna singola parte potrebbe controllare da sola. Uno dei migliori modi possibili attualmente è un metodo suggerito dallo stesso Dan Larimar quando si genera un numero casuale tra due parti.

Blog di BountyOne: EOS Smart Contract Security

stringa sha256_to_hex (const checksum256& sha256) {

ritorno a_hex ((char *) sha256.hash, sizeof (sha256.hash));

}

stringa sha1_to_hex (const checksum160& sha1) {

ritorno a_hex ((char *) sha1.hash, sizeof (sha1.hash));

}

modello <classe T>

Inline void hash_combine (std :: size_t& seme, const T& v) {

std :: hash<T> hasher;

seed ^ = hasher (v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);

}

Il codice di esempio sopra fornisce una generazione di numeri casuali ottimizzata tra 1 e 100. seed1 è il seed della casa e seed2 è il seed dell’utente sopra. Per riferimento, Dappub e EOSBetCasino hanno reso open source i loro contratti completi con l’implementazione del generatore di numeri casuali di un gioco di dadi equo tra il giocatore e la casa (sviluppatore).

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

restituisce hash% 100 + 1;

}

EOSBet ha ricevuto di recente violato di nuovo di 65.000 EOS quando un avversario ha ingannato il proprio contratto eosio.token per inviare EOS al suo portafoglio ogni volta che effettuava transazioni tra i propri portafogli. Il codice del contratto eosio.token notifica sia il mittente che il destinatario dei token EOS che ci sono token in arrivo. Per imitare il comportamento & facilitare l’hack, l’avversario ha creato due account, supponiamo che A & B. A aveva uno smart contract con un’azione con l’istruzione require_recipient (N (eosbetdice11)). Quando A ha facilitato la transazione da A a B tramite l’invito all’azione, ha informato il funzione di trasferimento nel contratto come se la chiamata fosse arrivata dal contratto eosio.token. Poiché non c’era un vero trasferimento di EOS nel contratto, ogni volta che l’hacker perdeva una scommessa, non perdeva nulla, ma veniva ricompensato quando vinceva la scommessa. Pertanto, il controllo solo del nome del contratto e del nome dell’azione non è sufficiente.

Controlli sulle notifiche dai contratti

Per mitigare il problema, la funzione dovrebbe verificare se il contratto è effettivamente il destinatario dei token o meno.

eosio_assert (transfer_data.from == _self || transfer_data.to == _self, "Deve essere un trasferimento in entrata o in uscita");

Quali sono le migliori pratiche da seguire durante lo sviluppo di un contratto intelligente su EOS?

I bug sono parte inevitabile di qualsiasi software. Le sue conseguenze vengono amplificate in un ambiente decentralizzato soprattutto se si tratta di transazioni di valore. Oltre alle garanzie specifiche EOS discusse sopra, ecco alcune delle precauzioni generali e delle migliori pratiche che i nuovi sviluppatori di contratti intelligenti dovrebbero tenere a mente:

  1. Sempre audit il contratto indipendentemente da società di revisione di contratti intelligenti di terze parti prima del rilascio su mainnet.
  2. Eseguire il debug di Caveman necessario (l’unico modo per eseguire il debug del contratto al momento) del contratto prima di rilasciarlo nella testnet. La documentazione EOSIO ha un file ottima guida per questo.
  3. Imposta la velocità di trasferimento limite sui prelievi per evitare perdite eccessive nei primi giorni di lancio della mainnet. Avere un programma di bug bounty per la divulgazione responsabile da parte di hacker white hat.
  4. Avere un kill switch per bloccare il contratto quando viene rilevato un bug.

Per implementarlo, manteniamo un flag nella tabella multi_index. Impostiamo il flag utilizzando un’azione che può essere chiamata solo dal proprietario del contratto. E poi controlliamo in ogni azione pubblica se la bandiera è impostata per essere congelata o meno. Di seguito viene fornita un’implementazione di esempio della funzione.

struct st_frozen {

uint64_t congelato;

};

typedef singleton<N (congelamento), 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);

}

// Azione pubblica

void freeze () {

require_auth (_self);

setFreezeFlag (1);

}

// Azione pubblica

void unfreeze () {

require_auth (_self);

setFreezeFlag (0);

}

// qualsiasi azione pubblica

void action (…) {

eosio_assert (getFreezeFlag (). frozen == 1, "Il contratto è congelato!");

}

  1. Tieniti aggiornato sui miglioramenti della sicurezza nelle librerie o sulle divulgazioni delle vulnerabilità sulla piattaforma. Aggiorna immediatamente le tue librerie quando necessario.
  2. Aprire almeno il codice del contratto in modo da mantenere l’equità nel gioco e gli sviluppatori indipendenti potrebbero aiutare a individuare i bug molto più rapidamente.

EOS Smart Contract Security: conclusione

Sono passati solo 5 mesi dal lancio di EOS, ma è cresciuto ben oltre le aspettative. I compromessi che ha fatto – DPOS, contratti intelligenti mutabili, 21 nodi minerari ecc. Hanno sicuramente affrontato pesanti critiche da parte dei massimalisti del decentramento. Tuttavia, non ha impedito alle dApp basate su Ethereum di passare a EOS data la scalabilità che la piattaforma offre loro oggi. Se sia EOS o Ethereum a vincere la guerra è ancora da decidere, ma EOS ha sicuramente vinto la battaglia. E rimarrà lo stesso fino a quando Ethereum non riuscirà a raggiungere la scalabilità di cui il mondo ha bisogno per correre “The World Computer”.

_________________________________________________________________________________________

Questo articolo è stato scritto da Rohan Agarwal

Bio – #Android Dev # Entrepreneur # Blockchain Dev & Co-fondatore del ricercatore @ Cypherock.com – Un portafoglio hardware sicuro per smartphone.

Linkedin – https://www.linkedin.com/in/rohanagarwal94/

GitHub – https://github.com/rohanagarwal94

Twitter – https://twitter.com/rohanagarwal94

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me