6. Die Blockchain

Dieses Kapitel behandelt

  • Sicherheitsverbesserungen am Spreadsheet

  • Lightweight (SPV) Wallets

  • Verringerung des Bandbreitenbedarfs von Wallets

In [ch05] haben wir Transaktionen diskutiert, die jedermann alle Transaktionen im Spreadsheet verifizieren lassen. Aber es bleiben immer noch zwei Dinge, die wir nicht überprüfen können–dass Lisa keine Transaktionen zensiert oder löscht. Wir werden Zensurfestigkeit in Kapitel 7 und 8 behandeln. Dieses Kapitel untersucht, wie es für Lisa unmöglich wird, Transaktionen zu entfernen oder zu ersetzen, ohne dass auffällt, dass sie an der Transaktionshistorie herumgespielt hat

Lisa tut dies, indem sie das Spreadsheet durch eine Blockchain (Abbildung 1) ersetzt. Die Blockchain enthält Transaktionen, die dadurch manipulationssicher gemacht werden, dass eine Gruppe von Transaktionen auf eine clevere Art gehasht und signiert wird. Diese Technik macht es einfach, einen Betrug kryptografisch zu beweisen, falls Lisa Transaktionen löscht oder ersetzt. Alle Prüfer behalten ihre eigenen Kopien der Blockchain und können diese vollständig validieren, um sicherzustellen, dass Lisa keine bereits bestätigten Transaktionen löscht.

Dieses Kapitel führt ausserdem ein leichtgewichtiges Wallet, oder Simplified Payment Verification (SPV, vereinfachte Zahlungsprüfung) Wallet, ein, das die Blockchain Verifikation auf jemand anderen verschiebt–einen Full Node–um Bandbreite und Speicherplatz zu sparen. Das wird dank der Blockchain möglich, kostet allerdings an anderer Stelle.

06 01
Abbildung 1. Die Bitcoin Blockchain

6.1. Lisa kann Transaktionen löschen

Wie zuvor mehrfach erwähnt, kann Lisa Transaktionen löschen. Zum Beispiel kann Lisa einen Keks vom Café kaufen, ihn essen und die Transaktion löschen. Natürlich würde sie das nie tun, weil sie vertrauenswürdigste Person der Welt ist, aber nicht alle ihre Kollegen wissen oder glauben dies. Nehmen wir mal an, sie löscht tatsächlich eine Transaktion, wie in Abbildung 2 gezeigt.

06 02
Abbildung 2. Lisa kauft einen Keks und rollt dann die Transaktion zurück. Sie hat dem Café gerade einen Keks gestohlen. Das Café und Lisa haben jetzt unterschiedliche UTXO Sets.

Später, wenn das Café merkt, dass die Transaktion verschwunden ist, kann es nicht beweisen, dass die Transaktion je im Spreadsheet enthalten war. Und Lisa kann nicht beweisen, dass die Transaktion nicht darin war. Die Situation ist lästig. Wenn es Aussage gegen Aussage steht, kann man sich auf einen langen, teuren Disput einlassen, möglicherweise mit Rechtsanwälten, Polizei, der Acme Versicherung und Privatdetektiven.

Wie kann man beweisen, dass eine Transaktion bestätigt war? Lisa braucht einen Weg, die Transaktionen und deren Reihenfolge so zu veröffentlichen, dass sie nicht mehr daran herumspielen kann.

6.2. Die Blockchain wird gebaut

Lisa kann Transaktionen löschen, weil niemand beweisen kann, dass sich die Liste der Transaktionen geändert hat. Was, wenn wir das System so ändern, dass nachweisbar wird, wenn sie an der Historie herumfummelt?

Unter deinen Kollegen schlagen einige Entwickler vor, das Cookie Token Spreadsheet wegzuschmeissen und es durch eine Blockchain zu ersetzen (Abbildung 3).

06 03
Abbildung 3. Eine Blockchain ist eine Kette von Blocks. Diese Blocks enthalten Transaktionen, und jeder Block referenziert seinen Vorgänger.
Blockchain Länge

Die Bitcoin Blockchain enthält hunderttausende von Blocks. Zum Zeitpunkt des Schreibens lag die Höhe der Kettenspitze bei 550.836.

In der Blockchain zeigt jeder Block auf seinen Vorgängerblock und besitzt eine implizierte Höhe, die angibt, wie weit dieser Block vom ersten Block entfernt ist. Der erste Block besitzt Höhe 0, der zweite Höhe 1 und so weiter. In Abbildung 3 ist die Chain Tip, also das Kettenende oder der letzte Block bei Höhe 7, was bedeutet, die Blockchain ist 8 Blocks lang. Alle 10 Minuten steckt Lisa unbestätigte Transaktionen zusammen in einen neuen Block und gibt ihn an alle Interessenten frei.

Die Blockchain speichert Transaktionen, genau wie das Spreadsheet das tat. Aber der Block enthält auch einen Block Header, um die Integrität der darin enthaltenen Transaktionen sowie der davor liegenden Blockchain zu schützen. Sagen wir mal, die Blockchain aus Abbildung 3 ist so gewachsen, dass sie 20 Blocks enthält, die Chain Tip also auf Höhe 19 liegt. Abbildung 4 zoomt in die letzten paar Blocks der Blockchain hinein.

06 04
Abbildung 4. Jeder Block Header schützt die Integrität der in dem Block enthaltenen Transaktionen sowie die Blockchain vor diesem Block.
Block Header
u06 01

Jeder Block enthält eine oder mehrere Transaktionen und einen Block Header. Der Block Header besteht aus

  • Dem Doppel-SHA256 Hash des Vorgänger Block Headers.

  • Dem kombinierten Hash der Transaktionen im Block, der Merkle’schen Wurzel oder Merkle Root.

  • Einem Zeitstempel des Herstellungszeitpunkts dieses Blockes

  • Lisas Signatur des Block Headers

u06 02

Der Hash eines Block Headers ist eine Kennung, ein Identifier für den Block, genauso wie ein Transaction Hash, oder eine Transaktions-ID (txid) eine Kennung oder ein Identifier für eine Transaktion ist. Ich bezeichne Block Header auch gelegentlich als Block ID.

Der linke Teil des Block Headers ist die Block ID des Vorgängerblockes in der Blockchain. Deshalb heisst das Ganze dann Block_chain_. Die vorigen Block Header Hashes bilden eine Kette, oder Chain, aus Block Headern.

Der zweite Teil von links ist der kombinierte Hash aller Transaktionen. Dies ist der Merkle Root von einem merkle tree, die Merkle’sche Wurzel eines Merkle’schen Baumes. Wir werden das in späteren Abschnitten dieses Buches besprechen, aber für den Moment sagen wir einfach, dass die Transaktionen im Block zu einem einzigen Hashwert zusammengehasht werden, der in den Block Header geschrieben wird. Man kann keine Transaktion im Block ändern, ohne damit den Merkle Root zu ändern.

Der dritte Teil von links ist die Herstellungszeit des Blockes. Diese Zeit ist nicht genau und steigt von Block zu Block nicht einmal in jedem Fall an, aber sie stimmt zumindest grob.

Der vierte Teil ist Lisas Block Signatur, die ein Genehmigungsstempel von Lisa ist, den jeder verifizieren kann. Lisas Signatur beweist, dass sie den Block einmal genehmigt hatte, was gegen sie verwendet werden könnte, falls sie einmal mogeln sollte. Du wirst gleich sehen, wie das funktioniert. Die digitale Signatur im Block Header führt neue Probleme ein, die wir in [ch07] beheben werden, indem wir die digitale Signatur durch etwas ersetzen werden, das wir Proof of Work nennen werden.

6.2.1. Lisa produziert einen Block

Geteilter Ordner? Ernsthaft?

Bitcoin benutzt keine geteilten Ordner. Der geteilte Ordner ist ein Platzhalter für Bitcoins Peer-to-Peer Netzwerk, das wir in [ch08] näher betrachten werden.

Lisa erzeugt grob alle 10 Minuten einen neuen Block, der unbestätigte Transaktionen enthält. Sie schreibt den Block in eine neue Datei in einem geteilten Ordner, oder Share. Jeder hat die Zugriffsrechte, um neue Dateien in dem Share anzulegen, aber niemand hat die Rechte, Dateien zu ändern oder zu löschen. Wenn Lisa einen Block in eine Datei in dem Share schreibt, bestätigt sie die Transaktionen in dem Block.

Angenommen, Lisa ist dabei, einen neuen Block auf Höhe 20 zu erstellen. Sie tut folgendes:

  1. Erzeugt eine Blockschablone.

  2. Signiert die Blockschablone, um sie zu vervollständigen.

  3. Veröffentlicht den Block.

Blockschablonen

Lisa beginnt, indem sie eine Blockschablone erstellt, einen Block ohne Signatur (Abbildung 5).

06 05
Abbildung 5. Lisa erstellt den neuen Block. Wir nennen ihn Blockschablone weil er noch nicht signiert ist.

