File size: 9,202 Bytes
4585d4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
import streamlit as st

# Authentication Page
st.header("πŸ” Authentication")

from pages import app

if not app.wallet:
    st.error("❌ Please connect a wallet first")
    st.info("πŸ‘ˆ Go to 'Import/Generate Wallet' to get started")
    st.stop()

if not app.is_registered:
    st.error("❌ Please register first")
    st.info("πŸ‘ˆ Go to 'Registration' to set up your 1P secret")
    st.stop()

if app.is_authenticated:
    st.success("βœ… You are already authenticated!")
    st.info("πŸ‘ˆ Go to 'Manage Wallet' to access wallet functions")

    if st.button("πŸ”„ Re-authenticate", type="secondary"):
        app.is_authenticated = False
        app.save_to_session()
        st.rerun()
    st.stop()

st.markdown("""
### 1P Authentication Process:
1. **Visual Grid Challenge** - Find your secret character in the colored grid
2. **Direction Input** - Enter the direction based on your character's color
3. **Multiple Rounds** - Complete several rounds for security
4. **Verification** - System verifies your responses
""")

# Initialize 1P verifier and solver components
class OnePVerifier:
    def __init__(self, secret: str, public_key_hex: str):
        self.secret = secret
        self.public_key = public_key_hex
        self.session_state = SessionState()
        self.nonce = None
        self.entropy_layers = []
        self.offsets = []
        self.rotateds = []
        self.color_maps = []
        self.expected_solutions = []
        self.skip_rounds = []

    def start_session(self) -> tuple[str, List[str], int]:
        self.nonce = generate_nonce()
        difficulty = self.session_state.d
        total_rounds = difficulty + (difficulty // 2)
        self.entropy_layers = generate_entropy_layers(self.nonce, total_rounds)
        rounds_range = list(range(total_rounds))
        self.skip_rounds = sorted(random.sample(rounds_range, k=total_rounds - difficulty))

        self.offsets = []
        self.rotateds = []
        self.color_maps = []
        self.expected_solutions = []
        grids = []

        # Build combined alphabet from all selected domains
        alphabet = ""
        for domain_chars in DOMAINS.values():
            alphabet += domain_chars
        alphabet = ''.join(set(alphabet))  # Remove duplicates

        for idx in range(total_rounds):
            offset = self.entropy_layers[idx] % len(alphabet)
            self.offsets.append(offset)
            rotated = alphabet[offset:] + alphabet[:offset]
            self.rotateds.append(rotated)
            color_map = {rotated[i]: COLORS[i % 4] for i in range(len(rotated))}
            self.color_maps.append(color_map)

            if idx in self.skip_rounds:
                expected = "S"
            else:
                assigned_color = color_map.get(self.secret, None)
                if assigned_color is None:
                    expected = "S"
                else:
                    direction = app.direction_mapping.get(assigned_color, "Skip")
                    expected = DIRECTION_MAP[direction]

            self.expected_solutions.append(expected)
            grids.append(self.display_grid(idx))

        return self.nonce, grids, total_rounds

    def display_grid(self, idx: int) -> str:
        chars_by_color = defaultdict(list)
        for ch, color in self.color_maps[idx].items():
            chars_by_color[color].append(ch)

        grid_html = f"""
        <div style="border: 2px solid #333; padding: 15px; margin: 10px; background: #f8f9fa; border-radius: 8px;">
        <h4>🎯 Round {idx + 1}</h4>
        <p><strong>Find your secret character and note its color!</strong></p>
        """

        color_hex_map = {"red": "#FF0000", "green": "#00AA00", "blue": "#0066FF", "yellow": "#FFD700"}

        for color in COLORS:
            chars = chars_by_color[color]
            if chars:
                grid_html += f'<div style="margin: 8px 0;"><strong style="color: {color_hex_map[color]};">{color.upper()}:</strong> '
                for char in chars:
                    grid_html += f'<span style="color: {color_hex_map[color]}; font-size: 18px; margin: 2px; padding: 4px; background: white; border-radius: 4px;">{char}</span> '
                grid_html += '</div>'

        grid_html += '</div>'
        return grid_html

    def verify_solution(self, candidates: List[str]) -> bool:
        allowed_skips = len(self.skip_rounds)
        input_skips = candidates.count('S')

        if input_skips > allowed_skips:
            return False

        for idx, expected in enumerate(self.expected_solutions):
            if expected == "S":
                if candidates[idx] != "S":
                    return False
            else:
                if candidates[idx] == "S":
                    continue
                if candidates[idx].upper() != expected:
                    return False

        return True

# Start authentication session
st.markdown("---")
st.subheader("🎯 1P Challenge")

if app.auth_session is None:
    st.info("Click 'Start Authentication' to begin the challenge")

    if st.button("πŸš€ Start Authentication", type="primary"):
        try:
            # Create verifier with user's secret and public key
            public_key_hex = app.wallet.public_key().to_bytes()[1:].hex()
            verifier = OnePVerifier(app.selected_secret, public_key_hex)
            nonce, grids, total_rounds = verifier.start_session()

            app.auth_session = {
                'verifier': verifier,
                'grids': grids,
                'total_rounds': total_rounds,
                'current_round': 0,
                'solutions': [],
                'nonce': nonce
            }
            app.save_to_session()
            st.rerun()
        except Exception as e:
            st.error(f"Failed to start authentication: {str(e)}")

else:
    session = app.auth_session
    current_round = session['current_round']
    total_rounds = session['total_rounds']

    if current_round < total_rounds:
        st.progress((current_round) / total_rounds, f"Round {current_round + 1} of {total_rounds}")

        # Display current grid
        st.markdown(session['grids'][current_round], unsafe_allow_html=True)

        # Show direction mapping as reference
        with st.expander("🧭 Your Direction Mapping Reference"):
            col1, col2 = st.columns(2)
            with col1:
                for color in COLORS[:2]:
                    direction = app.direction_mapping.get(color, "Skip")
                    emoji_map = {"Up": "⬆️", "Down": "⬇️", "Left": "⬅️", "Right": "➑️", "Skip": "⏭️"}
                    st.markdown(f"**{color.title()}**: {direction} {emoji_map[direction]}")
            with col2:
                for color in COLORS[2:]:
                    direction = app.direction_mapping.get(color, "Skip")
                    emoji_map = {"Up": "⬆️", "Down": "⬇️", "Left": "⬅️", "Right": "➑️", "Skip": "⏭️"}
                    st.markdown(f"**{color.title()}**: {direction} {emoji_map[direction]}")

        # Input for current round
        col1, col2 = st.columns([3, 1])
        with col1:
            user_input = st.radio(
                f"What direction for Round {current_round + 1}?",
                options=["⬆️ Up", "⬇️ Down", "⬅️ Left", "➑️ Right", "⏭️ Skip"],
                key=f"round_{current_round}",
                horizontal=True
            )

        with col2:
            st.markdown("<br>", unsafe_allow_html=True)  # Spacing
            if st.button("Next Round ▢️", type="primary"):
                # Map emoji selection to direction code
                direction_code = {
                    "⬆️ Up": "U",
                    "⬇️ Down": "D",
                    "⬅️ Left": "L",
                    "➑️ Right": "R",
                    "⏭️ Skip": "S"
                }[user_input]

                session['solutions'].append(direction_code)
                session['current_round'] += 1
                app.auth_session = session
                app.save_to_session()
                st.rerun()

    else:
        # Authentication complete - verify solutions
        st.success("πŸŽ‰ All rounds completed!")
        st.info("Verifying your responses...")

        verifier = session['verifier']
        solutions = session['solutions']

        if verifier.verify_solution(solutions):
            app.is_authenticated = True
            app.auth_session = None  # Clear session
            app.save_to_session()

            st.success("βœ… Authentication successful!")
            st.success("πŸŽ‰ Welcome to your secure 1P wallet!")
            st.balloons()

            st.info("πŸ‘ˆ Go to 'Manage Wallet' to access your wallet functions")
            st.rerun()

        else:
            st.error("❌ Authentication failed!")
            st.error("Your responses don't match the expected pattern.")
            st.warning("Please try again or check your secret character and direction mapping.")

            if st.button("πŸ”„ Try Again", type="secondary"):
                app.auth_session = None
                app.save_to_session()
                st.rerun()