Comment les Structures de Blockchain préviennent la falsification des données?

29 September 2017

Les structures de Blockchain sont au centre des systèmes comme Bitcoin et Ethereum. C’est un des piliers des Systèmes de Consensus Décentralisés.

Pour une monnaie, la Blockchain permet à **chaque participant de:

  • Vérifier à tout moment que personne n’a triché, par exemple en envoyant plusieurs fois la même unité (double spending).
  • Se passer d’une autorité centrale: chaque participant peut vérifier les données.
  • Détecter rapidement des données qui ont été trafiquées par un participant hostile.

Mais qu’est-ce qui rends les structures de blockchain capable d’assurer l’intégrité des données? Quelles étaient les limitations des méthodes précédentes? Comment ça marche? Je vous propose de “redécouvrir” les structures de blockchain.

On va partir d’un modèle simplifié et étudier ses limitations. Étapes par étapes, on va corriger ces limitations et voir comment la Blockchain émerge.

Modèle

Voilà le modèle simplifié qu’on va corriger:

On a un réseau de trois participants. Chaque participant peut envoyer un message aux deux autres. Ils se sont mis d’accord pour envoyer un message chacun leur tour dans un ordre prédéfinis. Ils veulent échanger une monnaie.

En d’autres termes, on a Alice, Bob et Chuck. Ils sont d’accord pour échanger une monnaie, des “FakeCoins” contre des services et des biens. Alice va décider de certaines opérations avec la monnaie et envoyer un mail aux deux autres. Ensuite, Bob envoie un mail. Puis Chuck, puis Alice de nouveau, etc.

Notez que:

  • J’ai un petit groupe de participants qui se connaissent déjà. Pas besoin de Protocoles P2P.
  • Ces participants se mettent d’accord sur une manière de faire avancer le système et ne vont pas tricher. Pas besoin de “Proof Of …”.

On se concentre sur le stockage et l’échange de l’état de notre consensus décentralisé.

Tout le monde commence avec 1 FakeCoin.

Modèle 1: ils envoient la base de données complète

On commence avec l’état:

Alice: 1
Bob: 1
Chuck: 1

A. Alice achète une lampe à Chuck: elle lui envoie un FakeCoin

Elle envoie un mail aux deux autres:

Bob et Chuck consultent le mail, ils sont d’accord. Chuck envoie une lampe à Alice.

B. Bob ne fait rien

Il envoie le mail précédent à chaque participant:

C. Chuck triche

Chuck décide de tricher. Il fait croire à chaque participant qu’il leur envoie ses FakeCoins. C’est une double dépense (Double Spending).

La tricherie de Chuck n’est pas visible, car nos participants ne partagent pas de mémoire.

Lorsqu’Alice ouvre le mail, elle voit que Chuck lui a envoyé deux FakeCoins. À sa connaissance, les règles du système on été respectées. Elle envoie donc deux Cookies à Chuck. Bob fait de meme. Pour lui aussi, les règles du système ont été respectées, il envoie deux pépites d’or à Chuck.

D. Alice envoie ses FakeCoins à Bob

D’après le message qu’elle a reçu au tour précédent, Alice peut envoyer deux FakeCoins à Bob:

Bob consulte le mail. Il voit le même état que celui envoyé par Chuck au tour précédent: pour lui, Alice n’a rien changé. Elle n’a pas fait de dépense.

À cet instant, TOUS les participants partage le même état. Ils sont tous d’accord pour dire que:

Alice: 0
Bob: 3
Chuck: 0

Les transactions se poursuivent il n’y a plus aucun indice de la tricherie de Chuck.

Plusieurs semaines plus tard, Alice ne comprends pas “Je n’ai toujours pas reçu les pépites de Bob”. Bob ne sait pas de quoi Alice parle. Le système ne fait plus consensus.

Notez que ce n’est pas un problème de sécurité ou d’authentification. Alice, Bob et Chuck on transféré des FakeCoins de leurs comptes respectifs.

Modèle 2: le log de transaction

Alice, Bob et Chuck reprennent à zéro. Ils ont appris la leçon et essaye quelques choses de différent. Au lieu d’envoyer seulement l’état, ils vont aussi envoyer la transaction. De cette manière, chaque participant peut rejouer l’opération et vérifier que l’état qu’ils connaissent correspond à l’état proposé.

A. Alice envoie un FakeCoin à Chuck

Elle envoie un message à chaque participant:

Transaction:
 Alice envoie 1 à Chuck
État:
 Alice: 0
 Bob: 1
 Chuck: 2

Tout le monde est d’accord.

B. Bob ne fait rien

Transaction:
 Aucune
État:
 Alice: 0
 Bob: 1
 Chuck: 2

Et le système continue comme ca. Lorsque Chuck tente de tricher, au tour suivant Alice envoie une transaction et un état à Bob qui vont déclencher une erreur. Le système ne va plus digèrer de cas invalides.

Modèle 3: Version Alpha

