In diesem Artikel werden wir uns zunächst theoretisch damit beschäftigen, wie man eine Prüfsumme zu einer Ethereum-Adresse gem. des Ethereum Improvement Proposals 55 (EIP-55) hinzufügt. Anschließend werden wir uns zum besseren Verständnis die entsprechende JavaScript-Implementierung des EIP-55 genauer anschauen. Zum Schluss werden wir unserer Ethereum-Adresse dann eine entsprechende Prüfsumme mittels der von uns bereits installierten Bibliothek hinzufügen. Wer ledigliche an der praktischen Implementierung interessiert ist, kann direkt zu dem Abschnitt „Generierung einer Prüfsumme mittels Ethereumjs-util“ springen.
Bevor es losgeht, schauen wir noch einmal kurz auf unsere Übersichtsfolie. Wir haben im letzten Artikel bereits mittels der Bibliothek ethereumjs-util aus einem unkomprimierten öffentlichen Schlüssel eine Ethereum-Adresse generiert. Dennoch bleibt in dieser Übersichtsfolie die Ethereum-Adresse noch orange markiert, da wir dieser Ethereum-Adresse noch eine Prüfsumme hinzufügen möchten. Hierzu werden wir ebenfalls wieder die Bibliothek ethereumjs-util verwenden.
Prüfsumme für Ethereum-Adressen (EIP-55)
Genau wie BIPs, welche für Bitcoin Improvment Proposals stehen, gibt es auch EIPs, welche für Ethereum Improvment Proposal stehen. Der EIP-55 fügt einer Ethereum-Adresse eine Prüfsumme hinzu, indem die Groß- und Kleinschreibung bestimmter Buchstaben einer Ethereum-Adresse verändert wird. Da alle Ethereum-Wallets Ethereum-Adressen unabhängig von der Groß- und Kleinschreibung gleichbehandeln, ist der Standard auch abwärtskompatibel, zumal ein Wallet, welches hinzugefügte Prüfsummen über die Groß- und Kleinschreibung noch nicht unterstützt, die entsprechenden Unterschiede in der Groß- und Kleinschreibung einfach ignoriert. Diejenigen Wallets, welche jedoch Prüfsummen unterstützen, sind in der Lage Fehler in der Adresse zu erkennen, um bspw. Benutzer davon abzuhalten Geld unwiderruflich an eine falsche Adresse zu senden.
Um jedoch die Funktionsweise des EIP-55 zu verstehen, ist es unerlässlich das hexadezimale System zu verstehen. Aus diesem Grund habe ich die Hex-Werte noch einmal mit einer entsprechenden Zuordnung ins Dezimalsystem hier eingefügt. Wichtig ist an dieser Stelle insbesondere sich die Werte größer oder gleich 8 einzuprägen, da unter anderem diese Werte die Grundlage für die Entscheidung, ob ein Buchstabe groß- oder kleingeschrieben wird, sein werden. Wir gucken uns die Funktionsweise des EIP-55 jedoch im Folgenden noch einmal genauer an.
Von der Ethereum-Adresse zur Ethereum-Adresse mit Prüfsumme
Schauen wir uns nun einmal an, wie man von einer Ethereum-Adresse zu einer Ethereum-Adresse mit Prüfsumme gelangt. Das EIP-55 geht von einer Ethereum-Adresse in hexadezimaler Darstellung, welche keine Großbuchstaben und auch kein Hex-Präfix enthält, aus. Diese haben wir bereits in einem vorherigen Artikel erzeugt.
Jetzt wird von dieser Ethereum-Adresse wieder ein Hash mittels der Keccak256-Hashfunktion erzeugt. Sodann wird für jedes Zeichen der Ethereum-Adresse geguckt, welchen Hex-Wert der Hash an dergleichen Stelle hat. Ist der Hex-Wert des Hashs größer oder gleich 8, so wird der Buchstabe in der neu generierten Ethereum-Adresse mit Prüfsumme großgeschrieben. Andernfalls wird der Wert aus der Ethereum-Adresse einfach übernommen. Zahlen werden selbstverständlich nicht verändert, auch wenn der entsprechende Hex-Wert des Hashs größer gleich 8 ist.
Konkretes Anwendungsbeispiel
Schauen wir uns hierzu ein konkretes Beispiel an. Wir beginnen mit einer gültigen Ethereum-Adresse ohne hex-Präfix. Danach erzeugen wir einen Hash mittels der Keccak256-Hashfunktion von dieser Ethereum-Adresse. Nun können wir die gleiche Ethereum-Adresse mit Prüfsumme erstellen, indem wir die Buchstaben großschreiben, welche an der entsprechenden Stelle im Hash einen Wert größer gleich 8 haben. Das d bleibt klein, da 7 kleiner als 8 ist. Das B wird großgeschrieben, da das e dem Wert 14 entspricht. Wir erinnern uns an dieser Stelle an die zuvor eingeblendete Zuordnung der Hexadezimalwerte zu den Dezimalwerten. Die Zahlen werden einfach übertragen, auch wenn der hierzu gehörigen Hexadezimalwert größer oder gleich 8 ist. Das Prinzip wird dann bis zum Ende der Adresse Stelle für Stelle so fortgeführt.
Doch warum enthält diese neue Ethereum-Adresse mit teilweise großgeschriebenen Buchstaben nun eine Prüfsumme? Dies liegt darin begründet, dass der Hash-Wert einer Ethereum-Adresse, welcher nur ein einziges anderes Zeichen hat, einen völlig neuen Hash erzeugen würde und die Prüfsumme, also die groß geschriebenen Buchstaben, nun völlig anders gesetzt wären. Wir gucken uns das Ganze anhand einer Ethereum-Adresse mit fehlerhafte Prüfsumme an. Nehmen wir also an, dass wir die obige Adresse mit Prüfsumme abschreiben und am Ende anstatt einer 5 eine 6 aufschreiben. Um zu überprüfen, ob es sich hierbei um eine Adresse mit korrekter Prüfsumme handelt, wandeln wir zuerst alle Großbuchstaben wieder in Kleinbuchstaben um und erhalten die ursprüngliche Ethereum-Adresse mit einer 6 anstatt einer 5 am Ende.
Jetzt erzeugen wir wieder einen Hash mittels der Keccak256-Hash-Funktion und erhalten hier einen völlig anderen Hash, als wir oben erhalten haben. Wenden wir nun wieder die gleichen Regeln wie im obigen Beispiel an, indem wir alle Buchstaben, welche an der entsprechenden Stelle im Hash einen Wert größer gleich 8 haben, großschreiben, stellen wir bereits beim ersten Buchstaben fest, dass die Prüfsumme eigentlich anders aussehen müsste. Somit könnten wir den Nutzer warnen, dass seine zuvor angegebene Adresse einen Fehler enthält, da die Prüfsumme nicht stimmt.
JavaScript Implementierung des EIP 55
Jetzt wo wir die Theorie verstanden haben, können wir uns auch die JavaScript Implementation aus dem EIP-55 zu Testzwecken kopieren und uns genau angucken, wie die zuvor behandelte Theorie in der Praxis umgesetzt wurde. Hierzu gehen wir auf das EIP-55 und scrollen runter zu der JavaScript Implementierung.
Wir legen kurz eine neue Datei an, kopieren die Implementierung hierein und ersetzen noch kurz das require durch eine Import-Anweisung (Zeile 1).
Sodann ersetzen wir das ‘0x’-Hex-Präfix bei der neu zu erstellenden Ethereum-Adresse mit Prüfsumme durch eine leere Zeichenkette, um bei der Ausgabe eine bessere Vergleichbarkeit zu ermöglichen (Zeile 7). Anschließend kommentieren wir die vorherige Zeile aus, um den Eingriff zu geringfügig wie möglich zu halten (Zeile 6). Danach geben wir uns den Hash der Ethereum-Adresse mittels Console.log aus (Zeile 9).
import createKeccakHash from 'keccak';
function toChecksumAddress (address) {
address = address.toLowerCase().replace('0x', '')
var hash = createKeccakHash('keccak256').update(address).digest('hex')
// var ret = '0x'
var ret = ''
console.log(hash)
for (var i = 0; i < address.length; i++) {
if (parseInt(hash[i], 16) >= 8) {
ret += address[i].toUpperCase()
} else {
ret += address[i]
}
}
return ret
}
console.log(toChecksumAddress("0xdb39fff37c994dcfd839641f6f89f6d15630dc16"))
Schlussendlich müssen wir die Funktion toChecksumAddress nur noch aufrufen, eine Ethereum-Adresse übergeben und das Ganze über die Konsole ausgeben lassen (Zeile 19). Wir erhalten dann folgende Ausgabe:
c5fa3584b9ecc5ab20ab948401d2769eec6f8e26b95d069ea547084592535f02
Db39ffF37C994dCFd839641f6f89f6D15630DC16
Jetzt sehen wir den Hash der ursprünglichen Ethereum-Adresse sowie die neue, um eine Prüfsumme angereicherte Ethereum-Adresse ohne das entsprechende Hex-Präfix ‘0x’. Dieses wurde nämlich in Zeile 6 entfernt.
Wir erkennen so gut, dass in der neuen Ethereum-Adresse mit Prüfsumme alle Buchstaben, welche im darüberliegenden Hex-Wert einen Wert größer gleich 8 zugeordnet werden können, hier großgeschrieben werden und alle Buchstaben, welche eben einen Wert kleiner 8 im darüberliegenden Hash zugeordnet werden, kleingeschrieben werden. Zahlen verändern sich prinzipiell nicht.
Jetzt wo wir das Prinzip komplett verstanden haben, können wir diese Funktion aus unseren bereits installierten Bibliothek ethereumjs-util importieren und auf unsere Ethereum-Adresse anwenden.
Generierung einer Prüfsumme mittels Ethereumjs-util
Vorerst räumen wir unseren bisherigen Code etwas auf, indem wir die nicht mehr notwendigen Zeilen entfernen und die Konstante „toPublicFromEthJS“ in „toPublic“ umbenennen. Als nächstes importieren wir uns die Funktion „toChecksumAddress“ aus unserer Bibliothek ethereumjs-util.
Um diese zu nutzen, legen wir wieder ein Konstante an, in der wir den Rückgabewert dieser Funktion speichert werden. Nun können wir die toChecksumAddress-Funktion verwenden, indem wir unsere bereits erstellte Ethereum-Adresse als Parameter übergeben.
Hierbei müssen wir zunächst unsere Ethereum-Adresse, welche als Puffer-Objekt gespeichert ist, in die hexadezimale Darstellung umwandeln und ein hex-Präfix hinzufügen (Zeile 19). Denn anders als in der rohen EIP-55 Implementierung verlangt diese Bibliothek bei der Eingabe einen String mit Hex-Präfix als übergebene Ethereum-Adresse, obgleich die Funktionsweise zur Erfüllung des EIP-55 intern natürlich die gleiche ist.
import {generateMnemonic, mnemonicToSeed} from 'bip39'
import HDKey from 'hdkey';
import {privateToPublic, publicToAddress, toChecksumAddress} from 'ethereumjs-util';
const mnemonic = generateMnemonic();
console.log(mnemonic);
const seed = await mnemonicToSeed(mnemonic);
const hdKey = HDKey.fromMasterSeed(Buffer.from(seed));
let childKey = hdKey.derive("m/44'/60'/0'/0/0");
const toPublicKey = privateToPublic(childKey._privateKey);
console.log('Public key from ethereumjs-util: ', toPublicKey.toString('hex'));
const toAddress = publicToAddress(toPublicKey);
console.log('Eth-address: ' + '0x' + toAddress.toString("hex"));
console.log("Ethereum checksummed address: ", toChecksumAddress('0x' + toAddress.toString("hex")));
Anzumerken ist hier vielleicht noch, dass diese Funktion, auch wenn sie denselben Namen trägt wie im EIP-55 über einen zusätzlichen optionalen Parameter noch in der Lage ist, dass EIP-1191 umzusetzen. Dieses kann mittels einer ChainId bspw. einen Benutzer davon abhalten, vom Mainnet Ether an Adressen zu senden, welche für das Testnet erstellt wurden. Dies geschieht durch die Einbeziehung einer ChainId sowie dem 0x-Hex-Präfix in den zu erstellenden Hash. Hier sei aber angemerkt, wie auch im Quellcode dieser Bibliothek vermerkt, dass die Verwendung dieses Standards dann wiederum natürlich nicht abwärtskompatibel mit dem Prüfsummen-Format des EIP-55 ist, da Programme, welche lediglich das EIP-55 unterstützen, eine Prüfsumme, die unter Einbeziehung der ChainId erstellt wurde, natürlich nicht als korrekte Prüfsumme werten können.
Obgleich wir mittlerweile das Ziel unserer Übersichtsfolie erreicht haben, nämlich aus einem Mnemonic eine Ethereum-Adresse (mit Prüfsumme) zu erstellen, sind wir mit unserem konsolenbasierten HD-Wallet noch nicht komplett fertig. Wir müssen zwar keine neue Theorie mehr lernen, jedoch möchten wir aus einer Mnemonic nicht nur eine Ethereum-Adresse erstellen, sondern eine Vielzahl von Ethereum-Adressen.
Im nächsten Artikel werden wir uns deswegen damit beschäftigen, den bis jetzt geschriebenen Code so zu modifizieren, dass wir uns beliebig viele private Schlüssel mit einer Mnemonic erstellen können.
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