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 16:09] 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. 
 + 
 +Le fichier contient donc 
 +nom de la source;nom de la cible;str de value;hex de S 
 + 
 +=== chargement === 
 + 
 +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(...)''
  
 ==== Chaîne ==== ==== Chaîne ====
Ligne 122: Ligne 167:
 Créons une classe ''BlockChain''. Créons une classe ''BlockChain''.
  
-Un objet ''Blockchain'' est initialisé avec les données des utilisateurs qui permettent de créer un bloc initialDès la création de la chaîne on crée donc ce premier bloc.+  * L'objet ''BlockChain'', à la création, va 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.
  
-La classe ''BlockChain'' devra également prévoir des méthodes pour : +Le bloc initial indique le compte initial des utilisateurs. Les transactions indiquent des échanges d'un utilisateur à l'autre.
-  * 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 ====+Ainsi, si Paul commence à 45, Judith à 92 et que Paul donne 5 à Judith, alors Paul aura 40 et Judith 97.
  
-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.+On peut garder l'état des différents comptes avec un dictionnaire qui devra se créer automatiquement à la lecture de ''0.blc'' :
  
-Quand l'utilisateur crée une demande ''%%D = (source, cible, montant)%%'', il doit : +<code python> 
-  * calculer ''h(D)'' ou ''h'' est une fonction de hachage+comptes 
-  * chiffrer ''z'' en utilisant sa clé Kpr, soit ''S'' le résultat+    "Paul": 45
-  * fournir ''S''la signature, à la suite de ''D''.+    "Judith": 92
 +    "Michel":52, 
 +    "Laure":39 
 +
 +</code>
  
-<WRAP box>**Exemple :** Pour l'échange ''%%("Paul""Judith", 5)%%''+La blockchainau gré des transactionsmet à jour les valeurs des différents comptes. 
-  * on fabrique le texte ''%%t = "Paul;Judith;5"%%'' + 
-  * on fait l'encodage de ce texte ''%%b = t.encode('utf8')%%'' +Il pourra être utile aussi de créer, à la lecture de ''0.blc'', un dictionnaire pour les clés :
-  * 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 = chiffrer_RSA(d(15533123, 2626193))''\\ On utilise la fonction ''chiffrer_RSA'' traitée dans le [[nsi:terminales:securite:rsa|cours RSA]]+
  
-On obtiendra ici : 
 <code python> <code python>
-("Paul","Judith",5,b'\x95\x1fR;T\xf1\x0c\x9d|\xa2\xc3\xc5\x9fnP_\xff5w\xae\xd9\x1dlz\xbd\x8b\x03^\xc0WW\xdcAk\xb1hz\xef\xa6\\!\xe7k\xb1h\xa9\xa1Jm\xd1\xbe\x00\x00\x01\x0e\xfd\xfb\xb7<\x9b\n\xb3\xfam\x10`\xe6\xaf\xe6\xc4\xfe\x0b\x00\x00\x00\xbd\xcd\x98v\xbbG-qc\xbdD0\n\xe5}\x86G4\xdf@\x0c')+keys = { 
 +    "Paul": (1175897184628169044965537), 
 +    "Judith": (1621857312386337340765537), 
 +    "Michel": (15421843742480106653, 65537)
 +    "Laure": (11452260091047556889, 65537) 
 +}
 </code> </code>
-</WRAP> 
  
-Pour vérifier la signature, on doit : +=== ajout de transaction ===
-  * calculer ''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 utilisateurs, on est sûr du propriétaire.+La classe ''BlockChain'' doit posséder une méthode pour ajouter une transaction.
  
-<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>+Une transaction est composée des informations : identifiant source, identifiant cible, valeur de la transaction, signature.
  
-==== Vérification d'un bloc ====+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,
  
-Lors de l'ajout d'un bloc, il va falloir prévoir une étape de vérificationPour chaque transaction : +On convient que la fonction renvoie ''False'' si les vérifications échouentAutrement on peut poursuivre en exécutant la transaction :
-  * vérifier que la signature, +
-  * 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 ====+  * mise à jour des comptes, 
 +  * création du bloc correspondant, 
 +  * ajout du bloc à la suite des autres, 
 +  * sauvegarde du bloc
  