Après plusieurs mois, Alice, Bob et Chuck décident d’ajouter de nouveaux utilisateurs. Chaque utilisateur promet de respecter les règles initiales (pas besoin de Proof Of… ni the P2P).

Ils découvrent que le système n’est pas scalable: la taille des messages augment linéairement avec le nombre d’utilisateurs. Doubler le nombre d’utilisateurs, c’est doubler la taille de chaque message, car ils ont besoin d’envoyer tout l’état à chaque fois.

Alice propose un changement:

La transaction est essentielle car elle permet de rejouer l’opération. Mais on ne s’intéresse pas au contenu de l’état: je veux juste pouvoir comparer l’état proposé et mon état et dire “identique” ou “différent”.

Elle propose d’utiliser un Hash de l’état plutôt que l’état complet. Au lieu de transmettre chaque ligne de l’état, on calcule hash("Alice:0,Bob:1,...") avec une fonction comme SHA384.

Transaction:
 Alice envoie 1 à Chuck
État:
 Alice: 0
 Bob: 1
 Chuck: 2
 ...
 Zardoz: 6

Deviens

Transaction:
 Alice envoie 1 à Chuck
Hash(État):
 c75a28f661fe089a8f97db5c67...

Vous avez probablement déjà utilisé CRC ou MD5 pour vérifier un fichier, c’est la même idée. Ce hash est l’Empreinte Digitale de notre base de données. Le moindre changement dans les comptes implique un hash différent. Le système conserve ses propriétés. Les utilisateurs peuvent toujours vérifier:

  • que la transaction est valide
  • qu’ils sont d’accord avec l’état global du système.

Et désormais, chaque message a une taille délimitée. Elle n’est plus fonction du nombre d’utilisateurs. Le système peut croître de nouveau.

Modèle 4: la Blockchain

Le système fonctionne sans problèmes pendant quelques mois. Alice, Bob et Chuck l’ouvrent au public. N’importe qui peut les rejoindre.

Ils découvrent un nouveau problème de scalabilité: Chaque participant doit parcourir tout les comptes pour hasher la base et vérifier un message / bloc. Maintenant que leur système est global les machines les moins performantes ne peuvent plus vérifier les nouveaux blocks assez rapidement.

C’est un risque fatal: Ils vont reposer sur une minorité de machines très puissantes pour vérifier les données. On risque de recentraliser le Consensus.

Bob trouve une idée:

Du moment que le calcul du Hash se fait à partir de l’ensemble de l’état, et si tout les participants sont d’accord sur la fonction à utiliser: on peut utiliser n’importe quelle fonction.

Lorsqu’on propose le bloc:

Transaction:
 Alice envoie 1 a Chuck
Hash(État):
 c75a28f661fe089a8f97db5c67...

Le hash représente l’état de la base de données. Mais la fonction choisie est arbitraire, par exemple:

- SHA384("Alice:1,Bob:2,Chuck:0")
- SHA384("Chuck:0,Alice:1,Bob:2")
- SHA512("Chuck:0,Bob:2,Alice:1")

Or l’état des comptes à tout moment, c’est une transaction appliquée à l’état précédent, pas vrai? On peut dire:

State[n] = apply(Transaction[n], State[n-1])

Donc, avec la fonction de concaténation de chaîne, on pourrait utiliser:

Hash(State) = Hash(Transaction[n] ⋅ State[n-1])

Cette fonction de hash contient tout l’état en cours. Une valeur différente implique un état différent.

Or, on peut aussi résumer State[n-1], l’état précédent à son hash aussi. On obtient une fonction récursive:

Hash(State) = Hash(Transaction[n] ⋅ Hash(State[n-1]))

Cette fonction de hash s’applique toujours à la totalité de l’état en cours. Une valeur différente implique un état différent.

Bob propose donc d’envoyer des messages qui conviennent un hash du message:

Transaction:
 Alice envoie 1 a Chuck
Hash(Transaction ⋅ Hash Précédent):
 11477b9dffc88d956c1cd51386...

Vous voyez la CHAINE de BLOCS? Chaque bloc contient l’empreinte de l’état complet, car il repose sur l’état précédent et la transformation actuelle. On chaîne chaque block avec les précédents et cette représentation permet de vérifier simplement un nouveau bloc. Vérifier mon état local avec l’état proposé est une opération simple et rapide à calculer. Peut importe la taille de la base de données.

Et Voilà!

En partant d’un modèle minimal de système décentralisé, on a redécouvert les structures de blockchain. On a vu comment partager les transactions et la somme de l’état le rends vérifiable. Et comment la chaîne de bloc rends la vérification peut coûteuse. On n’a plus besoin d’une autorité centrale pour protéger nos données.

Un changement dans la chaine va déclencher un changement dans tout les hash suivant. Comme des dominos, il est facile et rapide de détecter les données invalides.

Dans les prochains articles, on va creuser les autres composants utilisés pour le Consensus Décentralisé.


Laurent Senta

I wrote software for large distributed systems, web applications, and even robots. These days I focus on making developers, creators, and humans more productive through IPDX.co.