Ethereum HD-Wallet in JavaScript (BIP 32/44): Erweiterte Master-Keys und Child-Keys [Teil 4]

In diesem Artikel beschäftigen wir uns unter anderem damit, wie man aus einem Seed einen erweiterten privaten Master-Schlüssel und einen erweiterten öffentlichen Master-Schlüssel erstellt. Zudem schauen wir uns die Anwendungszwecke dieser Master-Schlüssel an und gehen auf deren Besonderheiten ein. Hierzu gucken wir uns eine neue JavaScript-Bibliothek genauer an und verwenden diese zur Erzeugung von Master-Schlüsseln. Ferner schauen wir uns an, wie man aus diesen Master-Schlüsseln weitere Kind-Schlüssel generiert und was der Unterschied zwischen einer normalen und einer gehärteten Ableitung von Kind-Schlüsseln ist.

Bevor wir starten, schauen wir noch einmal kurz in unsere Übersichtsfolie hinein. Wir haben in dieser Artikelreihe bereits erfolgreich eine Mnemonic generiert und hieraus einen Seed abgeleitet. Wir befinden uns jetzt im orange markierten Abschnitt und verwenden als theoretische Grundlage die BIPs 32 und 44. Zur Implementierung verwenden wir die JavaScript-Bibliothek hdkey, welche auf diesen BIPs basiert.

Die verwendeten BIPs und Bibliotheken zur Abbildung findet ihr am Ende des Artikels.

Seed zu Master-Schlüssel

Um aus einem Seed einen erweiterten privaten Master-Schlüssel zu generieren, benötigen wir die HMAC-SHA-512 Funktion. Diese ist eine Hash-Funktion, die mit einem Schlüssel arbeitet und folglich zwei Parameter erwartet, nämlich einmal den Eingabewert und einmal einen entsprechenden Schlüssel.

Um unser Ziel, nämlich die Erstellung eines erweiterten privaten Master-Schlüssels, zu erreichen, übergeben wir dieser Funktion als Eingabewert unseren Seed und als Schlüssel die Konstante “Bitcoin seed”. Es wird hierbei die Konstante “Bitcoin seed” verwendet, da auch Ethereum die BIPs, in diesem Fall das BIP 32, als Grundlage verwendet. Das können wir auch noch einmal unter dem Abschnitt Master Key Generation im BIP 32 nachschlagen. Die Umsetzung werden wir uns auch später noch einmal in der entsprechenden Bibliothek bei der Implementierung angucken.

Hieraus resultiert ein 512 Bit-Wert, wobei die Linken 256 Bit (=32 Byte) als privater Master-Schlüssel und die restlichen 256 Bit (=32 Byte) als Chain Code verwendet werden. Der Chain Code sorgt für eine zusätzliche Entropie bei der Ableitung von weiteren Kind-Schlüsseln. Er kann als ein separates 32 Byte, also 256 Bit großes Geheimnis betrachtet werden, ohne das es nicht möglich ist, weitere Kind-Schlüssel abzuleiten. Bei dem privaten Master Schlüssel und dem Chain Code handelt es sich um einen jeweils 32 Byte großen Integer-Wert, der also im Grunde genommen nichts anders darstellt als eine sehr große Zahl. Zusammengenommen bilden diese beiden Werte den erweiterten privaten Master-Schlüssel, welcher als klein m bezeichnet wird und aus dem dann weitere erweiterte Kind-Schlüssel entstehen können.

Berechnet man aus dem privaten Master-Schlüssel den öffentlichen Schlüssel und kombinierten diesen mit dem gleichen Chain Code, so erhält man den erweiterten öffentlichen Master-Schlüssel M, mit dem es möglich ist, weitere erweiterte öffentliche Kind-Schlüssel zu erzeugen. Hierauf kommen wir später noch einmal genauer zurück.

Voraussetzungen für die Implementierung

Im vergangenen Artikel haben wir einen Seed erstellt und ausgegeben. Wir kommentieren die Ausgabe jedoch wieder aus, da uns an dieser Stelle der Seed nur dazu dienen soll, einen erweiterten privaten Master-Schlüssel zu erstellen.

