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"
-}