Outils pour utilisateurs

Outils du site


nsi:tds:cryptographie:blockchain

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
nsi:tds:cryptographie:blockchain [2023/04/11 17:45] goupillwikinsi:tds:cryptographie:blockchain [2023/05/08 01:14] (Version actuelle) goupillwiki
Ligne 73: Ligne 73:
 </code> </code>
  
-=== Bloc d'échanges ===+=== Blocs d'échanges ===
  
 Les blocs ordinaires recevront l'empreinte (hachage) du bloc précédent et les informations de transaction. Les blocs ordinaires recevront l'empreinte (hachage) du bloc précédent et les informations de transaction.
Ligne 80: Ligne 80:
  
 <code python> <code python>
-("Paul", "Judith", 5),+("Paul", "Judith", 5, S)
 </code> </code>
  
-=== Information supplémentaire ===+  * Paul est la source (il donne de crédits) 
 +  * Judith est la cible (elle reçoit les crédits) 
 +  * 5 est la quantité de crédits échangés 
 +  * S est la signature permettant d'authentifier la transaction (vérifie que Paul en est l'auteur). 
 + 
 +Voir plus bas pour le fonctionnement de la signature.
  
 Le bloc s'achève avec l'empreinte (hachage) de l'ensemble des informations qu'il contient : Le bloc s'achève avec l'empreinte (hachage) de l'ensemble des informations qu'il contient :
Ligne 89: Ligne 94:
   * données (initialisation pour le bloc 0 et transaction pour les autres)   * données (initialisation pour le bloc 0 et transaction pour les autres)
  
-Pour le hachage, on pourra utiliser le module ''hashlib''+On utilise le module ''hashlib'' pour le hachage.
  
 <code python> <code python>
Ligne 104: Ligne 109:
  
 <WRAP tip>On peut indiquer l'empreinte de chaque bloc dans sa version ''hexdigest'', c'est du texte et c'est plus simple.</WRAP> <WRAP tip>On peut indiquer l'empreinte de chaque bloc dans sa version ''hexdigest'', c'est du texte et c'est plus simple.</WRAP>
 +
 +=== Signature ===
 +
 +Dans la transaction ''%%("Paul", "Judith", 5, S)%%'', la signature ''S'' permet d'être certain que Paul est bien à l'origine de la transaction.
 +
 +Voyons comment la signature sera crée :
 +  * Paul veut donner 5 unité à Judith. On crée le texte ''%%T = "Paul;Judith;5"%%''
 +  * on encode le texte et on hache. On utilise pour cela ''hashlib'' :\\ ''%%z = hashlib.sha256(T.encode('utf8'))%%''
 +  * on calcule la signature ''Sb'' par chiffrement de ''z'' en utilisant la clé privée de Paul :\\ ''%%S = rsa.cypher(z, Kpr)%%''\\ on utilise {{ :nsi:tds:cryptographie:rsa.py |}} correspondant à ce que l'on a vu dans le [[nsi:terminales:securite:rsa|cours RSA]]
 +  * ''Sb'' est de type ''bytes''. On préfère une écriture texte hexadécimale. On peut calculer ''S = Sb.hex()''.
 +  * la transaction est alors complétée : ''%%("Paul", "Judith", 5, S)%%''
 +
 +Dans l'exemple, la signature est :
 +
 +<code python>
 +'12d1cc8b27164fdc440bde8190347fe0484489bf9e04e71f2cd6edf182910dd493c02c68bf01fa752a297fa34c358d6a451deffc77df39b3'
 +</code>
 +
 +Au moment de la vérification, on reçoit (par exemple) ''%%("Paul", "Judith", 5, S)%%''.
 +
 +  * comme dans ce qui précède, on produit le texte ''%%T = "Paul;Judith;5"%%'',
 +  * on hache ce texte : ''%%z = hashlib.sha256(T.encode('utf8'))%%''
 +  * on cherche la version ''bytes'' de ''S'' : ''Sb = bytes.from_hex(S)''
 +  * on déchiffre ''Sb'' avec la ''Kpu'' de Paul : ''%%z2 = rsa.decypher(Sb, Kpu)%%''
 +  * on compare si ''z == z2''. Si oui, la signature est valide.
  
 === Sauvegarde === === Sauvegarde ===
  