import {generateMnemonic, mnemonicToSeed} from 'bip39'
import HDKey from 'hdkey';

const mnemonic = generateMnemonic();
console.log(mnemonic);

const seed = await mnemonicToSeed(mnemonic);
// console.log(seed.toString('hex'))

Diesen Seed übergeben wir gleich einer Funktion, die aus dem Seed, entsprechend der zuvor beschrieben Theorie, den erweiterten Master-Schlüssel (also privaten Master-Schlüssel und Chain Code) erzeugt.

Zuerst laden wir uns wieder mittels NPM die JavaScript-Bibliothek hdkey herunter und installieren diese mit dem üblichen Befehl.

npm i hdkey

Sodann stellen wir fest, dass in unserer package.json eine entsprechende Abhängigkeit aufgetaucht ist, die uns die Bibliothek mit der entsprechenden Version anzeigt.

  "dependencies": {
    "bip39": "^3.1.0",
    "hdkey": "^2.1.0",
  }

Verwendung der hdkey-Bibliothek

Nun importieren wir uns das Softwarepaket zur Verwendung in unsere eigenen JavaScript-Datei (ethWallet.js). Hierbei müssen wir berücksichtigen, dass es sich um ein CommonJS-Modul handelt und deswegen keine “named”-Exports unterstützt werden.

import HDKey from 'hdkey';

Wir können auf die entsprechenden Funktionen über die Punkt-Notation zugreifen. Einen erweiterten Master-Schlüssel erstellen wir, indem wir einfach unseren zuvor erstellten Seed der fromMasterSeed-Funktion dieser Bibliothek übergeben. Dann erhalten wir sowohl einen erweiterten privaten Master-Schlüssel (klein) “m” als auch einen erweiterten öffentlichen Master-Schlüssel (groß) “M”, aus dem wir im Folgenden dann weitere erweiterte Kind-Schlüssel ableiten können. Wir können uns den Chain Code sowie die Master-Schlüssel wie folgt ausgeben lassen:

const myHDKey = HDKey.fromMasterSeed(seed);
console.log(myHDKey);

Umsetzung in der hdkey-Bibliothek

Wir gucken uns zunächst einmal an, wie diese Ausgabe mit der zuvor beschriebenen Theorie zusammenhängt und wie ebendiese Theorie in der soeben genutzten hdkey-Bibliothek umgesetzt wurde. Obgleich dies für die eigentliche Implementierung nicht zwingend notwendig ist, können wir hierdurch jedoch ein besseres Verständnis der verwendeten Bibliothek erlangen. Wem die Implementierungsdetails dieser Bibiliothek nicht interessieren, kann direkt zu dem Abstatz „Fortsetzung der Implementierung“ weiterspringen.

In den entsprechenden Unterordner nodes_modules / hdkey / lib / befindet die Datei hdkey.js in der wir auch sehe können, wie die von uns soeben verwendete und zuvor beschriebene Funktion “fromMasterSeed” aufgebaut ist.

var MASTER_SECRET = Buffer.from('Bitcoin seed', 'utf8')

HDKey.fromMasterSeed = function (seedBuffer, versions) {
  var I = crypto.createHmac('sha512', MASTER_SECRET).update(seedBuffer).digest()
  var IL = I.slice(0, 32)
  var IR = I.slice(32)

  var hdkey = new HDKey(versions)
  hdkey.chainCode = IR
  hdkey.privateKey = IL

  return hdkey
}

Als Hash-Funktion verwenden wir, wie in der Theorie bereits erwähnt, die SHA-512-Funktion.

Wir sehen hier, dass ein “MASTER_SECRET” als Schlüssel für die HMAC-Funktion verwendet wird, welches weiter oben entsprechend definiert wurde und erwartungsgemäß “Bitcoin seed” lautet. Die Daten, welche in unserem Fall, wie bereits in der Theorie erwähnt, unseren Seed darstellen, werden über die Update-Funktion übergeben. Sowohl der Seed als auch das Master_Secret werden hier in Form eines Puffer-Objektes übergeben.