Sie sammelt mehrere Transaktionen, die sie dem Block hinzufügen will. Dann erzeugt sie den Block Header. Sie erzeugt die Vorgänger-Block ID, indem sie den Block Header des Vorgängerblocks hasht und das Ergebnis in den neuen Block Header schreibt. Der Merkle Root wird anhand der Transaktionen in der Blockschablone ermittelt und die Zeit wird auf die aktuelle Zeit gesetzt.

Blockvergütung

In Bitcoin enthält die Blockvergütung mehr als nur das neu geschaffene Geld. Es führt auch Transaktionsgebühren ein, die in [ch07] diskutiert werden. Das neu erzeugte Geld in einem Block wird als Blocksubvention oder block subsidy bezeichnet.

Die erste Transaktion in ihrem Block ist die Coinbase Transaktion. Solche Coinbase Transaktionen erzeugen 50 CT pro Block anstatt 7.200 CT, wie es in [ch05] der Fall war. Die Idee ist die, dass Lisa alle 10 Minuten einen neuen Block erzeugt, was bedeutet, dass ihre Tagesvergütung über 144 Blocks verteilt wird: es gibt 144 in 24 Stunden, und 144*50 CT = 7.200 CT. Wir sprechen mehr über Blockvergütungen und die Coinbase in [ch07].

Signieren des Blockes

Bevor Lisa mit dem Block fertig ist, muss sie ihn erst noch mit einem private Key signieren, den nur sie kennt, wie in Abbildung 6 dargestellt.

06 06
Abbildung 6. Lisa signiert einen Block mit ihrem Blocksignierer private Key. Der public Key ist unter den Kollegen allgemein bekannt.
Proof of Work, Arbeitsnachweis

Bitcoin Blocks werden nicht auf eine solche Weise signiert. Sie werden mit Proof of Work “signiert”, was in [ch07] beschrieben wird.

Lisa verwendet ihren Blocksignierer private Key, um den Block Header zu signieren. Die digitale Signatur legt sich fest auf

  • Die Vorgänger-Block ID, was bedeutet, Lisas Signatur legt sich auch auf die gesamte Blockchain vor diesem Block fest.

  • Den Merkle Root, was bedeutet, die Signatur legt sich auf alle Transaktionen in diesem neuen Block fest.

  • Den Zeitstempel

Würde sich irgendetwas in der Blockchain vor dem neuen Block oder in den Transaktionen innerhalb dieses Blockes ändern, so müsste sich auch der Inhalt dieses Block Headers ändern; dadurch würde die Signatur ungültig.

u06 03

Der public Key, der zu Lisas Blocksignierer private Key gehört, muss allen öffentlich zugänglich sein. Die Firma kann den public Key auf ihrem Intranet veröffentlichen und an das schwarze Brett am Eingang hängen. Die Signatur wird benötigt, weil (zur Zeit) nur Lisa neue Blocks an die Blockchain hängen darf. Zum Beispiel kann John einen neuen Block erzeugen und in den Share legen. Aber er wird ihn nicht korrekt signieren können, da er nicht über Lisas private Key verfügt, also wird niemand sonst Johns Block akzeptieren.

Die Benutzung von private Keys zum Signieren von Blocks könnte sich aus zwei Gründen als schlechte Idee entpuppen:

  • Lisas private Key kann gestohlen werden. Wenn das passiert, kann der Dieb gültige Blocks erzeugen und sie in den Share schreiben. Die Coinbase Transaktionen dieser Blocks würde der Dieb natürlich so abändern, dass sie an den PKH des Diebes gehen und nicht an Lisas.

  • Die Quellen für Lisas public Key–zum Beispiel das schwarze Brett und das Intranet–könnten kompromittiert und die public Keys durch die von irgendeinem Bösewicht ersetzt werden. Wenn das passiert, werden einige Prüfer dahingehend hereingelegt werden, dass sie Blocks anerkennen, die mit einem anderen als Lisas Blocksignierer Key signiert wurden. Der Bösewicht kann einen Teil der Prüfer täuschen. Ein Kollege sollte der Notiz auf dem schwarzen Brett nicht trauen, weil es leicht ist, diese mit einer Notiz mit einem gefälschten public Key zu ersetzen. Kollegen müssen den public Key aus verschiedenen Quellen erhalten, zum Beispiel durch das schwarze Brett, das Intranet und indem sie andere Kollegen fragen. Eine einzelne Quelle könnte zu einfach von einem Bösewicht manipuliert werden.

Die Art, wie Blocks signiert werden, wird sich in [ch07] ändern, von digitalen Signaturen auf Proof of Work.

Veröffentlichen eines Blocks

Ist der Block einmal signiert, muss Lisa ihn den Prüfern zugänglich machen. Sie benutzt den Share dafür, indem sie eine neue Datei erzeugt, block_20.dat, in die sie ihren neuen Block speichern will (Abbildung 7).

06 07
Abbildung 7. Lisa hat ihren neuen Block signiert und speichert ihn in eine neue Datei im Share.

Der Block ist jetzt veröffentlicht. Jeder, der Interesse hat, kann den Block aus dem Share lesen. Denk daran, dass keiner die Datei ändern oder löschen kann, weil die restriktive Rechtevergabe auf dem Share dies nicht zulässt. Nicht einmal Lisa selbst kann das. Es gibt aber einen Systemadministrator, der volle Rechte dazu hat und alles mit dem Share anstellen kann. Wir werden in [ch08] den Systemadministrator loswerden, wenn wir das Peer-to-Peer Netzwerk einführen.

Transaktionsauswahl

Wenn Lisa ihren Block zusammenbaut, sucht sie sich die Transaktionen aus, die sie darin einbetten will. Sie kann sich alles aussuchen, von null Transaktionen bis alle unbestätigten Transaktionen. Die Reihenfolge der Transaktionen ist nicht wichtig, solange alle Transaktionen nur Output ausgeben, die entweder in der Blockchain davor oder früher innerhalb dieses Blocks vorkommen. Zum Beispiel ist der Block in Abbildung 8 völlig in Ordnung.

06 08
Abbildung 8. Transaktionen müssen lediglich in Ausgabefolge, oder spending order ausgegeben werden. Davon abgesehen gibt es keine Einschränkungen.

Alle Transaktionen in diesem Block geben Transaktionen aus, die schon in der Blockchain sind, was bedeutet, sie nehmen alle nur Bezug auf Transaktionen links von sich. Aber der Block in Abbildung 9 ist ungültig.

06 09
Abbildung 9. Dieser Block ist ungültig, weil eine Transaktion einen Output ausgibt, der noch nicht existiert.

Er ist ungültig, weil eine Transaktion einen Output ausgibt, der nach–rechts von der ausgebenden Transaktion–platziert ist.

6.2.2. Wie schützt uns das vor Löschungen?

Angenommen, Lisa will einen Keks essen, ohne dafür zu bezahlen. Sie erzeugt eine Transaktion und legt sie in den Block, an dem sie zur Zeit arbeitet, an Blockhöhe 21. Sie erzeugt den Block Header, signiert ihn und schreibt den Block in eine neue Datei (block_21.dat) im Share (Abbildung 10).

06 10
Abbildung 10. Lisa erstellt einen Block, der ihre Zahlung für einen Keks enthält.

Das Café schaut im Share nach eingehenden Zahlungen. Wenn Lisa die Blockdatei in den Share schreibt, lädt das Café den Block herunter und verifiziert ihn. Zum Verifizieren eines Blockes muss Folgendes überprüft werden:

  • Die Block Header Signatur ist gültig. Die Signatur wird anhand von Lisas public Key verifiziert, der vom schwarzen Brett oder dem Intranet stammt.

  • Die Vorgänger Block ID existiert. das ist in diesem Fall Block 20.

  • Alle Transaktionen in dem Block sind gültig. Das wird mit dem gleichen Prüfverfahren festgestellt, wie in [ch05], mit einem privaten Unverbrauchten Transaktions Output (UTXO) Set.

  • Der kombinierte Hash aller Transaktionen passt zum Merkle Root im Block Header.

  • Der Zeitstempel liegt in einem vernünftigen Bereich.

Lisa hat für den Keks bezahlt, und das Café hat den Block, der Lisas Transaktion enthält, heruntergeladen und überprüft. Das Café gibt Lisa den Keks, und sie isst ihn.

Kann Lisa die Zahlung rückgängig machen, ohne dass man ihr Betrug nachweisen kann? Ihre einzige Option ist, eine veränderte Version von Block 21 anzufertigen, der ihre Transaktion nicht enthält, und diesen neuen Block als neue Datei block_21b.dat in den Share zu schreiben (Abbildung 11).

06 11
Abbildung 11. Lisa erzeugt einen alternativen Block auf Höhe 21, der ihre Transaktion nicht enthält.

