function gcd(a, b) { let x = Math.abs(a); let y = Math.abs(b); while (y !== 0) { const t = y; y = x % y; x = t; } return x; } function modPow(base, exp, mod) { let result = 1; let b = base % mod; let e = exp; while (e > 0) { if (e % 2 === 1) { result = (result * b) % mod; } b = (b * b) % mod; e = Math.floor(e / 2); } return result; } function modInverse(e, phi) { let oldR = e; let r = phi; let oldS = 1; let s = 0; while (r !== 0) { const q = Math.floor(oldR / r); [oldR, r] = [r, oldR - q * r]; [oldS, s] = [s, oldS - q * s]; } if (oldR !== 1) { return null; } return ((oldS % phi) + phi) % phi; } function isPrime(n) { if (n < 2) { return false; } for (let i = 2; i * i <= n; i += 1) { if (n % i === 0) { return false; } } return true; } const translations = { en: { heroTag: "Cryptography Trainer", heroTitle: "RSA Study Coach", heroCopy: "Step through RSA from intuition to full key generation, then test yourself.", languageLabel: "Language", roadmapTitle: "Learning Path", nextStep: "Mark step as understood", labTitle: "RSA Lab", runLab: "Run RSA walk-through", randomExample: "Random small example", quizTitle: "Quick Check", quizIntro: "What must hold for a valid public exponent e?" }, de: { heroTag: "Kryptografie-Trainer", heroTitle: "RSA-Lerncoach", heroCopy: "Gehe RSA Schritt für Schritt durch und überprüfe danach dein Verständnis.", languageLabel: "Sprache", roadmapTitle: "Lernpfad", nextStep: "Schritt als verstanden markieren", labTitle: "RSA-Labor", runLab: "RSA-Ablauf starten", randomExample: "Zufallsbeispiel", quizTitle: "Kurztest", quizIntro: "Was muss für einen gültigen öffentlichen Exponenten e gelten?" }, es: { heroTag: "Entrenador de criptografía", heroTitle: "Tutor de RSA", heroCopy: "Avanza por RSA paso a paso y comprueba tu comprensión.", languageLabel: "Idioma", roadmapTitle: "Ruta de aprendizaje", nextStep: "Marcar paso como entendido", labTitle: "Laboratorio RSA", runLab: "Ejecutar recorrido RSA", randomExample: "Ejemplo aleatorio", quizTitle: "Comprobación rápida", quizIntro: "¿Qué debe cumplirse para un exponente público e válido?" }, fr: { heroTag: "Entraîneur de cryptographie", heroTitle: "Coach RSA", heroCopy: "Parcours RSA étape par étape puis teste ta compréhension.", languageLabel: "Langue", roadmapTitle: "Parcours d'apprentissage", nextStep: "Marquer l'étape comme comprise", labTitle: "Laboratoire RSA", runLab: "Lancer le parcours RSA", randomExample: "Exemple aléatoire", quizTitle: "Vérification rapide", quizIntro: "Que faut-il pour qu'un exposant public e soit valide ?" } }; const steps = [ "Why modular arithmetic allows reversible operations", "How two primes p and q produce n = p * q", "Why phi(n) = (p - 1)(q - 1) matters", "How to choose e with gcd(e, phi(n)) = 1", "How to compute d so e * d ≡ 1 (mod phi(n))", "Why c = m^e mod n and m = c^d mod n work" ]; let activeStep = 0; const roadmapEl = document.getElementById("roadmap"); const nextStepBtn = document.getElementById("nextStep"); const runLabBtn = document.getElementById("runLab"); const randomBtn = document.getElementById("randomSmall"); const outputEl = document.getElementById("labOutput"); const langEl = document.getElementById("lang"); const quizFeedbackEl = document.getElementById("quizFeedback"); function renderRoadmap() { roadmapEl.innerHTML = ""; steps.forEach((step, index) => { const li = document.createElement("li"); li.textContent = step; if (index < activeStep) { li.classList.add("done"); } roadmapEl.appendChild(li); }); } function runLab() { const p = Number(document.getElementById("p").value); const q = Number(document.getElementById("q").value); const e = Number(document.getElementById("e").value); const m = Number(document.getElementById("m").value); const lines = []; if (!isPrime(p) || !isPrime(q)) { lines.push("Error: p and q must both be prime."); outputEl.textContent = lines.join("\n"); return; } const n = p * q; const phi = (p - 1) * (q - 1); const g = gcd(e, phi); lines.push(`1) Choose primes p=${p}, q=${q}`); lines.push(`2) Compute n = p*q = ${n}`); lines.push(`3) Compute phi(n) = (p-1)(q-1) = ${phi}`); lines.push(`4) Check gcd(e, phi(n)): gcd(${e}, ${phi}) = ${g}`); if (g !== 1) { lines.push("Invalid e: it must be coprime to phi(n). Try another e."); outputEl.textContent = lines.join("\n"); return; } const d = modInverse(e, phi); if (d === null) { lines.push("Could not compute d (mod inverse does not exist)."); outputEl.textContent = lines.join("\n"); return; } lines.push(`5) Compute private exponent d so e*d ≡ 1 mod phi(n): d = ${d}`); if (m < 0 || m >= n) { lines.push(`Message m must satisfy 0 <= m < n (${n}).`); outputEl.textContent = lines.join("\n"); return; } const c = modPow(m, e, n); const recovered = modPow(c, d, n); lines.push(`6) Encrypt c = m^e mod n = ${m}^${e} mod ${n} = ${c}`); lines.push(`7) Decrypt m = c^d mod n = ${c}^${d} mod ${n} = ${recovered}`); lines.push(""); lines.push(`Public key: (n=${n}, e=${e})`); lines.push(`Private key: (n=${n}, d=${d})`); lines.push(recovered === m ? "Result: success, decrypted message matches original." : "Result: mismatch."); outputEl.textContent = lines.join("\n"); } function randomSmallExample() { const primePairs = [ [11, 13], [17, 19], [23, 29], [31, 37] ]; const es = [3, 5, 7, 11, 17]; const pair = primePairs[Math.floor(Math.random() * primePairs.length)]; document.getElementById("p").value = pair[0]; document.getElementById("q").value = pair[1]; const phi = (pair[0] - 1) * (pair[1] - 1); const validE = es.find((candidate) => candidate < phi && gcd(candidate, phi) === 1) || 3; document.getElementById("e").value = validE; const n = pair[0] * pair[1]; document.getElementById("m").value = Math.floor(Math.random() * (n - 1)) + 1; runLab(); } function applyLanguage(lang) { const map = translations[lang] || translations.en; document.querySelectorAll("[data-i18n]").forEach((el) => { const key = el.dataset.i18n; if (map[key]) { el.textContent = map[key]; } }); } nextStepBtn.addEventListener("click", () => { if (activeStep < steps.length) { activeStep += 1; renderRoadmap(); } }); runLabBtn.addEventListener("click", runLab); randomBtn.addEventListener("click", randomSmallExample); langEl.addEventListener("change", (event) => applyLanguage(event.target.value)); document.querySelectorAll(".quiz-option").forEach((button) => { button.addEventListener("click", () => { const isCorrect = button.dataset.correct === "true"; quizFeedbackEl.textContent = isCorrect ? "Correct: e must be coprime to phi(n)." : "Not quite. Focus on coprimality with phi(n)."; quizFeedbackEl.className = `feedback ${isCorrect ? "good" : "bad"}`; }); }); renderRoadmap(); applyLanguage("en"); runLab();