reduce size

This commit is contained in:
overflowerror 2024-08-16 23:06:37 +02:00
parent 52309a8e21
commit c32124b3fc
6 changed files with 43 additions and 61 deletions

View file

@ -1,5 +1,5 @@
{ {
"algo": "SHA-256", "algo": "SHA-256",
"prefixBits": "15", "prefixBits": 15,
"input": "challenge:" "input": "challenge:"
} }

View file

@ -5,6 +5,5 @@ export type Challenge = {
input: string, input: string,
} }
export function validateChallenge(challenge: Challenge): boolean { export const validateChallenge = (challenge: Challenge): boolean =>
return !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input; !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input;
}

View file

@ -1,18 +0,0 @@
const suffixCharset = [
"0123456789",
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"+-",
].flatMap(set => set.split(""));
const suffixCharsetBits = 6;
export function makeSuffix(index: number): string {
let result = "";
while (index > 0) {
result += suffixCharset[index & ((1 << suffixCharsetBits) - 1)];
index >>= suffixCharsetBits;
}
return result;
}

View file

@ -2,7 +2,7 @@
width: 300px; width: 300px;
height: 70px; height: 70px;
padding: 19px; padding: 19px;
border: 1px solid grey; border: 1px solid #888;
box-sizing: border-box; box-sizing: border-box;
font-size: 20px; font-size: 20px;
font-family: sans-serif; font-family: sans-serif;
@ -19,18 +19,18 @@
height: 30px; height: 30px;
width: 30px; width: 30px;
display: inline-block; display: inline-block;
background-color: white; background-color: #fff;
cursor: pointer; cursor: pointer;
border: 1px solid black; border: 1px solid #000;
border-radius: 3px; border-radius: 3px;
} }
.captcha .checkbox:hover { .captcha .checkbox:hover {
background-color: lightgrey; background-color: #eee;
} }
.captcha .checkbox.checked { .captcha .checkbox.checked {
background-color: #2196F3; background-color: #29F;
} }
.captcha .checkbox.checked::before { .captcha .checkbox.checked::before {
@ -41,7 +41,7 @@
height: 15px; height: 15px;
top: 2px; top: 2px;
left: 9px; left: 9px;
border: white solid; border: #fff solid;
border-width: 0 5px 5px 0; border-width: 0 5px 5px 0;
transform: rotate(45deg); transform: rotate(45deg);
} }
@ -59,11 +59,11 @@
height: 17px; height: 17px;
border: 4px solid; border: 4px solid;
border-radius: 50%; border-radius: 50%;
border-color: black transparent black transparent; border-color: #000 transparent #000 transparent;
animation: captchaLoading infinite 1s; animation: _aw_l infinite 1s;
} }
@keyframes captchaLoading { @keyframes _aw_l {
to { to {
transform: rotate(.5turn) transform: rotate(.5turn)
} }

View file

@ -1,6 +1,3 @@
const hexCharset = "0123456789abcdef".split("");
export const sha256: AlgorithmIdentifier = "SHA-256";
declare global { declare global {
interface Uint8Array { interface Uint8Array {
@ -10,7 +7,7 @@ declare global {
} }
Uint8Array.prototype.toHex = function(): string { Uint8Array.prototype.toHex = function(): string {
return [...this].map(c => hexCharset[c >> 4] + hexCharset[c & 15]).join(""); return [...this].map(c => (c >> 4).toString(16) + (c & 15).toString(16)).join("");
}; };
Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boolean { Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boolean {
@ -27,6 +24,5 @@ Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boo
return true; return true;
}; };
export async function digest(algo: AlgorithmIdentifier, input: string): Promise<Uint8Array> { export const digest = async (algo: AlgorithmIdentifier, input: string): Promise<Uint8Array> =>
return new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input))); new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input)));
}

View file