-Il peut être intéressant de sauvegarder un bloc sous forme d'un fichier. +Chaque bloc sera sauvegardé sous forme d'un fichier. 
-On pourra par exemple sauvegarder un bloc sous cette forme :+ 
 +Le fichier aura pour nom l'empreinte du bloc précédent suivi de l'extension ''.blc'' 
 + 
 +Dans le cas du bloc initial, le bloc contiendra simplement les données. Par exemple :
  
 <code lang-none> <code lang-none>
-empreinte du bloc précédent en format hexdigest +Paul;45;11758971846281690449;65537 
-chaque ligne de donnée avec séparateur +Judith;92;16218573123863373407;65537 
-empreinte du bloc en format hexdigest+Michel;52;15421843742480106653;65537 
 +Laure;39;11452260091047556889;65537
 </code> </code>
  
-Il faudrait donc aussi prévoir de créer un bloc à partir d'un tel fichier.+Dans le cas d'un bloc d'échange, le bloc contiendra la transaction. Par exemple ''%%("Paul", "Judith", 5, S)%%''. Les 3 premiers éléments ne posent pas de problème. Pour ''S'' on voudrait une écriture pas trop lourde. On adopte une écriture hexadécimale : il suffit d'écrire ''S.hex()'' pour l'obtenir.
  
-==== Chaîne ====+Le fichier contient donc : 
 +nom de la source;nom de la cible;str de value;hex de S
  
-Créons une classe ''BlockChain''.+=== chargement ===
  
-Un objet ''Blockchain'' est initialisé avec les données des utilisateurs qui permettent de créer un bloc initial. Dès la création de la chaîne on crée donc ce premier bloc.+Si je sais que le dernier bloc à l'empreinte E, alors je peux chercher un fichier ''<E>.blc''. Si le fichier existe, on peut l'ouvrir et récupérer les différents champs : 
 +  * nom de la source 
 +  * nom de la cible 
 +  * value que l'on peut convertir en int 
 +  * signature qui est sous forme hex et que l'on peut passer en ''bytes'' ainsi : ''S = bytes.fromhex(...)''
  
-La classe ''BlockChain'' devra également prévoir des méthodes pour : +==== Chaîne ====
-  * ajouter un bloc : on donne une transaction, ''BlockChain'' utilise l'empreinte de son dernier bloc pour créer un nouveau bloc. +
-  * lire l'ensemble de la chaîne pour calculer les comptes de chaque utilisateur.\\ Par exemple, Paul commence à 45 et Judith à 92. Après l'échange où Paul donne 5 à Judith, Paul aura 40 et Judith 97.+
  
-==== Signature ====+Créons une classe ''BlockChain''.
  
-Dans la transaction ''%%("Paul""Judith"5)%%'' comment être certain que c'est bien Paul qui a émis cette demande ? Pour cela nous allons utiliser le concept de signature.+  * L'objet ''BlockChain'', à la créationva chercher l'existence d'un fichier ''0.blc'' pour s'amorcer.\\ Le fichier ''0.blc'' contient les données des comptes utilisateurs (exemple donné plus haut) 
 +  * chaque fois que la blockchain charge un bloc, elle lit l'empreinte de ce bloc et cherche s'il existe un fichier nommer d'après cette empreinte.\\ Si oui, la blockchain charge ce bloc et ainsi de suite.
  
-Quand l'utilisateur crée une demande ''%%D = (source, cible, montant)%%'', il doit : +Le bloc initial indique le compte initial des utilisateurs. Les transactions indiquent des échanges d'un utilisateur à l'autre.
-  * calculer ''z = h(D)'' ou ''h'' est une fonction de hachage, +
-  * chiffrer ''z'' en utilisant sa clé Kpr, soit ''S'' le résultat, +
-  * fournir ''S'', la signature, à la suite de ''D''.+
  
