diff --git a/public/challenge.json b/public/challenge.json index d66771e..e317988 100644 --- a/public/challenge.json +++ b/public/challenge.json @@ -1,5 +1,5 @@ { "algo": "SHA-256", - "prefixBits": "15", + "prefixBits": 15, "input": "challenge:" } \ No newline at end of file diff --git a/src/challenge.ts b/src/challenge.ts index b9045cd..8bf23d1 100644 --- a/src/challenge.ts +++ b/src/challenge.ts @@ -5,6 +5,5 @@ export type Challenge = { input: string, } -export function validateChallenge(challenge: Challenge): boolean { - return !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input; -} \ No newline at end of file +export const validateChallenge = (challenge: Challenge): boolean => + !!challenge.algo && challenge.prefixBits > 0 && !!challenge.input; \ No newline at end of file diff --git a/src/content.ts b/src/content.ts deleted file mode 100644 index 3c333f2..0000000 --- a/src/content.ts +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/default-styles.css b/src/default-styles.css index afa7db5..6cf1dea 100644 --- a/src/default-styles.css +++ b/src/default-styles.css @@ -2,7 +2,7 @@ width: 300px; height: 70px; padding: 19px; - border: 1px solid grey; + border: 1px solid #888; box-sizing: border-box; font-size: 20px; font-family: sans-serif; @@ -19,18 +19,18 @@ height: 30px; width: 30px; display: inline-block; - background-color: white; + background-color: #fff; cursor: pointer; - border: 1px solid black; + border: 1px solid #000; border-radius: 3px; } .captcha .checkbox:hover { - background-color: lightgrey; + background-color: #eee; } .captcha .checkbox.checked { - background-color: #2196F3; + background-color: #29F; } .captcha .checkbox.checked::before { @@ -41,7 +41,7 @@ height: 15px; top: 2px; left: 9px; - border: white solid; + border: #fff solid; border-width: 0 5px 5px 0; transform: rotate(45deg); } @@ -59,11 +59,11 @@ height: 17px; border: 4px solid; border-radius: 50%; - border-color: black transparent black transparent; - animation: captchaLoading infinite 1s; + border-color: #000 transparent #000 transparent; + animation: _aw_l infinite 1s; } -@keyframes captchaLoading { +@keyframes _aw_l { to { transform: rotate(.5turn) } diff --git a/src/hash.ts b/src/hash.ts index 65c347c..b7060b7 100644 --- a/src/hash.ts +++ b/src/hash.ts @@ -1,6 +1,3 @@ -const hexCharset = "0123456789abcdef".split(""); - -export const sha256: AlgorithmIdentifier = "SHA-256"; declare global { interface Uint8Array { @@ -10,7 +7,7 @@ declare global { } 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 { @@ -27,6 +24,5 @@ Uint8Array.prototype.hasPrefix = function(prefix: Uint8Array, bits: number): boo return true; }; -export async function digest(algo: AlgorithmIdentifier, input: string): Promise { - return new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input))); -} +export const digest = async (algo: AlgorithmIdentifier, input: string): Promise => + new Uint8Array(await crypto.subtle.digest(algo, new TextEncoder().encode(input))); diff --git a/src/main.ts b/src/main.ts index 5903a07..ec36ba9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,16 +1,19 @@ -import {makeSuffix} from "./content"; -import {digest, sha256} from "./hash"; +import {digest} from "./hash"; import "./default-styles.css"; import {Challenge, validateChallenge} from "./challenge"; -async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string): Promise { +const CLASS_CHECKBOX = "checkbox"; +const CLASS_LOADING = "loading"; +const CLASS_CHECKED = "checked"; + +const findHashWithPrefix = async (algo: string, hashPrefixBits: number, inputPrefix: string): Promise => { const hashPrefix = new Uint8Array(Array(Math.ceil(hashPrefixBits / 8)).map(_ => 0)); let iteration = 0; do { - var message = inputPrefix + makeSuffix(iteration++); - var hash = await digest(sha256, message); + var message = inputPrefix + (iteration++).toString(36); + var hash = await digest(algo, message); } while (!hash.hasPrefix(hashPrefix, hashPrefixBits)); return message; @@ -18,54 +21,56 @@ async function findHashWithPrefix(hashPrefixBits: number, inputPrefix: string): function initCaptchaContentAndGetCheckbox(captcha: Element): Element { const checkbox = document.createElement("div"); - checkbox.classList.add("checkbox"); - captcha.append(checkbox); + checkbox.classList.add(CLASS_CHECKBOX, CLASS_LOADING); const text = document.createElement("span"); text.innerText = "I am not a robot"; - captcha.append(text); + + captcha.append(checkbox, text); 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 { return async function() { 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); - this.classList.remove("loading"); - this.classList.add("checked"); + toggleLoading(this); + toggleChecked(this); } } -async function prepareCaptcha(captcha: Element) { +const prepareCaptcha = async (captcha: Element) => { const challengeUrl = captcha.getAttribute("data-challenge-url"); if (!challengeUrl) { - console.warn("No challenge URL found."); - return; + throw "No challenge URL found."; } const checkbox = initCaptchaContentAndGetCheckbox(captcha); - checkbox.classList.add("loading"); const challengeResponse = await fetch(challengeUrl); const challenge = await challengeResponse.json() as Challenge; if (!validateChallenge(challenge)) { - console.warn("Challenge is invalid."); - return; + throw "Challenge is invalid."; } - checkbox.classList.remove("loading"); + toggleLoading(checkbox); checkbox.addEventListener("click", prepareChallengeExecution(challenge)); } -window.addEventListener("load", () => { - console.log("load"); - [...document.getElementsByClassName("captcha")].forEach(prepareCaptcha); -}); +window.addEventListener("load", () => + [...document.getElementsByClassName("captcha")].forEach(prepareCaptcha) +);