Die neue Version gleicht der alten bis auf Lisas fehlende Transaktion. Weil sie an den Transaktionen im Block herumgepfuscht hat, muss sie den Merkle Root im Header mit einem Merkle Root aktualisieren, der zu dem neuen Satz an Transaktionen passt, die der Block jetzt enthält. Wenn sie den Header ändert, ist die Signatur nicht mehr gültig, und der Header muss neu signiert werden. Um den Block den Überprüfern zugänglich zu machen, muss sie den Block in den Share legen, zum Beispiel unter dem Dateinamen block_21b.dat.

Das Café hatte bereits die erste Version von Block 21 heruntergeladen. Wenn Lisa die neue Blockdatei schreibt, wird das Café herausfinden, dass es eine weitere Version des Blocks im Share gibt (Abbildung 12).

06 12
Abbildung 12. Das Café sieht zwei Versionen von Block 21, eine mit Lisas Transaktion und eine ohne.

Jetzt sieht das Café zwei verschiedene Blocks auf Höhe 21, einen mit der 10 CT Transaktion zum Café und einen ohne. Beide Blocke sind gleichermassen gültig, und aus der Perspektive eines Prüfers ist kein Block richtiger als der andere. Aber das Gute ist, dass das Café beweisen kann, dass Lisa ein schmutziges Spiel spielt, weil sie zwei verschiedene signierte Versionen des Blocks erzeugt hat. Die Signaturen beweisen, dass Lisa gemogelt hat, und jetzt haben wir nicht mehr eine Aussage-gegen-Aussage Situation. Lisa wird gefeuert oder zumindest ihrer mächtigen Stellung als Transaktionsverarbeiterin enthoben.

Was wäre, wenn es noch weitere Blocks hinter Block 21 gegeben hätte, als Lisa gemogelt hat? Nimm an, Blocks 22 und 23 waren bereits erstellt, als Lisa sich entschieden hat, ihre Transaktion zu löschen (Abbildung 13).

06 13
Abbildung 13. Lisa muss alternative Versionen des Blocks mit ihrer Transaktion und aller folgenden Blocks herstellen.
u06 04

Jetzt muss sie drei alternative Blocks erzeugen: 21, 22 und 23. Diese müssen alle durch gültige Blocks ersetzt werden.

Irgendetwas in einem Block zu ändern, macht den Block und alle Folgeblocks ungültig. Das liegt daran, dass jeder Block Header einen Zeiger auf den Vorgängerblock enthält–die Vorgänger Block ID–der ungültig wird, wenn sich der Vorgängerblock ändert.

6.2.3. Weshalb eine Blockchain benutzen?

Die Blockchain ist ein komplizierter Weg, einen Haufen Transaktionen zu signieren. Wäre es nicht viel einfacher, wenn Lisa schlicht alle Transaktionen, die je gemacht wurden, in einem riesigen Klumpen alle 10 Minuten signieren würde? Das würde dasselbe Ziel erreichen. Aber dieser Ansatz birgt einige Probleme:

  • Mit wachsender Anzahl Transaktionen dauert es immer länger, die gesamte Menge zu signieren.

  • Dasselbe gilt für die Überprüfenden–die Zeit, die zur Signaturprüfung gebraucht wird, wächst mit der Gesamtzahl an Transaktionen.

  • Es ist schwer für Prüfer, zu wissen, was sich seit der letzten Signatur geändert hat. Diese Information ist aber wertvoll für das Vorhalten des UTXO Sets.

Indem sie eine Blockchain benutzt, braucht Lisa nur den letzten Block von Transaktionen zu signieren, womit sie aber stillschweigend, indirekt über den Vorgänger Block ID Zeiger, alle historischen Transaktionen mit signiert, wie Abbildung 14 zeigt.

06 14
Abbildung 14. Jeder Block signiert alle Transaktionen, die je getätigt wurden, dank des Vorgänger Block ID Feldes im Header.

Die Signatur jedes Blocks erhärtet die Signaturen der Vorgängerblocks. Das wird sich als wichtig erweisen, wenn wir im nächsten Kapitel zu Proof of Work kommen werden.

Die Prüfer können auch leicht sehen, was sich seit dem letzten Block geändert hat, und ihre UTXO Sets entsprechend anpassen. Alle neuen Transaktionen stehen ja in dem neuen Block.

Die Blockchain liefert auch ein paar nette zusätzliche Features, die wir später besprechen werden, zum Beispiel den Merkle Tree.

6.3. Lightweight Wallets

Kollegen, die die Blockchain verifizieren, um sicherzugehen, dass sie gültige Finanzinformationen haben, benutzen eine Software, die die gesamte Blockchain herunterlädt und das UTXO Set jederzeit aktuell hält. Diese Software muss mehr oder weniger permanent laufen, um mit den neu produzierten Blocks Schritt zu halten. Wir nennen diese laufende Software einen vollständigen Knoten, oder Full Node. Ein Full Node kennt alle Transaktionen seit Block 0, dem Genesis Block. Die Firma und das Café sind typische Full Node Benutzer. Sie brauchen sich nicht auf Dritte zu verlassen, die ihnen die Finanzinformationen liefern: sie bekommen ihre Information direkt aus der Blockchain. Jedem steht es frei, diese Software laufen zu lassen, wenn er will.

Alternative Namen

Ein Lightweight Wallet wird gelegentlich auch als SPV Client oder SPV Wallet bezeichnet. SPV steht für simplified payment verification, also vereinfachte Zahlungsprüfung.

In [ch04] hatte ich eine mobile App eingeführt, mit denen die Kollegen ihre private Keys verwalten und Geld senden und empfangen können. Diese Wallet App wurde inzwischen auf das neue Blockchain System angepasst.

Weil die meisten Wallet Benutzer einen mobilen Datenvertrag haben, möchten sie nicht so viel Bandbreite auf das Herunterladen all der–für die uninteressanten–Blockdaten vergeuden. Die überwältigende Mehrheit der Blocks enthält keine Transaktionen, die sie betreffen, also würde es nur sinnlos ihren Datentarif verbrauchen, wenn sie alle Blockdaten herunterladen würden.

Die Full Node Entwickler und die Wallet Entwickler kooperieren, um Wallets zu ermöglichen, sich über das Internet mit Full Nodes zu verbinden und die relevanten Blockdaten auf eine solche Weise von diesen Nodes zu holen, dass dazu kein grosser Datenverkehr nötig ist.

Angenommen, Johns Wallet enthält zwei Adressen, @a und @b, und er möchte von einem Full Node bescheid bekommen, wenn Transaktionen bezügliche seines Wallets stattfinden. Er kann dazu eine Netzwerkverbindung zu einem Full Node eröffnen–zum Beispiel zu dem des Cafés. Das Wallet und der Full Node beginnen dann, miteinander zu reden wie in Abbildung 15 dargestellt.

06 15
Abbildung 15. Informationsaustausch zwischen einem Lightweight Wallet und einem Full Node.Der Full Node schickt alle Block Header und einen Bruchteil der Transaktionen an das Wallet.

Wir betrachten in [ch08] genauer, wie die Verbindung hergestellt wird und wie Wallet und Node Informationen zwischen sich austauschen. Ich liefere hier nur einen Überblick:

BIP37

Die Beschreibung dieses Prozesses findet sich vollständig in BIP37, hier [web-bips].

  1. Johns Wallet bittet den Full Node um alle neuen Block Header seit dem letzten, den das Wallet kennt, und um alle Transaktionen, die Johns Adressen betreffen.

  2. Der Full Node des Cafés schickt alle angefragten Block Header an das Wallet und mindestens alle Transaktionen, die Johns Adressen betreffen.

In Schritt 1 schickt das Wallet nicht die genaue Liste von Johns Adressen im Wallet. Das würde Johns Privacy beeinträchtigen, weil das Café dann wüsste, dass die ganzen Adressen von John zusammengehören, und könnte diese Information der Acme Versicherung verkaufen. Nicht nett. Johns Wallet schickt stattdessen einen Filter an den Full Node. Dieser Filter wird als Bloom Filter bezeichnet. Der Full Node benutzt ihn um festzustellen, ob er eine Transaktion an das Wallet schicken soll. Der Filter sagt dem Full Node, dass er alle Transaktionen bezüglich @a und @b schicken soll, aber auch, dass er andere Transaktionen schicken soll, die nicht zu Johns Wallet gehören, um zu verschleiern, welche Adressen tatsächlich zum Wallet gehören. Obwohl Bloom Filter nicht viel mit der Blockchain zu tun haben, widme ich ihnen hier dennoch einen Abschnitt, weil Lightweight Wallets sie intensiv benutzen.

In Schritt 2 werden Transaktionen und Block Header an Johns Wallet geschickt, aber nicht die kompletten Blocks (um Netzwerkbandbreite einzusparen). Johns Wallet genügt allerdings nicht nur die Transaktion und der Block Header, um zu überprüfen, dass die Transaktion im Block enthalten ist. Es wird etwas mehr benötigt: ein Teil des Merkle Baumes, ein sogenannter partial Merkle Tree, der beweist, dass eine oder mehrere Transaktionen in dem Block vorkommen.

