Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
.check { display: inline-flex; gap: 6px; align-items: center; font-weight: 400; margin: 0; }
#status { margin: 14px 0 0; font-size: 14px; color: var(--text-2); min-height: 1.5em; }
#status.error { color: var(--bad); }
.hint.warn { color: var(--bad); }

.attempt { margin-bottom: 14px; }
.attempt-head {
Expand Down Expand Up @@ -145,6 +146,13 @@
.verdict.no { color: var(--bad); }
.axis { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); margin: -6px 0 14px; }
.empty { color: var(--muted); font-size: 14px; }
.failure {
border: 1px solid var(--bad);
border-radius: 8px;
padding: 10px 12px;
color: var(--bad);
font-size: 14px;
}

.stats {
display: grid;
Expand Down Expand Up @@ -267,6 +275,9 @@ <h2 id="attempts-title">Salt attempts</h2>
const fmt = (n) => Math.round(n).toLocaleString("en-US");

const DEFAULTS = { password: "hunter2", hashName: "sha256", bits: 20, collisions: 5, max: 4000 };
// A low max can make every salt fail. Give up after this many salts instead of the
// library's default of 1,000, which would take minutes to animate.
const MAX_ATTEMPTS = 10;

const select = $("hash-name");
for (const name of HASH_NAMES) select.add(new Option(name, name));
Expand All @@ -290,15 +301,24 @@ <h2 id="attempts-title">Salt attempts</h2>
$("bits-out").value = bits;
$("collisions-out").value = c;
const s = stats.get(`${bits},${c}`);
if (s && !maxEdited) {
$("max").value = Math.ceil((s.mean + s.std) / 10) * 10;
$("max-hint").textContent =
`Salts whose chain runs longer are rejected. Default: mean + 1σ (mean ${fmt(s.mean)}).`;
}
if (!s) return;
if (!maxEdited) $("max").value = Math.ceil((s.mean + s.std) / 10) * 10;
const max = +$("max").value;
const hint = $("max-hint");
let warning = "";
if (max < s.mean - s.std) warning = " Far below the mean: almost every salt will be rejected.";
else if (max < s.mean) warning = " Below the mean: most salts will be rejected.";
hint.textContent =
`Salts whose chain runs longer are rejected. Default: mean + 1σ (mean ${fmt(s.mean)}).` +
warning;
hint.className = warning ? "hint warn" : "hint";
}
$("bits").addEventListener("input", updateParams);
$("collisions").addEventListener("input", updateParams);
$("max").addEventListener("input", () => (maxEdited = true));
$("max").addEventListener("input", () => {
maxEdited = true;
updateParams();
});

// Restores the parameters (not the results). Max goes back to the benchmark-based
// suggestion once the CSV has loaded, or to a fixed fallback before then.
Expand Down Expand Up @@ -360,7 +380,16 @@ <h2 id="attempts-title">Salt attempts</h2>
if (event.type === "done") {
finish(event);
} else if (event.type === "error") {
setStatus(event.message, true);
const noSalt = event.message.includes("no salt met the iteration bounds");
const message = noSalt ? noSaltMessage() : event.message;
setStatus(message, true);
if (noSalt) {
// Also show it under the bars, where the reader is looking.
const box = document.createElement("p");
box.className = "failure";
box.textContent = message;
$("attempts").append(box);
}
$("run").disabled = false;
}
return 0;
Expand Down Expand Up @@ -405,6 +434,18 @@ <h2 id="attempts-title">Salt attempts</h2>
return 0;
}

function noSaltMessage() {
const s = stats.get(`${run.bits},${run.collisions}`);
const typical = s
? ` At b = ${run.bits} and C = ${run.collisions}, a chain needs about ${fmt(s.mean)} ` +
`iterations on average.`
: "";
return (
`None of the ${MAX_ATTEMPTS} salts finished within ${fmt(run.max)} iterations, ` +
`so no hash was stored.${typical} Raise Max iterations, or press Reset to defaults.`
);
}

const pct = (iteration) => Math.min(100, (100 * iteration) / run.max);

function addAttempt(n, salt) {
Expand Down Expand Up @@ -446,7 +487,9 @@ <h2 id="attempts-title">Salt attempts</h2>
$("form").addEventListener("submit", (e) => {
e.preventDefault();
const max = +$("max").value;
run = { max, attempts: 0, kept: 0, total: 0, current: null };
const bits = +$("bits").value;
const collisions = +$("collisions").value;
run = { max, bits, collisions, attempts: 0, kept: 0, total: 0, current: null };
$("attempts").replaceChildren();
$("log").textContent = "";
$("result").hidden = true;
Expand All @@ -456,9 +499,10 @@ <h2 id="attempts-title">Salt attempts</h2>
type: "hash",
password: $("password").value,
hashName: select.value,
bits: +$("bits").value,
collisions: +$("collisions").value,
bits,
collisions,
max,
maxAttempts: MAX_ATTEMPTS,
});
});

Expand Down
7 changes: 5 additions & 2 deletions demo/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ _logger = logging.getLogger("chip")
_logger.addHandler(ForwardToPage())
_logger.setLevel(logging.DEBUG)

def run_hash(password, hash_name, bits, collisions, max_iterations):
def run_hash(password, hash_name, bits, collisions, max_iterations, max_attempts):
global _start
chip = Chip(
bits=bits,
collisions=collisions,
max_iterations=max_iterations,
hash_func=HASH_FUNCS[hash_name],
max_attempts=max_attempts,
)
_start = time.perf_counter()
encoded = hash_password(password, chip)
Expand Down Expand Up @@ -76,7 +77,9 @@ onmessage = async ({ data }) => {
try {
if (data.type === "hash") {
const run = pyodide.globals.get("run_hash");
const result = run(data.password, data.hashName, data.bits, data.collisions, data.max);
const result = run(
data.password, data.hashName, data.bits, data.collisions, data.max, data.maxAttempts,
);
const [encoded, ms] = result.toJs();
result.destroy();
run.destroy();
Expand Down
Loading