Blockchain

posted

2026-02-22

updated

2026-02-22
 
Kürzlich hatten wir auf der Arbeit über ein Feature gesprochen, dass ein Kunde für sein Projekt benötigt. Dabei ging es um sichere Transaktionen und so kam schnell das Stichwort "Blockchain" auf. 
Von meinen Chefs angestachelt habe ich mich also auf die Programmierung einer Blockchain gestürtzt. 
Ausprobieren
Lernen und verstehen 
Ich hatte bereits ein Verständnis davon, was eine Blockchain war, allerdings bei weitem nicht genug, um die Implementierung einfach so abzuleiten. 
Mir war klar, es ging um Daten die, wie der Name sagt, in Blöcken gespeichert werden und gehasht werden. Dieser Hash sollte irgendwie die Sicherheit garantieren, dass die Reihenfolge und der Inhalt der Blöcke nicht verändert werden kann. 
Jetzt musste ich mir aber erstmal anlesen, welche Bedingungen genau für die Blockchain gelten und wie das zu ihrer Sicherheit Beiträgt.
Nach einer kurzen Zeit auf der Suche nach einem gutem Tutorial, bin ich dann fündig geworden: https://www.youtube.com/watch?v=UYeXfzyvm2c
Etwas holprig manchmal im Ablauf zeigt das Video doch sehr anschaulich, wie eine Blockchain aussieht. 
Nachbauen 
Noch während ich das Video geschaut habe, habe ich bereits angefangen Teile des Projektes zu implementieren. 
Als Grundlage habe ich dazu wieder mein MVC-basiertes typescript-Framework aus der "Ein-Feature"-Reihe verwendet. 
Der Block 
Ich hatte vor allem aus dem Video mitgenommen,
welche Eigenschaften ein einzelner Block braucht: 

export class BlockModel extends Model<ModelCollection> {
    // ...
    public previous_hash: number = 0;
    public hash: number = 0;
    public nonce: number = 0;
    public data: string = "";

    // ...
}
 
Der Hash
 
Jetzt brauchte ich noch eine Funktion die den Hashwert berechnet.
In meiner Vorlage wurde dazu eine Bibliothek verwendet um den Hash nach Sha256 zu berechnen.
 
Allerdings war mir gerade nicht danach irgendwelche Bibliotheken zu verwenden und den Sha256-algorithmus wollte ich auch nicht implementieren, zumal es in Javascript mit Zahlen ab 54 Bits schwierig wird mit dem normalen <<number>> Type zu arbeiten.
 
Folgendes Beispiel sollte mathematisch betrachtet natürlich 1 ausgeben,
tut es aber aufgrund der begrenzten Genauigkeit der Repräsentation von Zahlen im Rechner nicht:
 
var j = 0xffffffffffffffff, i = j+1-j; 
console.log(i);  
// prints 0
 
Bei einer 64bit Fließkommazahl nach IEEE Standard 754,
in dem ein Bit für das Vorzeichen und 8 Bit für den Exponenten verwendet werden, bleiben nur 53 Bit für die Mantisse übrig.
 
Also habe ich eine eigene kleine Hashfunktion geschrieben basierend auf eine zufälligen Primzahl und ein paar bit-Operatoren, die einen Wert zwischen 0 und 1^16 ergibt.
 
public get_hash(data: string): number {
    const max_value = (1 << 16) - 1;
    const primenumber = 0xAE49;
    return data.split('').reduce<number>((current_hash: number, next_char: string): number => {
        const new_value = next_char.charCodeAt(0);
        const munch: number = (current_hash << 2) + (new_value ^ primenumber) * ((new_value + 1) ^ primenumber);
        return ((munch >> 2) & max_value);
    }, 0);
}
 
Ich habe keine Ahnung, ob diese Funktion den Ansprüchen der Definition von Hash-Funktionen genügen würde, aber für meine Zwecke ist es Ausreichend. Sie gibt bei kleinen Änderungen in <<data>> einigermaßen abweichende Resultate.
 
Den Block serialisieren
 
Der Hashfunktion kann ich allerdings nur Zeichenketten übergeben,
aber der Block hat ja noch den <<nonce>> und den <<previous_hash>>, welche beide vom Typ <<number>> sind.
 
Theoretisch hätte hier die <<toString()>> methode hier ausgereicht,
allerdings fand ich es ästhetischer die Zahlen in Hexadezimal und mit führenden Nullen zu verwenden.
 
function string_with_min_length(
    input: string, 
    min_length: number = 4, 
    char: string = '0'
): string {
    return (char.repeat(min_length) + input).substr(-min_length);
}

function string_from_hash(number: number) {
    return string_with_min_length(number.toString(16));
}
 
Jetzt muss der gesamte Inhalt nur noch als ein String zusammengefügt werden.
 
public get_complete_data(): string {
    return [
        string_from_hash(this.previous_hash),
        this.data,
        string_from_hash(this.nonce),
    ].join('');
}
 
Der Prove of Work
 
