Implikationen des „Constantinople“-Upgrades im Ethereum-Netzwerk auf Smart Contracts und die Self-destruct-Option

Mit dem „Constantinople-Upgrade wird eine zweite Möglichkeit, Smart Contracts auf der Ethereum-Blockchain zu deployen, eingeführt. Dies hat auch Auswirkungen auf die Unveränderlichkeit von Smart Contracts. Unser Software Developer und Ethereum-Spezialist Johannes Zweng erklärt euch in diesem Blogartikel, wie das funktioniert.

Im „Constantinople“-Upgrade gibt es (abgesehen vom Gas-price-Problematik über die wir bereits in einem anderen Artikel berichtet haben) noch eine weitere subtile Änderung, die noch nicht vielen bewusst ist. Vor allem betrifft das einen Punkt, den einige vermutlich bisher als unveränderlich angesehen haben: die Möglichkeit, Smart Contracts nachträglich zu verändern.

Und zwar wird es – vereinfacht gesagt – durch den neuen CREATE2-OP-Code (EIP 1014) mit ein paar Tricks möglich, den Programmcode eines Smart Contracts, der bereits auf der Blockchain installiert ist, nachträglich nochmals zu verändern. Dies funktioniert durch die Self-destruct-Option, also die Selbstzerstörung, und die darauffolgende Installation eines anderen Bytecodes an derselben Contract-Adresse, was allerdings nur unter bestimmten Umständen, auf die wir kurz eingehen wollen, möglich ist:

Wie soll die nachträgliche Änderung genau funktionieren?

Um das zu erklären, muss man kurz ausholen und erklären, wie Smart Contracts auf Ethereum (egal ob Token Contracts, Multi-Sig-Wallets oder beliebige andere Contracts) zu ihrer Ethereum-Adresse kommen:

In Ethereum gibt es, wie wir wissen, zwei Arten von „Accounts“, die eine Ethereum-Adresse besitzen:

  • normale Adressen mit Private Keys (sog. external owned accounts, kurz: EOA)
  • Smart Contracts haben keinen Private Key, sondern stattdessen einen Programmcode, der bestimmt, wie sich der Smart Contract verhält bzw. wer wie und wann Zugriff auf die ETH in dem Contract hat.

Bei „normalen“ Ethereum-Adressen (EOAs) ist das noch relativ klar: Ähnlich wie bei Bitcoin wird die öffentliche Adresse aus dem Public Key (der sich aus dem Private Key ergibt) abgeleitet (im Detail: die letzten 160 Bit des Keccak256*-Hashes des Public Keys stellen die Ethereum-Adresse dar).

„Keccak256“ ist der Name einer Hash-Funktion, ähnlich wie auch SHA256, und wird oft als Vorläufer von SHA3 bezeichnet. Tatsächlich ist SHA3 eine spezielle Form des Keccak-Algorithmus. In Ethereum wird an vielen Stellen Keccak als sicherer Hash-Algorithmus (anstelle von SHA256) verwendet.

Wenn man nun einen Smart Contract auf der Blockchain installiert („deployed“), so bekommt dieser auch eine Adresse zugewiesen. Diese Adresse wird dabei automatisch nach festgelegten Regeln erzeugt, die folgendermaßen aussehen:

Keccak256 ( sender_adress , nonce_of_sender )

„sender_address” ist dabei die Adresse des Absenders, der den Smart Contract deployed. Dies kann sowohl ein „external owned account“ (EOA), also eine „normale“ Adresse mit Private Key, als auch ein Smart Contract (auch Contracts können weitere, neue Contracts erstellen) sein.

Der zweite Teil „nonce“ (Kurzform von „number used once“) ist, wie der Name schon suggeriert, eine Zahl, die bei jeder Transaktion einer Adresse um eins hochgezählt wird. Das heißt, dass eine Adresse niemals zwei Transaktionen mit der gleichen Nonce senden kann. (Bei Smart Contracts ist das ähnlich, aber nicht ganz gleich: Dort wird die “Nonce“ jedes Mal erhöht, wenn der Contract einen neuen Contract deployed.) Auf jeden Fall folgt daraus, dass es unmöglich ist, gezielt zweimal einen Contract auf dieselbe Adresse zu deployen (da bei einem neuerlichen Deployment die Nonce des Senders bereits eine andere ist und dadurch der Contract automatisch eine andere, neue Adresse erhalten wird).