@ -1,16 +1,19 @@
import {makeSuffix} from "./content"; import {digest} from "./hash";
import {digest, sha256} from "./hash";
import "./default-styles.css"; import "./default-styles.css";
import {Challenge, validateChallenge} from "./challenge"; import {Challenge, validateChallenge} from "./challenge";
async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string): Promise<string> { const CLASS_CHECKBOX = "checkbox";
const CLASS_LOADING = "loading";
const CLASS_CHECKED = "checked";
const findHashWithPrefix = async (algo: string, hashPrefixBits: number, inputPrefix: string): Promise<string> => {
const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(_ => 0)); const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(_ => 0));
let iteration = 0; let iteration = 0;
do { do {
var message = inputPrefix + makeSuffix(iteration++); var message = inputPrefix + (iteration++).toString(36);
var hash = await digest(sha256, message); var hash = await digest(algo, message);
} while (!hash.hasPrefix(hashPrefix, hashPrefixBits)); } while (!hash.hasPrefix(hashPrefix, hashPrefixBits));
return message; return message;
@ -18,54 +21,56 @@ async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string):
function initCaptchaContentAndGetCheckbox(captcha: Element): Element { function initCaptchaContentAndGetCheckbox(captcha: Element): Element {
const checkbox = document.createElement("div"); const checkbox = document.createElement("div");
checkbox.classList.add("checkbox"); checkbox.classList.add(CLASS_CHECKBOX, CLASS_LOADING);
captcha.append(checkbox);
const text = document.createElement("span"); const text = document.createElement("span");
text.innerText = "I am not a robot"; text.innerText = "I am not a robot";
captcha.append(text);
captcha.append(checkbox, text);
return checkbox; return checkbox;
} }
const toggleChecked = (checkbox: Element) =>
checkbox.classList.toggle(CLASS_CHECKED);
const toggleLoading = (checkbox: Element) =>
checkbox.classList.toggle(CLASS_LOADING);
function prepareChallengeExecution(challenge: Challenge): () => Promise<void> { function prepareChallengeExecution(challenge: Challenge): () => Promise<void> {
return async function() { return async function() {
console.log("Calculating..."); console.log("Calculating...");
this.classList.add("loading"); toggleLoading(this);
const response = await findHashWithPrefix(challenge.prefixBits, challenge.input); const response = await findHashWithPrefix(challenge.algo, challenge.prefixBits, challenge.input);
console.log("Challenge Response: " + response); console.log("Challenge Response: " + response);
this.classList.remove("loading"); toggleLoading(this);
this.classList.add("checked"); toggleChecked(this);
} }
} }
async function prepareCaptcha(captcha: Element) { const prepareCaptcha = async (captcha: Element) => {
const challengeUrl = captcha.getAttribute("data-challenge-url"); const challengeUrl = captcha.getAttribute("data-challenge-url");
if (!challengeUrl) { if (!challengeUrl) {
console.warn("No challenge URL found."); throw "No challenge URL found.";
return;
} }
const checkbox = initCaptchaContentAndGetCheckbox(captcha); const checkbox = initCaptchaContentAndGetCheckbox(captcha);
checkbox.classList.add("loading");
const challengeResponse = await fetch(challengeUrl); const challengeResponse = await fetch(challengeUrl);
const challenge = await challengeResponse.json() as Challenge; const challenge = await challengeResponse.json() as Challenge;
if (!validateChallenge(challenge)) { if (!validateChallenge(challenge)) {
console.warn("Challenge is invalid."); throw "Challenge is invalid.";
return;
} }
checkbox.classList.remove("loading"); toggleLoading(checkbox);
checkbox.addEventListener("click", prepareChallengeExecution(challenge)); checkbox.addEventListener("click", prepareChallengeExecution(challenge));
} }
window.addEventListener("load", () => { window.addEventListener("load", () =>
console.log("load"); [...document.getElementsByClassName("captcha")].forEach(prepareCaptcha)
[...document.getElementsByClassName("captcha")].forEach(prepareCaptcha); );
});