Article écrit par Benoit Caccinolo
Nous avons tous entendu parler des crypto-monnaies Bitcoin ou Ethereum. Et nous regrettons tous de ne pas avoir acheté des Bitcoins quand ces derniers valaient quelques centimes et non pas 15 000 euros comme aujourd’hui. Même si nous n’avons pas fait fortune avec, il est intéressant de se pencher sur leur mode de fonctionnement.
Aujourd’hui quand nous payons avec une carte bleue, la transaction est stockée dans le grand livre de compte de notre banque. C’est sur ces grands livres de compte que repose l’ensemble de notre système monétaire actuel. Maintenant, que se passerait-il si des livres de compte venaient à disparaître ou étaient corrompus ?
Les crypto-monnaies n’ont pas ce problème car elles reposent sur des livres de compte librement accessibles et décentralisés. Tout le monde peut aujourd’hui télécharger le livre de compte des transactions Bitcoin. Il faut pour cela avoir à peu près 160Go d’espace disque.
Ce grand livre de compte porte un autre nom, la blockchain. Je vous propose aujourd’hui d’implémenter une blockchain minimaliste afin de comprendre comment les transactions Bitcoin sont structurées.
Qu’est-ce qu’une blockchain ?
La blockchain est une liste complète de tous les blocs de transactions complétées depuis le début du Bitcoin par exemple. Afin de renforcer la sécurité du système, la blockchain a été conçue de sorte que chaque bloc de transaction contienne le hash produit à partir du bloc précédent. La blockchain est une technologie de stockage et de transmission d’informations à faible coût, sécurisée, transparente, et fonctionnant sans organe central de contrôle. Par extension, la blockchain désigne une base de données sécurisée et distribuée (car partagée par ses différents utilisateurs), contenant un ensemble de transactions dont chacun peut vérifier la validité. La blockchain peut donc être comparée à un grand livre comptable public, anonyme et infalsifiable.
Structure d’un bloc
Commençons par définir la structure d’un bloc. Seulement les parties essentielles sont ajoutées dans un bloc pour l’instant :
index
: la position du bloc dans la chaînedate
: date de création du blocdata
: les données à stocker (exemple : liste de transactions)hash
: un hash SHA256 généré à partir des données contenues dans le blocpreviousHash
: le hash du bloc précédent
Ce qui donne le bloc suivant :
class Block { constructor(index, timeStamp, data, previousHash) { this.index = index; this.timeStamp = timeStamp; this.data = data; this.previousHash = previousHash; this.nonce = 0; this.hash = this.calculateHash(); } }
Génération du hash
Le hash d’un bloc est sa partie la plus importante, il garantit que le bloc est valide. Le hash est calculé à partir de toutes les données contenues dans le bloc. Cela signifie que si une donnée est modifiée dans le bloc, le hash n’est plus valide. Le hash peut être vu comme l’identifiant unique d’un bloc. Il est possible de trouver 2 blocs avec le même ID, en cas de conflit, mais ils auront un hash différent.
Pour calculer le hash d’un bloc, nous utiliserons la librairie CryptoJS https://www.npmjs.com/package/crypto-js.
var SHA256 = require("crypto-js/sha256"); calculateHash() { return SHA256(this.id + this.timeStamp + this.data + this.previousHash).toString(); }
Notons que pour un bloc donné, le hash du bloc précédent est utilisé pour le calcul du hash. Cela signifie que si un bloc est modifié, il faut recalculer son hash ainsi que le hash de tous les blocs suivants.
Attention, pour le moment, le hash n’est pas utilisé pour faire ce que l’on appelle du « minage ». Le hash est utilisé pour garantir l’intégrité du bloc.
Dans l’exemple suivant, si les données du bloc 0 sont modifiées, son hash change ainsi que celui de tous les blocs suivants.
La blockchain
Le premier bloc
Le premier bloc d’une blockchain est appelé GenesisBlock
, c’est le seul bloc de la chaîne à ne pas posséder de previousHash
. Il sera créé avec le code suivant :
createGenesisBlock() { const timeStamp = new Date().getTime(); this.blocks.push(new Block(0, timeStamp, "Genesis Block", null)); }
La méthode calculateHash
génère alors le hash du 1er bloc qui sera utilisé pour l’ajout du bloc suivant.
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Création d’un bloc
Pour générer un nouveau bloc, il nous faut connaître le hash du bloc précédent. La partie data est fournie par l’utilisateur le reste est généré comme suit :
generateNextBlock(blockData) { const previousBlock = this.getLatestBlock(); const nextIndex = previousBlock.index + 1; const nextDate = new Date().getTime(); const newBlock = new Block(nextIndex, nextDate, blockData, previousBlock.hash); return newBlock; }
Stockage de la blockchain
Pour l’instant, les blocs seront stockés dans un simple tableau, les données ne seront pas persistantes.
createGenesisBlock() { const timeStamp = new Date().getTime(); this.blocks.push(new Block(0, timeStamp, "Genesis Block", null)); }
La validation des blocs
À tout moment, il doit être possible de valider l’intégrité d’un bloc ou d’un ensemble de blocs. Pour qu’un bloc soit valide, il faut qu’il valide les règles suivantes :
- L’index du bloc doit suivre l’index du bloc précédent
- le
previousHash
correspond bien au hash du bloc précédent - le hash du bloc est valide
Pour faire cela voici le code :
isValidNewBlock(newBlock, previousBlock) { if (previousBlock.index + 1 !== newBlock.index) { console.log('invalid index', newBlock); return false; } if (previousBlock.hash !== newBlock.previousHash) { console.log('invalid previoushash', newBlock); return false; } if (newBlock.calculateHash() !== newBlock.hash) { console.log('invalid hash: ' + newBlock.calculateHash() + ' ' + newBlock.hash); return false; } return true; }
Pour valider le bloc Genesis, les règles sont les mêmes sauf que le previousHash
doit être null
.
isValidGenesisBlock() { let genesisBlock = this.blocks[0]; if (genesisBlock.index !== 0) { console.log('invalid index', genesisBlock); return false; } if (genesisBlock.previousHash !== null) { console.log('invalid previoushash', genesisBlock); return false; } if (genesisBlock.calculateHash() !== genesisBlock.hash) { console.log('invalid hash: ' + genesisBlock.calculateHash() + ' ' + genesisBlock.hash); return false; } return true; }
Maintenant que nous savons comment valider un bloc, il est possible de valider tout une blockchain. Dans un premier temps, il faut valider que le bloc Genesis est valide puis valider tous les blocs suivant. Voici le code pour faire cela :
isValidChain() { if (!this.isValidGenesisBlock(this.blocks[0])) { return false; } for (let i = 1; i < this.blocks.length; i++) { if (!this.isValidNewBlock(this.blocks[i], this.blocks[i - 1])) { return false; } } return true; }
Le minage
La validation des transactions dans un réseau de blockchains est un processus complexe. Une des briques principales permettant cette validation est ce que l’on appelle le Proof of Work. Il s’agit d’un puzzle à résoudre avant de pouvoir ajouter un bloc à la blockchain. La résolution de ce puzzle est ce qui s’appelle « miner ».
Le Proof of Work est une protection pour éviter que les mineurs soient tentés de tricher en ajoutant des blocs frauduleux. Pour un mineur, le fait de miner un bloc lui coûte en électricité. S’il trichait et qu’il était découvert, cette dépense serait perdue. À l’inverse, le fait de miner des blocs valides rapporte une récompense au mineur s’il résout le Proof of Work ; récompense sous forme de Bitcoins —créés ex nihilo pour l’occasion— si notre blockchain est utilisée dans le cadre de cette crypto-monnaie.
Comment implémenter le Proof of Work ?
Un des puzzles les plus répandus est le fait de trouver un hash valide pour le bloc débutant par un certain nombre de zéro suivant la difficulté souhaitée. Pour pouvoir modifier le hash, un compteur va être ajouté dans le bloc. Ce compteur est appelé nonce. En itérant sur le nonce nous modifions le hash jusqu’à tomber sur un hash débutant par le nombre de zéros souhaité. L’intérêt est que le puzzle est compliqué à trouver, mais il est très simple à valider.
Une méthode va être ajoutée à la classe Block
pour faire cela :
solveProofOfWork(difficulty = 4) { this.nonce = 0; while (true) { this.hash = this.calculateHash(); const valid = this.hash.slice(0, difficulty); if (valid === Array(difficulty + 1).join('0')) { console.log(this); return true; } this.nonce++; } }
Conclusion
J’espère que cette implémentation vous aura plu. Oui cette blockchain n’est pas complète. Il manque la partie communication entre plusieurs blockchains.