Skip to content

Commit 10ed4f0

Browse files
Make a demo application of real-world ZKP application
1 parent 8dec9c3 commit 10ed4f0

File tree

3 files changed

+260
-77
lines changed

3 files changed

+260
-77
lines changed

ZKP_Demo_Tool/ui/level_card.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QLabel, QPushButton
2+
3+
def create_level_card(parent, title, description, button_label, unlock_condition_met, launch_function):
4+
card = QFrame(parent)
5+
card.setFrameShape(QFrame.StyledPanel)
6+
card.setStyleSheet("""
7+
QFrame {
8+
background-color: #ffffff;
9+
border-radius: 10px;
10+
padding: 20px;
11+
border: 1px solid #ccc;
12+
}
13+
""")
14+
15+
layout = QVBoxLayout(card)
16+
17+
title_label = QLabel(title)
18+
title_label.setStyleSheet("font-size: 18px; font-weight: bold;")
19+
layout.addWidget(title_label)
20+
21+
desc_label = QLabel(description)
22+
desc_label.setWordWrap(True)
23+
desc_label.setStyleSheet("color: #555; font-size: 13px;")
24+
layout.addWidget(desc_label)
25+
26+
play_button = QPushButton(button_label)
27+
play_button.setFixedHeight(40)
28+
29+
if unlock_condition_met:
30+
play_button.setStyleSheet("""
31+
QPushButton {
32+
background-color: #3498db;
33+
color: white;
34+
font-weight: bold;
35+
border-radius: 8px;
36+
}
37+
QPushButton:hover {
38+
background-color: #2980b9;
39+
}
40+
""")
41+
play_button.clicked.connect(launch_function)
42+
else:
43+
play_button.setEnabled(False)
44+
play_button.setStyleSheet("""
45+
QPushButton {
46+
background-color: #bbb;
47+
color: #666;
48+
border-radius: 8px;
49+
}
50+
""")
51+
play_button.setToolTip("Earn more trust points to unlock this level.")
52+
53+
layout.addWidget(play_button)
54+
return card

ZKP_Demo_Tool/ui/level_selector.py

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
# ui/level_selector.py
2-
import os
3-
from PyQt5.QtWidgets import (
4-
QWidget, QVBoxLayout, QLabel, QPushButton, QMessageBox, QGridLayout
5-
)
1+
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton, QMessageBox, QGridLayout, QComboBox, QHBoxLayout
62
from PyQt5.QtGui import QFont
73
from PyQt5.QtCore import Qt
8-
9-
from levels.level1_triangle import Level1Triangle
10-
# We'll import level2_ring etc. later as they're written
4+
from levels.level2_bank_graph import Level2BankGraph
5+
from ui.level_card import create_level_card
116

127
class LevelSelector(QWidget):
138
def __init__(self):
@@ -18,47 +13,116 @@ def __init__(self):
1813
self.layout = QVBoxLayout()
1914
self.setLayout(self.layout)
2015

21-
title = QLabel("🔐 Zero-Knowledge Proof Puzzle Book")
22-
title.setAlignment(Qt.AlignCenter)
23-
title.setFont(QFont("Arial", 18, QFont.Bold))
24-
self.layout.addWidget(title)
16+
title_label = QLabel("🔒 The ZKP Puzzle Book")
17+
title_label.setFont(QFont("Cochin", 24, QFont.Bold))
18+
title_label.setStyleSheet("color: gold;")
19+
title_label.setAlignment(Qt.AlignCenter)
2520

2621
self.instructions = QLabel("Earn Trust Points by completing levels to unlock the next!")
2722
self.instructions.setAlignment(Qt.AlignCenter)
2823
self.layout.addWidget(self.instructions)
2924

25+
self.round_mode_dropdown = QComboBox()
26+
self.round_mode_dropdown.addItems([
27+
"Manual Mode",
28+
"Auto Mode - 5 Rounds",
29+
"Auto Mode - 10 Rounds",
30+
"Auto Mode - 20 Rounds"
31+
])
32+
self.layout.addWidget(self.round_mode_dropdown)
33+
3034
self.trust_points = 0
3135
self.level_buttons = []
3236
self.build_level_grid()
3337