Wir sehen hier in der fromMasterSeed-Funktion auch deutlich die Theorie abgebildet, denn der 512 Bit-Seed wird hier in zwei Hälften zerlegt (Zeile 5 und 6), wobei die Linken 256 Bit als privater Schlüssel und die rechten 256 Bit als Chain Code gespeichert werden (Zeile 9 und 10).

Fortsetzung Implementierung

Kommen wir nun zurück zu unserer bisherigen Implementierung. Da die entsprechenden Schlüssel sowie der Chain Code als Puffer-Objekt gespeichert sind, müssen wir diese für eine besser lesbare Ausgabe im Hex-Format ausgeben lassen. Zunächst fügen wir ein hex-Präfix hinzu, da die generierten Schlüssel dieses noch nicht enthalten. Danach geben wir den entsprechenden Schlüssel aus, indem wir über die Punkt-Notation hierauf zugreifen und den jeweiligen Schlüssel mittels der toString-Funktion in die hexadezimale Darstellung umwandeln.

console.log('0x' + myHDKey.privateKey.toString('hex'));
console.log('0x' + myHDKey.publicKey.toString('hex'));

Ein zusätzlicher, hier zwar nicht direkt relevanter, aber zum besseren Verständnis ggf. sinnvolle Hinweis ist, dass erweiterte Schüssel zur besseren Weitergabe serialisiert werden können. Diese enthalten dann noch zusätzliche Metainformationen sowie eine Prüfsumme. Wir können uns unsere erweiterten serialisierten Schlüssel wie folgt ausgeben:

console.log(myHDKey.privateExtendedKey);
console.log(myHDKey.publicExtendedKey);

Diese erkennt man an dem vorangestellten xprv bzw. xpub.

Den genauen Aufbau der serialisierten erweiterten Schlüssel findet ihr im BIP 32 im Abschnitt Serialization-Format.

Interessant ist vielleicht an dieser Stelle noch das 4 Byte große Feld “Child Number”, welches die Indizes auf 4 Byte begrenzt, sodass man für jeden Eltern-Schlüssel maximal ca. 4 Milliarden Kind-Schlüssel generieren kann. Wir kommen auf die Ableitung von Kind-Schlüsseln später noch einmal genauer zu sprechen.

Wir benötigen an dieser Stelle jedoch weder den privaten Master-Schlüssel, noch den Chain Code oder die serialisierten erweiterten Master-Schlüssel zur Ausgabe, sondern können mit unserer Bibliothek hdkey einfach weiterarbeiten, um uns einen entsprechenden Kind-Schlüssel zu generieren. Wichtig hierbei ist, dass natürlich auch Kind-Schlüssel wieder erweiterte Schlüssel sein können, die dann ebenfalls einen Chain Code enthalten und dazu genutzt werden können weitere Kind-Schlüssel zu erzeugen.

Es ist jedoch zum besseren Verständnis sinnvoll, sich noch einmal genauer mit der Theorie der Ableitung von Kind-Schlüsseln zu beschäftigen, um zu verstehen, was uns die Bibliothek hdkey im Hintergrund abnimmt und warum wir im Folgenden dieser Bibliothek bestimmte Parameter, wie den Ableitungspfad übergeben werden.

Erweiterte private Master-Schlüssel und erweiterte öffentliche Schlüssel

Schauen wir uns noch einmal genauer die Möglichkeiten an, welche uns ein erweiterter privater und ein erweiterter öffentlicher Schlüssel bieten. Wir illustrieren das hier am Beispiel des zuvor erstellen erweiterten privaten und öffentlichen Master-Schlüssel.

Aus den erweiterten privaten Master-Schlüssel mit Chain Code (klein m), sowie einen Index von 0 – ca. 2 Mrd. lassen sich ca. 2 Mrd. normale erweiterte private Kind-Schlüssel ableiten. Aus diesen erweiterten privaten Kind-Schlüsseln lässt sich natürlich auch wieder jeweils ein zugehöriger öffentliche Schlüssel berechnen.