Die beiden Schritte werden in einer Phase der Synchronisation direkt nach dem Verbindungsaufbau des Wallets zum Full Node des Cafés ausgeführt. Anschliessend werden an Johns Wallet immer dann, wenn Lisa neue Blocks generiert und das Café diese herunterlädt, die entsprechenden Block Header zusammen mit allen Transaktionen, die das Wallet betreffen, so wie vorhin beschrieben geschickt.

Als nächstes kommen Bloom Filter dran. Merkle Trees werden in Abschnitt 6.5 erklärt.

6.3.1. Bloom Filter verschleiern Adressen.

Johns Wallet enthält die zwei Adressen @a und @b, aber John möchte niemandem gegenüber preisgeben, dass @a und @b zum selben Wallet gehören. Er hat auch Grund, misstrauisch zu sein, denn ihm ist zu Ohren gekommen, dass die Acme Versicherung viel Geld für solche Informationen bezahlt, um die Prämien auf Basis des Kekskonsums der Leute “anzupassen”.

Erzeugung des Bloom Filters

Um zu vernebeln, welche Adressen zusammengehören, erzeugt Johns Wallet einen Bloom Filter, den es dem Full Node schickt (Abbildung 16).

06 16
Abbildung 16. Der Client sendet einen Bloom Filter an den Full Node, um zu vernebeln, welche Adressen zu dem Wallet gehören.

Der Bloom Filter ist eine Reihe von Bits, die, wie in [ch02] erwähnt, einen Wert von 0 oder 1 haben können. Johns Bloom Filter ist 8 Bit lang. [fig0617] stellt dar, wie er erzeugt wurde.

  1. Das Lightweight Wallet generiert einen Bloom Filter, um ihn dem Full Node zu schicken. Jede Adresse wird zum Bloom Filter addiert. image::images/ch06/06-17.svg[width='100%']

Das Wallet erzeugt die Reihe von Bits (den Bloom Filter) und initialisiert sie vollständig mit Nullen. Dann addiert sie die public Key Hashes (PKH) von allen Adressen von John zum Bloom Filter, beginnend mit PKHa, dem PKH von @a.

Weshalb drei Hashfunktionen?

Die Anzahl Hashfunktionen ist beliebig, ebenso wie die Grösse des Bloom Filters. Dieses Beispiel benutzt drei Hashfunktionen und 8 Bits.

Es schickt PKHa durch die erste der drei Hashfunktionen. Diese Hashfunktion liefert als Ergebnis den Wert 2. Dieser Wert ist der Index in den Bloom Filter. Das Bit an Index 2 (das dritte von links) wird dann auf 1 gesetzt. Dann wird PKHa durch die zweite Hashfunktion geschickt, die 0 als Resultat liefert, und das korrespondierende Bit (in der Abbildung das erste von links) wird auf 1 gesetzt. Schliesslich liefert die dritte Hashfunktion den Wert 6, und das Bit an Index 6 (das siebte von links) wird auf 1 gesetzt.

Als nächstes kommt der PKHb an die Reihe, mit dem auf die genau gleiche Weise verfahren wird. Die drei Hashfunktionen liefern als Outputs 5, 0 und 3. Diese drei Bits werden alle auf 1 gesetzt. Wohlgemerkt war Bit 0 bereits durch PKHa gesetzt, sodass dieses Bit hier nicht mehr geändert wird.

Der Bloom Filter ist fertig und kann an den Full Node geschickt werden.

Benutzung des Bloom Filters

Der Full Node empfängt den Bloom Filter vom Wallet und möchte ihn benutzen, um die Transaktionen herauszufiltern, die er an das Wallet schicken soll.

Angenommen, Lisa hat gerade einen neuen Block im Share veröffentlicht und der Full Node hat den Block verifiziert. Der Full Node will jetzt den neuen Block Header und alle darin enthaltenen relevanten Transaktionen an das Wallet schicken. Wie benutzt der Full Node den Bloom Filter, um herauszufinden, welche Transaktionen er schicken soll?

Der Block enthält drei Transaktionen: Tx1, Tx2, and Tx3 (Abbildung 17).

06 18
Abbildung 17. Der zu sendende Block enthält drei Transaktionen; nur eine betrifft John.

Tx1 und Tx3 haben nichts mit Johns Adressen zu tun, aber Tx2 zahlt an Johns Adresse @b. Schauen wir uns an, wie der Full Node den Bloom Filter benutzt (Abbildung 18).

06 19
Abbildung 18. Der Full Node benutzt den Bloom Filter um festzustellen, welche Transaktionen für das Wallet “interessant” sind.

Für jeden Output in einer Transaktion prüft der Node, ob irgendein PKH zu dem Filter passt. Er beginnt mit Tx1, die einen einzelnen Output an PKHL enthält. Um zu prüfen, ob PKHL durch den Filter passt, schickt er PKHL durch dieselben drei Hashfunktionen, die Johns Wallet beim Erzeugen des Filters benutzt hat. Die Hashfunktionen liefern die Indizes 5, 1 und 0. Die Bits an Index 5 und 0 sind beide 1, doch das Bit an Index 1 ist 0. Ein 0 Bit bedeutet, PKHL interessiert Johns Wallet definitiv nicht. Wenn Johns Wallet Interesse am PKHL hätte, hätte das Wallet ihn zu dem Filter hinzugenommen und damit Bit 1 auf 1 gesetzt. Weil PKHL der einzige PKH in Tx1 ist, ist Johns Wallet folglich nicht an dieser Transaktion interessiert.

Die nächste Transaktion ist Tx2. Sie enthält zwei PKHs: PKHb und PKHX. Es beginnt mit PKHb. Der Durchlauf durch die drei Hashfunktionen liefert die Indizes 5, 0 und 3. Alle drei Bits haben den Wert 1. Das heisst, der Code kann nicht sicher sagen, dass die Transaktion das Wallet wirklich interessiert, aber er kann auch nicht mit Sicherheit sagen, dass sie es nicht interessiert. Weitere PKHs in der Transaktion zu überprüfen ist sinnlos, weil der Node bereits beschlossen hat, dass Tx2 an das Wallet geschickt werden sollte.

Die letzte Transaktion hat zwei Outputs an PKHY und PKHZ. Es beginnt mit PKHY, was auf 2, 7 und 4 zeigt. Sowohl Bits 4 als auch 7 sind 0, was bedeutet, PKHY ist für das Wallet definitiv nicht von Interesse. Machen wir mit PKHZ weiter, was als Resultat Bits 2, 3 und 0 liefert. Alle drei Bits haben den Wert 1. Das wiederum bedeutet, dass Tx3 für das Wallet vielleicht von Interesse sein könnte, also schickt der Node auch diese Transaktion. Johns Wallet enthält PKHZ zwar nicht, aber der Bloom Filter versucht, mehr zu finden als nötig um einen gewissen Grad an Privacy zu erreichen. Wir nennen dies einen false positive Treffer oder Scheintreffer.

Das Ergebnis der Bloom Filterung ist, dass der Node die Transaktionen Tx2 und Tx3 an das Wallet schicken wird. Wie die Transaktionen geschickt werden, ist eine völlig andere Geschichte, die in Abschnitt 6.5 beschrieben wird.

Im Folgenden wird es schwierig. Du kannst den Abschnitt ruhig überspringen und zu Abschnitt 6.4 springen.

Die obige Beschreibung ist eine Vereinfachung dessen, was tatsächlich passiert. Wir haben nur die PKHs der beschriebenen Transaktions Outputs getestet, was alle Transaktionen erwischen würde, die Cookie Token an irgendwelche von Johns Adressen zahlen. Aber was ist mit den Transaktionen, die Geld von Johns Adressen ausgeben? Wir könnten zwar sagen, dass der Full Node dies Transaktionen nicht an das Wallet zu senden braucht, weil es die ja selbst erzeugt hat und sie deshalb sowieso schon kennt. Leider muss man aber diese Transaktionen trotzdem übermitteln, und zwar aus zwei Gründen.

Erstmal kann es sein, dass nicht diese Wallet App die Transaktion erzeugt hat. John kann viele Wallet Apps benutzen, die Adressen aus demselben Seed erzeugen. Erinnere dich zum Beispiel daran, wie in [ch04] ein Wallet aus einem mnemonic sentence wiederhergestellt wurde? Dieser mnemonic sentence kann von vielen Wallets gleichzeitig benutzt werden. John möchte vielleicht von einem der Wallets eine Zahlung tätigen und einem anderen Wallet über die Zahlung auf informiert werden, damit er dort das Gesamtsaldo überschauen kann.

Zweitens möchte John bescheid bekommen, wenn die Zahlung bestätigt wurde. Die Wallet App mag die Transaktion zwar schon haben, aber sie wird in der App noch als unconfirmed angezeigt. John möchte wissen, wann die Transaktion in einen Block Eingang gefunden hat, also muss der Node ihm diese Transaktion schicken, sobald sie in einem Block gelandet ist.