3438
def build_level_grid(self):
39+
# Horizontal layout or grid for cards
40+
card_layout = QHBoxLayout()
41+
42+
43+
44+
# Level 2: Unlocks after 3 trust points
45+
level2_card = create_level_card(
46+
parent=self,
47+
title="Level 2: 5-Node Ring",
48+
description="Demonstrate your skill on a cyclic 5-node graph with non-repeating color constraints.",
49+
button_label="Play Now",
50+
unlock_condition_met=(self.trust_points >= 3),
51+
launch_function=self.load_level2
52+
)
53+
54+
# Add cards to layout
55+
56+
card_layout.addWidget(level2_card)
57+
58+
# Then add to main layout in your init or builder method:
59+
self.layout.addLayout(card_layout)
60+
bottom_layout = QHBoxLayout()
61+
bottom_layout.setSpacing(20)
62+
bottom_layout.setContentsMargins(20, 20, 20, 20)
63+
self.mode_selector = QComboBox()
64+
self.mode_selector.addItems(["⌨️ Manual Mode", "🤖 Auto Mode"])
65+
self.mode_selector.setFixedHeight(40)
66+
self.mode_selector.setStyleSheet("""
67+
QComboBox {
68+
background-color: #2c3e50;
69+
color: white;
70+
font-weight: bold;
71+
font-size: 14px;
72+
padding: 5px 10px;
73+
border-radius: 8px;
74+
}
75+
QComboBox::drop-down {
76+
border: none;
77+
}
78+
""")
79+
bottom_layout.addWidget(self.mode_selector)
80+
3581
grid = QGridLayout()
3682
levels = [
37-
{"name": "Level 1: Triangle", "required_points": 0, "function": self.load_level1},
38-
{"name": "Level 2: 5-Node Ring", "required_points": 3, "function": self.coming_soon},
39-
{"name": "Level 3: Bipartite Graph", "required_points": 6, "function": self.coming_soon},
40-
{"name": "Level 4: Social Network", "required_points": 9, "function": self.coming_soon}
83+
{"name": "💠 Level 2.5: Multi-Path Challenge", "required_points": 0, "function": self.load_level2, "color": "#27ae60"}
4184
]
4285