Berechnet man von dem erweiterten privaten Master-Schlüssel m den öffentlichen Schlüssel und kombiniert diesen mit dem gleichen Chain Code, so erhält man den erweiterten öffentlichen Master-Schlüssel groß M. Generiert man mit diesem erweiterten öffentlichen Schlüssel unter Zuhilfenahme derselben Indizes (also von 0 – 2 Mrd.) weitere erweiterte öffentliche Kind-Schlüssel so erhält man ebenfalls wieder die entsprechende Anzahl an erweiterten öffentlichen Kind-Schlüsseln, also ca. 2 Mrd. Der wichtige Punkt ist hier, dass diese öffentlichen Kind-Schlüssel dieselben sind, wie die öffentlichen Schlüssel der Kind-Schlüssel des erweiterten privaten Master-Schlüssels klein m.

Dies führt uns zu den besonderen Einsatzgebieten der normalen Ableitung von Kind-Schlüsseln. Diese könnten bspw. bei einer E-Commerce Anwendung genutzt werden, um für verschiedene Transaktionen neue öffentliche Schlüssel zu generieren, die benötigt werden, um Ether zu empfangen. Sollte die E-Commerce Anwendung zu irgendeinem Zeitpunkt kompromittiert werden, so gelangt ein Angreifer lediglich an den erweiterten öffentliche Schlüssel und die generierten öffentlichen Kind-Schlüssel und hat weiterhin keinen Zugriff auf die darin enthaltenen Ether, da diese lediglich über die dazugehörigen privaten Schlüssel transferierbar sind. Diese liegen in diesem Beispielszenario jedoch nicht auf demselben Server, da sie dort schlichtweg nicht benötigt werden.

Diese Methode der Schlüsselgenerierung hat jedoch auch einen entscheidenden Nachteil:

Kommt doch einmal ein privater Kind-Schlüssel zusammen mit dem erweiterten öffentlichen Eltern-Schlüssel, welcher den Chain Code enthält, abhanden, so sind alle privaten Kind-Schlüssel und auch der private Eltern-Schlüssel ableitbar und somit nicht mehr sicher.

Wir kommen hierauf im Detail gleich zu sprechen. Um dieses Sicherheitsrisiko zu vermeiden, gibt die so genannte gehärtete Ableitung von Kind-Schlüsseln. Die gehärtete Ableitung von Kind-Schlüsseln kennzeichnen wir in der Abbildung durch ein separates gelbes Schloss auf den Schlüsseln. Wichtig für den weiteren Verlauf ist hier, dass diese gehärteten Kind-Schlüssel laut Spezifikation, also BIP 32, bei einem Index von 2.147.483.648 beginnen. Hierauf werden wir später noch genauer eingehen.

Diese gehärtete Kind-Schlüssel können zwar als sicherer betrachtet werden, da bei einer Kompromittierung eines Kind-Schlüssels der Elternschlüssel weiterhin geschützt ist. Jedoch haben diese gehärteten Kind-Schlüssel auch wieder einen entscheidenden Nachteil:

Man kann nicht unter Zuhilfenahme eines erweiterten öffentlichen Schlüssels die öffentlichen Kind-Schlüssel erstellen. Dies führt dazu, dass man die zu einem gehärtete privaten Kind-Schlüssel gehörigen öffentlichen Schlüssel nicht wie in dem vorherigen Beispiel unkompliziert auf einen Webserver generieren kann, ohne dort einen privaten Eltern-Schlüssel zu hinterlegen.

Somit kann man sagen, dass sowohl gehärteten als auch die normalen Ableitungen ihre Daseinsberechtigung für spezifische Anwendungszwecke haben und in der Praxis kombiniert mittels eines so genannten Ableitungspfads, auf den wir im nächsten Artikel noch zu sprechen kommen, eingesetzt werden.

Normale vs. gehärtete Ableitung

Schauen wir uns noch einmal kurz im Detail an, wie sich die Ableitung von gehärteten und normalen Schlüsseln unterscheiden, um besser zu verstehen, wieso die gehärtete Ableitung in der zuvor angesprochenen Hinsicht eine höhere Sicherheit aufweist.

Zunächst gucken wir uns einmal an, wie eine normale Ableitung eines Kind-Schlüssels im Detail funktioniert. Hierzu setzen wir voraus, dass ein privater Eltern-Schlüssel, welcher auf der obersten Ebene dem Master-Schlüssel entspricht, vorhanden ist. Zusätzlich zu diesem privaten Schlüssel benötigen wir den dazugehörigen öffentlichen Schlüssel sowie einen Index von (0 – 2 Mrd.).