Was der Node tatsächlich testet, sind die folgenden Dinge: (Abbildung 19):

  • Die txid der Transaktion

  • Alle Transaktions-Output Verweise (TXO) in den Inputs.

  • Alle Datenobjekte in Signatur Scripts

  • Alle Datenobjekte der Outputs

06 20
Abbildung 19. Mehrere Dinge in einer Transaktion werden durch den Bloom Filter getestet, um festzustellen, ob die Transaktion möglicherweise interessant ist.

Damit Johns Wallet über Geldausgaben informiert wird, muss es entweder alle seine public Keys oder alle seine UTXO Verweise zum Bloom Filter hinzufügen.

Drosseln von Privacy und Datenverkehr
u06 05

Der Sinn der Bloom Filter ist die Verbesserung der Benutzer-Privacy. Der Level an Privacy kann durch das Verhältnis von 1en im Bloom Filter und durch dessen Grösse justiert werden. Je mehr 1en im Bloom Filter relativ zu seiner Grösse auftauchen, desto mehr Scheintreffer gibt es. Je mehr Scheintreffer, desto mehr unnötige Transaktionen muss der Full Node an das Wallet schicken. Mehr unnötige Transaktion bedeuten mehr verschwendeter Datenverkehr aber auch verbesserte Privacy.

Machen wir ein paar Überschlagsrechnungen. Der Bloom Filter im Beispiel vorhin hat 8 Bit, vom dem fünf 1en sind. Der Output einer einzelnen Hashfunktion hat daher eine 5/8 Wahrscheinlichkeit, eine dieser 1en zu treffen. Für einen einzelnen Test ist also die Wahrscheinlichkeit, dass alle drei Hashfunktionen eine 1 treffen, (5/8)3. Die Wahrscheinlichkeit, dass ein einzelner Test negativ ist–mindestens eine der drei Hashfunktionen zeigt auf eine 0–ist dann 1 – (5/8)3. Der Full Node führt auf jeder Transaktion mehrere Tests aus, typischerweise neun für eine Transaktion mit zwei Inputs und zwei Outputs. Checken wir das gegen die Liste von Tests, die der Full Node abarbeitet:

  • Die txid der Transaktion (1)

  • Alle TXO Verweise in den Inputs (2)

  • Alle Datenobjekte in Signatur Scripts (public Key und Signatur × 2 = 4)

  • Alle Datenobjekte in den Outputs (2)

Die Wahrscheinlichkeit, dass alle neun Tests negativ sind, ist (1 – (5/8)3)9 ≈ 0.08. Es werden also fast alle—92/100—Transaktionen an das Wallet gesendet. Das zeigt, dass nur drei 0en in den 8 Bits des Bloom Filter zu haben nicht hilft, die Daten wesentlich zu reduzieren, aber es hilft besser, die Privacy zu schützen.

Um weniger Scheintreffer zu bekommen, muss Johns Wallet einen grösseren Bloom Filter benutzen, damit das Verhältnis (Anzahl Einsen / Grösse des Bloom Filters) sinkt.

Definieren wir ein paar Symbole:

t = Anzahl Tests auf einer Transaktion (9) p = Wahrscheinlichkeit, dass eine Transaktion für uninteressant gehalten wird. r = Verhältnis der Anzahl 1en / Grösse des Bloom Filters

Wir können unsere Berechnung wie folgt verallgemeinern:

\[(1-r^3)^t=p \Rightarrow 1-r^3=p^{\frac{1}{t}} \Rightarrow r^3=1-p^{\frac{1}{t}} \\ \Rightarrow r=\sqrt[3]{1-p^{\frac{1}{t}}}\]

Sagen wir, du willst 1/10 aller Transaktionen bekommen (unter der Annahme, dass alle Transaktionen wie die vorige Transaktion aus zwei Inputs und zwei Outputs bestehen). Wie gross musst du den Bloom Filter machen?

\[t = 9, p = \frac{9}{10} \\ r = \sqrt[3]{1-p^{\frac{1}{t}}} = \sqrt[3]{1-(\frac{9}{10})^{\frac{1}{9}}} ≈ 0.23\]

Diese Berechnung liefert, dass der Bloom Filter etwa 6/0.23 ≈ 26 sein sollte, um nur 1/10 aller Transaktionen zu bekommen. Der Bloom Filter muss aber ein Vielfaches von 8 Bit sein, also ist 26 nicht erlaubt. Wir können auf 32 Bit aufrunden.

Denk daran, dass diese groben Berechnungen ein wenig auf falschen Annahmen beruhen, was die Transaktions-Merkmale betrifft. Wir berücksichtigen auch nicht, das die Anzahl 1en im Beispiel nicht genau sechs ist, sondern alles zwischen drei und sechs sein kann, angesichts der Tatsache, dass Johns Adressen auch den selben Satz an Indizes für beide Adressen hätten generieren können. Aber dieser Vorgang sollte dir helfen, eine Idee davon zu bekommen, wie gross ein Bloom Filter sein muss.

Probleme mit Bloom Filterm

Bloom Filter werden schon lange bei vielen Lightweight Wallets benutzt, aber sie haben Probleme:

Privacy

Ein Node, der Bloom Filter von einem Lightweight Wallet bekommt, kann ziemlich genau feststellen, welche Adressen zu einem Wallet gehören. Je mehr Bloom Filter er sammelt, dest höher die Genauigkeit. Siehe [web-bloom-filter-privacy] für Details.

Performance

Wenn ein Full Node zum ersten Mal einen Bloom Filter von einem Lightweight Wallet bekommt, muss der Node die gesamte Blockchain scannen und nach passenden Transaktionen suchen. Dieser Prozess ist I/O-Intensiv und kann mehrere Minuten dauern, je nachdem, auf was für Hardware der Node läuft. Dies kann bei einer Denial-of-Service (DoS) Attacke bösartig benutzt werden, um Full Node anzugreifen, sodass sie nicht mehr antworten.

Neue Bitcoin Improvement Proposals (BIPs), BIP157 und BIP158 sind zur Lösung vorgeschlagen worden, aber noch sind sie weder sehr verbreitet noch gut getestet. Die Idee dabei ist, den Vorgang so umzukehren, dass ein Full Node für jeden Block einen Filter zum Lightweight Wallet schickt. Dieser Filter enthält Informationen darüber, welche Adressen der Block betrifft. Das Lightweight Wallet prüft, ob seine Adressen auf den Filter passen und wenn ja, lädt er den ganzen Block. Der Block kann dabei von überall geladen werden, nicht nur von dem Full Node, der den Filter gesendet hat.

6.4. Wo waren wir?

Zu Orientierung, Abbildung 20 zeigt einen Teil dessen, was ich in [wallet-connection] in [ch01] skizziert hatte, wo Bobs Wallet nicht über Alices Zahlung an Bob informiert wurde.

06 21
Abbildung 20. Ein Bitcoin Wallet wird von einem Full Node über eine eingehende Zahlung informiert.

In dem Beispiel in diesem Kapitel hat John einen Bloom Filter an den Full Node des Cafés geschickt, um nur ihn betreffende Informationen zu bekommen. Der Full Node hat einen Block mit zwei Transaktionen erhalten, die John interessieren, zumindest laut Johns Bloom Filter.

Als nächstes werden der Header des neuen Blocks und die potenziell interessanten Transaktionen an Johns Wallet gesendet.

6.5. Merkle Trees

Nachdem der vollständige Knoten festgelegt hat, welche Transaktionen an das Wallet gesendet werden sollen, muss er den neuen Blockheader und alle Transaktionen senden, an denen Johns Wallet interessiert sein könnte.

06 22
Abbildung 21. Der Full Node füttert dem Lightweight Wallet den Block Header und die potentiell relevanten Transaktionen.

Der Full Node hat festgestellt, dass die Transaktionen Tx2 und Tx3 an das Wallet geschickt werden müssen. Wenn der Node nur den Header und die beiden Transaktionen schickt, dann kann Johns Wallet aber nicht prüfen, dass die Transaktionen zu dem Block gehören. Der Merkle Root hängt von drei Transaktionen ab, Tx1, Tx2 und Tx3, aber das Wallet bekommt nur Tx2 und Tx3 vom Full Node. So kann das Wallet den Merkle Root des Block Headers nicht wieder erstellen. Es braucht mehr Informationen, um zu prüfen, dass die Transaktionen in den Block wirklich eingebettet sind. Denke daran, dass wir sparsam mit dem Datenverkehr sein wollten, also alle Transaktionen im Block zu schicken ist nicht gut genug.

6.5.1. Erzeugen des Merkle Root