So hatte ich nun meinen Block dem ich Daten geben konnte, aus dem er dann einen Hash generiert, was Rechnerisch nicht sehr aufwendig ist.
Der Clou bei der Blockchain ist jetzt, dass dieser Hash eine weitere Bedingung erfüllen muss, um gültig zu sein. 

In meinem Beispiel habe ich mich zuerst dazu entschieden, dass die ersten 16 Bit des Hashes eine 0 sein müssen, später habe ich das auf 8 Bit reduziert, um etwas schneller zu einem Ergebnis zu kommen.
(Und 0 selbst ist bei mir auch kein gültiger Hash, das hat aber andere Gründe)
 
public has_valid_hash(): boolean {
    return (this.hash & 0xf000) === 0x0 && this.hash !== 0;
}
 
Falls der berechnete Hash nicht die oben genannte Bedingung erfüllt,
wird der <<nonce>> des Blocks hochgezählt und verändert dann wiederum den Hash, bis dieser die Bedingung erfüllt. 

Wenn die Hashfunktion richtig funktioniert, ist es nicht möglich eine Lösung für diese Bedingung zu finden ohne alle Lösungen durchzuprobieren. Das bedeutet, das eine gewisse Rechenleistung nötig ist, bis ein gültiger Hash für einen Block gefunden wurde.
 
public mine() {
    this.is_computing_hash = true;
    while(this.is_computing_hash) {
        this.hash = this.compute_hash();
        this.is_computing_hash = !this.has_valid_hash();
        if (this.is_computing_hash) {
            this.nonce = (this.nonce + 1) & 0xffff;
            if (this.nonce === 0) console.warn("restarting starting nonce on block " + this.id);
        }
    }
}
 
Der erste Test
 
Natürlich wollte ich, dass sich in meinem Testprojekt zur Blockchain auch etwas tut, also habe ich den Hashwert des Blocks nichts sofort berechnet, sondern pro Frame immer nur einen neuen Hash berechnet.
 
Jetzt verdeutlicht einem das Projekt die Rechenarbeit, die hinter dem erstellen von neuen Blöcken steht.
 
Auf Tastendruck habe ich schließlich immer einen neuen Block hinzugefügt, der den Tastennamen als Datenpacket beinhaltet.
 
public key_pressed(event: KeyboardEvent) {
    const blocks = this.models.blocks.all();
    const new_block = this.models.blocks.insert_new();
    new_block.data = "--------" + event.code + "--------";
    new_block.mine();
    return this.views.main.blocks.set(this.models.blocks.all());
}
 
Die BlockCHAIN
 
Bisher waren sind alle Blöcke nur für sich allein berechnet worden.
Die Blockchain aber besteht aus mehreren Blöcken, die aufeinanderfolgen.
So kommen wir schließlich auch dazu die letzte Eigenschaft des Blocks zu verwenden: den <<previous_hash>>.
 
Eigentlich könnte es ausreichen, wenn wir in der <<key_pressed>> Funktion weiter oben, den letzten erstellten Block heraussuchen und dessen <<hash>> als <<previous_hash>> des neuen Blocks verwende.
 
Allerdings habe berechne ich den gültigen Hash des Blocks ja verzögert,
um den Rechenaufwand deutlich zu machen, daher musste ich noch etwas Logik einbauen, die auf die Fertigstellung des vorherigen Blocks wartet, bevor der eigene Hash berechnet wird.

Jetzt habe ich eine lange valide Blockchain, zu der ich neue Blöcke hinzufügen kann.

Der finale Schliff
 
Um etwas mehr Wert aus der Anwendung zu bekommen,
habe ich noch ein feature hinzugefügt,
mit dem sich nur ein Teil der Blöcke als Blockchain definieren lassen.
Die Blöcke der aktiven Kette werden gelb markiert.
 
Dabei wird der <<previous_hash>> des ersten Blocks auf 0 gesetzt und sein Hash neu berechnet. Ist er fertig ändert sich dadurch auch der <<previous_hash>>des folgenden Blocks, bis der letzte Block in der Kette erreicht wurde.
 
Die Demonstration
 
Ausprobieren

Jetzt können wir demonstrieren, dass das berechnen einzelner Blöcke einigermaßen schnell geht, allerdings dauert es vergleichsweise lange, wenn der Kette am Anfang ein Block hinzugefügt werden soll.
 
Dazu generieren wir erst ein paar (~20) Blöcke mit zufälligen Daten, 
durch Tastatureingaben und wählen dann mit der Maus den zweiten und den letzten Block aus. Es dauert eine Weile bis die Berechnung abgeschlossen ist. 

Fügen wir nun einen weiteren Block mit der Tastatur hinzu, ist dieser Recht schnell berechnet (bei mir dauert es rund 10 Sekunden).
 
Selektieren wir aber jetzt den ersten und den letzten Block,
also wenn wir einen Block am Anfang der Kette einfügen wollen,
muss die gesamte Blockchain neu berechnet werden, um gültig zu sein.
 
Auch nennenswert ist, wenn wir jetzt den ersten und einen Block aus der Mitte auswählen, wird nichts neu berechnet, da das entfernen von Blöcken die Blockchain nicht ungültig macht.
 

Releases

Comments

captcha