4386
for i, level in enumerate(levels):
44-
btn = QPushButton(level["name"])
45-
if self.trust_points < level["required_points"]:
46-
btn.setEnabled(False)
47-
btn.setText(f"🔒 {level['name']} (Locked)")
48-
else:
49-
btn.clicked.connect(level["function"])
50-
self.level_buttons.append(btn)
51-
grid.addWidget(btn, i // 2, i % 2)
87+
button = QPushButton(level["name"])
5288

53-
self.layout.addLayout(grid)
89+
if self.trust_points >= level["required_points"]:
90+
button.setEnabled(True)
91+
button.setStyleSheet(f"""
92+
QPushButton {{
93+
background-color: {level['color']};
94+
color: white;
95+
font-weight: bold;
96+
font-size: 16px;
97+
padding: 15px;
98+
border-radius: 10px;
99+
border: 2px solid white;
100+
}}
101+
QPushButton:hover {{
102+
background-color: #2c3e50;
103+
border: 2px solid gold;
104+
}}
105+
""")
106+
button.clicked.connect(level["function"])
107+
else:
108+
button.setEnabled(False)
109+
button.setStyleSheet("""
110+
QPushButton {
111+
background-color: #444;
112+
color: #aaa;
113+
font-weight: bold;
114+
font-size: 16px;
115+
padding: 15px;
116+
border-radius: 10px;
117+
border: 2px dashed gray;
118+
}
119+
""")
120+
button.setToolTip(f"🔒 Requires {level['required_points']} Trust Points to unlock.")
54121

55-
def load_level1(self):
56-
self.hide()
57-
self.level_window = Level1Triangle(parent_selector=self)
58-
self.level_window.show()
122+
grid.addWidget(button, 0, i)
59123

60-
def coming_soon(self):
61-
QMessageBox.information(self, "Locked Level", "Complete earlier levels to unlock this level!")
124+
125+
self.layout.addLayout(grid)
62126

63127
def update_trust_points(self, points_earned):
64128
self.trust_points += points_earned
@@ -70,3 +134,22 @@ def rebuild(self):
70134
btn.setParent(None)
71135
self.level_buttons = []
72136
self.build_level_grid()
137+
138+
def get_round_mode(self):
139+
mode_text = self.round_mode_dropdown.currentText()
140+
if "Manual" in mode_text:
141+
return False, 3
142+
elif "5" in mode_text:
143+
return True, 5
144+
elif "10" in mode_text:
145+
return True, 10
146+
else:
147+
return True, 20
148+
149+
def load_level2(self):
150+
self.hide()
151+
auto_mode, auto_rounds = self.get_round_mode()
152+
self.level_window = Level2BankGraph(parent_selector=self, auto_mode=auto_mode, auto_rounds=auto_rounds)
153+
154+
self.level_window.show()
155+

ZKP_Demo_Tool/ui/node_item.py

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,110 @@
1-
from PyQt5.QtWidgets import (
2-
QGraphicsEllipseItem, QGraphicsTextItem, QToolTip,
3-
QDialog, QVBoxLayout, QPushButton, QHBoxLayout
4-
)
5-
from PyQt5.QtGui import QBrush, QColor, QFont
6-
from PyQt5.QtCore import Qt
1+
# node_item.py
2+
from PyQt5.QtWidgets import QGraphicsEllipseItem, QGraphicsTextItem
3+
from PyQt5.QtGui import QBrush, QColor, QFont, QPen
4+
from PyQt5.QtCore import Qt, QPointF, QPropertyAnimation
75

86
class NodeItem(QGraphicsEllipseItem):
9-
def __init__(self, node_id, pos, parent_level):
7+
def __init__(self, node_id, pos, parent, label=None):
108
super().__init__(-20, -20, 40, 40)
119
self.node_id = node_id
12-
self.parent_level = parent_level
10+
self.parent = parent
1311
self.setBrush(QBrush(Qt.gray))
12+
self.setPen(QPen(Qt.black, 2))
13+
self.setZValue(1)
14+
1415
self.setFlag(QGraphicsEllipseItem.ItemIsSelectable)
1516
self.setAcceptHoverEvents(True)
1617
self.setPos(pos)
18+
1719
self.locked = False
20+
self.role = None
21+
self.label = label if label else f"Node {node_id}"
22+
self.hash_display = None
23+
24+
# Label below node
25+
self.text_item = QGraphicsTextItem(self.label, self)
26+
self.text_item.setFont(QFont("Arial", 9))
27+
self.text_item.setDefaultTextColor(Qt.white)
28+
self.text_item.setPos(-self.text_item.boundingRect().width() / 2, 22)
1829

19-
self.text = QGraphicsTextItem(str(node_id))
20-
self.text.setDefaultTextColor(Qt.white)
21-
self.text.setFont(QFont("Arial", 10, QFont.Bold))
22-
self.text.setParentItem(self)
23-
self.text.setPos(-8, -10)
30+
# Lock visual indicator
31+
self.lock_icon = QGraphicsTextItem("🔒", self)
32+
self.lock_icon.setFont(QFont("Arial", 12))
33+
self.lock_icon.setDefaultTextColor(Qt.darkGray)
34+
self.lock_icon.setPos(-10, -35)
35+
self.lock_icon.setVisible(False)
36+
def set_label(self, label_text):
37+
if hasattr(self, 'label'):
38+
self.scene().removeItem(self.label)
2439

25-
# Define the allowed color palette
26-
self.allowed_colors = {
27-
"Red": "#E74C3C",
28-
"Green": "#2ECC71",
29-
" Blue": "#3498DB",
30-
"Yellow": "#F1C40F"
31-
}
40+
from PyQt5.QtWidgets import QGraphicsTextItem
41+
from PyQt5.QtCore import Qt
3242

43+
self.label = QGraphicsTextItem(label_text)
44+
self.label.setDefaultTextColor(Qt.white)
45+
self.label.setParentItem(self)
46+
self.label.setPos(-20, -35)
47+
48+
def set_color(self, color):
49+
if not self.locked:
50+
self.setBrush(QBrush(QColor(color)))
51+
def change_color(self):
52+
current_color = self.brush().color()
53+
next_color = self.get_next_color(current_color)
54+
self.setBrush(QBrush(next_color))
55+
self.parent.node_colors[self.node_id] = next_color
3356
def mousePressEvent(self, event):
34-
if not self.locked and not self.parent_level.committed:
35-
self.show_color_selection_dialog()
36-
super().mousePressEvent(event)
57+
self.change_color()
58+
def get_next_color(self, current_color):
59+
color_cycle = [Qt.red, Qt.green, Qt.blue] # Or your fixed palette
60+
try:
61+
i = color_cycle.index(current_color)
62+
return color_cycle[(i + 1) % len(color_cycle)]
63+
except ValueError:
64+
return color_cycle[0]
65+
def lock(self):
66+
self.locked = True
67+
self.setBrush(QBrush(Qt.gray))
68+
self.setPen(QPen(Qt.darkGray, 2))
69+
self.lock_icon.setVisible(True)
70+
self.update()
3771

38-
def show_color_selection_dialog(self):
39-
dialog = QDialog()
40-
dialog.setWindowTitle(f"Select a color for Node {self.node_id}")
41-
layout = QVBoxLayout()
72+
def unlock(self):
73+
self.locked = False
74+
self.setPen(QPen(Qt.black, 2))
75+
self.setBrush(QBrush(Qt.gray))
76+
self.lock_icon.setVisible(False)
77+
self.update()
4278

43-
button_row = QHBoxLayout()
44-
for label, hex_code in self.allowed_colors.items():
45-
btn = QPushButton(label)
46-
btn.setStyleSheet(f"background-color: {hex_code}; color: black; font-weight: bold;")
47-
btn.clicked.connect(lambda _, c=hex_code: self.select_color(dialog, c))
48-
button_row.addWidget(btn)
79+
def show_hash(self, short_hash):
80+
if not self.hash_display:
81+
self.hash_display = QGraphicsTextItem(short_hash, self)
82+
self.hash_display.setFont(QFont("Courier", 7))
83+
self.hash_display.setDefaultTextColor(Qt.lightGray)
84+
self.hash_display.setPos(-self.hash_display.boundingRect().width() / 2, 35)
85+
else:
86+
self.hash_display.setPlainText(short_hash)
4987

50-
layout.addLayout(button_row)
51-
dialog.setLayout(layout)
52-
dialog.exec_()
88+
def hide_hash(self):
89+
if self.hash_display:
90+
self.scene().removeItem(self.hash_display)
91+
self.hash_display = None
5392

54-
def select_color(self, dialog, color_hex):
55-
self.setBrush(QBrush(QColor(color_hex)))
56-
self.parent_level.node_colors[self.node_id] = color_hex
57-
self.parent_level.update_narration(f"🎨 Prover colored node {self.node_id} with color.")
58-
dialog.accept()
93+
def set_label(self, label):
94+
self.label = label
95+
self.text_item.setPlainText(label)
5996

60-
def hoverEnterEvent(self, event):
61-
if self.locked:
62-
QToolTip.showText(event.screenPos(), "🔒 This node is committed", self.parent_level)
63-
else:
64-
QToolTip.showText(event.screenPos(), f" Node {self.node_id}: Click to choose a color", self.parent_level)
97+
def pulse_red(self):
98+
# Animate node glow red for non-repudiation breach
99+
animation = QPropertyAnimation(self, b"opacity")
100+
animation.setDuration(800)
101+
animation.setKeyValueAt(0, 1.0)
102+
animation.setKeyValueAt(0.5, 0.3)
103+
animation.setKeyValueAt(1, 1.0)
104+
animation.start()
105+
self.setBrush(QBrush(QColor("red")))
106+
def set_label(self, text):
107+
label = QGraphicsTextItem(text, self)
108+
label.setDefaultTextColor(Qt.black)
109+
label.setFont(QFont("Arial", 10))
110+
label.setPos(-10, -10) # Adjust position if needed

0 commit comments

Comments
 (0)