Spaces:
Build error
Build error
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() |