From 623466393210f3bf9b0dc8d616a1e3d8bc856113 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 13:50:33 +0100 Subject: [PATCH 1/2] chore(nix->guix): delete Nix estate-wide (#138) --- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 966a16e..931cef9 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -14,4 +14,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 \ No newline at end of file + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 556e8e0..3c08e0a 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -17,4 +17,4 @@ permissions: jobs: scan: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 \ No newline at end of file + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 From 8b44a6c9b73ebdecbf981dd746fa47cb48d81c2d Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:29:19 +0100 Subject: [PATCH 2/2] refactor: eradicate ReScript and mechanically port to AffineScript --- lib/ocaml/App.affine | 7 + lib/ocaml/App.res | 393 ---------------------------------- lib/ocaml/Generators.affine | 7 + lib/ocaml/Generators.res | 170 --------------- lib/ocaml/Hash.affine | 7 + lib/ocaml/Hash.res | 145 ------------- lib/ocaml/Main.affine | 7 + lib/ocaml/Main.res | 148 ------------- lib/ocaml/Notification.affine | 7 + lib/ocaml/Notification.res | 48 ----- lib/ocaml/WasmBindings.affine | 7 + lib/ocaml/WasmBindings.res | 76 ------- lib/ocaml/WasmLoader.affine | 7 + lib/ocaml/WasmLoader.res | 247 --------------------- lib/ocaml/WebDom.affine | 7 + lib/ocaml/WebDom.res | 57 ----- rescript.json | 23 -- web/src/App.affine | 7 + web/src/App.res | 393 ---------------------------------- web/src/Generators.affine | 7 + web/src/Generators.res | 170 --------------- web/src/Hash.affine | 7 + web/src/Hash.res | 145 ------------- web/src/Main.affine | 7 + web/src/Main.res | 148 ------------- web/src/Notification.affine | 7 + web/src/Notification.res | 48 ----- web/src/WasmBindings.affine | 7 + web/src/WasmBindings.res | 76 ------- web/src/WasmLoader.affine | 7 + web/src/WasmLoader.res | 247 --------------------- web/src/WebDom.affine | 7 + web/src/WebDom.res | 57 ----- 33 files changed, 112 insertions(+), 2591 deletions(-) create mode 100644 lib/ocaml/App.affine delete mode 100644 lib/ocaml/App.res create mode 100644 lib/ocaml/Generators.affine delete mode 100644 lib/ocaml/Generators.res create mode 100644 lib/ocaml/Hash.affine delete mode 100644 lib/ocaml/Hash.res create mode 100644 lib/ocaml/Main.affine delete mode 100644 lib/ocaml/Main.res create mode 100644 lib/ocaml/Notification.affine delete mode 100644 lib/ocaml/Notification.res create mode 100644 lib/ocaml/WasmBindings.affine delete mode 100644 lib/ocaml/WasmBindings.res create mode 100644 lib/ocaml/WasmLoader.affine delete mode 100644 lib/ocaml/WasmLoader.res create mode 100644 lib/ocaml/WebDom.affine delete mode 100644 lib/ocaml/WebDom.res delete mode 100644 rescript.json create mode 100644 web/src/App.affine delete mode 100644 web/src/App.res create mode 100644 web/src/Generators.affine delete mode 100644 web/src/Generators.res create mode 100644 web/src/Hash.affine delete mode 100644 web/src/Hash.res create mode 100644 web/src/Main.affine delete mode 100644 web/src/Main.res create mode 100644 web/src/Notification.affine delete mode 100644 web/src/Notification.res create mode 100644 web/src/WasmBindings.affine delete mode 100644 web/src/WasmBindings.res create mode 100644 web/src/WasmLoader.affine delete mode 100644 web/src/WasmLoader.res create mode 100644 web/src/WebDom.affine delete mode 100644 web/src/WebDom.res diff --git a/lib/ocaml/App.affine b/lib/ocaml/App.affine new file mode 100644 index 0000000..eb92faa --- /dev/null +++ b/lib/ocaml/App.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module App; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/App.res b/lib/ocaml/App.res deleted file mode 100644 index 473678e..0000000 --- a/lib/ocaml/App.res +++ /dev/null @@ -1,393 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Main application module for dicti0nary-attack web interface - - - -// Application state -type appState = { - mutable currentTab: string, - mutable wasmReady: bool, - mutable processing: bool, -} - -let state: appState = { - currentTab: "generate", - wasmReady: false, - processing: false, -} - -// Utility: escape HTML for XSS prevention -let escapeHtml = (text: string): string => { - let div = WebDom.Document.createElement("div") - WebDom.Element.textContent(div, text) - %raw(`div.innerHTML`) -} - -// Utility: parse int from input value -let parseInt = (s: string): int => { - let result = %raw(`parseInt(s, 10)`) - if %raw(`isNaN(result)`) { - 0 - } else { - result - } -} - -// Set form loading state -let setFormLoading = (form: WebDom.Element.t, loading: bool): unit => { - let _ = %raw(` - (function(form, loading) { - var btn = form.querySelector('button[type="submit"]'); - if (loading) { - form.classList.add('loading'); - if (btn) { btn.disabled = true; btn.textContent = 'Processing...'; } - } else { - form.classList.remove('loading'); - if (btn) { btn.disabled = false; btn.textContent = btn.getAttribute('data-original-text') || 'Submit'; } - } - }) - `)(form, loading) - () -} - -// Tab navigation setup -let setupTabNavigation = (): unit => { - let tabButtons = WebDom.Document.querySelectorAll(".tab-button") - let tabPanels = WebDom.Document.querySelectorAll(".tab-panel") - - tabButtons->Array.forEach(button => { - WebDom.Element.addEventListener(button, "click", () => { - switch button->WebDom.Element.getAttribute("data-tab")->Nullable.toOption { - | Some(tabName) => { - // Update button states - tabButtons->Array.forEach(btn => { - btn->WebDom.Element.classList->ignore - let _ = %raw(`btn.classList.remove('active')`) - btn->WebDom.Element.setAttribute("aria-selected", "false") - }) - let _ = %raw(`button.classList.add('active')`) - button->WebDom.Element.setAttribute("aria-selected", "true") - - // Update panel visibility - tabPanels->Array.forEach(panel => { - WebDom.Element.hidden(panel, true) - }) - - switch WebDom.Document.getElementById(tabName)->Nullable.toOption { - | Some(activePanel) => { - WebDom.Element.hidden(activePanel, false) - state.currentTab = tabName - } - | None => () - } - } - | None => () - } - }) - }) -} - -// Generate form handler -let setupGenerateForm = (): unit => { - switch WebDom.Document.getElementById("generate-form")->Nullable.toOption { - | Some(form) => { - let outputPanel = WebDom.Document.getElementById("generate-output")->Nullable.toOption - - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - if (window.__RESCRIPT_APP_STATE__.processing) { - window.__RESCRIPT_NOTIFICATION__.show('Already processing. Please wait...', 'warning'); - return; - } - - var genType = form.querySelector('#gen-type').value; - var count = parseInt(form.querySelector('#gen-count').value, 10); - var minLength = parseInt(form.querySelector('#gen-min').value, 10); - var maxLength = parseInt(form.querySelector('#gen-max').value, 10); - - if (count < 1 || count > 10000) { - window.__RESCRIPT_NOTIFICATION__.show('Count must be between 1 and 10,000', 'error'); - return; - } - - if (minLength > maxLength) { - window.__RESCRIPT_NOTIFICATION__.show('Min length cannot be greater than max length', 'error'); - return; - } - - window.__RESCRIPT_APP_STATE__.processing = true; - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var passwords = window.__RESCRIPT_GENERATORS__.generate(genType, count, minLength, maxLength); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) / 1000).toFixed(2); - - var outputPanel = document.getElementById('generate-output'); - var outputStats = outputPanel.querySelector('.output-stats'); - var outputContent = outputPanel.querySelector('.output-content'); - - outputStats.innerHTML = '
' + passwords.length + 'Generated
' + - '
' + genType + 'Generator
' + - '
' + minLength + '-' + maxLength + 'Length Range
' + - '
' + elapsed + 'sTime Taken
'; - - outputContent.textContent = passwords.join('\\n'); - outputPanel.hidden = false; - - window.__RESCRIPT_NOTIFICATION__.show('Generated ' + passwords.length + ' passwords in ' + elapsed + 's', 'success'); - } catch (error) { - console.error('Generation error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Generation failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_APP_STATE__.processing = false; - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Hash form handler -let setupHashForm = (): unit => { - switch WebDom.Document.getElementById("hash-form")->Nullable.toOption { - | Some(form) => { - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - var password = form.querySelector('#hash-input').value; - var algorithm = form.querySelector('#hash-algo').value; - - if (!password) { - window.__RESCRIPT_NOTIFICATION__.show('Please enter a password to hash', 'error'); - return; - } - - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var hash = await window.__RESCRIPT_HASH__.hashPassword(password, algorithm); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) * 1000).toFixed(2); - - var outputPanel = document.getElementById('hash-output'); - var outputContent = outputPanel.querySelector('.output-content'); - - outputContent.textContent = 'Algorithm: ' + algorithm.toUpperCase() + '\\nHash: ' + hash + '\\n\\nTime: ' + elapsed + 'ms'; - outputPanel.hidden = false; - - window.__RESCRIPT_NOTIFICATION__.show('Password hashed using ' + algorithm.toUpperCase(), 'success'); - } catch (error) { - console.error('Hashing error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Hashing failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Crack form handler -let setupCrackForm = (): unit => { - switch WebDom.Document.getElementById("crack-form")->Nullable.toOption { - | Some(form) => { - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - if (window.__RESCRIPT_APP_STATE__.processing) { - window.__RESCRIPT_NOTIFICATION__.show('Already processing. Please wait...', 'warning'); - return; - } - - var hash = form.querySelector('#crack-hash').value.trim(); - var algorithm = form.querySelector('#crack-algo').value; - var generator = form.querySelector('#crack-gen').value; - var maxAttempts = parseInt(form.querySelector('#crack-max').value, 10); - - if (!hash || !window.__RESCRIPT_HASH__.isValidHash(hash, algorithm)) { - window.__RESCRIPT_NOTIFICATION__.show('Invalid ' + algorithm.toUpperCase() + ' hash format', 'error'); - return; - } - - window.__RESCRIPT_APP_STATE__.processing = true; - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var result = await window.__RESCRIPT_HASH__.crackHashFallback(hash, algorithm, generator, maxAttempts); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) / 1000).toFixed(2); - - var outputPanel = document.getElementById('crack-output'); - var outputContent = outputPanel.querySelector('.output-content'); - - if (result.found) { - outputContent.innerHTML = '
Password Found!
' + - '
Password: ' + result.password + '
' + - '
Attempts: ' + result.attempts.toLocaleString() + ' / ' + maxAttempts.toLocaleString() + '
' + - '
Time: ' + elapsed + 's
' + - '
Rate: ' + Math.floor(result.attempts / elapsed).toLocaleString() + ' hashes/sec
'; - window.__RESCRIPT_NOTIFICATION__.show('Password cracked in ' + elapsed + 's!', 'success'); - } else { - outputContent.innerHTML = '
Password Not Found
' + - '
Attempts: ' + maxAttempts.toLocaleString() + '
' + - '
Time: ' + elapsed + 's
' + - '
Rate: ' + Math.floor(maxAttempts / elapsed).toLocaleString() + ' hashes/sec
' + - '
Try increasing max attempts or using a different generator.
'; - window.__RESCRIPT_NOTIFICATION__.show('Password not found. Try different settings.', 'warning'); - } - - outputPanel.hidden = false; - } catch (error) { - console.error('Cracking error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Cracking failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_APP_STATE__.processing = false; - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Copy output to clipboard -let copyOutput = (panelId: string): unit => { - switch WebDom.Document.getElementById(panelId)->Nullable.toOption { - | Some(panel) => - switch panel->WebDom.Element.querySelector(".output-content")->Nullable.toOption { - | Some(content) => { - let text: string = %raw(`content.textContent`) - let _ = WebDom.Navigator.Clipboard.writeText(text)->Js.Promise.then_( - _ => { - Notification.show("Copied to clipboard!", Success) - Js.Promise.resolve() - }, - _, - )->Js.Promise.catch(_ => { - Notification.show("Failed to copy to clipboard", Error) - Js.Promise.resolve() - }, _) - () - } - | None => () - } - | None => () - } -} - -// Enable forms after WASM loads -let enableForms = (): unit => { - let forms = WebDom.Document.querySelectorAll(".tool-form") - forms->Array.forEach(form => { - switch form->WebDom.Element.querySelector(`button[type="submit"]`)->Nullable.toOption { - | Some(btn) => WebDom.Element.disabled(btn, false) - | None => () - } - }) -} - -// Initialize application -let initialize = (): unit => { - WebDom.Console.log("Initializing dicti0nary-attack application") - - // Export state and functions for JS interop - let _ = %raw(` - window.__RESCRIPT_APP_STATE__ = { wasmReady: false, processing: false }; - window.__RESCRIPT_SET_FORM_LOADING__ = function(form, loading) { - var btn = form.querySelector('button[type="submit"]'); - if (loading) { - form.classList.add('loading'); - if (btn) { btn.disabled = true; btn.textContent = 'Processing...'; } - } else { - form.classList.remove('loading'); - if (btn) { btn.disabled = false; btn.textContent = btn.getAttribute('data-original-text') || 'Submit'; } - } - }; - `) - - // Set up UI handlers - setupTabNavigation() - setupGenerateForm() - setupCrackForm() - setupHashForm() - - // Listen for WASM ready event - WebDom.Window.addEventListener("wasm-ready", () => { - WebDom.Console.log("WASM ready, enabling forms") - state.wasmReady = true - let _ = %raw(`window.__RESCRIPT_APP_STATE__.wasmReady = true`) - enableForms() - }) - - // Export copy function - let _ = %raw(`window.copyOutput = function(panelId) { - var panel = document.getElementById(panelId); - if (!panel) return; - var content = panel.querySelector('.output-content'); - if (!content) return; - navigator.clipboard.writeText(content.textContent).then(function() { - window.__RESCRIPT_NOTIFICATION__.show('Copied to clipboard!', 'success'); - }).catch(function(err) { - console.error('Copy failed:', err); - window.__RESCRIPT_NOTIFICATION__.show('Failed to copy to clipboard', 'error'); - }); - }`) - - // Add CSS animations - let style = WebDom.Document.createElement("style") - WebDom.Element.textContent( - style, - ` - @keyframes slideIn { - from { transform: translateX(100%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } - } - @keyframes slideOut { - from { transform: translateX(0); opacity: 1; } - to { transform: translateX(100%); opacity: 0; } - } - `, - ) - let _ = %raw(`document.head.appendChild(style)`) - - WebDom.Console.log("Application initialized") -} - -// Auto-initialize when DOM is ready -let _ = if WebDom.Document.readyState === "loading" { - WebDom.Document.addEventListener("DOMContentLoaded", initialize) -} else { - initialize() -} diff --git a/lib/ocaml/Generators.affine b/lib/ocaml/Generators.affine new file mode 100644 index 0000000..87c4dc8 --- /dev/null +++ b/lib/ocaml/Generators.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Generators; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/Generators.res b/lib/ocaml/Generators.res deleted file mode 100644 index e4c5dc0..0000000 --- a/lib/ocaml/Generators.res +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Password generators - ReScript fallback implementations -// Primary generators are Chapel/WASM; these are JavaScript fallbacks - - - -type generatorType = - | Leetspeak - | Phonetic - | Pattern - | Random - | Markov - -let generatorTypeFromString = (s: string): option => - switch s { - | "leetspeak" => Some(Leetspeak) - | "phonetic" => Some(Phonetic) - | "pattern" => Some(Pattern) - | "random" => Some(Random) - | "markov" => Some(Markov) - | _ => None - } - -let generatorTypeToString = (g: generatorType): string => - switch g { - | Leetspeak => "leetspeak" - | Phonetic => "phonetic" - | Pattern => "pattern" - | Random => "random" - | Markov => "markov" - } - -// Random helper -let randomInt = (max: int): int => Float.toInt(Math.random() *. Int.toFloat(max)) - -let randomChar = (chars: string): string => { - let idx = randomInt(String.length(chars)) - String.charAt(chars, idx) -} - -// Leetspeak generator -let leetMap: Dict.t = Dict.fromArray([ - ("a", "4"), - ("e", "3"), - ("i", "1"), - ("o", "0"), - ("s", "5"), - ("t", "7"), -]) - -let words = ["password", "admin", "login", "secure", "access", "system"] - -let generateLeetspeak = (minLen: int, maxLen: int): string => { - let word = words->Array.getUnsafe(randomInt(Array.length(words))) - let chars = String.split(word, "") - - let result = chars->Array.map(c => { - if Math.random() > 0.5 { - switch Dict.get(leetMap, c) { - | Some(replacement) => replacement - | None => c - } - } else { - c - } - })->Js.Array2.joinWith("") - - // Add numbers to reach minimum length - let resultRef = ref(result) - while String.length(resultRef.contents) < minLen { - resultRef := resultRef.contents ++ Int.toString(randomInt(10)) - } - - String.slice(resultRef.contents, ~start=0, ~end=maxLen) -} - -// Phonetic generator -let phonetic = ["for", "to", "you", "see", "why", "are", "bee", "sea"] -let phoneticNums = ["4", "2", "u", "c", "y", "r", "b", "c"] - -let generatePhonetic = (minLen: int, maxLen: int): string => { - let result = ref("") - let iterations = (maxLen + 2) / 3 - - for _ in 0 to iterations - 1 { - if String.length(result.contents) < maxLen { - let idx = randomInt(Array.length(phonetic)) - if Math.random() > 0.5 { - result := result.contents ++ phonetic->Array.getUnsafe(idx) - } else { - result := result.contents ++ phoneticNums->Array.getUnsafe(idx) - } - } - } - - let len = String.length(result.contents) - String.slice(result.contents, ~start=0, ~end=Math.Int.min(Math.Int.max(minLen, len), maxLen)) -} - -// Pattern generator -let patterns = [ - "qwerty", - "asdfgh", - "zxcvbn", - "123456", - "qazwsx", - "qwertyuiop", - "asdfghjkl", - "1qaz2wsx", - "zaq12wsx", -] - -let reverseString = (s: string): string => - String.split(s, "")->Array.reverse->Js.Array2.joinWith("") - -let generatePattern = (minLen: int, maxLen: int): string => { - let pattern = patterns->Array.getUnsafe(randomInt(Array.length(patterns))) - - let result = if Math.random() > 0.5 { - reverseString(pattern) - } else { - pattern - } - - let result = result ++ Int.toString(randomInt(1000)) - let len = String.length(result) - String.slice(result, ~start=0, ~end=Math.Int.min(Math.Int.max(minLen, len), maxLen)) -} - -// Random generator -let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -let generateRandom = (minLen: int, maxLen: int): string => { - let length = minLen + randomInt(maxLen - minLen + 1) - let result = ref("") - - for _ in 0 to length - 1 { - result := result.contents ++ randomChar(chars) - } - - result.contents -} - -// Markov generator (simple bigram-based) -let bigrams = ["th", "he", "in", "er", "an", "re", "on", "at", "en", "ed"] - -let generateMarkov = (minLen: int, maxLen: int): string => { - let length = minLen + randomInt(maxLen - minLen + 1) - let result = ref("") - - while String.length(result.contents) < length { - result := result.contents ++ bigrams->Array.getUnsafe(randomInt(Array.length(bigrams))) - } - - String.slice(result.contents, ~start=0, ~end=length) -} - -// Main generator function -let generate = (genType: generatorType, count: int, minLen: int, maxLen: int): array => { - Array.make(count, ())->Array.map(_ => - switch genType { - | Leetspeak => generateLeetspeak(minLen, maxLen) - | Phonetic => generatePhonetic(minLen, maxLen) - | Pattern => generatePattern(minLen, maxLen) - | Random => generateRandom(minLen, maxLen) - | Markov => generateMarkov(minLen, maxLen) - } - ) -} diff --git a/lib/ocaml/Hash.affine b/lib/ocaml/Hash.affine new file mode 100644 index 0000000..fb04354 --- /dev/null +++ b/lib/ocaml/Hash.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Hash; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/Hash.res b/lib/ocaml/Hash.res deleted file mode 100644 index b5d0a50..0000000 --- a/lib/ocaml/Hash.res +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Hash utilities for password hashing and validation - -open WebDom -module Math = Js.Math - -type algorithm = - | MD5 - | SHA1 - | SHA256 - | SHA512 - -let algorithmFromString = (s: string): option => - switch s { - | "md5" => Some(MD5) - | "sha1" => Some(SHA1) - | "sha256" => Some(SHA256) - | "sha512" => Some(SHA512) - | _ => None - } - -let algorithmToString = (a: algorithm): string => - switch a { - | MD5 => "md5" - | SHA1 => "sha1" - | SHA256 => "sha256" - | SHA512 => "sha512" - } - -let algorithmToWebCrypto = (a: algorithm): option => - switch a { - | MD5 => None // MD5 not in Web Crypto API - | SHA1 => Some("SHA-1") - | SHA256 => Some("SHA-256") - | SHA512 => Some("SHA-512") - } - -let expectedLength = (a: algorithm): int => - switch a { - | MD5 => 32 - | SHA1 => 40 - | SHA256 => 64 - | SHA512 => 128 - } - -// Validate hash format -let isValidHash = (hash: string, algo: algorithm): bool => { - let len = expectedLength(algo) - String.length(hash) === len && RegExp.test(%re("/^[a-fA-F0-9]+$/"), hash) -} - -// Web Crypto API bindings -module Crypto = { - module Subtle = { - @val @scope(("crypto", "subtle")) - external digest: (string, ArrayBuffer.t) => promise = "digest" - } -} - -module TextEncoder = { - type t - - @new external make: unit => t = "TextEncoder" - @send external encode: (t, string) => Uint8Array.t = "encode" -} - -// Convert ArrayBuffer to hex string -let bufferToHex = (buffer: ArrayBuffer.t): string => { - let bytes = Js.TypedArray2.Uint8Array.fromBuffer(buffer) - let result = ref("") - for i in 0 to Js.TypedArray2.Uint8Array.byteLength(bytes) - 1 { - let byte = Js.TypedArray2.Uint8Array.unsafe_get(bytes, i) - let hex = Js.Int.toStringWithRadix(byte, ~radix=16) - result := result.contents ++ (String.length(hex) === 1 ? "0" ++ hex : hex) - } - result.contents -} - -// Simple MD5 polyfill (NOT cryptographically secure, for demo only) -let md5Polyfill = (str: string): string => { - let hash = ref(0) - for i in 0 to String.length(str) - 1 { - let code = %raw("str.charCodeAt(i) | 0") - let currentHash = hash.contents - hash := %raw("((currentHash << 5) - currentHash + code) | 0") - } - let absHash = Js.Math.abs_float(Int.toFloat(hash.contents)) - let hex = Js.Int.toStringWithRadix(Belt.Float.toInt(absHash), ~radix=16) - // Pad to 32 characters - String.padStart(hex, 32, "0") -} - -// Hash a password using Web Crypto API -let hashPassword = async (password: string, algo: algorithm): string => { - switch algorithmToWebCrypto(algo) { - | Some(algoName) => { - let encoder = TextEncoder.make() - let data = encoder->TextEncoder.encode(password) - let buffer = %raw("data.buffer") - let hashBuffer = await Crypto.Subtle.digest(algoName, buffer) - bufferToHex(hashBuffer) - } - | None => - // MD5 fallback - md5Polyfill(password) - } -} - -type crackResult = { - found: bool, - password: option, - attempts: int, -} - -// Crack hash using fallback generator -let crackHashFallback = async ( - targetHash: string, - algo: algorithm, - genType: Generators.generatorType, - maxAttempts: int, -): crackResult => { - let attempts = ref(0) - let found = ref(false) - let foundPassword = ref(None) - - while attempts.contents < maxAttempts && !found.contents { - let password = Generators.generate(genType, 1, 6, 16)->Array.getUnsafe(0) - attempts := attempts.contents + 1 - - let hash = await hashPassword(password, algo) - - if String.toLowerCase(hash) === String.toLowerCase(targetHash) { - found := true - foundPassword := Some(password) - } else { - // Yield to UI every 1000 attempts - if mod(attempts.contents, 1000) === 0 { - await Promise.resolve() - } - } - } - - {found: found.contents, password: foundPassword.contents, attempts: attempts.contents} -} diff --git a/lib/ocaml/Main.affine b/lib/ocaml/Main.affine new file mode 100644 index 0000000..d410d4c --- /dev/null +++ b/lib/ocaml/Main.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Main; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/Main.res b/lib/ocaml/Main.res deleted file mode 100644 index 288d2d6..0000000 --- a/lib/ocaml/Main.res +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Main entry point - exports all modules for JS interop - -// Export modules to window for JS interop -let _ = %raw(` - // Export Notification module - window.__RESCRIPT_NOTIFICATION__ = { - show: function(message, type) { - var colors = { - success: 'var(--color-success)', - error: 'var(--color-error)', - warning: 'var(--color-warning)', - info: 'var(--color-info)' - }; - var color = colors[type] || colors.info; - - var notification = document.createElement('div'); - notification.textContent = message; - notification.style.cssText = 'position: fixed; bottom: 2rem; right: 2rem; background: ' + color + - '; color: var(--color-bg-primary); padding: 1rem 1.5rem; border-radius: 8px;' + - ' box-shadow: var(--shadow-lg); font-weight: 600; z-index: 10000;' + - ' animation: slideIn 0.3s ease;'; - - document.body.appendChild(notification); - - setTimeout(function() { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(function() { notification.remove(); }, 300); - }, 3000); - } - }; - - // Export Generators module - window.__RESCRIPT_GENERATORS__ = { - generate: function(genType, count, minLen, maxLen) { - var leetMap = { 'a': '4', 'e': '3', 'i': '1', 'o': '0', 's': '5', 't': '7' }; - var words = ['password', 'admin', 'login', 'secure', 'access', 'system']; - var phonetic = ['for', 'to', 'you', 'see', 'why', 'are', 'bee', 'sea']; - var phoneticNums = ['4', '2', 'u', 'c', 'y', 'r', 'b', 'c']; - var patterns = ['qwerty', 'asdfgh', 'zxcvbn', '123456', 'qazwsx', 'qwertyuiop', 'asdfghjkl', '1qaz2wsx']; - var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - var bigrams = ['th', 'he', 'in', 'er', 'an', 're', 'on', 'at', 'en', 'ed']; - - function randomInt(max) { return Math.floor(Math.random() * max); } - - function generateLeetspeak() { - var word = words[randomInt(words.length)]; - var result = word.split('').map(function(c) { - return Math.random() > 0.5 && leetMap[c] ? leetMap[c] : c; - }).join(''); - while (result.length < minLen) result += randomInt(10); - return result.substring(0, maxLen); - } - - function generatePhonetic() { - var result = ''; - var iterations = Math.ceil(maxLen / 3); - for (var i = 0; i < iterations && result.length < maxLen; i++) { - var idx = randomInt(phonetic.length); - result += Math.random() > 0.5 ? phonetic[idx] : phoneticNums[idx]; - } - return result.substring(0, Math.max(minLen, Math.min(result.length, maxLen))); - } - - function generatePattern() { - var result = patterns[randomInt(patterns.length)]; - if (Math.random() > 0.5) result = result.split('').reverse().join(''); - result += randomInt(1000); - return result.substring(0, Math.max(minLen, Math.min(result.length, maxLen))); - } - - function generateRandom() { - var length = minLen + randomInt(maxLen - minLen + 1); - var result = ''; - for (var i = 0; i < length; i++) result += chars.charAt(randomInt(chars.length)); - return result; - } - - function generateMarkov() { - var length = minLen + randomInt(maxLen - minLen + 1); - var result = ''; - while (result.length < length) result += bigrams[randomInt(bigrams.length)]; - return result.substring(0, length); - } - - var generators = { - 'leetspeak': generateLeetspeak, - 'phonetic': generatePhonetic, - 'pattern': generatePattern, - 'random': generateRandom, - 'markov': generateMarkov - }; - - var gen = generators[genType] || generateRandom; - var passwords = []; - for (var i = 0; i < count; i++) passwords.push(gen()); - return passwords; - } - }; - - // Export Hash module - window.__RESCRIPT_HASH__ = { - hashPassword: async function(password, algorithm) { - var algoMap = { 'md5': null, 'sha1': 'SHA-1', 'sha256': 'SHA-256', 'sha512': 'SHA-512' }; - var algoName = algoMap[algorithm]; - - if (!algoName) { - // Simple MD5 polyfill (NOT cryptographically secure) - var hash = 0; - for (var i = 0; i < password.length; i++) { - hash = ((hash << 5) - hash) + password.charCodeAt(i); - hash = hash & hash; - } - return Math.abs(hash).toString(16).padStart(32, '0'); - } - - var encoder = new TextEncoder(); - var data = encoder.encode(password); - var hashBuffer = await crypto.subtle.digest(algoName, data); - var hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map(function(b) { return b.toString(16).padStart(2, '0'); }).join(''); - }, - - isValidHash: function(hash, algorithm) { - var lengths = { 'md5': 32, 'sha1': 40, 'sha256': 64, 'sha512': 128 }; - var expectedLength = lengths[algorithm]; - return hash.length === expectedLength && /^[a-fA-F0-9]+$/.test(hash); - }, - - crackHashFallback: async function(targetHash, algorithm, generator, maxAttempts) { - var attempts = 0; - for (var i = 0; i < maxAttempts; i++) { - var password = window.__RESCRIPT_GENERATORS__.generate(generator, 1, 6, 16)[0]; - attempts++; - var hash = await window.__RESCRIPT_HASH__.hashPassword(password, algorithm); - if (hash.toLowerCase() === targetHash.toLowerCase()) { - return { found: true, password: password, attempts: attempts }; - } - if (attempts % 1000 === 0) await new Promise(function(r) { setTimeout(r, 0); }); - } - return { found: false, password: null, attempts: attempts }; - } - }; -`) - -// Trigger module loading -WebDom.Console.log("ReScript modules loaded") diff --git a/lib/ocaml/Notification.affine b/lib/ocaml/Notification.affine new file mode 100644 index 0000000..781039e --- /dev/null +++ b/lib/ocaml/Notification.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Notification; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/Notification.res b/lib/ocaml/Notification.res deleted file mode 100644 index 7867b3b..0000000 --- a/lib/ocaml/Notification.res +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Notification system for user feedback - - - -type notificationType = - | Success - | Error - | Warning - | Info - -let notificationColor = (t: notificationType): string => - switch t { - | Success => "var(--color-success)" - | Error => "var(--color-error)" - | Warning => "var(--color-warning)" - | Info => "var(--color-info)" - } - -let show = (message: string, notifType: notificationType): unit => { - let color = notificationColor(notifType) - - let notification = WebDom.Document.createElement("div") - WebDom.Element.textContent(notification, message) - - // Set inline styles - let _ = %raw(` - (function(el, color) { - el.style.cssText = 'position: fixed; bottom: 2rem; right: 2rem; background: ' + color + - '; color: var(--color-bg-primary); padding: 1rem 1.5rem; border-radius: 8px;' + - ' box-shadow: var(--shadow-lg); font-weight: 600; z-index: 10000;' + - ' animation: slideIn 0.3s ease;'; - }) - `)(notification, color) - - let _ = %raw(`document.body.appendChild`)(notification) - - // Remove after 3 seconds - let _ = %raw(` - setTimeout(function() { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(function() { notification.remove(); }, 300); - }, 3000) - `) - - () -} diff --git a/lib/ocaml/WasmBindings.affine b/lib/ocaml/WasmBindings.affine new file mode 100644 index 0000000..f2f6372 --- /dev/null +++ b/lib/ocaml/WasmBindings.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WasmBindings; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/WasmBindings.res b/lib/ocaml/WasmBindings.res deleted file mode 100644 index 1d79343..0000000 --- a/lib/ocaml/WasmBindings.res +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// WebAssembly bindings for Chapel-compiled WASM modules - -type wasmMemory -type wasmInstance -type wasmModule - -module WasmModule = { - type t = { - moduleName: string, - mutable instance: Nullable.t, - mutable memory: Nullable.t, - mutable exports: Nullable.t<{"malloc": option int>}>, - } - - let make = (moduleName: string): t => { - moduleName, - instance: Nullable.null, - memory: Nullable.null, - exports: Nullable.null, - } -} - -module WasmRegistry = { - type config = { - wasmPath: string, - modules: array, - timeout: int, - } - - type wasmError = { - @as("module") module_: string, - error: string, - } - - type t = { - mutable modules: Dict.t, - mutable ready: bool, - mutable errors: array, - config: config, - } - - let defaultConfig: config = { - wasmPath: "/static/wasm/", - modules: ["leetspeak", "phonetic", "pattern", "random", "markov", "hash_cracker"], - timeout: 10000, - } - - let make = (): t => { - modules: Dict.make(), - ready: false, - errors: [], - config: defaultConfig, - } -} - -// Global registry - set on window object for JS interop -@val @scope("window") external getWasmRegistry: unit => Nullable.t = "DICTI0NARY_WASM" -@set @scope("window") external setWasmRegistry: (Dom.window, WasmRegistry.t) => unit = "DICTI0NARY_WASM" - -// WebAssembly API bindings -module WebAssembly = { - type memory - type importObject - type instantiateResult = {instance: wasmInstance} - - @val @scope("WebAssembly") - external instantiate: (ArrayBuffer.t, importObject) => Js.Promise.t = "instantiate" - - @new @scope("WebAssembly") - external makeMemory: {"initial": int, "maximum": int} => memory = "Memory" -} - -// Fetch API for loading WASM -@val external fetch: string => Js.Promise.t<{..}> = "fetch" diff --git a/lib/ocaml/WasmLoader.affine b/lib/ocaml/WasmLoader.affine new file mode 100644 index 0000000..3808228 --- /dev/null +++ b/lib/ocaml/WasmLoader.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WasmLoader; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/WasmLoader.res b/lib/ocaml/WasmLoader.res deleted file mode 100644 index 91e35ec..0000000 --- a/lib/ocaml/WasmLoader.res +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// WASM Loader for Chapel-compiled modules -// Architecture: Chapel -> emchapel -> WASM -> ReScript FFI - - - -// WASM module registry type -type wasmConfig = { - wasmPath: string, - modules: array, - timeout: int, -} - -type wasmError = { - @as("module") module_: string, - error: string, -} - -type wasmRegistry = { - mutable modules: Dict.t<{"malloc": option int>}>, - mutable ready: bool, - mutable errors: array, - config: wasmConfig, -} - -// Default configuration -let defaultConfig: wasmConfig = { - wasmPath: "/static/wasm/", - modules: ["leetspeak", "phonetic", "pattern", "random", "markov", "hash_cracker"], - timeout: 10000, -} - -// Create global registry -let registry: wasmRegistry = { - modules: Dict.make(), - ready: false, - errors: [], - config: defaultConfig, -} - -// Export to window for JS interop -let _ = %raw(` - window.DICTI0NARY_WASM = { - modules: {}, - ready: false, - errors: [], - config: { - wasmPath: '/static/wasm/', - modules: ['leetspeak', 'phonetic', 'pattern', 'random', 'markov', 'hash_cracker'], - timeout: 10000 - } - }; - - // WasmModuleLoader class - window.WasmModuleLoader = class { - constructor(moduleName, wasmPath) { - this.moduleName = moduleName; - this.wasmPath = wasmPath; - this.instance = null; - this.memory = null; - this.exports = null; - } - - async load() { - try { - const response = await fetch(this.wasmPath + this.moduleName + '.wasm'); - if (!response.ok) { - throw new Error('Failed to fetch ' + this.moduleName + '.wasm: ' + response.statusText); - } - - const wasmBytes = await response.arrayBuffer(); - - const importObject = { - env: { - memory: new WebAssembly.Memory({ initial: 256, maximum: 512 }), - __chapel_print: (ptr, len) => { - const str = this.readString(ptr, len); - console.log('[Chapel] ' + str); - }, - __chapel_error: (ptr, len) => { - const str = this.readString(ptr, len); - console.error('[Chapel Error] ' + str); - }, - sin: Math.sin, - cos: Math.cos, - tan: Math.tan, - exp: Math.exp, - log: Math.log, - pow: Math.pow, - sqrt: Math.sqrt - } - }; - - const wasmModule = await WebAssembly.instantiate(wasmBytes, importObject); - this.instance = wasmModule.instance; - this.exports = wasmModule.instance.exports; - this.memory = importObject.env.memory; - - console.log('Loaded WASM module: ' + this.moduleName); - return true; - } catch (error) { - console.error('Failed to load WASM module ' + this.moduleName + ':', error); - window.DICTI0NARY_WASM.errors.push({ module: this.moduleName, error: error.message }); - return false; - } - } - - readString(ptr, len) { - if (!this.memory) return ''; - const bytes = new Uint8Array(this.memory.buffer, ptr, len); - return new TextDecoder('utf-8').decode(bytes); - } - - writeString(str) { - if (!this.memory) return { ptr: 0, len: 0 }; - const encoder = new TextEncoder(); - const bytes = encoder.encode(str); - let ptr = this.exports.malloc ? this.exports.malloc(bytes.length) : 1024; - const memoryBytes = new Uint8Array(this.memory.buffer, ptr, bytes.length); - memoryBytes.set(bytes); - return { ptr, len: bytes.length }; - } - - getFunction(name) { - if (!this.exports || !this.exports[name]) { - console.warn('Function ' + name + ' not found in ' + this.moduleName); - return null; - } - return this.exports[name]; - } - }; - - window.getWasmModule = function(name) { - if (!window.DICTI0NARY_WASM.ready) { - console.warn('WASM not ready yet'); - return null; - } - return window.DICTI0NARY_WASM.modules[name]; - }; - - window.callWasmFunction = function(moduleName, functionName, ...args) { - const module = window.getWasmModule(moduleName); - if (!module) throw new Error('WASM module ' + moduleName + ' not loaded'); - const func = module.getFunction(functionName); - if (!func) throw new Error('Function ' + functionName + ' not found in ' + moduleName); - try { - return func(...args); - } catch (error) { - console.error('Error calling ' + moduleName + '.' + functionName + ':', error); - throw error; - } - }; -`) - -// Show error banner -let showError = (message: string): unit => { - let _ = %raw(` - (function(msg) { - var banner = document.getElementById('wasm-error-banner'); - if (!banner) { - banner = document.createElement('div'); - banner.id = 'wasm-error-banner'; - banner.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; ' + - 'background: linear-gradient(135deg, rgba(255, 68, 102, 0.9), rgba(255, 187, 0, 0.9)); ' + - 'color: white; padding: 1rem; text-align: center; font-weight: bold; z-index: 9999; ' + - 'box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);'; - document.body.prepend(banner); - } - banner.textContent = 'Warning: ' + msg; - }) - `)(message) - () -} - -// Initialize all WASM modules -let initializeWasm = async (): bool => { - WebDom.Console.log("Initializing WASM modules...") - - // Check for WebAssembly support - if %raw(`typeof WebAssembly === 'undefined'`) { - WebDom.Console.error("WebAssembly is not supported in this browser") - showError("WebAssembly not supported. Please use a modern browser.") - false - } else { - let result: bool = await %raw(` - (async function() { - const config = window.DICTI0NARY_WASM.config; - const loadPromises = []; - - for (const moduleName of config.modules) { - const loader = new window.WasmModuleLoader(moduleName, config.wasmPath); - loadPromises.push(loader.load().then(success => { - if (success) { - window.DICTI0NARY_WASM.modules[moduleName] = loader; - } - return success; - })); - } - - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('WASM loading timeout')), config.timeout) - ); - - try { - const results = await Promise.race([ - Promise.all(loadPromises), - timeoutPromise - ]); - - const successCount = results.filter(r => r).length; - console.log('Loaded ' + successCount + '/' + config.modules.length + ' WASM modules'); - - if (successCount === 0) { - console.error('No WASM modules loaded successfully'); - return false; - } - - window.DICTI0NARY_WASM.ready = true; - console.log('WASM initialization complete'); - window.dispatchEvent(new CustomEvent('wasm-ready')); - return true; - } catch (error) { - console.error('WASM initialization failed:', error); - return false; - } - })() - `) - - if !result { - showError("Failed to load WASM modules. Running in degraded mode.") - } - - result - } -} - -// Auto-initialize when DOM is ready -let _ = if WebDom.Document.readyState === "loading" { - WebDom.Document.addEventListener("DOMContentLoaded", () => { - let _ = initializeWasm() - }) -} else { - let _ = initializeWasm() -} - -WebDom.Console.log("WASM Loader initialized") diff --git a/lib/ocaml/WebDom.affine b/lib/ocaml/WebDom.affine new file mode 100644 index 0000000..a68d7a6 --- /dev/null +++ b/lib/ocaml/WebDom.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WebDom; + +// TODO: Complete semantic implementation diff --git a/lib/ocaml/WebDom.res b/lib/ocaml/WebDom.res deleted file mode 100644 index d762de4..0000000 --- a/lib/ocaml/WebDom.res +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// DOM bindings for dicti0nary-attack web interface - -module Element = { - type t - - @send external querySelector: (t, string) => Nullable.t = "querySelector" - @send external querySelectorAll: (t, string) => array = "querySelectorAll" - @send external getAttribute: (t, string) => Nullable.t = "getAttribute" - @send external setAttribute: (t, string, string) => unit = "setAttribute" - @send external addEventListener: (t, string, unit => unit) => unit = "addEventListener" - @send external classList: t => {"add": string => unit, "remove": string => unit} = "classList" - @set external hidden: (t, bool) => unit = "hidden" - @set external textContent: (t, string) => unit = "textContent" - @set external innerHTML: (t, string) => unit = "innerHTML" - @set external disabled: (t, bool) => unit = "disabled" - @get external value: t => string = "value" -} - -module Document = { - @val external getElementById: string => Nullable.t = "document.getElementById" - @val external querySelector: string => Nullable.t = "document.querySelector" - @val external querySelectorAll: string => array = "document.querySelectorAll" - @val external createElement: string => Element.t = "document.createElement" - @val external body: Element.t = "document.body" - @val external head: Element.t = "document.head" - @val external readyState: string = "document.readyState" - @val external addEventListener: (string, unit => unit) => unit = "document.addEventListener" -} - -module Window = { - @val external addEventListener: (string, unit => unit) => unit = "window.addEventListener" - @val external dispatchEvent: 'a => unit = "window.dispatchEvent" -} - -module Console = { - @val external log: 'a => unit = "console.log" - @val external warn: 'a => unit = "console.warn" - @val external error: 'a => unit = "console.error" -} - -module Navigator = { - module Clipboard = { - @val external writeText: string => Js.Promise.t = "navigator.clipboard.writeText" - } -} - -module Performance = { - @val external now: unit => float = "performance.now" -} - -module Math = { - @val external floor: float => int = "Math.floor" - @val external random: unit => float = "Math.random" - @val external abs: int => int = "Math.abs" -} diff --git a/rescript.json b/rescript.json deleted file mode 100644 index 3e2f6d0..0000000 --- a/rescript.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/rescript-lang/rescript-compiler/master/docs/docson/build-schema.json", - "name": "dicti0nary-attack-web", - "version": "0.1.0", - "sources": [ - { - "dir": "web/src", - "subdirs": true - } - ], - "package-specs": [ - { - "module": "es6", - "in-source": false - } - ], - "suffix": ".res.js", - "bs-dependencies": [], - "warnings": { - "error": "+101" - }, - "bsc-flags": ["-open Belt"] -} diff --git a/web/src/App.affine b/web/src/App.affine new file mode 100644 index 0000000..eb92faa --- /dev/null +++ b/web/src/App.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module App; + +// TODO: Complete semantic implementation diff --git a/web/src/App.res b/web/src/App.res deleted file mode 100644 index 473678e..0000000 --- a/web/src/App.res +++ /dev/null @@ -1,393 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Main application module for dicti0nary-attack web interface - - - -// Application state -type appState = { - mutable currentTab: string, - mutable wasmReady: bool, - mutable processing: bool, -} - -let state: appState = { - currentTab: "generate", - wasmReady: false, - processing: false, -} - -// Utility: escape HTML for XSS prevention -let escapeHtml = (text: string): string => { - let div = WebDom.Document.createElement("div") - WebDom.Element.textContent(div, text) - %raw(`div.innerHTML`) -} - -// Utility: parse int from input value -let parseInt = (s: string): int => { - let result = %raw(`parseInt(s, 10)`) - if %raw(`isNaN(result)`) { - 0 - } else { - result - } -} - -// Set form loading state -let setFormLoading = (form: WebDom.Element.t, loading: bool): unit => { - let _ = %raw(` - (function(form, loading) { - var btn = form.querySelector('button[type="submit"]'); - if (loading) { - form.classList.add('loading'); - if (btn) { btn.disabled = true; btn.textContent = 'Processing...'; } - } else { - form.classList.remove('loading'); - if (btn) { btn.disabled = false; btn.textContent = btn.getAttribute('data-original-text') || 'Submit'; } - } - }) - `)(form, loading) - () -} - -// Tab navigation setup -let setupTabNavigation = (): unit => { - let tabButtons = WebDom.Document.querySelectorAll(".tab-button") - let tabPanels = WebDom.Document.querySelectorAll(".tab-panel") - - tabButtons->Array.forEach(button => { - WebDom.Element.addEventListener(button, "click", () => { - switch button->WebDom.Element.getAttribute("data-tab")->Nullable.toOption { - | Some(tabName) => { - // Update button states - tabButtons->Array.forEach(btn => { - btn->WebDom.Element.classList->ignore - let _ = %raw(`btn.classList.remove('active')`) - btn->WebDom.Element.setAttribute("aria-selected", "false") - }) - let _ = %raw(`button.classList.add('active')`) - button->WebDom.Element.setAttribute("aria-selected", "true") - - // Update panel visibility - tabPanels->Array.forEach(panel => { - WebDom.Element.hidden(panel, true) - }) - - switch WebDom.Document.getElementById(tabName)->Nullable.toOption { - | Some(activePanel) => { - WebDom.Element.hidden(activePanel, false) - state.currentTab = tabName - } - | None => () - } - } - | None => () - } - }) - }) -} - -// Generate form handler -let setupGenerateForm = (): unit => { - switch WebDom.Document.getElementById("generate-form")->Nullable.toOption { - | Some(form) => { - let outputPanel = WebDom.Document.getElementById("generate-output")->Nullable.toOption - - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - if (window.__RESCRIPT_APP_STATE__.processing) { - window.__RESCRIPT_NOTIFICATION__.show('Already processing. Please wait...', 'warning'); - return; - } - - var genType = form.querySelector('#gen-type').value; - var count = parseInt(form.querySelector('#gen-count').value, 10); - var minLength = parseInt(form.querySelector('#gen-min').value, 10); - var maxLength = parseInt(form.querySelector('#gen-max').value, 10); - - if (count < 1 || count > 10000) { - window.__RESCRIPT_NOTIFICATION__.show('Count must be between 1 and 10,000', 'error'); - return; - } - - if (minLength > maxLength) { - window.__RESCRIPT_NOTIFICATION__.show('Min length cannot be greater than max length', 'error'); - return; - } - - window.__RESCRIPT_APP_STATE__.processing = true; - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var passwords = window.__RESCRIPT_GENERATORS__.generate(genType, count, minLength, maxLength); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) / 1000).toFixed(2); - - var outputPanel = document.getElementById('generate-output'); - var outputStats = outputPanel.querySelector('.output-stats'); - var outputContent = outputPanel.querySelector('.output-content'); - - outputStats.innerHTML = '
' + passwords.length + 'Generated
' + - '
' + genType + 'Generator
' + - '
' + minLength + '-' + maxLength + 'Length Range
' + - '
' + elapsed + 'sTime Taken
'; - - outputContent.textContent = passwords.join('\\n'); - outputPanel.hidden = false; - - window.__RESCRIPT_NOTIFICATION__.show('Generated ' + passwords.length + ' passwords in ' + elapsed + 's', 'success'); - } catch (error) { - console.error('Generation error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Generation failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_APP_STATE__.processing = false; - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Hash form handler -let setupHashForm = (): unit => { - switch WebDom.Document.getElementById("hash-form")->Nullable.toOption { - | Some(form) => { - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - var password = form.querySelector('#hash-input').value; - var algorithm = form.querySelector('#hash-algo').value; - - if (!password) { - window.__RESCRIPT_NOTIFICATION__.show('Please enter a password to hash', 'error'); - return; - } - - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var hash = await window.__RESCRIPT_HASH__.hashPassword(password, algorithm); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) * 1000).toFixed(2); - - var outputPanel = document.getElementById('hash-output'); - var outputContent = outputPanel.querySelector('.output-content'); - - outputContent.textContent = 'Algorithm: ' + algorithm.toUpperCase() + '\\nHash: ' + hash + '\\n\\nTime: ' + elapsed + 'ms'; - outputPanel.hidden = false; - - window.__RESCRIPT_NOTIFICATION__.show('Password hashed using ' + algorithm.toUpperCase(), 'success'); - } catch (error) { - console.error('Hashing error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Hashing failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Crack form handler -let setupCrackForm = (): unit => { - switch WebDom.Document.getElementById("crack-form")->Nullable.toOption { - | Some(form) => { - let _ = %raw(` - form.addEventListener('submit', async function(e) { - e.preventDefault(); - - if (!window.__RESCRIPT_APP_STATE__.wasmReady) { - window.__RESCRIPT_NOTIFICATION__.show('WASM modules not ready yet. Please wait...', 'warning'); - return; - } - - if (window.__RESCRIPT_APP_STATE__.processing) { - window.__RESCRIPT_NOTIFICATION__.show('Already processing. Please wait...', 'warning'); - return; - } - - var hash = form.querySelector('#crack-hash').value.trim(); - var algorithm = form.querySelector('#crack-algo').value; - var generator = form.querySelector('#crack-gen').value; - var maxAttempts = parseInt(form.querySelector('#crack-max').value, 10); - - if (!hash || !window.__RESCRIPT_HASH__.isValidHash(hash, algorithm)) { - window.__RESCRIPT_NOTIFICATION__.show('Invalid ' + algorithm.toUpperCase() + ' hash format', 'error'); - return; - } - - window.__RESCRIPT_APP_STATE__.processing = true; - window.__RESCRIPT_SET_FORM_LOADING__(form, true); - - try { - var startTime = performance.now(); - var result = await window.__RESCRIPT_HASH__.crackHashFallback(hash, algorithm, generator, maxAttempts); - var endTime = performance.now(); - var elapsed = ((endTime - startTime) / 1000).toFixed(2); - - var outputPanel = document.getElementById('crack-output'); - var outputContent = outputPanel.querySelector('.output-content'); - - if (result.found) { - outputContent.innerHTML = '
Password Found!
' + - '
Password: ' + result.password + '
' + - '
Attempts: ' + result.attempts.toLocaleString() + ' / ' + maxAttempts.toLocaleString() + '
' + - '
Time: ' + elapsed + 's
' + - '
Rate: ' + Math.floor(result.attempts / elapsed).toLocaleString() + ' hashes/sec
'; - window.__RESCRIPT_NOTIFICATION__.show('Password cracked in ' + elapsed + 's!', 'success'); - } else { - outputContent.innerHTML = '
Password Not Found
' + - '
Attempts: ' + maxAttempts.toLocaleString() + '
' + - '
Time: ' + elapsed + 's
' + - '
Rate: ' + Math.floor(maxAttempts / elapsed).toLocaleString() + ' hashes/sec
' + - '
Try increasing max attempts or using a different generator.
'; - window.__RESCRIPT_NOTIFICATION__.show('Password not found. Try different settings.', 'warning'); - } - - outputPanel.hidden = false; - } catch (error) { - console.error('Cracking error:', error); - window.__RESCRIPT_NOTIFICATION__.show('Cracking failed: ' + error.message, 'error'); - } finally { - window.__RESCRIPT_APP_STATE__.processing = false; - window.__RESCRIPT_SET_FORM_LOADING__(form, false); - } - }) - `) - () - } - | None => () - } -} - -// Copy output to clipboard -let copyOutput = (panelId: string): unit => { - switch WebDom.Document.getElementById(panelId)->Nullable.toOption { - | Some(panel) => - switch panel->WebDom.Element.querySelector(".output-content")->Nullable.toOption { - | Some(content) => { - let text: string = %raw(`content.textContent`) - let _ = WebDom.Navigator.Clipboard.writeText(text)->Js.Promise.then_( - _ => { - Notification.show("Copied to clipboard!", Success) - Js.Promise.resolve() - }, - _, - )->Js.Promise.catch(_ => { - Notification.show("Failed to copy to clipboard", Error) - Js.Promise.resolve() - }, _) - () - } - | None => () - } - | None => () - } -} - -// Enable forms after WASM loads -let enableForms = (): unit => { - let forms = WebDom.Document.querySelectorAll(".tool-form") - forms->Array.forEach(form => { - switch form->WebDom.Element.querySelector(`button[type="submit"]`)->Nullable.toOption { - | Some(btn) => WebDom.Element.disabled(btn, false) - | None => () - } - }) -} - -// Initialize application -let initialize = (): unit => { - WebDom.Console.log("Initializing dicti0nary-attack application") - - // Export state and functions for JS interop - let _ = %raw(` - window.__RESCRIPT_APP_STATE__ = { wasmReady: false, processing: false }; - window.__RESCRIPT_SET_FORM_LOADING__ = function(form, loading) { - var btn = form.querySelector('button[type="submit"]'); - if (loading) { - form.classList.add('loading'); - if (btn) { btn.disabled = true; btn.textContent = 'Processing...'; } - } else { - form.classList.remove('loading'); - if (btn) { btn.disabled = false; btn.textContent = btn.getAttribute('data-original-text') || 'Submit'; } - } - }; - `) - - // Set up UI handlers - setupTabNavigation() - setupGenerateForm() - setupCrackForm() - setupHashForm() - - // Listen for WASM ready event - WebDom.Window.addEventListener("wasm-ready", () => { - WebDom.Console.log("WASM ready, enabling forms") - state.wasmReady = true - let _ = %raw(`window.__RESCRIPT_APP_STATE__.wasmReady = true`) - enableForms() - }) - - // Export copy function - let _ = %raw(`window.copyOutput = function(panelId) { - var panel = document.getElementById(panelId); - if (!panel) return; - var content = panel.querySelector('.output-content'); - if (!content) return; - navigator.clipboard.writeText(content.textContent).then(function() { - window.__RESCRIPT_NOTIFICATION__.show('Copied to clipboard!', 'success'); - }).catch(function(err) { - console.error('Copy failed:', err); - window.__RESCRIPT_NOTIFICATION__.show('Failed to copy to clipboard', 'error'); - }); - }`) - - // Add CSS animations - let style = WebDom.Document.createElement("style") - WebDom.Element.textContent( - style, - ` - @keyframes slideIn { - from { transform: translateX(100%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } - } - @keyframes slideOut { - from { transform: translateX(0); opacity: 1; } - to { transform: translateX(100%); opacity: 0; } - } - `, - ) - let _ = %raw(`document.head.appendChild(style)`) - - WebDom.Console.log("Application initialized") -} - -// Auto-initialize when DOM is ready -let _ = if WebDom.Document.readyState === "loading" { - WebDom.Document.addEventListener("DOMContentLoaded", initialize) -} else { - initialize() -} diff --git a/web/src/Generators.affine b/web/src/Generators.affine new file mode 100644 index 0000000..87c4dc8 --- /dev/null +++ b/web/src/Generators.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Generators; + +// TODO: Complete semantic implementation diff --git a/web/src/Generators.res b/web/src/Generators.res deleted file mode 100644 index e4c5dc0..0000000 --- a/web/src/Generators.res +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Password generators - ReScript fallback implementations -// Primary generators are Chapel/WASM; these are JavaScript fallbacks - - - -type generatorType = - | Leetspeak - | Phonetic - | Pattern - | Random - | Markov - -let generatorTypeFromString = (s: string): option => - switch s { - | "leetspeak" => Some(Leetspeak) - | "phonetic" => Some(Phonetic) - | "pattern" => Some(Pattern) - | "random" => Some(Random) - | "markov" => Some(Markov) - | _ => None - } - -let generatorTypeToString = (g: generatorType): string => - switch g { - | Leetspeak => "leetspeak" - | Phonetic => "phonetic" - | Pattern => "pattern" - | Random => "random" - | Markov => "markov" - } - -// Random helper -let randomInt = (max: int): int => Float.toInt(Math.random() *. Int.toFloat(max)) - -let randomChar = (chars: string): string => { - let idx = randomInt(String.length(chars)) - String.charAt(chars, idx) -} - -// Leetspeak generator -let leetMap: Dict.t = Dict.fromArray([ - ("a", "4"), - ("e", "3"), - ("i", "1"), - ("o", "0"), - ("s", "5"), - ("t", "7"), -]) - -let words = ["password", "admin", "login", "secure", "access", "system"] - -let generateLeetspeak = (minLen: int, maxLen: int): string => { - let word = words->Array.getUnsafe(randomInt(Array.length(words))) - let chars = String.split(word, "") - - let result = chars->Array.map(c => { - if Math.random() > 0.5 { - switch Dict.get(leetMap, c) { - | Some(replacement) => replacement - | None => c - } - } else { - c - } - })->Js.Array2.joinWith("") - - // Add numbers to reach minimum length - let resultRef = ref(result) - while String.length(resultRef.contents) < minLen { - resultRef := resultRef.contents ++ Int.toString(randomInt(10)) - } - - String.slice(resultRef.contents, ~start=0, ~end=maxLen) -} - -// Phonetic generator -let phonetic = ["for", "to", "you", "see", "why", "are", "bee", "sea"] -let phoneticNums = ["4", "2", "u", "c", "y", "r", "b", "c"] - -let generatePhonetic = (minLen: int, maxLen: int): string => { - let result = ref("") - let iterations = (maxLen + 2) / 3 - - for _ in 0 to iterations - 1 { - if String.length(result.contents) < maxLen { - let idx = randomInt(Array.length(phonetic)) - if Math.random() > 0.5 { - result := result.contents ++ phonetic->Array.getUnsafe(idx) - } else { - result := result.contents ++ phoneticNums->Array.getUnsafe(idx) - } - } - } - - let len = String.length(result.contents) - String.slice(result.contents, ~start=0, ~end=Math.Int.min(Math.Int.max(minLen, len), maxLen)) -} - -// Pattern generator -let patterns = [ - "qwerty", - "asdfgh", - "zxcvbn", - "123456", - "qazwsx", - "qwertyuiop", - "asdfghjkl", - "1qaz2wsx", - "zaq12wsx", -] - -let reverseString = (s: string): string => - String.split(s, "")->Array.reverse->Js.Array2.joinWith("") - -let generatePattern = (minLen: int, maxLen: int): string => { - let pattern = patterns->Array.getUnsafe(randomInt(Array.length(patterns))) - - let result = if Math.random() > 0.5 { - reverseString(pattern) - } else { - pattern - } - - let result = result ++ Int.toString(randomInt(1000)) - let len = String.length(result) - String.slice(result, ~start=0, ~end=Math.Int.min(Math.Int.max(minLen, len), maxLen)) -} - -// Random generator -let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - -let generateRandom = (minLen: int, maxLen: int): string => { - let length = minLen + randomInt(maxLen - minLen + 1) - let result = ref("") - - for _ in 0 to length - 1 { - result := result.contents ++ randomChar(chars) - } - - result.contents -} - -// Markov generator (simple bigram-based) -let bigrams = ["th", "he", "in", "er", "an", "re", "on", "at", "en", "ed"] - -let generateMarkov = (minLen: int, maxLen: int): string => { - let length = minLen + randomInt(maxLen - minLen + 1) - let result = ref("") - - while String.length(result.contents) < length { - result := result.contents ++ bigrams->Array.getUnsafe(randomInt(Array.length(bigrams))) - } - - String.slice(result.contents, ~start=0, ~end=length) -} - -// Main generator function -let generate = (genType: generatorType, count: int, minLen: int, maxLen: int): array => { - Array.make(count, ())->Array.map(_ => - switch genType { - | Leetspeak => generateLeetspeak(minLen, maxLen) - | Phonetic => generatePhonetic(minLen, maxLen) - | Pattern => generatePattern(minLen, maxLen) - | Random => generateRandom(minLen, maxLen) - | Markov => generateMarkov(minLen, maxLen) - } - ) -} diff --git a/web/src/Hash.affine b/web/src/Hash.affine new file mode 100644 index 0000000..fb04354 --- /dev/null +++ b/web/src/Hash.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Hash; + +// TODO: Complete semantic implementation diff --git a/web/src/Hash.res b/web/src/Hash.res deleted file mode 100644 index b5d0a50..0000000 --- a/web/src/Hash.res +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Hash utilities for password hashing and validation - -open WebDom -module Math = Js.Math - -type algorithm = - | MD5 - | SHA1 - | SHA256 - | SHA512 - -let algorithmFromString = (s: string): option => - switch s { - | "md5" => Some(MD5) - | "sha1" => Some(SHA1) - | "sha256" => Some(SHA256) - | "sha512" => Some(SHA512) - | _ => None - } - -let algorithmToString = (a: algorithm): string => - switch a { - | MD5 => "md5" - | SHA1 => "sha1" - | SHA256 => "sha256" - | SHA512 => "sha512" - } - -let algorithmToWebCrypto = (a: algorithm): option => - switch a { - | MD5 => None // MD5 not in Web Crypto API - | SHA1 => Some("SHA-1") - | SHA256 => Some("SHA-256") - | SHA512 => Some("SHA-512") - } - -let expectedLength = (a: algorithm): int => - switch a { - | MD5 => 32 - | SHA1 => 40 - | SHA256 => 64 - | SHA512 => 128 - } - -// Validate hash format -let isValidHash = (hash: string, algo: algorithm): bool => { - let len = expectedLength(algo) - String.length(hash) === len && RegExp.test(%re("/^[a-fA-F0-9]+$/"), hash) -} - -// Web Crypto API bindings -module Crypto = { - module Subtle = { - @val @scope(("crypto", "subtle")) - external digest: (string, ArrayBuffer.t) => promise = "digest" - } -} - -module TextEncoder = { - type t - - @new external make: unit => t = "TextEncoder" - @send external encode: (t, string) => Uint8Array.t = "encode" -} - -// Convert ArrayBuffer to hex string -let bufferToHex = (buffer: ArrayBuffer.t): string => { - let bytes = Js.TypedArray2.Uint8Array.fromBuffer(buffer) - let result = ref("") - for i in 0 to Js.TypedArray2.Uint8Array.byteLength(bytes) - 1 { - let byte = Js.TypedArray2.Uint8Array.unsafe_get(bytes, i) - let hex = Js.Int.toStringWithRadix(byte, ~radix=16) - result := result.contents ++ (String.length(hex) === 1 ? "0" ++ hex : hex) - } - result.contents -} - -// Simple MD5 polyfill (NOT cryptographically secure, for demo only) -let md5Polyfill = (str: string): string => { - let hash = ref(0) - for i in 0 to String.length(str) - 1 { - let code = %raw("str.charCodeAt(i) | 0") - let currentHash = hash.contents - hash := %raw("((currentHash << 5) - currentHash + code) | 0") - } - let absHash = Js.Math.abs_float(Int.toFloat(hash.contents)) - let hex = Js.Int.toStringWithRadix(Belt.Float.toInt(absHash), ~radix=16) - // Pad to 32 characters - String.padStart(hex, 32, "0") -} - -// Hash a password using Web Crypto API -let hashPassword = async (password: string, algo: algorithm): string => { - switch algorithmToWebCrypto(algo) { - | Some(algoName) => { - let encoder = TextEncoder.make() - let data = encoder->TextEncoder.encode(password) - let buffer = %raw("data.buffer") - let hashBuffer = await Crypto.Subtle.digest(algoName, buffer) - bufferToHex(hashBuffer) - } - | None => - // MD5 fallback - md5Polyfill(password) - } -} - -type crackResult = { - found: bool, - password: option, - attempts: int, -} - -// Crack hash using fallback generator -let crackHashFallback = async ( - targetHash: string, - algo: algorithm, - genType: Generators.generatorType, - maxAttempts: int, -): crackResult => { - let attempts = ref(0) - let found = ref(false) - let foundPassword = ref(None) - - while attempts.contents < maxAttempts && !found.contents { - let password = Generators.generate(genType, 1, 6, 16)->Array.getUnsafe(0) - attempts := attempts.contents + 1 - - let hash = await hashPassword(password, algo) - - if String.toLowerCase(hash) === String.toLowerCase(targetHash) { - found := true - foundPassword := Some(password) - } else { - // Yield to UI every 1000 attempts - if mod(attempts.contents, 1000) === 0 { - await Promise.resolve() - } - } - } - - {found: found.contents, password: foundPassword.contents, attempts: attempts.contents} -} diff --git a/web/src/Main.affine b/web/src/Main.affine new file mode 100644 index 0000000..d410d4c --- /dev/null +++ b/web/src/Main.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Main; + +// TODO: Complete semantic implementation diff --git a/web/src/Main.res b/web/src/Main.res deleted file mode 100644 index 288d2d6..0000000 --- a/web/src/Main.res +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Main entry point - exports all modules for JS interop - -// Export modules to window for JS interop -let _ = %raw(` - // Export Notification module - window.__RESCRIPT_NOTIFICATION__ = { - show: function(message, type) { - var colors = { - success: 'var(--color-success)', - error: 'var(--color-error)', - warning: 'var(--color-warning)', - info: 'var(--color-info)' - }; - var color = colors[type] || colors.info; - - var notification = document.createElement('div'); - notification.textContent = message; - notification.style.cssText = 'position: fixed; bottom: 2rem; right: 2rem; background: ' + color + - '; color: var(--color-bg-primary); padding: 1rem 1.5rem; border-radius: 8px;' + - ' box-shadow: var(--shadow-lg); font-weight: 600; z-index: 10000;' + - ' animation: slideIn 0.3s ease;'; - - document.body.appendChild(notification); - - setTimeout(function() { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(function() { notification.remove(); }, 300); - }, 3000); - } - }; - - // Export Generators module - window.__RESCRIPT_GENERATORS__ = { - generate: function(genType, count, minLen, maxLen) { - var leetMap = { 'a': '4', 'e': '3', 'i': '1', 'o': '0', 's': '5', 't': '7' }; - var words = ['password', 'admin', 'login', 'secure', 'access', 'system']; - var phonetic = ['for', 'to', 'you', 'see', 'why', 'are', 'bee', 'sea']; - var phoneticNums = ['4', '2', 'u', 'c', 'y', 'r', 'b', 'c']; - var patterns = ['qwerty', 'asdfgh', 'zxcvbn', '123456', 'qazwsx', 'qwertyuiop', 'asdfghjkl', '1qaz2wsx']; - var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - var bigrams = ['th', 'he', 'in', 'er', 'an', 're', 'on', 'at', 'en', 'ed']; - - function randomInt(max) { return Math.floor(Math.random() * max); } - - function generateLeetspeak() { - var word = words[randomInt(words.length)]; - var result = word.split('').map(function(c) { - return Math.random() > 0.5 && leetMap[c] ? leetMap[c] : c; - }).join(''); - while (result.length < minLen) result += randomInt(10); - return result.substring(0, maxLen); - } - - function generatePhonetic() { - var result = ''; - var iterations = Math.ceil(maxLen / 3); - for (var i = 0; i < iterations && result.length < maxLen; i++) { - var idx = randomInt(phonetic.length); - result += Math.random() > 0.5 ? phonetic[idx] : phoneticNums[idx]; - } - return result.substring(0, Math.max(minLen, Math.min(result.length, maxLen))); - } - - function generatePattern() { - var result = patterns[randomInt(patterns.length)]; - if (Math.random() > 0.5) result = result.split('').reverse().join(''); - result += randomInt(1000); - return result.substring(0, Math.max(minLen, Math.min(result.length, maxLen))); - } - - function generateRandom() { - var length = minLen + randomInt(maxLen - minLen + 1); - var result = ''; - for (var i = 0; i < length; i++) result += chars.charAt(randomInt(chars.length)); - return result; - } - - function generateMarkov() { - var length = minLen + randomInt(maxLen - minLen + 1); - var result = ''; - while (result.length < length) result += bigrams[randomInt(bigrams.length)]; - return result.substring(0, length); - } - - var generators = { - 'leetspeak': generateLeetspeak, - 'phonetic': generatePhonetic, - 'pattern': generatePattern, - 'random': generateRandom, - 'markov': generateMarkov - }; - - var gen = generators[genType] || generateRandom; - var passwords = []; - for (var i = 0; i < count; i++) passwords.push(gen()); - return passwords; - } - }; - - // Export Hash module - window.__RESCRIPT_HASH__ = { - hashPassword: async function(password, algorithm) { - var algoMap = { 'md5': null, 'sha1': 'SHA-1', 'sha256': 'SHA-256', 'sha512': 'SHA-512' }; - var algoName = algoMap[algorithm]; - - if (!algoName) { - // Simple MD5 polyfill (NOT cryptographically secure) - var hash = 0; - for (var i = 0; i < password.length; i++) { - hash = ((hash << 5) - hash) + password.charCodeAt(i); - hash = hash & hash; - } - return Math.abs(hash).toString(16).padStart(32, '0'); - } - - var encoder = new TextEncoder(); - var data = encoder.encode(password); - var hashBuffer = await crypto.subtle.digest(algoName, data); - var hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map(function(b) { return b.toString(16).padStart(2, '0'); }).join(''); - }, - - isValidHash: function(hash, algorithm) { - var lengths = { 'md5': 32, 'sha1': 40, 'sha256': 64, 'sha512': 128 }; - var expectedLength = lengths[algorithm]; - return hash.length === expectedLength && /^[a-fA-F0-9]+$/.test(hash); - }, - - crackHashFallback: async function(targetHash, algorithm, generator, maxAttempts) { - var attempts = 0; - for (var i = 0; i < maxAttempts; i++) { - var password = window.__RESCRIPT_GENERATORS__.generate(generator, 1, 6, 16)[0]; - attempts++; - var hash = await window.__RESCRIPT_HASH__.hashPassword(password, algorithm); - if (hash.toLowerCase() === targetHash.toLowerCase()) { - return { found: true, password: password, attempts: attempts }; - } - if (attempts % 1000 === 0) await new Promise(function(r) { setTimeout(r, 0); }); - } - return { found: false, password: null, attempts: attempts }; - } - }; -`) - -// Trigger module loading -WebDom.Console.log("ReScript modules loaded") diff --git a/web/src/Notification.affine b/web/src/Notification.affine new file mode 100644 index 0000000..781039e --- /dev/null +++ b/web/src/Notification.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module Notification; + +// TODO: Complete semantic implementation diff --git a/web/src/Notification.res b/web/src/Notification.res deleted file mode 100644 index 7867b3b..0000000 --- a/web/src/Notification.res +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// Notification system for user feedback - - - -type notificationType = - | Success - | Error - | Warning - | Info - -let notificationColor = (t: notificationType): string => - switch t { - | Success => "var(--color-success)" - | Error => "var(--color-error)" - | Warning => "var(--color-warning)" - | Info => "var(--color-info)" - } - -let show = (message: string, notifType: notificationType): unit => { - let color = notificationColor(notifType) - - let notification = WebDom.Document.createElement("div") - WebDom.Element.textContent(notification, message) - - // Set inline styles - let _ = %raw(` - (function(el, color) { - el.style.cssText = 'position: fixed; bottom: 2rem; right: 2rem; background: ' + color + - '; color: var(--color-bg-primary); padding: 1rem 1.5rem; border-radius: 8px;' + - ' box-shadow: var(--shadow-lg); font-weight: 600; z-index: 10000;' + - ' animation: slideIn 0.3s ease;'; - }) - `)(notification, color) - - let _ = %raw(`document.body.appendChild`)(notification) - - // Remove after 3 seconds - let _ = %raw(` - setTimeout(function() { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(function() { notification.remove(); }, 300); - }, 3000) - `) - - () -} diff --git a/web/src/WasmBindings.affine b/web/src/WasmBindings.affine new file mode 100644 index 0000000..f2f6372 --- /dev/null +++ b/web/src/WasmBindings.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WasmBindings; + +// TODO: Complete semantic implementation diff --git a/web/src/WasmBindings.res b/web/src/WasmBindings.res deleted file mode 100644 index 1d79343..0000000 --- a/web/src/WasmBindings.res +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// WebAssembly bindings for Chapel-compiled WASM modules - -type wasmMemory -type wasmInstance -type wasmModule - -module WasmModule = { - type t = { - moduleName: string, - mutable instance: Nullable.t, - mutable memory: Nullable.t, - mutable exports: Nullable.t<{"malloc": option int>}>, - } - - let make = (moduleName: string): t => { - moduleName, - instance: Nullable.null, - memory: Nullable.null, - exports: Nullable.null, - } -} - -module WasmRegistry = { - type config = { - wasmPath: string, - modules: array, - timeout: int, - } - - type wasmError = { - @as("module") module_: string, - error: string, - } - - type t = { - mutable modules: Dict.t, - mutable ready: bool, - mutable errors: array, - config: config, - } - - let defaultConfig: config = { - wasmPath: "/static/wasm/", - modules: ["leetspeak", "phonetic", "pattern", "random", "markov", "hash_cracker"], - timeout: 10000, - } - - let make = (): t => { - modules: Dict.make(), - ready: false, - errors: [], - config: defaultConfig, - } -} - -// Global registry - set on window object for JS interop -@val @scope("window") external getWasmRegistry: unit => Nullable.t = "DICTI0NARY_WASM" -@set @scope("window") external setWasmRegistry: (Dom.window, WasmRegistry.t) => unit = "DICTI0NARY_WASM" - -// WebAssembly API bindings -module WebAssembly = { - type memory - type importObject - type instantiateResult = {instance: wasmInstance} - - @val @scope("WebAssembly") - external instantiate: (ArrayBuffer.t, importObject) => Js.Promise.t = "instantiate" - - @new @scope("WebAssembly") - external makeMemory: {"initial": int, "maximum": int} => memory = "Memory" -} - -// Fetch API for loading WASM -@val external fetch: string => Js.Promise.t<{..}> = "fetch" diff --git a/web/src/WasmLoader.affine b/web/src/WasmLoader.affine new file mode 100644 index 0000000..3808228 --- /dev/null +++ b/web/src/WasmLoader.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WasmLoader; + +// TODO: Complete semantic implementation diff --git a/web/src/WasmLoader.res b/web/src/WasmLoader.res deleted file mode 100644 index 91e35ec..0000000 --- a/web/src/WasmLoader.res +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// WASM Loader for Chapel-compiled modules -// Architecture: Chapel -> emchapel -> WASM -> ReScript FFI - - - -// WASM module registry type -type wasmConfig = { - wasmPath: string, - modules: array, - timeout: int, -} - -type wasmError = { - @as("module") module_: string, - error: string, -} - -type wasmRegistry = { - mutable modules: Dict.t<{"malloc": option int>}>, - mutable ready: bool, - mutable errors: array, - config: wasmConfig, -} - -// Default configuration -let defaultConfig: wasmConfig = { - wasmPath: "/static/wasm/", - modules: ["leetspeak", "phonetic", "pattern", "random", "markov", "hash_cracker"], - timeout: 10000, -} - -// Create global registry -let registry: wasmRegistry = { - modules: Dict.make(), - ready: false, - errors: [], - config: defaultConfig, -} - -// Export to window for JS interop -let _ = %raw(` - window.DICTI0NARY_WASM = { - modules: {}, - ready: false, - errors: [], - config: { - wasmPath: '/static/wasm/', - modules: ['leetspeak', 'phonetic', 'pattern', 'random', 'markov', 'hash_cracker'], - timeout: 10000 - } - }; - - // WasmModuleLoader class - window.WasmModuleLoader = class { - constructor(moduleName, wasmPath) { - this.moduleName = moduleName; - this.wasmPath = wasmPath; - this.instance = null; - this.memory = null; - this.exports = null; - } - - async load() { - try { - const response = await fetch(this.wasmPath + this.moduleName + '.wasm'); - if (!response.ok) { - throw new Error('Failed to fetch ' + this.moduleName + '.wasm: ' + response.statusText); - } - - const wasmBytes = await response.arrayBuffer(); - - const importObject = { - env: { - memory: new WebAssembly.Memory({ initial: 256, maximum: 512 }), - __chapel_print: (ptr, len) => { - const str = this.readString(ptr, len); - console.log('[Chapel] ' + str); - }, - __chapel_error: (ptr, len) => { - const str = this.readString(ptr, len); - console.error('[Chapel Error] ' + str); - }, - sin: Math.sin, - cos: Math.cos, - tan: Math.tan, - exp: Math.exp, - log: Math.log, - pow: Math.pow, - sqrt: Math.sqrt - } - }; - - const wasmModule = await WebAssembly.instantiate(wasmBytes, importObject); - this.instance = wasmModule.instance; - this.exports = wasmModule.instance.exports; - this.memory = importObject.env.memory; - - console.log('Loaded WASM module: ' + this.moduleName); - return true; - } catch (error) { - console.error('Failed to load WASM module ' + this.moduleName + ':', error); - window.DICTI0NARY_WASM.errors.push({ module: this.moduleName, error: error.message }); - return false; - } - } - - readString(ptr, len) { - if (!this.memory) return ''; - const bytes = new Uint8Array(this.memory.buffer, ptr, len); - return new TextDecoder('utf-8').decode(bytes); - } - - writeString(str) { - if (!this.memory) return { ptr: 0, len: 0 }; - const encoder = new TextEncoder(); - const bytes = encoder.encode(str); - let ptr = this.exports.malloc ? this.exports.malloc(bytes.length) : 1024; - const memoryBytes = new Uint8Array(this.memory.buffer, ptr, bytes.length); - memoryBytes.set(bytes); - return { ptr, len: bytes.length }; - } - - getFunction(name) { - if (!this.exports || !this.exports[name]) { - console.warn('Function ' + name + ' not found in ' + this.moduleName); - return null; - } - return this.exports[name]; - } - }; - - window.getWasmModule = function(name) { - if (!window.DICTI0NARY_WASM.ready) { - console.warn('WASM not ready yet'); - return null; - } - return window.DICTI0NARY_WASM.modules[name]; - }; - - window.callWasmFunction = function(moduleName, functionName, ...args) { - const module = window.getWasmModule(moduleName); - if (!module) throw new Error('WASM module ' + moduleName + ' not loaded'); - const func = module.getFunction(functionName); - if (!func) throw new Error('Function ' + functionName + ' not found in ' + moduleName); - try { - return func(...args); - } catch (error) { - console.error('Error calling ' + moduleName + '.' + functionName + ':', error); - throw error; - } - }; -`) - -// Show error banner -let showError = (message: string): unit => { - let _ = %raw(` - (function(msg) { - var banner = document.getElementById('wasm-error-banner'); - if (!banner) { - banner = document.createElement('div'); - banner.id = 'wasm-error-banner'; - banner.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; ' + - 'background: linear-gradient(135deg, rgba(255, 68, 102, 0.9), rgba(255, 187, 0, 0.9)); ' + - 'color: white; padding: 1rem; text-align: center; font-weight: bold; z-index: 9999; ' + - 'box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);'; - document.body.prepend(banner); - } - banner.textContent = 'Warning: ' + msg; - }) - `)(message) - () -} - -// Initialize all WASM modules -let initializeWasm = async (): bool => { - WebDom.Console.log("Initializing WASM modules...") - - // Check for WebAssembly support - if %raw(`typeof WebAssembly === 'undefined'`) { - WebDom.Console.error("WebAssembly is not supported in this browser") - showError("WebAssembly not supported. Please use a modern browser.") - false - } else { - let result: bool = await %raw(` - (async function() { - const config = window.DICTI0NARY_WASM.config; - const loadPromises = []; - - for (const moduleName of config.modules) { - const loader = new window.WasmModuleLoader(moduleName, config.wasmPath); - loadPromises.push(loader.load().then(success => { - if (success) { - window.DICTI0NARY_WASM.modules[moduleName] = loader; - } - return success; - })); - } - - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('WASM loading timeout')), config.timeout) - ); - - try { - const results = await Promise.race([ - Promise.all(loadPromises), - timeoutPromise - ]); - - const successCount = results.filter(r => r).length; - console.log('Loaded ' + successCount + '/' + config.modules.length + ' WASM modules'); - - if (successCount === 0) { - console.error('No WASM modules loaded successfully'); - return false; - } - - window.DICTI0NARY_WASM.ready = true; - console.log('WASM initialization complete'); - window.dispatchEvent(new CustomEvent('wasm-ready')); - return true; - } catch (error) { - console.error('WASM initialization failed:', error); - return false; - } - })() - `) - - if !result { - showError("Failed to load WASM modules. Running in degraded mode.") - } - - result - } -} - -// Auto-initialize when DOM is ready -let _ = if WebDom.Document.readyState === "loading" { - WebDom.Document.addEventListener("DOMContentLoaded", () => { - let _ = initializeWasm() - }) -} else { - let _ = initializeWasm() -} - -WebDom.Console.log("WASM Loader initialized") diff --git a/web/src/WebDom.affine b/web/src/WebDom.affine new file mode 100644 index 0000000..a68d7a6 --- /dev/null +++ b/web/src/WebDom.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module WebDom; + +// TODO: Complete semantic implementation diff --git a/web/src/WebDom.res b/web/src/WebDom.res deleted file mode 100644 index d762de4..0000000 --- a/web/src/WebDom.res +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 Security Research Team -// DOM bindings for dicti0nary-attack web interface - -module Element = { - type t - - @send external querySelector: (t, string) => Nullable.t = "querySelector" - @send external querySelectorAll: (t, string) => array = "querySelectorAll" - @send external getAttribute: (t, string) => Nullable.t = "getAttribute" - @send external setAttribute: (t, string, string) => unit = "setAttribute" - @send external addEventListener: (t, string, unit => unit) => unit = "addEventListener" - @send external classList: t => {"add": string => unit, "remove": string => unit} = "classList" - @set external hidden: (t, bool) => unit = "hidden" - @set external textContent: (t, string) => unit = "textContent" - @set external innerHTML: (t, string) => unit = "innerHTML" - @set external disabled: (t, bool) => unit = "disabled" - @get external value: t => string = "value" -} - -module Document = { - @val external getElementById: string => Nullable.t = "document.getElementById" - @val external querySelector: string => Nullable.t = "document.querySelector" - @val external querySelectorAll: string => array = "document.querySelectorAll" - @val external createElement: string => Element.t = "document.createElement" - @val external body: Element.t = "document.body" - @val external head: Element.t = "document.head" - @val external readyState: string = "document.readyState" - @val external addEventListener: (string, unit => unit) => unit = "document.addEventListener" -} - -module Window = { - @val external addEventListener: (string, unit => unit) => unit = "window.addEventListener" - @val external dispatchEvent: 'a => unit = "window.dispatchEvent" -} - -module Console = { - @val external log: 'a => unit = "console.log" - @val external warn: 'a => unit = "console.warn" - @val external error: 'a => unit = "console.error" -} - -module Navigator = { - module Clipboard = { - @val external writeText: string => Js.Promise.t = "navigator.clipboard.writeText" - } -} - -module Performance = { - @val external now: unit => float = "performance.now" -} - -module Math = { - @val external floor: float => int = "Math.floor" - @val external random: unit => float = "Math.random" - @val external abs: int => int = "Math.abs" -}