Das zugrundeliegende „Kommando“ (auch „OP-Code“ genannt) zum Installieren (deployen) von Smart Contracts ist der „CREATE“-OP-Code (manchmal auch als „CREATE1“ bezeichnet). Im Moment ist dies (also vor dem „Constantinople“-Upgrade) der einzige Weg, neue Contracts auf die Ethereum-Blockchain zu deployen. Mit dem „Constantinople“-Upgrade (konkret mit EIP 1014) kommt jetzt ein zweiter OP-Code zum Deployen neuer Contracts hinzu – mit dem Namen „CREATE2“. Prinzipiell funktioniert dieser OP-Code beinahe identisch wie der alte CREATE1-OP-Code, nur dass die Ableitung der neuen Adresse für den deployten Contract nach einem anderen Schema erfolgt:

Und zwar bekommt ein Contract, der mit CREATE2 deployed wurde, folgende neue Adresse:

Keccak256( 0xFF , sender_address , salt , Keccak256( init_code_des_contracts ))

Wie man sieht, wird die Adresse hier auch aus einem Keccak256 Hash errechnet, in den wie gehabt die Adresse des Senders einfließt (und ein paar zusätzliche Punkte). Anstatt der „nonce fließt hier allerdings der (mit Keccak256 gehashte) „Init-Code“ des Contracts ein (den Wert „0xFF“ und „Salt“ können wir in dem Kontext ignorieren, da diese statisch bzw. vom Sender frei gewählt werden können). Dadurch, dass die (niemals gleiche „Nonce“) nicht mehr in die Adress-Ableitung miteinfließt, ergibt sich eine wesentliche Änderung: Wenn derselbe Sender denselben „Init-Code“ (was das genau ist, erklären wir später) zweimal deployen würde, würde der daraus resultierende Contract beide Male auf der gleichen Ethereum-Adresse in der Blockchain landen!

Aber zweimal auf derselben Adresse deployen, geht das überhaupt?

Richtig, das geht eigentlich nicht. Ein Contract-Deployment schlägt automatisch fehl, wenn die Adresse auf der Blockchain schon „besetzt“ ist.

Was heißt „besetzt“ im Detail?

Falls die Adresse eine normale Private-Key-Adresse ist, die bereits mindestens eine ausgehende Transaktion hatte, oder es eine Smart-Contract-Adresse ist (also ein Code hinterlegt ist), schlägt die Deployment-Transaktion fehl.

(Anmerkung am Rande: Wenn die Adresse bisher nur eingehende Transaktionen hat, dann ist das kein Abbruchsgrund beim Deployen. Stattdessen „erbt“ der neu deployte Contract die etwaige bereits vorhandene Balance auf einer Adresse. Auf diese Weise kann man einem Contract Ether zukommen lassen, bevor es den Code auf der Blockchain überhaupt gibt.)

Allerdings gibt es für Smart Contracts eine Möglichkeit, sich selbst wieder aus der Blockchain zu entfernen. Das geht nicht mit jedem Contract automatisch, sondern der Contract muss dafür in seinem Programm-Code ein sog. Self-destruct-Kommando vorgesehen haben. (Optimalerweise ist das so abgesichert, dass es nicht von jedem aufgerufen werden kann, sondern dass nur der Owner oder andere speziell befugte Sender Zugriff besitzen → das war z. B. der Bug bei der Parity Wallet, hier war das self-destruct nicht abgesichert und irgendjemand hat die Library einfach „self-destructed“ und somit aus der Blockchain entfernt. Aber das ist eine andere Geschichte.)

Wichtig ist nur, dass nach einem self-destruct ein Smart Contract und alle Infos zu ihm (alle State-Infos und Variablen) wieder von der Blockchain entfernt werden und nichts im Wege steht, erneut auf diese Adresse einen Contract zu deployen. Wenn wir nun erneut auf dieselbe Adresse deployen wollen, müssen wir nur dieselben Parameter beim CREATE2-Kommando verwenden und schon erhält der neue Smart Contract dieselbe Adresse.