Es wird Zeit, aufzuklären, wie Lisa den Merkle Root erzeugt hat. Nimm an, Lisa fängt gerade an, den Block Header in Abbildung 21 zu erzeugen. Sie muss den kombinierten Hash aller Transaktionen berechnen, der als Merkle Root bezeichnet wird (Abbildung 22). Man berechnet den Merkle Root, indem man eine Hierarchie von kryptografischen Hashes erzeugt, einen Merkle Tree.

06 23
Abbildung 22. Lis erzeugt einen Merkle Root aus den Transaktionen in einem Block.

Die Transaktionen werden so sortiert, wie sie im Block liegen. Wenn die Anzahl Objekte ungerade ist, wird das letzte Objekt dupliziert und am Schluss angehängt. Dieses zusätzliche Objekt wird nicht zum Block hinzugenommen; es wir nur vorübergehend dupliziert zum Zwecke der Merkle Tree Berechnung.

Jedes Objekt (in diesem Falle Transaktionen) wird mit Doppel-SHA256 gehasht. Das resultiert in vier Hashwerten von je 256 Bits.

Die Hashwerte werden paarweise concateniert, zwei Hashes werden also verbunden, indem man den zweiten Hash an den Ersten hinten anhängt. Zum Beispiel ergibt abc concateniert mit def als Ergebnis abcdef.

Die vier Hashwerte sind jetzt zu zwei concatenierten Werten geworden. Weil zwei eine gerade Zahl ist, müssen wir kein zusätzliches Objekt ans Ende hängen. Die beiden concatenierten Werte werden separat voneinander gehasht, was zu zwei 256 Bit Hashes führt.

Diese zwei Hashwerte werden zu einem einzelnen 512 Bit Wert concateniert. Dieser Wert wird gehasht, was den 256 Bit Merkle Root liefert. Der Merkle Root wird in den Block Header geschrieben. Wenn eine Transaktion dazukommt, gelöscht oder geändert wird, muss der Merkle Root neu berechnet werden (Abbildung 23).

06 24
Abbildung 23. Eine Änderung in den Transaktionen verursacht eine Änderung im Merkle Root, wodurch die Signatur ungültig wird.

Das ist schön, denn wenn Lisa den Block Header signiert, weisst du, dass die Signatur ungültig wird, wenn jemand an den Transaktionen darin herumpfuscht.

6.5.2. Beweisen, dass eine Transaktion in einem Block enthalten ist

Der Full Node möchte die Transaktionen Tx2 und Tx3 an Johns Wallet schicken, weil er glaubt, dass sie für Johns Wallet interessant sein könnten. Der Full Node möchte beweisen, dass sowohl Tx2 als auch Tx3 in dem Block enthalten sind. Aber fangen wir mit dem Beweis für eine einzelne Transaktion an, Tx2. Wir schauen uns später im Kapitel ein grösseres, komplexeres Beispiel an.

Wie kann der Full Node dem Wallet beweisen, dass Tx2 im Block enthalten ist? Es kann einen partiellen Merkle Tree liefern, der Tx2 mit dem Merkle Root im Block Header verbindet. Die Idee dabei ist, das absolute Minimum an das Lightweight Wallet zu schicken–gerade genug, um zu verifizieren, dass Tx2 wirklich im Block enthalten ist. In diesem Beispiel schickt der Node das Zeug aus Abbildung 24 an das Lightweight Wallet.

06 25
Abbildung 24. Das absolute Minimum, um zu beweisen, dass Tx2 Teil des Blockes ist. Der Full Node schickt es an das Wallet.

Das Lightweight Wallet benutzt dann diese Information zur Überprüfung, ob Tx2 in dem Block vorkommt, indem es die Zwischen-Hashes in Richtung Root berechnet und verifiziert, dass der Hash von Tx2 unter den Hashes ist, die der Full Node geschickt hat (Abbildung 25).

06 26
Abbildung 25. Das Lightweight Wallet verifiziert, dass Tx2 im Block liegt, indem es den Merkle Root rekonstruiert.

Die Hashfunktionen sind aus dem Diagramm entfernt worden, damit es leichter zu lesen ist. Das Wallet kann jetzt sicher sein, dass Tx2 in dem Block enthalten ist.

6.5.3. Wie es wirklich funktioniert

Das folgende beschreibt detailliert, wie man einen partiellen Merkle Tree erzeugt und verifiziert. Wenn du willst, kannst du diesen Abschnitt überspringen und direkt zu Abschnitt 6.6 springen.
Erzeugen eines partiellen Merke Trees

Der partielle Merkle Tree ist eine gestutzte Version des vollen Merkle Trees, die nur jene Teile enthält, welche zu dem Beweis nötig sind, dass Tx2 zu dem Baum gehört. Der Full Node sendet dreierlei an das Wallet:

  1. Den Block Header

  2. Den partiellen Merkle Tree

  3. Tx2

Konstruieren wir den partiellen Merkle Tree. Der Full Node kennt die Anzahl Transaktionen im Block, also kennt er die Form des Merkle Trees. Um den partiellen Merkle Tree zu konstruieren inspiziert der Full Node die Hashes im Merkle Tree, beginnend am Merkle Root und in einer Abwärtsbewegung den Baum hinab, linker Ast zuerst (Abbildung 26).

06 27
Abbildung 26. Der Full Node konstruiert einen partiellen Merkle Tree, der Tx2 mit dem Merkle Root im Block Header verbindet.

Der partielle Merkle Tree besteht aus

  • Einer Zahl, die die Gesamtzahl der Transaktionen im Block angibt

  • Einer Liste Flags

  • Einer Liste Hashes

Bei jedem Schritt macht man zweierlei mit dem aktuellen Hash, wie in der folgenden Tabelle dargestellt:

  1. Füge das Flag zu der Liste von Flags hinzu. ✘ bedeutet, im Zweig dieses Hashes ist nichts Interessantes ; ✔ heisst, dieser Zweig enthält eine interessante Transaktion.

  2. Wenn das Flag ✘ ist oder der Hash eine interessante txid ist, füge den Hash der Liste von Hashes hinzu.

Schritt Interessante txid? Liste der Flags Ist Flag ✘ oder Hash eine interessante txid? Liste der Hashes

1

ja

nein

-

2

ja

✔✔

nein

-

3

nein

✔✔✘

ja

3

4

ja

✔✔✘✔

ja

3 4

5

nein

✔✔✘✔✘

ja

3 4 5

Dieser Sortierschritt heisst depth-first, oder erst abwärts, was bedeutet, dass man im Baum erstmal so weit herunter geht wie möglich, bevor man sich seitwärts bewegt. Aber du gehst nicht in Zweige herunter, in denen sich keine interessanten Transaktionen befinden. Das wird in der Liste der Flags als ✘ markiert. Du hörst bei ✘ auf, weil du keine unnötigen Daten an das Wallet schicken willst, daher der Begriff partieller Merkle Tree.

Jetzt, wo der Full Node den partiellen Merkle Tree konstruiert hat, schickt der Node den Block Header und den partiellen Merkle Tree an das Wallet und schickt dann die eigentliche Transaktion Tx2. Die Block Header zusammen mit dem partiellen Merkle Tree werden oft als Merkle Proof bezeichnet.

Verifizieren des partiellen Merkle Tree

Das Wallet hat einen Block Header, einen partiellen Merkle Tree und die Transaktion Tx2 vom Full Node erhalten. Das ist alles, was das Wallet benötigt, um zu verifizieren, dass Tx2 sich tatsächlich im Block befindet. Das Ziel ist, zu verifizieren, dass es einen Weg gibt, Tx2 mit dem Merkle Root des Block Headers zu “verbinden”. Es beginnt damit, den partiellen Merkle Tree zu verifizieren (Abbildung 27).

06 28
Abbildung 27. Das Wallet verifiziert den partiellen Merkle Tree.

Benutze die Anzahl Transaktionen (drei), die vom Full Node empfangen wurden, um die Merkle Tree Struktur aufzubauen. Das Wallet weiss, wie ein Merkle Tree mit drei Transaktionen aussieht.

Benutze die Liste der Flags und Hashes, um Hashes an den Merkle Tree zu heften, in depth-first Reihenfolge, wie folgt.

Schritt Nächtes Flag aus Liste Rest der Flag-Liste Ist Flag ✘ oder bist du ganz unten? Anzufügender Hash Liste der Hashes

1

✔✘✔✘

nein

-

3 4 5

2

✘✔✘

nein

-

3 4 5

3

✔✘

ja

3

4 5

4

ja

4

5

5

ja

5

Das Wallet hat jetzt genügend Hashes (3, 4 und 5) an den Merkle Tree gehängt, um die Leerstellen auf dem Weg nach oben im partiellen Merkle Tree auszufüllen. Zuerst wird den Hash von Schritt 2 aus 3 und 4 berechnet, und dann der Root aus 2 und 5.

Vergleiche den berechneten Merkle Root mit dem Merkle Root im Block Header–dem tatsächlichen Merkle Root–und überprüfe, dass beide identisch sind. Prüfe auch, dass der Hash von Tx2 in der Liste von Hashes ist, die vom Full Node empfangen wurden (Abbildung 28).