-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.+et dans ce cas la fonction renvoie ''True''.
  
-Il faudrait alors prévoir de recréer la chaîne à partir de cette sauvegarde.+===== Proposition d'implémentation =====
  
 +Il faut utiliser, comme déjà dit, {{ :nsi:tds:cryptographie:rsa.py |}}
 +
 +<code python>
 +# blockchain.py
 +import hashlib
 +import os
 +import rsa
 +
 +class Block:
 +    def __init__(self, empreinte_precedent:str, data):
 +        """
 +        empreinte_precedent: empreinte du bloc précédent. "0" si premier bloc
 +        data: tableau donnant la liste des données à prendre en compte
 +        """
 +        self.data = data
 +        self.empreinte_precedent = empreinte_precedent
 +    
 +    def empreinte(self) -> str:
 +        """
 +        renvoie l'empreinte du bloc courant
 +        """
 +        # mettre empreinte_precedent,
 +        # suivi de la version texte de data (__str__ ci dessous)
 +        # encoder en utf8 pour obtenir un bytes b
 +        # faire le hash  avec hashlib.sha256(b)
 +        # renvoyer le hexdigest du hash obtenu
 +        
 +    def __str__(self) -> str:
 +        """
 +        renvoie une version texte de data
 +        """
 +        # pas besoin d'ajouter les empreintes
 +    
 +    def save(self):
 +        """
 +        sauvegarde le bloc dans un fichier dont le nom est l'empreinte
 +        du bloc précédent suivit de l'extension .blc
 +        """
 +        # le fichier doit contenir le contenu de __str__
 +        # 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    
 +
 +
 +class BlockChain:
 +    def __init__(self):
 +        """
 +        lance la procédure de chargement pour construire la blockchain
 +        """
 +        self.blocs = []
 +        self.keys = {}
 +        self.credits = {}
 +        self.load()
 +
 +    def load(self):
 +        """
 +        charge la blockchain selon les fichiers
 +        """
 +        # créer une liste self.blocs vide
 +        # cherche le fichier 0.bloc qui est l'amorce
 +        # ce fichier contient :
 +        #   0
 +        #   les infos des comptes sous la forme : identifiant:str;credit:int;n:int;e:int
 +        #   empreinte du bloc
 +        # 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,
 +        # charger le fichier, lire la transaction, l'ajouter à la chaîne
 +    
 +        # pour info, on peut obtenir les noms de fichiers en faisant :
 +        # fichiers = [f for f in os.listdir('./') if f.endswith('.blc')]
 +        
 +    def add_transaction(id_source:str, id_cible:str, value:int, s:str) -> bool:
 +        """
 +        id_source: identifiant de la source de l'échange
 +        id_cible: identifiant de la cible de l'échange
 +        value: quantité à transférer de id_source à id_cible
 +        s: signature pour authentifie la transaction
 +        renvoie True si la transaction a réussi, False sinon
 +        """
 +        # vérifie que source et cible existent bien
 +        # vérifie la signature
 +        # 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
 +        #   en indiquant l'empreinte du dernier bloc pour l'empreinte précédente
 +        # ajoute le bloc dans la liste des blocs
 +        # sauvegarder le bloc
 +
 +    def empreinte_transaction(self, id_source:str, id_cible:str, value:int) -> bytes:
 +        """
 +        id_source: identifiant source
 +        id_cible: identifiant cible
 +        value: quantité  à transférer
 +        renvoie l'empreinte sous forme digest
 +        """
 +        # fabrique le texte formé de la façon id_source;id_cible;value
 +        # encode, hash
 +        # renvoie digest
 +
 +    def credit(self, identifiant:str) -> int:
 +        """
 +        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 transaction(self, id_cible:str, value:int):
 +        """
 +        renvoie la transaction signée
 +        """
 +        # 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>
  
 +        
 +        
nsi/tds/cryptographie/blockchain.1681222146.txt.gz · Dernière modification : de goupillwiki