Dieser Artikel setzt Grundkenntnisse in Ethereum voraus. Leser*innen sollten mit den Begrifflichkeiten Schlüssel(paar), Signatur, Ethereum, Mining, Blöcke und Blockchain vertraut sein. Dazu eignet sich der Blockchain-Podcast mit Stefan Tilkov und Lukas Dohmen hervorragend. Teil 3 dieser Serie dreht sich um Altcoins.
Im ersten Artikel dieser Serie ging es um die Kryptowährung Bitcoin. Nun möchte ich Ethereum näher beleuchten. Dabei handelt es sich um eine Blockchain-Technologie, die von Anfang an auf die Ausführung von Code ausgelegt ist.
Die Homepage verkündet:
Ethereum is a decentralized platform that runs smart contracts: applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third-party interference.
Daher rührt auch die Bezeichnung The World Computer für Ethereum.
In diesem Abschnitt möchte ich Ethereum gegenüber Bitcoin abgrenzen und erläutern, wie Transaktionen in Ethereum funktionieren. Doch kurz vorab: bei Ethereum gibt es begrifflich einen Unterschied zwischen der Blockchain und der eigentlichen Kryptowährung, was am Ende des Artikels noch erläutert wird.
Konten
Wir erinnern uns: In Bitcoin können Transaktionen mehrere Quellen und Senken haben. Als Quellen sind bisher unverbrauchte Senken zulässig.
Ethereum hingegen funktioniert viel eher wie eine klassische Bank. Sogenannte Externally Owned Accounts (EOAs) sind von einem Schlüsselpaar gedeckt. Transaktionen haben genau ein Zielkonto und spezifizieren optional Betrag (Value) und Daten. Fürs erste können wir die Daten ignorieren. Der Betrag gibt die Menge an Ether an – die Währung von Ethereum –, die das Zielkonto erhalten soll. Das Quellkonto ergibt sich aus der Signatur der Transaktion.
Ethereum unterscheidet mehrere Arten von Transaktionen, je nachdem, ob Daten oder Betrag vorhanden sind:
- Zahlung
- Eine Transaktion, die einen Betrag enthält.
- Aufruf
- Eine Transaktion, die Daten enthält.
Beides lässt sich kombinieren. Transaktionen, denen beides fehlt, sind zwar möglich, aber nutzlos.
Übertragen in Java-Quelltext kann man sich eine Transaktion wie folgt vorstellen:
Der Begriff Aufruf deutet schon an, dass Ethereum in irgendeiner Form Routinen unterstützt.
Verträge
Neben EOAs gibt es in Ethereum auch Vertragskonten, für die sich der Begriff Smart Contract etabliert hat. Verträge werden im Gegensatz zu EOAs nicht von einem Schlüsselpaar repräsentiert; stattdessen werden sie von einem anderen Konto aus erzeugt.
Ähnlich wie ein EOA kann auch ein Vertrag über einen Kontostand (Balance) verfügen. Darüber hinaus enthält ein Vertrag auch Bytecode der Ethereum Virtual Machine (EVM) sowie quasi beliebigen Zustand.
Übertragen auf Java entspricht ein Contract einer Instanz einer Klasse mit öffentlichen und privaten Methoden. Von einer Klasse kann es mehrere Instanzen geben und auch ein Vererbungskonzept existiert. Üblicherweise schreibt man Klassen nicht direkt in JVM-Bytecode, sondern benutzt einen Compiler, der Hochsprache in Bytecode übersetzt. In Ethereum heißt die populärste Sprache Solidity.
Verträge werden niemals von selbst tätig, sondern können nur auf eingehende Aufrufe reagieren und dabei auch mit anderen Verträgen interagieren oder Beträge an EOAs versenden. Trotz Ethereums Natur als verteiltes System sind Verträge daher nie nebenläufig: Wenn eine Transaktion einen Vertrag erzeugt oder aufruft, werden alle Programmschritte sequenziell ausgeführt. Wichtig ist, dass alle diese Verarbeitungsschritte von einer einzigen Transaktion ausgehen: ein Vertragsaufruf, der zu weiteren Vertragsaufrufen führt, erzeugt dabei keine neuen Transaktionen. Eine Transaktion kann immer nur von einem EOA ausgehen.
Erzeugt wird ein Vertrag, in dem man eine Transaktion generiert, die in den Daten den Bytecode enthält und als Empfängeradresse die spezielle Adresse 0x0
definiert hat.
Ein einmal definierter Vertrag ist unveränderlich und kann prinzipiell für alle Ewigkeit von beliebigen Transaktionen aufgerufen werden. Ein möglicher Lebenszyklus lässt sich wie folgt darstellen:
Ein Vertrag ist insofern ähnlich zu einer Kapitalgesellschaft im Wirtschaftsrecht: Er existiert unabhängig von Personen (EOAs) und kann selbst Verträge aufrufen und aufgerufen werden sowie Geld versenden und empfangen.
Wenn das Subjekt, das den Vertrag erzeugt hat, die Kontrolle über seinen privaten Schlüssel verloren hat, existiert der Vertrag trotzdem weiter.
Die einzige Möglichkeit, einen Vertrag wieder aufzulösen, besteht darin, dass der Vertrag die SELFDESTRUCT
-Operation ausführt.
Diese Möglichkeit muss der Vertrag selbst vorgesehen haben; Konto A darf nicht einfach so Vertrag V auflösen.
Hochsprache Solidity
Verträge direkt in Bytecode zu implementieren, wäre so ähnlich wie Assembler-Code zu schreiben. Daher benutzt man eine Hochsprache wie Solidity, eine getypte, objektorientierte Sprache für Verträge. Das folgende Beispiel zeigt eine einfache Implementierung eines Vertrages, der im Auftrag eines anderen Kontos Währung verwaltet:
Ein Solidity-Programm beginnt mit einem pragma, welches die Sprachversion festlegt. Leider ist dies oft notwendig, denn Solidity verfügt über keine formale Spezifikation und weist daher fast ausschließlich – wie der C-Standard sagen würde – implementation defined behaviour auf. Dank Versionsnummer kleiner eins dürfen auch des öfteren inkompatible Änderungen in den Compiler Einzug halten.
Obiger Contract erinnert stark an eine Klasse.
Er verfügt über zwei Instanzvariablen: balance
und owner
.
Der Konstruktor nutzt ein spezielles Schlüsselwort.
Sobald ein Vertrag durch Transaktion an die Empfängeradresse 0x0
erzeugt wird, wird dieser Konstruktur ausgeführt.
Hier wird die balance
mit 0 initialisiert und anschließend owner
auf die Absenderadresse gesetzt.
Die addfund
-Methode ist öffentlich aufrufbar (public
) und kann bei Transaktionen auch Ether entgegennehmen (payable
).
Sie führt folgende Schritte durch:
- Eine aufrufende Transaktion muss von derselben Adresse stammen, die auch den Vertrag angelegt hat.
- Die
balance
wird um den gesendeten Betrag erhöht. - Die neue
balance
wird zurückgeliefert.
Eigentlich ist die balance
-Variable redundant:
Das Ethereum-Netzwerk weiß zu jedem Zeitpunkt, wie viele Ether in einem Vertrag hinterlegt sind.
Dies passiert immer implizit, wenn eine payable
-Methode aufgerufen wird.
Schlussendlich gibt es noch die withdraw
-Methode.
Auch hier wird die Aufruferadresse geprüft.
Anschließend zerstört sich der Vertrag selbst und sendet die gesamten hinterlegten Ether an die owner
-Adresse.
Kompiliert zu Bytecode, sieht der Vertrag so aus:
608060405234801561001057600080fd5b506000808190555033600160006101000a81548173ffff ffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffff ffffffff1602179055506101be806100686000396000f30060806040526004361061004c57600035 7c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063 3ccfd60b146100515780638f9595d414610068575b600080fd5b34801561005d57600080fd5b5061 0066610086565b005b61007061011d565b6040518082815260200191505060405180910390f35b60 0160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffff ffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614 15156100e257600080fd5b600160009054906101000a900473ffffffffffffffffffffffffffffff ffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b60006001600090549061 01000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffff ffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017b5760 0080fd5b3460008082825401925050819055506000549050905600a165627a7a72305820b4d2d5a2 acfa0e197908c7c57826d35a987f64b29f8b97020a4ca96f63ce66630029
Gerade wegen der Unzulänglichkeiten in Solidity ist es bei wichtigen Verträgen durchaus üblich, dass Autor*innen sich den generierten Bytecode noch einmal ansehen, gegebenenfalls auch unter Zuhilfenahme von Tools.
Betrachten wir nun eine Beispielinteraktion mit dem Vertrag Wallet
.
Ein Aufruf muss als Daten zwingend erhalten, welche Methode mit welchen Parametern ausgeführt werden soll.
(In unserem Fall gibt es keine Parameter.)
Werden die Daten weggelassen, wird standardmäßig eine Ausweichmethode aufgerufen, die aber in unserem Vertrag nicht definiert ist.
Eine Transaktion ohne Daten (d.h. eine einfache Zahlung) würde folglich scheitern.
Im obigen Diagramm ist explizit von Transfer die Rede, um eine Abgrenzung zu Zahlung zu illustrieren: Eine Zahlung steht für eine eigene Transaktion; ein Transfer heißt hier nur, dass innerhalb einer Transaktion eine Gutschrift für Konto A stattfindet.
Der Vertrag kann also als virtueller Geldbeutel bezeichnet werden. Striche man die require
-Anweisung aus der addfund
-Methode, würde aus dem Vertrag eine Spendenkasse, die Einzahlung durch beliebige Adressen erlaubt.
Sinnvollerweise müsste man dann aber auch selfdestruct
durch einen einfachen Transfer ersetzen (mittels owner.send(balance)
).
Die wesentlichen Unterschiede zu Bitcoin-Skripten lassen sich daher so zusammenfassen, denn diese:
- können nur ein einziges Mal (erfolgreich) aufgerufen werden
- können nicht mit anderen Skripten interagieren.
- folgen einem primitiven Programmiermodell, das nur Stack-Manipulationen ohne Rekursion, Schleifen, und Schreiben von externem Zustand zulässt (Lesen von externem Zustand ist hingegen auch in Bitcoin erlaubt, z.B. die aktuelle Zeit)
Man nennt Sprachen wie den EVM-Bytecode auch Turing-vollständig, da sie in der Lage sind, beliebige berechenbare Funktionen auszuführen. Klammert man also Ein-/Ausgabeoperationen aus, lassen sich alle bekannten Algorithmen auch auf der EVM implementieren.
Weitere Beispiele für Smart Contracts haben Stefan Tilkov und Marc Jansing in ihrem Artikel herausgearbeitet.
Adresskompatibilität
Die starke Ähnlichkeit von Ethereum-Konten zu gewöhnlichen Bankkonten wird auch dadurch untermauert, dass es ein Adressformat gibt, welches an das IBAN-Format angelehnt ist. Dieses Inter exchange Client Address Protocol (ICAP) erzeugt fiktive IBAN-Adressen, z.B.
XE60HAMICDXSV5QXVJA7TJW47Q9CHWKJD
Fiktiv ist diese Adresse, weil XE
als Länderkennung im IBAN-Standard nicht vorgesehen ist.
Diesem Code folgt die Prüfziffer (60
), die nach den üblichen IBAN-Regeln gebildet wird.
Schlussendlich folgt die Repräsentation der Ethereum-Adresse; allerdings passen in die 30 Stellen nur 155 von 160 Bit.
Man benötigt also eine Adresse, deren erstes Byte Null ist.
Dieses Adressformat ist bis auf Weiteres als Spielerei zu betrachten, denn eine SEPA-Überweisung an eine Ethereum-Adresse kann man auch mit dem ICAP-Format nicht durchführen. Desweiteren kann man nur mit einer ICAP-Adresse keine Daten an einen Vertrag übermitteln.
Ethereum vs. Bitcoin
Ein paar Unterschiede zwischen Bitcoin und Ethereum haben sich bereits herauskristallisiert, es gibt aber noch mehr Vergleichspunkte:
- Wertschöpfung
- Auch Ethereum kennt Wertschöpfung durch Mining. Im Gegensatz zu Bitcoin gibt es aber keine Coinbase-Transaktionen. Stattdessen enthält ein Block die Adresse des Miners, welcher implizit fünf Ether (+ X, nach einer komplizierten Formel) gutgeschrieben werden.
- Mehrfachverwendung einer Adresse
- In Ethereum ist es üblich, eine Adresse mehrfach zu nutzen. Insbesondere bei Verträgen bleibt die Adresse über die Lebenszeit konstant. Es ist nicht möglich, aus einem existierenden Vertrag bei jeder Transaktion einen neuen Vertrag zu konstruieren, es sei denn, dies wurde im Vertrag explizit so vorgegeben.
- Wechselgeld
- Da Ethereum-Transaktionen immer nur genau eine Empfängeradresse spezifizieren, ist es weder möglich noch notwendig, eine Wechselgeldadresse anzugeben. Insofern entspricht Ethereum viel eher einem klassischen Bank-ähnlichem Kontenmodell.
- Adresserzeugung
- Bei Bitcoin ergeben sich sämtliche Adressen aus dem Hash eines Keys. Bei P2PKH ist die Adresse komplett beliebig (außer man benutzt BIP 32/BIP 39). Bei P2SH ist die Adresse vom Skript abhängig. Bei Ethereum verhält es sich ähnlich, allerdings werden Vertragsadressen aus der Erzeugeradresse und einem Transaktionszähler (nonce) ermittelt.
- Ausdrucksstärke
- EVM-Verträge können im Gegensatz zu Bitcoin-Skripten beliebige Algorithmen abdecken. Sie können auch mehr Bedingungen stellen. So ist es beispielsweise für eine Bitcoin-Transaktion T zwar möglich, bestimmte Anforderungen an eine Transaktion U zu stellen, die T als Quelle angibt, aber die Senke von U kann nicht beeinflusst werden. Allerdings muss U nicht zwingend existieren. In Ethereum können dagegen beliebige Geldflüsse definiert werden, die aber dann auch komplett ablaufen.
- Gebühren
- Dieses Thema wurde im Artikel noch gar nicht angesprochen.
Bei beiden Blockchain-Systemen gibt es Transaktionsgebühren, die Miner zusätzlich zur Block-Belohnung kassieren.
Diese Gebühren können von jedem Subjekt, das eine Transaktion anstößt, frei festgelegt werden.
Je höher die Gebühr, desto eher haben die Miner einen Anreiz, die Transaktion in den nächsten Block aufzunehmen.
In Bitcoin ist diese Gebühr fix und prinzipiell unabhängig vom Skript. Sie wird festgelegt, in dem eine Differenz zwischen Summe von Senken und Summe von Quellen übrig gelassen wird; d.h. die Transaktion verteilt nicht alle Senken vollständig. Je größer eine Transaktion ist (z.B. mehr Quellen), desto mehr Gebühren werden Miner verlangen.
In Ethereum ist diese Gebühr ein Multiplikator: jede Instruktion in der EVM, einschließlich reine Zahlungen oder Erzeugung von Verträgen, haben einen zugeordneten Spritverbrauch. Der Spritpreis multipliziert mit dem Gesamtverbrauch ergibt die Gesamtgebühr. Da sich zur Beginn einer Transaktion noch nicht exakt sagen lässt, wie viele Instruktionen tatsächlich ausgeführt werden müssen, gibt jede Transaktion zusätzlich zum Spritpreis noch die Tankgröße an. Wenn der Tank ausreicht, wird dem Miner der Gesamtpreis gutgeschrieben. Läuft der Tank leer, wird die Transaktion (fast) folgenlos abgebrochen, denn der Miner erhält trotzdem den verbrauchten Spritpreis. - Währung
- Bei beiden Netzwerken gibt es eine eingebaute Währung: Bei Bitcoin Bitcoins und bei Ethereum Ether. Über diese Währung werden Transfers und Gebühren abgewickelt. Mittels spezieller Verträge können bei Ethereum aber auch virtuelle Währungen angelegt werden (sogenannte Tokens). Seit EIP/ERC 20 existiert dafür auch ein Standard. Stand Februar 2019 gibt es im öffentlichen Ethereum-Netzwerk bereits weit über 150.000 solcher Token-Verträge. Im Gegensatz zu Bitcoin ist Ethereum daher nicht nur eine Kryptowährung, sondern auch eine Plattform für Kryptowährungen.