-<WRAP box>**Exemple :** Pour l'échange ''%%("Paul""Judith", 5)%%'', +Ainsi, si Paul commence à 45, Judith à 92 et que Paul donne à Judithalors Paul aura 40 et Judith 97
-  * on fabrique le texte ''%%t = "Paul;Judith;5"%%'' + 
-  * on fait l'encodage de ce texte ''%%b = t.encode('utf8')%%'' +On peut garder l'état des différents comptes avec un dictionnaire qui devra se créer automatiquement à la lecture de ''0.blc'' :
-  * on calcule l'empreinte ''%%z = hashlib.sha256(b)%%'' +
-  * on obtient la version bytes de ''z'' avec ''d = z.digest()'' +
-  * reste à chiffrer avec Kpr de Paul : ''S = cypher(d, (11758971846281690449, 3602673231362159633))''\\ On utilise la fonction ''cypher'' du module {{ :nsi:tds:cryptographie:rsa.py |}} correspondant à ce que l'on a vu dans le [[nsi:terminales:securite:rsa|cours RSA]]+
  
-On obtiendra ici pour ''S'' : 
 <code python> <code python>
-b"\x12\xd1\xcc\x8b'\x16O\xdcD\x0b\xde\x81\x904\x7f\xe0HD\x89\xbf\x9e\x04\xe7\x1f,\xd6\xed\xf1\x82\x91\r\xd4\x93\xc0,h\xbf\x01\xfau*)\x7f\xa3L5\x8djE\x1d\xef\xfcw\xdf9\xb3"+comptes = { 
 +    "Paul": 45, 
 +    "Judith": 92, 
 +    "Michel":52, 
 +    "Laure":39 
 +}
 </code> </code>
  
-et on place ce ''S'' comme 4e élément de la transaction : ''%%("Paul""Judith"5, S)%%''. +La blockchainau gré des transactionsmet à jour les valeurs des différents comptes.
-</WRAP>+
  
-Pour vérifier la signatureon doit : +Il pourra être utile aussi de créer, à la lecture de ''0.blc'', un dictionnaire pour les clés :
-  * calculer ''z = h(D)'', +
-  * déchiffrer ''S'' en utilisant la clé Kpu associée à la source indiquée dans ''D'', +
-  * vérifier que ''z'' est bien égal au résultat du déchiffrement.+
  
