from collections import defaultdict from typing import List, Dict, Tuple from utils.helpers import generate_nonce, generate_entropy_layers class OneRoundVerifier: """ A simplified, one-round version of the 1P authentication system. Use this for quick challenges where a full multi-round authentication is not needed. """ def __init__(self, secret: str, direction_mapping: Dict[str, str], colors: List[str], direction_map: Dict[str, str], domains: Dict[str, str]): self.secret = secret self.direction_mapping = direction_mapping self.colors = colors self.direction_map = direction_map self.domains = domains self.nonce = None self.color_map = {} def generate_challenge(self) -> Tuple[str, str]: """ Generates a one-round challenge grid. Returns: Tuple containing (grid_html, expected_direction_code) """ self.nonce = generate_nonce() entropy = generate_entropy_layers(self.nonce, 1)[0] # Build combined alphabet from all domains alphabet = "" for domain_chars in self.domains.values(): alphabet += domain_chars alphabet = ''.join(set(alphabet)) # Remove duplicates # Create rotated alphabet based on entropy offset = entropy % len(alphabet) rotated = alphabet[offset:] + alphabet[:offset] # Create color mapping self.color_map = {rotated[i]: self.colors[i % len(self.colors)] for i in range(len(rotated))} # Determine expected solution assigned_color = self.color_map.get(self.secret, None) if assigned_color is None: expected = "S" # Skip if secret character not in grid else: direction = self.direction_mapping.get(assigned_color, "Skip") expected = self.direction_map[direction] # Generate the grid HTML grid_html = self._generate_grid_html() return grid_html, expected def _generate_grid_html(self) -> str: """Generate HTML for the challenge grid.""" chars_by_color = defaultdict(list) for ch, color in self.color_map.items(): chars_by_color[color].append(ch) grid_html = """

🎯 One-Round Authentication

Find your secret character and note its color!

""" color_hex_map = {"red": "#FF0000", "green": "#00AA00", "blue": "#0066FF", "yellow": "#FFD700"} for color in self.colors: chars = chars_by_color[color] if chars: grid_html += f'
{color.upper()}: ' for char in chars: grid_html += f'{char} ' grid_html += '
' grid_html += '
' return grid_html def verify_solution(self, user_input: str, expected: str) -> bool: """ Verify if the user's solution matches the expected direction. Args: user_input: User's input direction code ("U", "D", "L", "R", "S") expected: Expected direction code Returns: True if the authentication is successful, False otherwise """ return user_input == expected def run_one_round_authentication(secret: str, direction_mapping: Dict[str, str], colors: List[str], direction_map: Dict[str, str], domains: Dict[str, str]) -> Tuple[str, str]: """ Helper function to run a single round of authentication. Args: secret: The user's secret character direction_mapping: Mapping of colors to directions colors: List of available colors direction_map: Mapping of direction names to codes domains: Available character domains Returns: Tuple containing (grid_html, expected_direction_code) """ verifier = OneRoundVerifier(secret, direction_mapping, colors, direction_map, domains) return verifier.generate_challenge()