06 29
Abbildung 28. Das Wallet checkt, dass die Merkle Roots passen und dass Tx2 in der Liste der Hashes enthalten ist. Wenn ja, ist bewiesen, dass Tx2 sich tatsächlich in dem Block befindet.

Wenn die Transaktion zu einem der Hashes im partiellen Merkle Tree passt, und der partielle Merkle Tree Root zum Merkle Root im Block Header passt, dass hat der Full Node bewiesen, dass Tx2 Teil des Blocks ist.

Aber der Full Node wollte zwei Transaktionen aus diesem Block schicken. Wie würde der Merkle Proof mit zwei Transaktionen aussehen? Schickt man mehrere Merkle Proofs? Nein–wir lassen das als Übung am Ende des Kapitels.

Mit tausenden von Transaktionen in einem Block umgehen

Der Block im vorigen Beispiel hatte nur drei Transaktionen, Man hat nicht viel Platz gespart, indem man den Header, den partiellen Merkle Tree und Tx2 geschickt hat. Man hätte genausogut alle drei txids schicken können anstatt den partiellen Merkle Tree–das wäre viel einfacher. Aber der Vorteil von Merkle Proofs werden erkennbar, wenn die Anzahl Transaktionen im Block steigt.

Angenommen, der Full Node hat gerade einen Block mit 12 Transaktionen verifiziert. Durch Testen aller Transaktionen gegen den Bloom Filter des Wallets hat er festgestellt, dass zwei der Transaktionen potentiell interessant für das Wallet sind. Abbildung 29 zeigt, wie das aussehen würde.

06 30
Abbildung 29. Konstruktion eines partiellen Merkle Tree aus 12 Transaktionen und zwei interessanten Transaktionen

Der Full Node braucht nur den Block Header, die Zahl 12, 14 Flags und sieben Hashes zu übermitteln. Das summiert sich zu etwa 240 Bytes, viel weniger Daten als den Block Header und alle 12 txids (etwa 464 bytes).

Checken wir mal ein paar grobe Zahlen, um zu sehen, wie der Merkle Proof grössenmässig dasteht im Vergleich zur Grösse des vollen Blocks und dem vereinfachten Ansatz, alle txids zu schicken, bei wachsender Anzahl Transaktionen (Tabelle 1).

Tabelle 1. Grösse des Merkle Proofs verglichen mit der Blockgrösse und dem vereinfachten Beweis für verschiedene Blockgrössen
Anzahl Transaktionen im Block Blockgrösse (Bytes) Grösse Einfachbeweis (Bytes) Grösse Merkle Proof (Bytes) Länge der Hashliste

1

330

112

112

1

10

2,580

400

240

5

100

25,080

3,280

336

8

1,000

250,080

32,080

432

11

10,000

2,500,080

320,080

560

15

100,000

25,000,080

3,200,080

656

18

80-Byte Header

Bitcoins Block Header ist immer 80 Bytes lang. Die Cookie Token Block Header sind wegen der Signatur ein wenig grösser. Im nächsten Kapitel ändern wir den Block Header, um dem von Bitcoin ähnlicher zu werden, und in [ch11] sprechen wir über die Version, die ebenfalls im Block Header steht.

Tabelle 1 geht von einer einheitlichen Grösse aller Transaktionen von 250 Bytes aus, und dass man nur eine Transaktion beweisen möchte. Die Blockgrösse wird berechnet als der 80 Byte Block Header plus die Anzahl Transaktionen mal 250. Der einfache Beweis wird berechnet als der 80 Byte Block Header plus die Anzahl Transaktionen mal 32. Der Merkle Proof ergibt sich zu 80 Bytes Block Header plus die Länge der Hashliste mal 32. Ignoriere die Flags und Anzahl Transaktionen, weil diese vernachlässigbar sind.

Die Merkle Proofs wachsen nicht so schnell wie die vereinfachten Beweise, weil Merkle Proofs_logarithmisch_ mit der Anzahl Transaktionen wachsen, einfache Beweise hingegen linear. Wenn sich der Block in der Grösse verdoppelt, vergrössert sich der Merkle Proof ungefähr um einen konstanten Term von 32 Byte, wogegen sich der einfache Beweis in der Grösse verdoppelt.

6.6. Sicherheit von Lightweight Wallets

Lightweight Wallets scheinen ganz nett zu sein für das Cookie Token System. Das sind sie auch, aber die Benutzer sollten sich darüber klar sein, worauf sie im Gegensatz zu einem Full Node verzichten.

Full Node verifizieren die komplette Geschichte der Blockchain und wissen positiv, dass das Geld, das eine Transaktion ausgibt, existiert, und dass die Signaturen gültig sind.

Ein Lightweight Wallet kennt die gesamte Kette von Block Headern. Es kann verifizieren, dass Lisa jeden Block Header korrekt signiert hat. Wenn das Wallet eine Transaktion und einen Merkle Proof bekommt, kann es prüfen, ob die Transaktion in dem Block enthalten ist und ob der Block von Lisa signiert wurde. Aber es gibt eine Menge Dinge, die es nicht kann. Zu Beispiel:

  • Dass die Script Programme in der Transaktion alle OK als Resultat haben, was normalerweise die Verifikation aller Signaturen aller Inputs bedeutet.

  • Dass die ausgegebenen Outputs nicht bereits vorher ausgegeben wurden

  • Dass es alle relevanten Transaktionen erhält

Das Lightweight Wallet weiss auch nicht, welchen Regeln der Full Node folgt. Der Full Node könnte eine Regel übernommen haben, die die doppelte Belohnung an Lisa ausschüttet. Ein typischer Full Node würde jeden Block, der Lisa zu viel bezahlt, als ungültig betrachten, weil das keine der Regeln ist, mit denen er einverstanden ist, und den Block verwerfen.

Das Lightweight Wallet muss darauf vertrauen, dass der Full Node an seiner Stelle verifiziert und dass der Full Node den Regeln folgt, die das Wallet von ihm erwartet.

Der Full Node kann relevante Information vor dem Wallet geheimhalten. Das bedeutet, das Wallet bekommt über bestimmte eingehende oder ausgehende Transaktionen nicht bescheid.

Ein Lightweight Wallet übergibt die Verantwortung für die Verifikation dem Full Node, mit dem es verbunden ist. Nimm an, Lisa produziert einen ungültigen Block–zum Beispiel einen Block mit einer Transaktion, die einen Output ausgibt, der nicht existiert. Wenn der Full Node diesen Block erhält, sollte er den Block prüfen und verwerfen, weil er ungültig ist. Aber es könnte Gelegenheiten geben, bei denen der Full Node, gewollt oder ungewollt, den Fehler nicht feststellt. Vielleicht macht das Café mit Lisa gemeinsame Sache, um John hereinzulegen–wer weiss? Das Café und Lisa können, zumindest vorübergehend, John glauben lassen, er hätte Geld bekommen das er in Wirklichkeit nicht bekommen hat.

John kann wenigstens zwei Massnahmen durchführen, um das Risiko, von einem Full Node hinter’s Licht geführt zu werden, zu verringern:

Sich mit mehreren Full Nodes gleichzeitig verbinden

Die meisten Lightweight Wallet in Bitcoin tun dies automatisch. Alle Full Node, mit denen Johns Wallet verbunden ist, müssten zu einer Verschwörung gehören, um John zu übertölpeln (Abbildung 30).

06 31
Abbildung 30. Johns Wallet ist mit mehreren Full Nodes verbunden. Hoffentlich kollaborieren sie nicht alle, um John zu täuschen.
Trusted Node

Viele Bitcoin Wallets unterstützen Verbindungen zu einem Trusted Node. Frag dein Wallet Software Entwicklerteam, wenn du nicht sicher bist.

Verbindung zu einem Trusted Node

Ein Trusted Node ist ein Full Node, den John selbst auf einem Computer laufen lässt, den er kontrolliert (Abbildung 31). Auf diese Weise kann John ein Lightweight Wallet auf seinem Mobiltelefon benutzen, um Daten zu sparen, während er gleichzeitig sicher ist, dass er von seinem Full Node korrekte Informationen erhält.

06 32
Abbildung 31. John hat einen Trusted Node aufgesetzt, mit dem sein Lightweight Wallet sich verbindet.

Diese letzte Option ist nützlich, wenn John besorgt ist, dass manche Nodes vielleicht Regeländerungen zustimmen würden, gegen die er etwas hätte. Der einzige Weg, absolut sicher zu sein, dass du den Regeln folgst, die du willst, ist, deinen eigenen Node laufen zu lassen.

6.7. Zusammenfassung

Dieses Kapitel hat die Blockchain beschrieben, und wie sie es Full Nodes ermöglicht, zu beweisen, wenn Lisa Transaktionen ändert oder löscht. Die Blockchain ist eine Abfolge von Blocks, die durch kryptografische Hashes miteinander verbunden sind.

u06 08

