|
| 1 | +import sys |
| 2 | +import hashlib |
| 3 | +import random |
| 4 | +from PyQt5.QtWidgets import ( |
| 5 | + QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QGraphicsScene, |
| 6 | + QGraphicsView, QGraphicsEllipseItem, QGraphicsTextItem, QGraphicsLineItem, |
| 7 | + QHBoxLayout, QTextEdit |
| 8 | +) |
| 9 | +from PyQt5.QtGui import QPen, QBrush, QColor, QFont |
| 10 | +from PyQt5.QtCore import Qt, QPointF |
| 11 | + |
| 12 | +class Node(QGraphicsEllipseItem): |
| 13 | + def __init__(self, name, pos, color, node_type, description=""): |
| 14 | + super().__init__(-30, -30, 60, 60) |
| 15 | + self.setBrush(QBrush(color)) |
| 16 | + self.setPen(QPen(Qt.white, 2)) |
| 17 | + self.setPos(pos) |
| 18 | + self.name = name |
| 19 | + self.node_type = node_type |
| 20 | + self.description = description |
| 21 | + |
| 22 | + self.label = QGraphicsTextItem(name) |
| 23 | + self.label.setDefaultTextColor(Qt.white) |
| 24 | + self.label.setFont(QFont("Arial", 8)) |
| 25 | + self.label.setParentItem(self) |
| 26 | + self.label.setPos(-30, 40) |
| 27 | + |
| 28 | +class Edge: |
| 29 | + def __init__(self, scene, node1, node2): |
| 30 | + self.line = QGraphicsLineItem(node1.x(), node1.y(), node2.x(), node2.y()) |
| 31 | + self.line.setPen(QPen(Qt.gray, 2, Qt.SolidLine)) |
| 32 | + scene.addItem(self.line) |
| 33 | + self.nodes = (node1, node2) |
| 34 | + |
| 35 | +class ZKPVerifierExplained(QWidget): |
| 36 | + def __init__(self): |
| 37 | + super().__init__() |
| 38 | + self.setWindowTitle("🔐 ZKP Simulation with Verifier Explanation") |
| 39 | + self.setGeometry(100, 100, 1300, 850) |
| 40 | + self.setStyleSheet("background-color: #1e1e1e; color: white;") |
| 41 | + |
| 42 | + self.layout = QVBoxLayout() |
| 43 | + self.setLayout(self.layout) |
| 44 | + |
| 45 | + self.scene = QGraphicsScene() |
| 46 | + self.view = QGraphicsView(self.scene) |
| 47 | + self.layout.addWidget(self.view) |
| 48 | + |
| 49 | + self.statement_label = QLabel("🎓 Knowledge Statement: 'I know (u, v) ∈ G such that employee u has access to account v.'") |
| 50 | + self.statement_label.setStyleSheet("padding: 8px; font-size: 16px;") |
| 51 | + self.layout.addWidget(self.statement_label) |
| 52 | + |
| 53 | + self.protocol_log = QTextEdit() |
| 54 | + self.protocol_log.setReadOnly(True) |
| 55 | + self.protocol_log.setStyleSheet("background-color: #111; color: #ddd; font-family: monospace;") |
| 56 | + self.layout.addWidget(self.protocol_log) |
| 57 | + |
| 58 | + self.button_layout = QHBoxLayout() |
| 59 | + self.commit_button = QPushButton("🔐 Step 1: Commit to Secret Edge") |
| 60 | + self.challenge_account_btn = QPushButton("Step 2: Reveal Account") |
| 61 | + self.challenge_employee_btn = QPushButton("Step 2: Reveal Employee") |
| 62 | + self.challenge_account_btn.setEnabled(False) |
| 63 | + self.challenge_employee_btn.setEnabled(False) |
| 64 | + self.button_layout.addWidget(self.commit_button) |
| 65 | + self.button_layout.addWidget(self.challenge_account_btn) |
| 66 | + self.button_layout.addWidget(self.challenge_employee_btn) |
| 67 | + self.layout.addLayout(self.button_layout) |
| 68 | + |
| 69 | + self.commit_button.clicked.connect(self.commit_phase) |
| 70 | + self.challenge_account_btn.clicked.connect(lambda: self.reveal("account")) |
| 71 | + self.challenge_employee_btn.clicked.connect(lambda: self.reveal("employee")) |
| 72 | + |
| 73 | + self.accounts = [] |
| 74 | + self.employees = [] |
| 75 | + self.edges = [] |
| 76 | + self.secret_edge = None |
| 77 | + self.nonce = None |
| 78 | + self.commitment = None |
| 79 | + |
| 80 | + self.build_graph() |
| 81 | + |
| 82 | + def build_graph(self): |
| 83 | + account_data = [ |
| 84 | + ("Vault#011", "$800K"), |
| 85 | + ("Account#992", "Suspicious Flow"), |
| 86 | + ("Loan#743", "Offshore Link") |
| 87 | + ] |
| 88 | + |
| 89 | + emp_data = [ |
| 90 | + ("Alice", "Teller - Branch A"), |
| 91 | + ("Bob", "Manager - HQ"), |
| 92 | + ("Claire", "Auditor - Internal Affairs") |
| 93 | + ] |
| 94 | + |
| 95 | + for i, (name, desc) in enumerate(account_data): |
| 96 | + node = Node(name, QPointF(200, 100 + i * 200), QColor("gold"), "account", desc) |
| 97 | + self.accounts.append(node) |
| 98 | + self.scene.addItem(node) |
| 99 | + |
| 100 | + for i, (name, desc) in enumerate(emp_data): |
| 101 | + node = Node(name, QPointF(1000, 100 + i * 200), QColor("skyblue"), "employee", desc) |
| 102 | + self.employees.append(node) |
| 103 | + self.scene.addItem(node) |
| 104 | + |
| 105 | + access_list = [ |
| 106 | + (0, 0), |
| 107 | + (1, 1), |
| 108 | + (2, 2), |
| 109 | + (0, 1) |
| 110 | + ] |
| 111 | + |
| 112 | + for a_idx, e_idx in access_list: |
| 113 | + self.edges.append(Edge(self.scene, self.accounts[a_idx], self.employees[e_idx])) |
| 114 | + |
| 115 | + def commit_phase(self): |
| 116 | + self.secret_edge = random.choice(self.edges) |
| 117 | + u, v = self.secret_edge.nodes |
| 118 | + self.nonce = str(random.randint(100000, 999999)) |
| 119 | + combined = u.name + v.name + self.nonce |
| 120 | + self.commitment = hashlib.sha256(combined.encode()).hexdigest() |
| 121 | + |
| 122 | + self.protocol_log.clear() |
| 123 | + self.protocol_log.append("🔐 [Commit Phase]") |
| 124 | + self.protocol_log.append(f"Secret Edge: {u.name} ↔ {v.name} (kept hidden)") |
| 125 | + self.protocol_log.append(f"Nonce (random salt): {self.nonce}") |
| 126 | + self.protocol_log.append(f"Commitment = SHA256({u.name} + {v.name} + nonce)") |
| 127 | + self.protocol_log.append(f"→ {self.commitment}\n") |
| 128 | + |
| 129 | + self.challenge_account_btn.setEnabled(True) |
| 130 | + self.challenge_employee_btn.setEnabled(True) |
| 131 | + |
| 132 | + def reveal(self, challenge_type): |
| 133 | + u, v = self.secret_edge.nodes |
| 134 | + self.protocol_log.append(f" [Challenge Phase] Verifier asks to reveal: {challenge_type.upper()}") |
| 135 | + |
| 136 | + if challenge_type == "account": |
| 137 | + revealed = u.name |
| 138 | + self.protocol_log.append(f"Prover reveals: {revealed} + nonce") |
| 139 | + self.protocol_log.append("\nVerifier tries every employee to match the hash:") |
| 140 | + for emp in self.employees: |
| 141 | + trial = hashlib.sha256((u.name + emp.name + self.nonce).encode()).hexdigest() |
| 142 | + match = "✅ MATCH" if trial == self.commitment else "❌" |
| 143 | + self.protocol_log.append(f" • H({u.name} + {emp.name} + {self.nonce}) → {trial[:20]}... {match}") |
| 144 | + else: |
| 145 | + revealed = v.name |
| 146 | + self.protocol_log.append(f"Prover reveals: {revealed} + nonce") |
| 147 | + self.protocol_log.append("\nVerifier tries every account to match the hash:") |
| 148 | + for acc in self.accounts: |
| 149 | + trial = hashlib.sha256((acc.name + v.name + self.nonce).encode()).hexdigest() |
| 150 | + match = "✅ MATCH" if trial == self.commitment else "❌" |
| 151 | + self.protocol_log.append(f" • H({acc.name} + {v.name} + {self.nonce}) → {trial[:20]}... {match}") |
| 152 | + |
| 153 | + self.protocol_log.append("\n🛡️ If any match → verifier is convinced.") |
| 154 | + self.challenge_account_btn.setEnabled(False) |
| 155 | + self.challenge_employee_btn.setEnabled(False) |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + app = QApplication(sys.argv) |
| 159 | + window = ZKPVerifierExplained() |
| 160 | + window.show() |
| 161 | + sys.exit(app.exec_()) |
0 commit comments