diff --git a/SwiftChain/Models/Blockchain.swift b/SwiftChain/Models/Blockchain.swift index 6ee43f3..0df73f0 100644 --- a/SwiftChain/Models/Blockchain.swift +++ b/SwiftChain/Models/Blockchain.swift @@ -32,7 +32,7 @@ class Blockchain { genesisBlock.previousHash = "0000" genesisBlock.index = 0 genesisBlock.timestamp = Int(Date().timeIntervalSince1970) - genesisBlock.mineBlock() + let _ = genesisBlock.mineBlock() chain.append(genesisBlock) print("Genesis block created -- hash: \(genesisBlock.hash)") } diff --git a/SwiftChain/Models/MemPool.swift b/SwiftChain/Models/MemPool.swift index 3cf3019..dbd155e 100644 --- a/SwiftChain/Models/MemPool.swift +++ b/SwiftChain/Models/MemPool.swift @@ -29,17 +29,22 @@ class MemPool { Valide une transaction avant de l'ajouter au pool */ private func validateTransaction(_ transaction: Transaction) -> Bool { - // Basic validations + // Vérifications basiques if transaction.amount <= 0 { return false } - // Skip balance check for mining rewards + // Pas besoin de vérifier la signature pour les récompenses de minage if transaction.type == "MINING_REWARD" { return true } - // Check if sender has enough balance + // Vérifier la signature + if !transaction.isSignatureValid() { + return false + } + + // Vérifier le solde return accountManager.canProcessTransaction(transaction) } diff --git a/SwiftChain/Models/Node.swift b/SwiftChain/Models/Node.swift new file mode 100644 index 0000000..ac4ce1d --- /dev/null +++ b/SwiftChain/Models/Node.swift @@ -0,0 +1,172 @@ +// +// Node.swift +// SwiftChain +// +// Created by Victor on 27/11/2024. +// + +import Foundation +import Network + +class Node { + private var peers: [NWConnection] = [] + private let port: NWEndpoint.Port = 8333 // Port standard + private var listener: NWListener? + private let blockchain: Blockchain + + // Queue pour gérer les connexions de manière asynchrone + private let queue = DispatchQueue(label: "com.blockchain.node") + + init(blockchain: Blockchain) { + self.blockchain = blockchain + setupListener() + } + + enum MessageType: String, Codable { + case transaction = "TX" + } + + struct NetworkMessage: Codable { + let type: MessageType + let data: Data + } + + // Configuration du listener + private func setupListener() { + let parameters = NWParameters.tcp + do { + listener = try NWListener(using: parameters, on: port) + listener?.stateUpdateHandler = { [weak self] state in + switch state { + case .ready: + print("Node is listening on port \(self?.port.rawValue ?? 0)") + case .failed(let error): + print("Listener failed with error: \(error)") + default: + break + } + } + + listener?.newConnectionHandler = { [weak self] connection in + self?.handleNewConnection(connection) + print("New incoming connection from: \(connection.endpoint)") + } + + listener?.start(queue: queue) + } catch { + print("Failed to create listener: \(error)") + } + } + + // Connexion à un pair + func connectToPeer(host: String) { + let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: port) + let connection = NWConnection(to: endpoint, using: .tcp) + + connection.stateUpdateHandler = { [weak self] state in + switch state { + case .ready: + print("Connected to peer: \(host)") + self?.peers.append(connection) + self?.startReceiving(connection) + case .failed(let error): + print("Connection failed: \(error)") + case .cancelled: + print("Connection cancelled to: \(host)") + default: + break + } + } + + connection.start(queue: queue) + } + + // Méthode pour lister les pairs connectés + func listPeers() { + if peers.isEmpty { + print("Aucun pair connecté") + return + } + + print("\nPairs connectés (\(peers.count)):") + for (index, peer) in peers.enumerated() { + print("\(index + 1). \(peer.endpoint)") + } + } + + // Propagation d'une transaction + func broadcastTransaction(_ transaction: Transaction) { + do { + let transactionData = try JSONEncoder().encode(transaction) + let message = NetworkMessage(type: .transaction, data: transactionData) + let messageData = try JSONEncoder().encode(message) + + print("Broadcasting transaction to \(peers.count) peers") + + for peer in peers { + peer.send(content: messageData, completion: .contentProcessed { error in + if let error = error { + print("Failed to send transaction: \(error)") + } else { + print("Transaction sent successfully to: \(peer.endpoint)") + } + }) + } + } catch { + print("Failed to encode transaction: \(error)") + } + } + + // Réception des messages + private func startReceiving(_ connection: NWConnection) { + connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) { [weak self] content, _, isComplete, error in + if let data = content { + self?.handleReceivedData(data, from: connection) + } + + if let error = error { + print("Receive error: \(error)") + } + + if !isComplete && error == nil { + self?.startReceiving(connection) + } + } + } + + // Traitement d'une transaction reçue + private func handleReceivedTransaction(_ transaction: Transaction) { + if blockchain.submitTransaction(transaction) { + // Propager aux autres pairs si la transaction est valide + broadcastTransaction(transaction) + } + } + + private func handleReceivedData(_ data: Data, from connection: NWConnection) { + do { + print("Received data of size: \(data.count) bytes") + let message = try JSONDecoder().decode(NetworkMessage.self, from: data) + print("Message decoded successfully, type: \(message.type)") + + switch message.type { + case .transaction: + let transaction = try JSONDecoder().decode(Transaction.self, from: message.data) + print("Transaction decoded: \(transaction.sender) -> \(transaction.receiver): \(transaction.amount)") + + if blockchain.submitTransaction(transaction) { + print("Transaction added to mempool successfully") + } else { + print("Failed to add transaction to mempool") + } + } + } catch { + print("Failed to decode received data: \(error)") + } + } + + private func handleNewConnection(_ connection: NWConnection) { + connection.start(queue: queue) + peers.append(connection) + startReceiving(connection) + } +} diff --git a/SwiftChain/Models/Transaction.swift b/SwiftChain/Models/Transaction.swift index 5390c98..cfab5fd 100644 --- a/SwiftChain/Models/Transaction.swift +++ b/SwiftChain/Models/Transaction.swift @@ -7,16 +7,67 @@ import Foundation -class Transaction { - var sender: String - var receiver: String - var amount: Int - var type: String - +class Transaction: Codable { + let sender: String + let receiver: String + let amount: Int + let type: String + var signature: Data? + var senderPublicKey: Data? + init(sender: String, receiver: String, amount: Int, type: String) { self.sender = sender self.receiver = receiver self.amount = amount self.type = type } + + // Pour encoder/décoder les Data optionnels + enum CodingKeys: String, CodingKey { + case sender, receiver, amount, type, signature, senderPublicKey + } + + // Données à signer + func messageToSign() -> Data { + return "\(sender)\(receiver)\(amount)\(type)".data(using: .utf8)! + } + + required init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + sender = try container.decode(String.self, forKey: .sender) + receiver = try container.decode(String.self, forKey: .receiver) + amount = try container.decode(Int.self, forKey: .amount) + type = try container.decode(String.self, forKey: .type) + signature = try container.decodeIfPresent(Data.self, forKey: .signature) + senderPublicKey = try container.decodeIfPresent(Data.self, forKey: .senderPublicKey) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(sender, forKey: .sender) + try container.encode(receiver, forKey: .receiver) + try container.encode(amount, forKey: .amount) + try container.encode(type, forKey: .type) + try container.encodeIfPresent(signature, forKey: .signature) + try container.encodeIfPresent(senderPublicKey, forKey: .senderPublicKey) + } + + // Vérifier la validité de la signature + func isSignatureValid() -> Bool { + guard let signature = signature, + let publicKeyData = senderPublicKey else { + return false + } + + // Pas besoin de vérifier la signature pour les récompenses de minage + if type == "MINING_REWARD" { + return true + } + + return Wallet.verifySignature( + for: self, + signature: signature, + publicKeyData: publicKeyData + ) + } } diff --git a/SwiftChain/Models/Wallet.swift b/SwiftChain/Models/Wallet.swift new file mode 100644 index 0000000..8dff926 --- /dev/null +++ b/SwiftChain/Models/Wallet.swift @@ -0,0 +1,47 @@ +// +// Wallet.swift +// SwiftChain +// +// Created by Victor on 27/11/2024. +// + +import Foundation +import CryptoKit + +class Wallet { + private let privateKey: Curve25519.Signing.PrivateKey + let publicKey: Curve25519.Signing.PublicKey + let address: String + + init() { + // Générer une nouvelle paire de clés + privateKey = Curve25519.Signing.PrivateKey() + publicKey = privateKey.publicKey + + // Créer une adresse au format swift_(hash) + let pubKeyData = publicKey.rawRepresentation + let hash = SHA256.hash(data: pubKeyData) + let hashString = hash.compactMap { String(format: "%02x", $0) }.joined() + address = "swift_" + hashString.prefix(40) + } + + // Signer une transaction + func signTransaction(_ transaction: Transaction) -> Data? { + let messageData = transaction.messageToSign() + return try? privateKey.signature(for: messageData) + } + + // Vérifier une signature + static func verifySignature(for transaction: Transaction, signature: Data, publicKeyData: Data) -> Bool { + guard let publicKey = try? Curve25519.Signing.PublicKey(rawRepresentation: publicKeyData) else { + return false + } + + return (publicKey.isValidSignature(signature, for: transaction.messageToSign())) + } + + // Obtenir la clé publique en format Data + func getPublicKeyData() -> Data { + return publicKey.rawRepresentation + } +} diff --git a/SwiftChain/main.swift b/SwiftChain/main.swift index b883dff..09a8d1c 100644 --- a/SwiftChain/main.swift +++ b/SwiftChain/main.swift @@ -9,8 +9,10 @@ import Foundation let blockchain = Blockchain() +let node = Node(blockchain: blockchain) var command: String? var currentMinerAddress = "MINER1" // Adresse par défaut du mineur +var wallets: [String: Wallet] = [:] print(""" Blockchain CLI - Commandes disponibles: @@ -20,6 +22,8 @@ Blockchain CLI - Commandes disponibles: - pending : Voir les transactions en attente - validity : Vérifier la validité de la chaîne - setminer : Changer l'adresse du mineur +- connect : Se connecter à un pair +- peers : Liste des pairs connectés - exit : Quitter """) @@ -30,6 +34,15 @@ repeat { command = readLine()?.lowercased() switch command { + case "connect": + print("Entrez l'adresse du pair (ex: 192.168.1.100):") + if let host = readLine() { + node.connectToPeer(host: host) + } + + case "peers": + node.listPeers() + case "mine": if let block = blockchain.createBlock(minerAddress: currentMinerAddress) { print("Bloc miné avec succès. Récompense envoyée à \(currentMinerAddress)") @@ -38,27 +51,6 @@ repeat { print("Échec du minage") } - case "send": - print("Adresse de l'expéditeur:") - let sender = readLine() ?? "" - print("Adresse du destinataire:") - let receiver = readLine() ?? "" - print("Montant:") - if let amountStr = readLine(), let amount = Int(amountStr) { - let transaction = Transaction( - sender: sender, - receiver: receiver, - amount: amount, - type: "TRANSFER" - ) - - if blockchain.submitTransaction(transaction) { - print("Transaction ajoutée au mempool") - } else { - print("Transaction invalide") - } - } - case "balance": print("Entrer l'adresse:") if let address = readLine() { @@ -78,11 +70,11 @@ repeat { print("Transactions en attente: \(transactions.count)") for (index, tx) in transactions.enumerated() { print(""" - \(index + 1). De: \(tx.sender) - À: \(tx.receiver) - Montant: \(tx.amount) - Type: \(tx.type) - """) + \(index + 1). De: \(tx.sender) + À: \(tx.receiver) + Montant: \(tx.amount) + Type: \(tx.type) + """) } case "validity": @@ -92,6 +84,48 @@ repeat { case "exit": print("Au revoir!") + // Ajoutons une nouvelle commande pour créer un wallet + case "createwallet": + let wallet = Wallet() + wallets[wallet.address] = wallet + print("Nouveau wallet créé!") + print("Adresse: \(wallet.address)") + + // Modifions la commande send + case "send": + print("Votre adresse (wallet):") + guard let senderAddress = readLine(), + let wallet = wallets[senderAddress] + else { + print("Wallet non trouvé") + break + } + + print("Adresse du destinataire:") + guard let receiverAddress = readLine() else { break } + + print("Montant:") + guard let amountStr = readLine(), + let amount = Int(amountStr) else { break } + + let transaction = Transaction( + sender: senderAddress, + receiver: receiverAddress, + amount: amount, + type: "TRANSFER" + ) + + // Signer la transaction + transaction.senderPublicKey = wallet.getPublicKeyData() + transaction.signature = wallet.signTransaction(transaction) + + if blockchain.submitTransaction(transaction) { + node.broadcastTransaction(transaction) + print("Transaction propagée au réseau!") + } else { + print("Erreur lors de l'envoi de la transaction") + } + default: print("Commande inconnue") }