Ferner benötigen wir noch den Chain Code, welcher auf der obersten Ebene ebenfalls durch den erweiterten öffentlichen Master-Schlüssel mit Chain Code, bekannt ist. Anschließend können wir den öffentliche Schlüssel zusammen mit dem Index als zu “hashenden” Wert in die HMAC-SHA 512 Funktion hineinpacken und den Chain Code als Schlüssel für die HMAC-Funktion nutzen. Dies ist auch der Grund, warum wir weiter oben geschrieben haben, dass der Chain Code als ein zusätzliches Geheimnis betrachtet werden kann, um Kind-Schlüssel abzuleiten. Hieraus resultiert eine 64 Byte, also 512 Bit große Ausgabe, welche einmal in der Mitte geteilt wird. Die rechten 32 Byte werden als neuer Chain-Code verwendet und dienen somit der Ableitung von weiteren Kind-Schlüsseln. Die anderen, linken 32 Byte werden zur Berechnung des neuen privaten Kind-Schlüssels verwendet. Zusätzlich wird der private Eltern-Schlüssel zur Berechnung benötigt. Das modulo n bewirkt, dass wir im Bereich der zulässigen privaten Schlüssel bleiben. Das n und sämtliche anderen Parameter der von Ethereum verwendeten elliptischen Kurve, wurden in dem Standard secp256k1 definiert und können hier auf S. 9 nachgelesen werden.

Nun wird auch klar, dass wir den privaten Eltern-Schlüssel berechnen können, wenn uns der private Kind-Schlüssel und der erweiterte öffentliche Schlüssel (mit Chain-Code) bekannt ist, da wir in der unteren Formel den 32 Byte Wert mithilfe des erweiterten öffentlichen Schlüssels berechnen können, n ohnehin bekannt ist und dann durch den abhandengekommenen privaten Kind-Schlüssel nun das letzte Element zur Berechnung des privaten Elternschlüssels auch vorhanden ist. Man müssten nur noch die Formel entsprechend hin zum privaten Eltern-Schlüssel umstellen, um diesen zu berechnen.

Schauen wir uns nun an, wie es bei der gehärten Ableitung aussieht. Bei der gehärteten Ableitung benötigen wir den privaten Eltern-Schlüssel, und einen Chain Code. Der entscheidende Unterschied ist hier, dass wir den Index zusammen mit den privaten Eltern-Schlüssel und nicht den öffentlichen Eltern-Schlüssel als Eingabewert Wert nehmen.

Dies bewirkt, dass die zur Berechnung des privaten Kind-Schlüssels notwendigen 32 Byte nur erzeugt werden können, wenn man im Besitz des privaten Eltern-Schlüssels ist. Kommt mal ein privater Kind-Schlüssel abhanden, so fehlen einem Angreifer zur Berechnung des privaten Eltern-Schlüssels – im Gegensatz zu der normalen Ableitung – immer noch die notwendigen 32 Byte, welche sich nur erzeugen lassen, wenn man bereits im Besitz des privaten Eltern-Schlüssels ist. Wir sehen also, dass diese Form der Ableitung den Eltern-Schlüssel wesentliche besser schützt als die normale Ableitung.

Im nächsten Artikel werden wir uns anschauen, wie man über mehrere Ableitungen von Kind-Schlüssel eine Baumstruktur erstellen kann und wie man sich mittels eines so genannten Ableitungspfades innerhalb dieser Baumstruktur zurechtfindet bzw. einmal generierte Schlüssel in dieser Baumstruktur wiederfindet.

BIPs und verwendete Bibliotheken

BIPs:
[1] https://github.com/bitcoin/bips
[2] https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[3] https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[4] https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

Genutzte Bibliotheken:
[5] https://www.npmjs.com/package/bip39
[6] https://www.npmjs.com/package/hdkey
[7] https://www.npmjs.com/package/ethereumjs-util

Weitere Links:

[8] https://www.secg.org/sec2-v2.pdf

Nach oben scrollen