Aber stopp! Zurück zum CREATE2: Hier war doch auch noch der „init_code“ Teil der Adress-Berechnung. Das heißt also, sobald wir einen anderen Init-Code verwenden, bekommt der neue Contract erst wieder eine andere Adresse. Heißt das dann nicht auch, dass man nur denselben Contract an dieselbe Adresse deployen kann (also den Code gar nicht ändern kann)?

Nein, auch wenn es auf den ersten Blick so scheint, ist das nicht so!

Um das zu erklären, müssen wir kurz ausholen und erklären, was der „init_code eigentlich genau ist: Wenn man einen neuen Contract in einer Transaktion auf Ethereum deployed (egal ob mit CREATE1 oder CREATE2), gibt man der Transaktion im Data-Feld den „init_code“ mit. Dies ist gültiger Programmcode für die virtuelle Maschine von Ethereum, aber dieser ist nicht der Code, der auf der Blockchain (als Code des neu deployten Smart Contracts) landet! Der „init_code“ wird während der Deployment-Transaktion ausgeführt und muss als Rückgabewert etwas zurückliefern. Und das, was der Code zurückliefert, wird daraufhin der neue Programm-Code des neuen Smart Contracts.

Wenn man „klassisch“ einen Smart Contract in der Programmiersprache Solidity schreibt und dann kompiliert, sieht der „init_code“ sehr simpel aus: Im Prinzip beinhaltet der „init_code“-Teil den eigentlichen Smart-Contract-Code und macht (neben ein paar Initialisierungs-Sachen) nicht viel mehr, als diesen statischen Code zu retournieren.

Das bedeutet, dass im Normalfall zwei unterschiedliche Contracts in Solidity auch unterschiedlichen „init_code“ haben werden, also in Folge auch unterschiedliche Smart-Contract-Adressen (selbst mit CREATE2).

Das muss aber nicht immer zwingend so sein. Man könnte auch den „init_code“ so gestalten, dass er den finalen Programm-Code nicht bereits statisch beinhaltet, sondern diesen während der Deploy-Transaktion dynamisch erstellt. Zum Beispiel könnte der „init_code“ den finalen Programmcode einfach aus einer öffentlichen Variable eines anderen Contracts auslesen.

Das würde somit bedeuten, dass der „init_code” immer gleich aussieht, aber abhängig von den äußeren Umständen (also in dem Beispiel abhängig davon, was gerade in der Public Variable des anderen Contracts steht) unterschiedlichen Code zurückliefert.

Somit haben wir ein Setup konstruiert, das es uns ermöglichen würde, auf dieselbe Adresse (weil derselbe „init_code“) unterschiedlichen Contract Code deployen zu können.

Geht jetzt die (Ethereum-)Welt unter?

Nein, so schlimm ist das nicht. Die Antwort lautet wie so oft: „Kommt drauf an.” Man darf eigentlich nicht von vornherein davon ausgehen, dass ein Contract, der heute existiert, für immer in der gleichen Form bestehen wird. Denn das „Self-destruct“-Kommando gibt es heute schon. Das heißt, dass man sich bereits jetzt als Transaktions-Sender vor einer Transaktion vergewissern sollte, ob es den Contract eigentlich noch gibt, bzw. vor dem Handeln mit größeren Summen genau auditieren sollte, ob der Contract wohl nicht plötzlich „verschwinden“ kann.

Diese Vorsichtsmaßnahme gilt nach dem „Constantinople“-Upgrade nun in verstärkter Form. Jetzt kann ein Contract nicht nur verschwinden, sondern könnte zu einem späteren Zeitpunkt anderen Code beinhalten als zuvor. Allerdings – heute wie zukünftig – muss zwingend eine Voraussetzung gegeben sein, damit das überhaupt passieren kann: Der Contract muss eine Möglichkeit beinhalten, sich self-destructen zu können.

In Zukunft sollte man es sich bei Sicherheits-Audits zur bewährten Praxis machen, die Abwesenheit von self-destruct zu prüfen (sowohl direkt im Code als auch in externen Abhängigkeiten oder Libraries). Denn nur ein Contract, der sich nicht self-destructen kann, ist wirklich unveränderlich. Das gilt schon heute und das wird auch nach „Constantinople“ noch so sein.

Möchten Sie immer über die neusten Informationen in der Kryptowelt informiert sein? Melden Sie sich für unseren Newsletter an!