Der Merkle Root im Block Header ist der kombinierte Hash aller enthaltenen Transaktionen. Dieser Hash wird durch das Hashen der Transaktionen in einer Merkle Tree Struktur erzeugt. Hashes werden paarweise concateniert, und das Ergebnis wird gehasht, um eine Ebene näher an Root zu kommen.

u06 10

Ein Full Node kann einem Lightweight Wallet beweisen, dass eine bestimmte Transaktion in einem Block ist, indem er einen Merkle Proof an das Wallet schickt. Der Merkle Proof besteht aus dem Block Header und einem partiellen Merkle Tree. Der Merkle Proof wächst logarithmisch mit der Anzahl Transaktionen im Block.

u06 11

Aus Gründen der Privacy wollen Wallets nicht einfach die Transaktionen, welche für sie relevant sind. Um zu vernebeln, welche Adressen zu ihnen gehören, benutzt das Wallet Bloom Filter, um sich für mehr Transaktionen anzumelden als die, für die es sich eigentlich interessiert. Es erzeugt einen Bloom Filter und schickt ihn an den Full Node.

u06 12

Der Full Node prüft verschiedene Dinge in den Transaktionen–zum Beispiel PKHs in Outputs–mit Hilfe von drei Hashfunktionen. Wenn eines der Objekte auf Indizes hasht, die auf 1 stehen, schickt der Node die Transaktion. Wenn nicht, schickt der Node die Transaktion nicht.

Diese Kapitel hat das Problem mit gelöschten oder veränderten Transaktionen gelöst. Lisa kann den Inhalt der Blockchain nicht mehr ändern, ohne dass man ihr den Betrug nachweisen kann.

Lisa kann immer noch Transaktionen zensieren. Sie kann sich weigern, Transaktionen zu bestätigen, die ihr geschickt werden. Sie hat die ultimative Macht über das, was in die Blockchain Eingang findet und was nicht. In [ch07] machen wir es für einen einzelnen Akteur wie Lisa viel schwerer, solche Entscheidungen durchzusetzen.

6.7.1. Systemänderungen

Wir haben die Blockchain eingeführt, die das Spreadsheet auf Lisas Computer ersetzt (Tabelle 2). Dieses Kapitel hat auch ein neues Konzept speziell für das Cookie Token System eingeführt: den Share. Dieser geteilte Ordner wird in [ch08] durch ein Peer-to-Peer Netzwerk von Full Node ersetzt.

Tabelle 2. Das Spreadsheet ist durch die Blockchain ersetzt worden. Wir haben auch den geteilten Ordner, oder Share, eingeführt, der als Platzhalter für das Bitcoin Netzwerk dient.
Cookie Tokens Bitcoin Behandelt in

1 Cookie Token

1 bitcoin

[ch02]

*Das Spreadsheet

*Die Blockchain

Kapitel 6

Lisa

Ein Miner

[ch07]

Block Signatur

Proof of Work

[ch07]

Der geteilte Ordner

Das Bitcoin Netzwerk

[ch08]

Die Blockchain ist jetzt fast schon so wie bei Bitcoin, aber mit einem wichtigen Unterschied: Lisa signiert die Blocks mit digitalen Signaturen, wogegen sie in Bitcoin mit Proof of Work signiert werden.

Es ist mal wieder Zeit, eine neue Version des Cookie Token Systems freizugeben. Schaut euch die tollen neuen Features in Tabelle 3 an!

Tabelle 3. Release Notes, Cookie Token 6.0
Version Feature Wie

new6.0

Hindert Lisa am Löschen von Transaktionen

Signierte Blöcke in einer Blockchain

Voll validierende Nodes

Lädt und verifiziert die gesamte Blockchain

Lightweight Wallet spart Daten

Bloom Filter und Merkle Proofs

5.0

Mehrere “Coins” in einer Zahlung

Mehrere Inputs in Transaktionen

Jeder kann das Spreadsheet überprüfen

Signaturen in Transaktionen öffentlich einsehbar

Sender legt Kriterien für das Ausgeben fest

Script Programme in Transaktionen

6.8. Übungen

6.8.1. Wärm dich auf

  1. Wie zeigt ein Block in der Blockchain auf den Vorgängerblock?

  2. Auf welche Information legt sich der Merkle Root fest (“committet” er sich)?

  3. Zu welcher Information committet sich Lisas Block Signatur?

    u06 13
  4. Wie werden Cookie Tokens (oder Bitcoin) erzeugt?

  5. Welche Transaktionen würden einen Bloom Filter passieren, er nur aus 1en (1) besteht?

  6. Welches Zeug in einer Transaktion prüft der Full Node, wenn er bestimmt, ob er eine Transaktion an ein Lightweight Wallet schicken soll? Überspringe diese Übung, wenn du nicht die schwierige Passage über Bloom Filter gelesen hast.

    u06 14
  7. Die Hashfunktionen, mit denen die Bloom Filter erstellt werden, sind keine kryptografischen Hashfunktionen. Weshalb nicht?

6.8.2. Grabe tiefer

  1. Zeichne die Struktur eines Merkle Trees aus einem Block mit fünf Transaktionen.

    u06 15
  2. Lisa signiert alle Blöcke with ihrem Blocksignierer-private Key. Der public Key wird über mehrere Quellen veröffentlicht, wie das Intranet und das schwarze Brett. Nenne mindestens ein Sicherheitsrisiko, das dieses Modell hat. Es gibt im Wesentlichen zwei solche Risiken.

  3. Es gibt zwei Stellen, an denen eine Einzelperson Transaktionen zensieren oder blocken kann. Welche zwei Stellen?

  4. Angenommen, Lisa erzeugt einen Block im Share auf der gleichen Height wie ein anderer Block. Der neue Block enthält dieselben Transaktionen wie der andere Block, ausser dass eine Transaktion durch eine andere Transaktion ersetzt wurde, die dasselbe Geld ausgibt. Sie probiert eine Double-Spend Attacke. Würde das durch einen Full Node erkannt, der

    1. Den Originalblock noch nicht heruntergeladen hat?

    2. Den Originalblock bereits heruntergeladen hat?

Für die Übungen 12-15 musst du die schwierigen Passagen gelesen haben, vor denen ich dich früher im Kapitel gewarnt hatte.
  1. Konstruiere einen Bloom Filter von 8 Bits für die beiden Adressen @1 und @2 wobei @1 auf die Indizes 6, 1 und 7, und @2 auf 1, 5, und 7 hasht. Nimm dann an, dass ein Full Node unseren Bloom Filter benutzen will um zu entscheiden, ob er die folgenden Transaktionen an unser Wallet schicken soll:

    u06 16

    Dieses Bild zeigt die Ergebnisse der Hashfunktionen für verschiedene Teile der Transaktion. Würde der Full Node diese Transaktion an das Lightweight Wallet senden?

  2. Als wir in Abschnitt 6.5.2 den Merkle Proof erzeugt hatten, hatten wir nur den Beweis für eine einzelne Transaktion erzeugt, Tx2. Konstruiere in dieser Übung einen partiellen Merkle Tree für beide Transaktionen Tx2 und Tx3. Die Anzahl Transaktionen in diesem Block beträgt drei.

    u06 17
  3. In Abschnitt 6.5.3.3 hatten wir einen partiellen Merkle Tree aus einem Block mit 12 Transaktionen konstruiert. Welche txids findet der Full Node interessant?

u06 18
  1. Angenommen du hast den Root eines partiellen Merkle Trees berechnet, wie in der vorigen Übung. Was musst du sonst noch tun, um zu verifizieren, dass eine bestimmte Transaktion in diesem Block enthalten ist?

6.9. Zusammenfassung

  • Transaktionen werden in Blocks gepackt, die Lisa signiert, um sie zur Verantwortung ziehen zu können, wenn sie versucht, Transaktionen zu löschen.

  • Jede Block Signatur committet sich auf die Transaktionen in dem Block und allen Vorgängerblocks, sodass die Historie nicht verfälscht werden kann, ohne dass man alle Blocks ab dem verfälschten Block erneut signiert.

  • Die Transaktionen in einem Block werden kollektiv in eine Merkle Tree Struktur gehasht, um einen Merkle Root zu erzeugen, der in den Block Header geschrieben wird. Dies macht es möglich, ein Lightweight Wallet zu schreiben.

  • Lightweight Wallets sparen Bandbreite, tun dies aber auf Kosten verringerter Sicherheit.

  • Die Sicherheit eines Lightweight Walles ist reduziert, weil solche Wallets die Transaktionen nicht vollständig prüfen können, und weil ein Full Node Transaktionen vor ihnen geheim halten kann.

  • Der einzige Weg, absolut sicher zu gehen, dass die Block Regeln eingehalten werden, ist, seinen eigenen Node zu betreiben.

  • Die Sicherheit eines Lightweight Wallets kann verbessert werden, indem es sich zu mehreren Full Nodes verbindet.

  • Lisa kann immer noch Transaktionen zensieren.