-La signature permet de s'assurer que l'émetteur de la demande est bien le propriétaire de la clé privée liée à Kpu. Puisque la chaîne connaît les Kpu de tous les utilisateurson est sûr du propriétaire.+<code python> 
 +keys = { 
 +    "Paul": (1175897184628169044965537), 
 +    "Judith": (16218573123863373407, 65537), 
 +    "Michel": (15421843742480106653, 65537), 
 +    "Laure": (11452260091047556889, 65537) 
 +
 +</code>
  
-<WRAP tip>On aimerait ajouter une méthode dans ''BlockChain'' qui se chargerait de faire tout ce travail. Mais l'utilisateur doit calculer ''S'' de son côté car il ne doit jamais donner ''Kpr'' au gestionnaire de la blockchain. On peut si on veut créer une fonction indépendante qui se charge de produire ''S'' en fonction des données de la transaction et de ''Kpr''.</WRAP>+=== ajout de transaction ===
  
-==== Vérification d'un bloc ====+La classe ''BlockChain'' doit posséder une méthode pour ajouter une transaction.
  
-Lors de l'ajout d'un bloc, il va falloir prévoir une étape de vérification. Pour chaque transaction : +Une transaction est composée des informations identifiant sourceidentifiant cible, valeur de la transaction, signature.
-  * vérifier que la signature est valide, +
-  * vérifier que la demande est valide compte-tenu des comptes en l'état actuel de la chaîne\\ //Ne pas autoriser Paul à donner 5 s'il ne possède que 3.//+
  
-==== Sauvegarde ====+la blockchain doit : 
 +  * vérifier que les identifiants existent, 
 +  * vérifier la signature, 
 +  * vérifier que la source dispose d'assez de crédits pour la transaction envisagée,
  
-Il serait intéressant de prévoir la sauvegarde de la chaîne. Chaque bloc serait sauvegardé dans un fichier séparé et nommé avec un nom comme ''1.bc'' pour le bloc de rang 1.+On convient que la fonction renvoie ''False'' si les vérifications échouentAutrement on peut poursuivre en exécutant la transaction :
  
-Il faudrait alors prévoir de recréer la chaîne à partir de cette sauvegarde.+  * mise à jour des comptes, 
 +  * création du bloc correspondant, 
 +  * ajout du bloc à la suite des autres, 
 +  * sauvegarde du bloc
  
-Vous pouvez aussi sauvegarder la chaîne dans un fichier unique. Dans ce cas il faut prévoir un séparateur entre les blocs. Par exemple une ligne ne contenant que ''%%---%%''.+et dans ce cas la fonction renvoie ''True''.
  
 ===== Proposition d'implémentation ===== ===== Proposition d'implémentation =====
Ligne 180: Ligne 223:
  
 <code python> <code python>
 +# blockchain.py
 import hashlib import hashlib
 +import os
 import rsa import rsa
  
-class Bloc:+class Block:
     def __init__(self, empreinte_precedent:str, data):     def __init__(self, empreinte_precedent:str, data):
         """         """
Ligne 197: Ligne 242:
         """         """
         # mettre empreinte_precedent,         # mettre empreinte_precedent,
-        # suivi du version texte de data+        # suivi de la version texte de data (__str__ ci dessous)
         # encoder en utf8 pour obtenir un bytes b         # encoder en utf8 pour obtenir un bytes b
         # faire le hash  avec hashlib.sha256(b)         # faire le hash  avec hashlib.sha256(b)
Ligne 211: Ligne 256:
         """         """
         sauvegarde le bloc dans un fichier dont le nom est l'empreinte         sauvegarde le bloc dans un fichier dont le nom est l'empreinte
-        du bloc précédent suivit de l'extension .bloc+        du bloc précédent suivit de l'extension .blc
         """         """
         # le fichier doit contenir le contenu de __str__         # le fichier doit contenir le contenu de __str__
-        # si le fichier existe, il ne faut rien faire+        # si le fichier existe déjà, il ne faut rien faire 
 +         
 +    def next_filename(self) -> str: 
 +        """ 
 +        renvoie le nom de fichier du bloc suivant 
 +        """ 
 +        return self.FOLDER + self.empreinte() + self.EXT    
  
  
Ligne 222: Ligne 273:
         lance la procédure de chargement pour construire la blockchain         lance la procédure de chargement pour construire la blockchain
         """         """
 +        self.blocs = []
 +        self.keys = {}
 +        self.credits = {}
         self.load()         self.load()
  
Ligne 234: Ligne 288:
         #   les infos des comptes sous la forme : identifiant:str;credit:int;n:int;e:int         #   les infos des comptes sous la forme : identifiant:str;credit:int;n:int;e:int
         #   empreinte du bloc         #   empreinte du bloc
-        # crée le bloc0 avec les infos du fichier mettre bloc0 dans self.blocs+        # créer le bloc0 avec les infos du fichier mettre bloc0 dans self.blocs 
 +        # créer un self.keys = { identifiant: (n, e) } pour stocker les clés des comptes 
 +        # créer un self.credits = { identifiant: credit } pour stocker les crédits des comptes
         # à partir de là, tant que l'empreinte du dernier bloc correspond à un fichier,         # à partir de là, tant que l'empreinte du dernier bloc correspond à un fichier,
-        # charger le fichier et ajouter le bloc correspondant +        # charger le fichier, lire la transaction, l'ajouter à la chaîne 
- +     
- +        # pour info, on peut obtenir les noms de fichiers en faisant 
-        soit data est de type list. Alors +        # fichiers = [f for f in os.listdir('./'if f.endswith('.blc')]
-        data: liste des comptes sous la forme () +
-              où identifiant est l'identifiant du compte +
-              credit est la quantité de crédits sur le compte +
-              (n, eest la clé publique associée au compte +
-        Sinon, data est None, alors : +
-        les données sont lues dans les fichiers +
-        """+
                  
-        # si data != None 
-        # il faut créer le premier bloc : bloc0 
-        # puis créer la chaine, 
-        # par exemple c'est c'est un tableau contenant bloc0 
-        # [bloc0] 
-        # peut aussi créer un dictionnaire comptes récupérant les infos du bloc 0 
-        # comptes = { identifiant: (compte, n, e) } 
-        # au gré des transactions, la valeur de compte pourra changer 
-        # remarque : rien ne vous empêche de créer une classe pour structurer les comtpes... 
-        # si data =  
-     
     def add_transaction(id_source:str, id_cible:str, value:int, s:str) -> bool:     def add_transaction(id_source:str, id_cible:str, value:int, s:str) -> bool:
         """         """
Ligne 270: Ligne 308:
         # vérifie la signature         # vérifie la signature
         # vérifie si le compte source a assez de crédits         # vérifie si le compte source a assez de crédits
 +        # modifie l'état des comptes concernés
         # crée un bloc pour la transaction         # crée un bloc pour la transaction
 +        #   en indiquant l'empreinte du dernier bloc pour l'empreinte précédente
         # ajoute le bloc dans la liste des blocs         # ajoute le bloc dans la liste des blocs
         # sauvegarder le bloc         # sauvegarder le bloc
-        # modifie l'état des comptes concernés 
  
     def empreinte_transaction(self, id_source:str, id_cible:str, value:int) -> bytes:     def empreinte_transaction(self, id_source:str, id_cible:str, value:int) -> bytes:
Ligne 280: Ligne 319:
         id_cible: identifiant cible         id_cible: identifiant cible
         value: quantité  à transférer         value: quantité  à transférer
-        renvoie l'empreinte+        renvoie l'empreinte sous forme digest
         """         """
         # fabrique le texte formé de la façon id_source;id_cible;value         # fabrique le texte formé de la façon id_source;id_cible;value
-        # encode, hash, +        # encode, hash 
-        # renvoie digest() +        # renvoie digest 
-     + 
-    def save_bloc(self):+    def credit(self, identifiant:str-> int:
         """         """
-        sauvegarde +        renvoie la valeur de crédit pour identifiant
         """         """
 +</code>
 +
 +Il faudra donc créer manuellement un bloc initial ''0.bloc'' contenant le texte :
 +<code lang-none>
 +Paul;45;11758971846281690449;65537
 +Judith;92;16218573123863373407;65537
 +Michel;52;15421843742480106653;65537
 +Laure;39;11452260091047556889;65537
 +</code>
 +
 +On a besoin de simuler un client agissant sur la blockchain (un des utilisateurs). Si par exemple Paul veut faire une transaction, il faut qu'il calcule la signature. On va automatiser tout cela.
 +<code python>
 +# client.py
 +
 +import hashlib
 +import rsa
 +
 +class Client:
 +    def __init__(self, identifiant:str, Kpr):
 +        """
 +        identifiant: identifiant du compte
 +        Kpr: clé privée (n,d)
 +        """
 +        self.identifiant = identifiant
 +        self.Kpr = Kpr
          
-    def read_bloc_file(filename:str):+    def transaction(self, id_cible:str, value:int):
         """         """
-        filename: nom de fichier du bloc à ajouter+        renvoie la transaction signée
         """         """
-        # il faut ouvrir+        # la transaction signée est formée ainsi : 
 +        # (id_source, id_cible, value, signature) 
 +        # pour le calcul de la signature, on reprend le principe 
 +        # rencontré dans BlockChain.empreinte_transaction : 
 +        #   fabrique le texte formé de la façon id_source;id_cible;value 
 +        #   encode, hash, digest 
 +        #   calcule rsa.cypher sur le digest avec Kpr 
 +        # le résultat est la signature 
 +</code>
  
 +On pourra par montrer le fonctionnement de la façon suivante :
 +
 +<code>
 +# demo.py
 +
 +from blockchain import BlockChain
 +from client import Client
 +
 +b = BlockChain() # charge le fichier 0.bloc
 +paul = Client("Paul", (11758971846281690449, 3602673231362159633))
 +judith = Client("Judith", (16218573123863373407, 6385025328607971353))
 +
 +print(f"Le compte actuel de Paul est de {b.credit('Paul')}")
 +print(f"Le compte actuel de Judith est de {b.credit('Judith')}")
 +t = paul.transaction("Judith", 5)
 +success = b.add_transaction(*t) # * pour ventiler les morceaux du tuple t dans les arguments
 +
 +if success:
 +    print("Transaction effectuée.")
 +    # la transaction a été enregistrée et sera donc pris en compte
 +    # à la prochaine exécution du script
 +else:
 +    print("Échec de la transaction.")
 </code> </code>
  
 +        
 +        
nsi/tds/cryptographie/blockchain.1681227932.txt.gz · Dernière modification : de goupillwiki