index.jsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import React, { useState, useEffect, useMemo } from "react";
  2. import styles from "./styles.module.css";
  3. // A helper function to perform SHA-256 hashing.
  4. // It takes a string, encodes it, hashes it, and returns a hex string.
  5. async function sha256(message) {
  6. try {
  7. const msgBuffer = new TextEncoder().encode(message);
  8. const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
  9. const hashArray = Array.from(new Uint8Array(hashBuffer));
  10. const hashHex = hashArray
  11. .map((b) => b.toString(16).padStart(2, "0"))
  12. .join("");
  13. return hashHex;
  14. } catch (error) {
  15. console.error("Hashing failed:", error);
  16. return "Error hashing data";
  17. }
  18. }
  19. // Generates a random hex string of a given byte length
  20. const generateRandomHex = (bytes = 16) => {
  21. const buffer = new Uint8Array(bytes);
  22. crypto.getRandomValues(buffer);
  23. return Array.from(buffer)
  24. .map((byte) => byte.toString(16).padStart(2, "0"))
  25. .join("");
  26. };
  27. // Icon components for better visual feedback
  28. const CheckIcon = () => (
  29. <svg
  30. xmlns="http://www.w3.org/2000/svg"
  31. className={styles.iconGreen}
  32. fill="none"
  33. viewBox="0 0 24 24"
  34. stroke="currentColor"
  35. >
  36. <path
  37. strokeLinecap="round"
  38. strokeLinejoin="round"
  39. strokeWidth={2}
  40. d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
  41. />
  42. </svg>
  43. );
  44. const XCircleIcon = () => (
  45. <svg
  46. xmlns="http://www.w3.org/2000/svg"
  47. className={styles.iconRed}
  48. fill="none"
  49. viewBox="0 0 24 24"
  50. stroke="currentColor"
  51. >
  52. <path
  53. strokeLinecap="round"
  54. strokeLinejoin="round"
  55. strokeWidth={2}
  56. d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
  57. />
  58. </svg>
  59. );
  60. // Main Application Component
  61. export default function App() {
  62. // State for the challenge, initialized with a random 16-byte hex string.
  63. const [challenge, setChallenge] = useState(() => generateRandomHex(16));
  64. // State for the nonce, which is the variable we can change
  65. const [nonce, setNonce] = useState(0);
  66. // State to store the resulting hash
  67. const [hash, setHash] = useState("");
  68. // A flag to indicate if the current hash is the "winning" one
  69. const [isMining, setIsMining] = useState(false);
  70. const [isFound, setIsFound] = useState(false);
  71. // The mining difficulty, i.e., the required number of leading zeros
  72. const difficulty = "00";
  73. // Memoize the combined data to avoid recalculating on every render
  74. const combinedData = useMemo(
  75. () => `${challenge}${nonce}`,
  76. [challenge, nonce],
  77. );
  78. // This effect hook recalculates the hash whenever the combinedData changes.
  79. useEffect(() => {
  80. let isMounted = true;
  81. const calculateHash = async () => {
  82. const calculatedHash = await sha256(combinedData);
  83. if (isMounted) {
  84. setHash(calculatedHash);
  85. setIsFound(calculatedHash.startsWith(difficulty));
  86. }
  87. };
  88. calculateHash();
  89. return () => {
  90. isMounted = false;
  91. };
  92. }, [combinedData, difficulty]);
  93. // This effect handles the automatic mining process
  94. useEffect(() => {
  95. if (!isMining) return;
  96. let miningNonce = nonce;
  97. let continueMining = true;
  98. const mine = async () => {
  99. while (continueMining) {
  100. const currentData = `${challenge}${miningNonce}`;
  101. const currentHash = await sha256(currentData);
  102. if (currentHash.startsWith(difficulty)) {
  103. setNonce(miningNonce);
  104. setIsMining(false);
  105. break;
  106. }
  107. miningNonce++;
  108. // Update the UI periodically to avoid freezing the browser
  109. if (miningNonce % 100 === 0) {
  110. setNonce(miningNonce);
  111. await new Promise((resolve) => setTimeout(resolve, 0)); // Yield to the browser
  112. }
  113. }
  114. };
  115. mine();
  116. return () => {
  117. continueMining = false;
  118. };
  119. }, [isMining, challenge, nonce, difficulty]);
  120. const handleMineClick = () => {
  121. setIsMining(true);
  122. };
  123. const handleStopClick = () => {
  124. setIsMining(false);
  125. };
  126. const handleResetClick = () => {
  127. setIsMining(false);
  128. setNonce(0);
  129. };
  130. const handleNewChallengeClick = () => {
  131. setIsMining(false);
  132. setChallenge(generateRandomHex(16));
  133. setNonce(0);
  134. };
  135. // Helper to render the hash with colored leading characters
  136. const renderHash = () => {
  137. if (!hash) return <span>...</span>;
  138. const prefix = hash.substring(0, difficulty.length);
  139. const suffix = hash.substring(difficulty.length);
  140. const prefixColor = isFound ? styles.hashPrefixGreen : styles.hashPrefixRed;
  141. return (
  142. <>
  143. <span className={`${prefixColor} ${styles.hashPrefix}`}>{prefix}</span>
  144. <span className={styles.hashSuffix}>{suffix}</span>
  145. </>
  146. );
  147. };
  148. return (
  149. <div className={styles.container}>
  150. <div className={styles.innerContainer}>
  151. <div className={styles.grid}>
  152. {/* Challenge Block */}
  153. <div className={styles.block}>
  154. <h2 className={styles.blockTitle}>1. Challenge</h2>
  155. <p className={styles.challengeText}>{challenge}</p>
  156. </div>
  157. {/* Nonce Control Block */}
  158. <div className={styles.block}>
  159. <h2 className={styles.blockTitle}>2. Nonce</h2>
  160. <div className={styles.nonceControls}>
  161. <button
  162. onClick={() => setNonce((n) => n - 1)}
  163. disabled={isMining}
  164. className={styles.nonceButton}
  165. >
  166. <svg
  167. xmlns="http://www.w3.org/2000/svg"
  168. className={styles.iconSmall}
  169. fill="none"
  170. viewBox="0 0 24 24"
  171. stroke="currentColor"
  172. >
  173. <path
  174. strokeLinecap="round"
  175. strokeLinejoin="round"
  176. strokeWidth={2}
  177. d="M20 12H4"
  178. />
  179. </svg>
  180. </button>
  181. <span className={styles.nonceValue}>{nonce}</span>
  182. <button
  183. onClick={() => setNonce((n) => n + 1)}
  184. disabled={isMining}
  185. className={styles.nonceButton}
  186. >
  187. <svg
  188. xmlns="http://www.w3.org/2000/svg"
  189. className={styles.iconSmall}
  190. fill="none"
  191. viewBox="0 0 24 24"
  192. stroke="currentColor"
  193. >
  194. <path
  195. strokeLinecap="round"
  196. strokeLinejoin="round"
  197. strokeWidth={2}
  198. d="M12 4v16m8-8H4"
  199. />
  200. </svg>
  201. </button>
  202. </div>
  203. </div>
  204. {/* Combined Data Block */}
  205. <div className={styles.block}>
  206. <h2 className={styles.blockTitle}>3. Combined Data</h2>
  207. <p className={styles.combinedDataText}>{combinedData}</p>
  208. </div>
  209. </div>
  210. {/* Arrow pointing down */}
  211. <div className={styles.arrowContainer}>
  212. <svg
  213. xmlns="http://www.w3.org/2000/svg"
  214. className={styles.iconGray}
  215. fill="none"
  216. viewBox="0 0 24 24"
  217. stroke="currentColor"
  218. >
  219. <path
  220. strokeLinecap="round"
  221. strokeLinejoin="round"
  222. strokeWidth={2}
  223. d="M19 14l-7 7m0 0l-7-7m7 7V3"
  224. />
  225. </svg>
  226. </div>
  227. {/* Hash Output Block */}
  228. <div
  229. className={`${styles.hashContainer} ${isFound ? styles.hashContainerSuccess : styles.hashContainerError}`}
  230. >
  231. <div className={styles.hashContent}>
  232. <div className={styles.hashText}>
  233. <h2 className={styles.blockTitle}>4. Resulting Hash (SHA-256)</h2>
  234. <p className={styles.hashValue}>{renderHash()}</p>
  235. </div>
  236. <div className={styles.hashIcon}>
  237. {isFound ? <CheckIcon /> : <XCircleIcon />}
  238. </div>
  239. </div>
  240. </div>
  241. {/* Mining Controls */}
  242. <div className={styles.buttonContainer}>
  243. {!isMining ? (
  244. <button
  245. onClick={handleMineClick}
  246. className={`${styles.button} ${styles.buttonCyan}`}
  247. >
  248. Auto-Mine
  249. </button>
  250. ) : (
  251. <button
  252. onClick={handleStopClick}
  253. className={`${styles.button} ${styles.buttonYellow}`}
  254. >
  255. Stop Mining
  256. </button>
  257. )}
  258. <button
  259. onClick={handleNewChallengeClick}
  260. className={`${styles.button} ${styles.buttonIndigo}`}
  261. >
  262. New Challenge
  263. </button>
  264. <button
  265. onClick={handleResetClick}
  266. className={`${styles.button} ${styles.buttonGray}`}
  267. >
  268. Reset Nonce
  269. </button>
  270. </div>
  271. </div>
  272. </div>
  273. );
  274. }