import React, { useState, useEffect, useMemo } from 'react'; import styles from './styles.module.css'; // A helper function to perform SHA-256 hashing. // It takes a string, encodes it, hashes it, and returns a hex string. async function sha256(message) { try { const msgBuffer = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } catch (error) { console.error("Hashing failed:", error); return "Error hashing data"; } } // Generates a random hex string of a given byte length const generateRandomHex = (bytes = 16) => { const buffer = new Uint8Array(bytes); crypto.getRandomValues(buffer); return Array.from(buffer) .map(byte => byte.toString(16).padStart(2, '0')) .join(''); }; // Icon components for better visual feedback const CheckIcon = () => ( ); const XCircleIcon = () => ( ); // Main Application Component export default function App() { // State for the challenge, initialized with a random 16-byte hex string. const [challenge, setChallenge] = useState(() => generateRandomHex(16)); // State for the nonce, which is the variable we can change const [nonce, setNonce] = useState(0); // State to store the resulting hash const [hash, setHash] = useState(''); // A flag to indicate if the current hash is the "winning" one const [isMining, setIsMining] = useState(false); const [isFound, setIsFound] = useState(false); // The mining difficulty, i.e., the required number of leading zeros const difficulty = "00"; // Memoize the combined data to avoid recalculating on every render const combinedData = useMemo(() => `${challenge}${nonce}`, [challenge, nonce]); // This effect hook recalculates the hash whenever the combinedData changes. useEffect(() => { let isMounted = true; const calculateHash = async () => { const calculatedHash = await sha256(combinedData); if (isMounted) { setHash(calculatedHash); setIsFound(calculatedHash.startsWith(difficulty)); } }; calculateHash(); return () => { isMounted = false; }; }, [combinedData, difficulty]); // This effect handles the automatic mining process useEffect(() => { if (!isMining) return; let miningNonce = nonce; let continueMining = true; const mine = async () => { while (continueMining) { const currentData = `${challenge}${miningNonce}`; const currentHash = await sha256(currentData); if (currentHash.startsWith(difficulty)) { setNonce(miningNonce); setIsMining(false); break; } miningNonce++; // Update the UI periodically to avoid freezing the browser if (miningNonce % 100 === 0) { setNonce(miningNonce); await new Promise(resolve => setTimeout(resolve, 0)); // Yield to the browser } } }; mine(); return () => { continueMining = false; } }, [isMining, challenge, nonce, difficulty]); const handleMineClick = () => { setIsMining(true); } const handleStopClick = () => { setIsMining(false); } const handleResetClick = () => { setIsMining(false); setNonce(0); } const handleNewChallengeClick = () => { setIsMining(false); setChallenge(generateRandomHex(16)); setNonce(0); } // Helper to render the hash with colored leading characters const renderHash = () => { if (!hash) return ...; const prefix = hash.substring(0, difficulty.length); const suffix = hash.substring(difficulty.length); const prefixColor = isFound ? styles.hashPrefixGreen : styles.hashPrefixRed; return ( <> {prefix} {suffix} > ); }; return (
{challenge}
{combinedData}
{renderHash()}