diff --git a/example-app/app/pages/bench.vue b/example-app/app/pages/bench.vue new file mode 100644 index 0000000..abbb9e3 --- /dev/null +++ b/example-app/app/pages/bench.vue @@ -0,0 +1,187 @@ + + + \ No newline at end of file diff --git a/example-app/app/pages/index.vue.old b/example-app/app/pages/index.vue.old deleted file mode 100644 index e1609f6..0000000 --- a/example-app/app/pages/index.vue.old +++ /dev/null @@ -1,411 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example-app/app/utils/worker-name.ts b/example-app/app/utils/worker-name.ts deleted file mode 100644 index 7d7e09f..0000000 --- a/example-app/app/utils/worker-name.ts +++ /dev/null @@ -1,6 +0,0 @@ -const adjectives = ['swift', 'silent', 'hidden', 'clever', 'brave', 'sharp', 'shadow', 'crimson', 'bright', 'quiet', 'loud', 'happy', 'dark', 'evil', 'good', 'intelligent', 'lovely', 'mysterious', 'peaceful', 'powerful', 'pure', 'quiet', 'shiny', 'sleepy', 'strong', 'sweet', 'tall', 'warm', 'gentle', 'kind', 'nice', 'polite', 'rough', 'rude', 'scary', 'shy', 'silly', 'smart', 'strange', 'tough', 'ugly', 'vivid', 'wicked', 'wise', 'young', 'sleepy']; -const nouns = ['fox', 'river', 'stone', 'cipher', 'link', 'comet', 'falcon', 'signal', 'anchor', 'spark', 'stone', 'comet', 'rocket', 'snake', 'snail', 'shark', 'elephant', 'cat', 'dog', 'whale', 'orca', 'cactus', 'flower', 'frog', 'toad', 'apple', 'strawberry', 'raspberry', 'lemon', 'bot', 'gopher', 'dinosaur', 'racoon', 'penguin', 'chameleon', 'atom', 'particle', 'witch', 'wizard', 'warlock', 'deer'] - -export function getWorkerName() { - return `${adjectives[Math.floor(Math.random() * adjectives.length)]}-${nouns[Math.floor(Math.random() * nouns.length)]}`; -} \ No newline at end of file diff --git a/example-app/app/utils/worker.ts b/example-app/app/utils/worker.ts deleted file mode 100644 index b5d05db..0000000 --- a/example-app/app/utils/worker.ts +++ /dev/null @@ -1,170 +0,0 @@ -// This worker just sits on another thread and waits for message to solve -// challenges so that we dont block the render thread - -import { - type WorkerRequest, - type SolutionMessage, - WorkerMessageType, - WorkerResponseType, - ChallengeStrategy, -} from "~/types/pow"; - -const worker_name = getWorkerName(); -let solver: SolverModule | null = null; - -let atomic_nonce: Int32Array | null = null; -let atomic_solution: Int32Array | null = null; - -async function loadWasmSolver(module: WebAssembly.Module) { - if (atomic_nonce === null || atomic_solution === null) { - throw createError("Atomics not initialized"); - } - - console.debug(`[${worker_name}]: Loading WASM solver`); - - solver = await WebAssembly.instantiate(module, { - env: { - __get_solution: () => Atomics.load(atomic_solution!, 0), - __set_solution: (value: number) => Atomics.store(atomic_solution!, 0, value), - __cmpxchg_solution: (expected: number, replacement: number) => Atomics.compareExchange(atomic_solution!, 0, expected, replacement), - __fetch_add_nonce: (value: number) => Atomics.add(atomic_nonce!, 0, value), - __log: (ptr: number, len: number) => { - const string_data = new Uint8Array(solver!.exports.memory.buffer, ptr, len); - console.log(`[${worker_name}]: ${new TextDecoder().decode(string_data)}`); - }, - } - }) as unknown as SolverModule; - console.debug(`[${worker_name}]: WASM solver loaded`); -} - -onmessage = async (event: MessageEvent) => { - if (event.data.type === WorkerMessageType.Init) { - console.log(`[${worker_name}]: Initializing...`); - - atomic_nonce = new Int32Array(event.data.sab, 0, 1); - atomic_solution = new Int32Array(event.data.sab, 4, 1); - - try { - await loadWasmSolver(event.data.module); - } catch (error: any) { - console.error(`[${worker_name}]: Failed to load WASM solver:`, error); - postMessage({ - type: WorkerResponseType.Error, - error: `Could not load WASM solver: ${error.message}`, - } as SolutionMessage); - return; - } - - if (!solver) { - console.error(`[${worker_name}]: Failed to load WASM solver`); - postMessage({ - type: WorkerResponseType.Error, - error: "Failed to load WASM solver", - } as SolutionMessage); - return; - } - - postMessage({ - type: WorkerResponseType.Ok, - } as SolutionMessage); - return; - } - - if (!solver) { - postMessage({ - type: WorkerResponseType.Error, - error: "WASM solver not loaded", - } as SolutionMessage); - return; - } - - const { strategy } = event.data; - - const encoder = new TextEncoder(); - - let solution: number; - let target: string = event.data.target; - let target_bytes, target_ptr; - let memory; - switch (strategy) { - case ChallengeStrategy.LeadingZeroes: - const { difficulty } = event.data; - console.debug(`[${worker_name}]: recieved ${strategy} challenge: ${target}, difficulty: ${difficulty}`); - - target_bytes = encoder.encode(target); - - target_ptr = solver.exports.malloc(target_bytes.length); - if (target_ptr === 0 || target_ptr === null) { - console.error(`[${worker_name}]: Failed to allocate memory for challenge string`); - postMessage({ - type: WorkerResponseType.Error, - error: "Failed to allocate memory for challenge string", - } as SolutionMessage); - return; - } - - memory = new Uint8Array(solver.exports.memory.buffer); - memory.set(target_bytes, target_ptr); - - solution = solver.exports.solve_leaading_zeroes_challenge( - target_ptr, - target.length, - difficulty, - ); - console.debug(`[${worker_name}]: WASM solver found nonce: ${solution}`); - break; - case ChallengeStrategy.TargetNumber: - const { salt } = event.data; - console.debug(`[${worker_name}]: recieved ${strategy} challenge: ${target}, salt: ${salt}`); - - const salt_bytes = encoder.encode(salt); - target_bytes = encoder.encode(target); - - const salt_ptr = solver.exports.malloc(salt_bytes.length); - if (salt_ptr === 0 || salt_ptr === null) { - console.error(`[${worker_name}]: Failed to allocate memory for salt string`); - postMessage({ - type: WorkerResponseType.Error, - error: "Failed to allocate memory for salt string", - } as SolutionMessage); - return; - } - - target_ptr = solver.exports.malloc(target_bytes.length); - if (target_ptr === 0 || target_ptr === null) { - console.error(`[${worker_name}]: Failed to allocate memory for target string`); - postMessage({ - type: WorkerResponseType.Error, - error: "Failed to allocate memory for target string", - } as SolutionMessage); - return; - } - - memory = new Uint8Array(solver.exports.memory.buffer); - memory.set(salt_bytes, salt_ptr); - memory.set(target_bytes, target_ptr); - - solution = solver.exports.solve_target_number_challenge( - target_ptr, - target_bytes.length, - salt_ptr, - salt_bytes.length, - ); - console.debug(`[${worker_name}]: WASM solver found nonce: ${solution}`); - - break; - } - - // we are just assuming that if its less than -1, its the min i32 - if (solution < 0) { - return postMessage({ - type: WorkerResponseType.Error, - error: "failed to solve challenge", - } as SolutionMessage); - } - - postMessage({ - type: WorkerResponseType.Solution, - nonce: solution === -1 ? null : solution.toString() - } as SolutionMessage); -}; diff --git a/example-app/config.toml b/example-app/config.toml index 7860602..b4ecde7 100644 --- a/example-app/config.toml +++ b/example-app/config.toml @@ -1,4 +1,4 @@ -algorithm = "argon2id" +algorithm = "argon2" strategy = "target_number" [leading_zeroes] diff --git a/example-app/server/api/pow/algorithm.get.ts b/example-app/server/api/pow/algorithm.get.ts new file mode 100644 index 0000000..5a8a58e --- /dev/null +++ b/example-app/server/api/pow/algorithm.get.ts @@ -0,0 +1,7 @@ +import { defineEventHandler } from 'h3' + +export default defineEventHandler((event) => { + return { + algorithm: config.algorithm + } +}) \ No newline at end of file diff --git a/example-app/server/api/pow/algorithm.put.ts b/example-app/server/api/pow/algorithm.put.ts new file mode 100644 index 0000000..4d88e10 --- /dev/null +++ b/example-app/server/api/pow/algorithm.put.ts @@ -0,0 +1,43 @@ +import { defineEventHandler } from 'h3' +import { ChallengeAlgorithm } from '@impost/lib'; +import * as z from 'zod'; + +const algorithmSchema = z.object({ + algorithm: z.enum(ChallengeAlgorithm), +}); + +export default defineEventHandler(async (event) => { + const body = await readValidatedBody(event, algorithmSchema.safeParse); + + if (!body.success) { + throw createError({ + statusCode: 400, + statusMessage: 'Validation failed' + }) + } + + switch (body.data.algorithm) { + case 'sha256': + case 'argon2': + config.algorithm = body.data.algorithm; + config.strategy = config.strategy || 'leading_zeroes'; + switch (config.strategy) { + case 'leading_zeroes': + config.leading_zeroes.difficulty = config.leading_zeroes.difficulty || 4; + break; + case 'target_number': + config.target_number.max_number = config.target_number.max_number || 10_000; + break; + } + break; + case 'kctf': + config.algorithm = body.data.algorithm; + config.kctf = config.kctf || {}; + config.kctf.difficulty = config.kctf.difficulty || 100; + break; + } + + return { + message: `Algorithm set to ${config.algorithm}` + }; +}); \ No newline at end of file diff --git a/example-app/server/api/pow/challenge.get.ts b/example-app/server/api/pow/challenge.get.ts index 4b708c4..c63c346 100644 --- a/example-app/server/api/pow/challenge.get.ts +++ b/example-app/server/api/pow/challenge.get.ts @@ -27,11 +27,11 @@ export default defineEventHandler(async () => { break; } break; - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: switch (config.strategy) { case ChallengeStrategy.LeadingZeroes: challenge_config = { - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.LeadingZeroes, difficulty: config.leading_zeroes.difficulty, parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS }, @@ -39,7 +39,7 @@ export default defineEventHandler(async () => { break; case ChallengeStrategy.TargetNumber: challenge_config = { - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.TargetNumber, difficulty: config.target_number.max_number, parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS }, diff --git a/example-app/server/api/pow/challenge.post.ts b/example-app/server/api/pow/challenge.post.ts index 02efaee..0217732 100644 --- a/example-app/server/api/pow/challenge.post.ts +++ b/example-app/server/api/pow/challenge.post.ts @@ -5,7 +5,7 @@ import { outstandingChallenges } from '~~/server/utils/pow'; const challengeSchema = z.object({ salt: z.string(), - // either a string if the algorithm is kCTF, or a number if the algorithm is Argon2id or SHA256 + // either a string if the algorithm is kCTF, or a number if the algorithm is Argon2 or SHA256 solution: z.string().or(z.number()), }) diff --git a/example-app/server/api/pow/difficulty.get.ts b/example-app/server/api/pow/difficulty.get.ts index 7c021d2..44dbb37 100644 --- a/example-app/server/api/pow/difficulty.get.ts +++ b/example-app/server/api/pow/difficulty.get.ts @@ -1,15 +1,25 @@ import { defineEventHandler } from 'h3' -import { ChallengeStrategy } from '@impost/lib'; +import { ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib'; export default defineEventHandler((event) => { let difficulty: number; - switch (config.strategy) { - case ChallengeStrategy.LeadingZeroes: - difficulty = config.leading_zeroes.difficulty!; + console.log("CONFIG", config); + + switch (config.algorithm) { + case ChallengeAlgorithm.SHA256: + case ChallengeAlgorithm.Argon2: + switch (config.strategy) { + case ChallengeStrategy.LeadingZeroes: + difficulty = config.leading_zeroes.difficulty!; + break; + case ChallengeStrategy.TargetNumber: + difficulty = config.target_number.max_number!; + break; + } break; - case ChallengeStrategy.TargetNumber: - difficulty = config.target_number.max_number!; + case ChallengeAlgorithm.kCTF: + difficulty = config.kctf.difficulty!; break; } diff --git a/example-app/server/api/pow/difficulty.put.ts b/example-app/server/api/pow/difficulty.put.ts index 8c1a632..706ef20 100644 --- a/example-app/server/api/pow/difficulty.put.ts +++ b/example-app/server/api/pow/difficulty.put.ts @@ -1,35 +1,43 @@ import { defineEventHandler } from 'h3' -import { ChallengeStrategy } from '@impost/lib'; +import { ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib'; export default defineEventHandler(async (event) => { const body = await readBody(event) let difficulty = body.difficulty; - switch (config.strategy) { - case ChallengeStrategy.LeadingZeroes: - if (!difficulty || difficulty < 1 || difficulty > 64) { - throw createError({ - statusCode: 400, - statusMessage: 'Invalid request', - }); - } + switch (config.algorithm) { + case ChallengeAlgorithm.SHA256: + case ChallengeAlgorithm.Argon2: + switch (config.strategy) { + case ChallengeStrategy.LeadingZeroes: + if (!difficulty || difficulty < 1 || difficulty > 64) { + throw createError({ + statusCode: 400, + statusMessage: 'Invalid request', + }); + } - config.leading_zeroes.difficulty = difficulty; + config.leading_zeroes.difficulty = difficulty; + break; + case ChallengeStrategy.TargetNumber: + if (!difficulty || difficulty < 1 || difficulty > 100_000_000) { + throw createError({ + statusCode: 400, + statusMessage: 'Invalid request', + }); + } + + config.target_number.max_number = difficulty; + break; + } break; - case ChallengeStrategy.TargetNumber: - if (!difficulty || difficulty < 1 || difficulty > 100_000_000) { - throw createError({ - statusCode: 400, - statusMessage: 'Invalid request', - }); - } - - config.target_number.max_number = difficulty; + case ChallengeAlgorithm.kCTF: + config.kctf.difficulty = difficulty; break; } return { - message: 'Challenge difficulty set' + message: `Challenge difficulty set to ${difficulty}` }; }); \ No newline at end of file diff --git a/example-app/server/api/pow/index.get.ts b/example-app/server/api/pow/index.get.ts new file mode 100644 index 0000000..8a3661d --- /dev/null +++ b/example-app/server/api/pow/index.get.ts @@ -0,0 +1,35 @@ +import { ChallengeAlgorithm } from '@impost/lib'; +import { defineEventHandler } from 'h3' + +export default defineEventHandler((event) => { + let difficulty: number; + + switch (config.algorithm) { + case ChallengeAlgorithm.SHA256: + case ChallengeAlgorithm.Argon2: + switch (config.strategy) { + case 'leading_zeroes': + difficulty = config.leading_zeroes.difficulty!; + break; + case 'target_number': + difficulty = config.target_number.max_number!; + break; + } + break; + case ChallengeAlgorithm.kCTF: + difficulty = config.kctf.difficulty!; + break; + default: + throw createError({ + statusCode: 500, + statusMessage: 'Unknown algorithm', + }) + break; + } + + return { + difficulty, + algorithm: config.algorithm, + strategy: config.strategy || undefined, + } +}) \ No newline at end of file diff --git a/example-app/server/api/pow/strategy.get.ts b/example-app/server/api/pow/strategy.get.ts new file mode 100644 index 0000000..917ae68 --- /dev/null +++ b/example-app/server/api/pow/strategy.get.ts @@ -0,0 +1,14 @@ +import { defineEventHandler } from "h3"; +import { ChallengeAlgorithm } from "@impost/lib"; + +export default defineEventHandler(async (event) => { + if (config.algorithm === ChallengeAlgorithm.kCTF) { + return { + strategy: undefined, + } + } + + return { + strategy: config.strategy, + } +}); \ No newline at end of file diff --git a/example-app/server/api/pow/strategy.put.ts b/example-app/server/api/pow/strategy.put.ts new file mode 100644 index 0000000..cb87ce0 --- /dev/null +++ b/example-app/server/api/pow/strategy.put.ts @@ -0,0 +1,43 @@ +import { defineEventHandler } from 'h3' +import { ChallengeAlgorithm, ChallengeStrategy } from '@impost/lib'; +import * as z from 'zod'; + +const strategySchema = z.object({ + strategy: z.enum(ChallengeStrategy), +}); + +export default defineEventHandler(async (event) => { + const body = await readValidatedBody(event, strategySchema.safeParse); + + if (!body.success) { + throw createError({ + statusCode: 400, + statusMessage: 'Validation failed' + }) + } + + switch (config.algorithm) { + case ChallengeAlgorithm.SHA256: + case ChallengeAlgorithm.Argon2: + config.strategy = body.data.strategy; + switch (config.strategy) { + case ChallengeStrategy.LeadingZeroes: + config.leading_zeroes = config.leading_zeroes || {}; + config.leading_zeroes.difficulty = config.leading_zeroes.difficulty || 4; + break; + case ChallengeStrategy.TargetNumber: + config.target_number = config.target_number || {}; + config.target_number.max_number = config.target_number.max_number || 10_000; + break; + } + break; + case ChallengeAlgorithm.kCTF: + return { + message: "Strategy cannot be set for kCTF" + } + } + + return { + message: `Strategy set to ${config.strategy}` + }; +}); \ No newline at end of file diff --git a/example-app/server/utils/config.ts b/example-app/server/utils/config.ts index 0a922c3..a477ced 100644 --- a/example-app/server/utils/config.ts +++ b/example-app/server/utils/config.ts @@ -20,16 +20,16 @@ const SHA256Schema = z.discriminatedUnion("strategy", [ }), ]); -const Argon2idSchema = z.discriminatedUnion("strategy", [ +const Argon2Schema = z.discriminatedUnion("strategy", [ z.object({ - algorithm: z.literal(ChallengeAlgorithm.Argon2id), + algorithm: z.literal(ChallengeAlgorithm.Argon2), strategy: z.literal(ChallengeStrategy.LeadingZeroes), leading_zeroes: z.object({ difficulty: z.number().int().min(1).max(64), }), }), z.object({ - algorithm: z.literal(ChallengeAlgorithm.Argon2id), + algorithm: z.literal(ChallengeAlgorithm.Argon2), strategy: z.literal(ChallengeStrategy.TargetNumber), target_number: z.object({ max_number: z.number().int().min(1).max(100_000), @@ -44,7 +44,7 @@ const KCTFSchema = z.object({ }), }); -export const Config = z.union([SHA256Schema, Argon2idSchema, KCTFSchema]); +export const Config = z.union([SHA256Schema, Argon2Schema, KCTFSchema]); export type Config = z.infer; diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 5f82633..b7681f0 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -2,7 +2,7 @@ import { UUID } from "uuidv7"; export enum ChallengeAlgorithm { SHA256 = "sha256", - Argon2id = "argon2id", + Argon2 = "argon2", kCTF = "kctf", } @@ -10,7 +10,7 @@ export function algorithmToInt(algorithm: ChallengeAlgorithm): number { switch (algorithm) { case ChallengeAlgorithm.SHA256: return 0; - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: return 1; case ChallengeAlgorithm.kCTF: return 2; @@ -37,7 +37,7 @@ export function strategyToInt(strategy: ChallengeStrategy): number { // In this case, the client will repeatedly hash a number with has until it // finds a hash thaat starts with *difficulty* leading zeroes export interface ChallengeLeadingZeroes { - algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; + algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.LeadingZeroes; salt: string; // random string difficulty: number; @@ -46,7 +46,7 @@ export interface ChallengeLeadingZeroes { // In this case, the server generates a random number, and the client will hash // the salt (a random string) + a random number until it finds a hash that is equal to challenge export interface ChallengeTargetNumber { - algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; + algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.TargetNumber; salt: string; // random string target: string; // hash of salt + random number diff --git a/packages/lib/src/solver.ts b/packages/lib/src/solver.ts index 2cd4b1a..e8ba6e1 100644 --- a/packages/lib/src/solver.ts +++ b/packages/lib/src/solver.ts @@ -31,14 +31,14 @@ export async function init_solver(env: SolverEnv, module: WebAssembly.Module): P } type Argon2LeadingZeroesParams = { - name: ChallengeAlgorithm.Argon2id; + name: ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.LeadingZeroes; salt: string; difficulty: number; }; type Argon2TargetNumberParams = { - name: ChallengeAlgorithm.Argon2id; + name: ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.TargetNumber; salt: string; target: string; @@ -90,7 +90,7 @@ export function solve(solver: SolverModule, algorithm: SolveParams): string | nu let ret: string | number; switch (algorithm.name) { case ChallengeAlgorithm.SHA256: - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: switch (algorithm.strategy) { case ChallengeStrategy.LeadingZeroes: { ret = solver.exports.solve(algorithmToInt(algorithm.name), strategyToInt(ChallengeStrategy.LeadingZeroes), salt_ptr, salt_buf.length, algorithm.difficulty, 0, 0); diff --git a/packages/lib/src/validator.ts b/packages/lib/src/validator.ts index 215a838..1aebb08 100644 --- a/packages/lib/src/validator.ts +++ b/packages/lib/src/validator.ts @@ -31,7 +31,7 @@ export interface SHA256ChallengeConfig { } export interface Argon2ChallengeConfig { - algorithm: ChallengeAlgorithm.Argon2id; + algorithm: ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.LeadingZeroes | ChallengeStrategy.TargetNumber; difficulty: number; parameters: Object; @@ -69,8 +69,8 @@ async function encode_challenge(inner_challenge: InnerChallenge, parameters: Obj } break; } - case ChallengeAlgorithm.Argon2id: { - challenge.algorithm = ChallengeAlgorithm.Argon2id; + case ChallengeAlgorithm.Argon2: { + challenge.algorithm = ChallengeAlgorithm.Argon2; challenge.salt = inner_challenge.salt; switch (inner_challenge.strategy) { case ChallengeStrategy.LeadingZeroes: { @@ -130,7 +130,7 @@ export async function generate_challenge(config: ChallengeConfig): Promise 64) { @@ -235,7 +235,7 @@ export async function validate_challenge(challenge: Challenge, challenge_solutio switch (challenge.algorithm) { case ChallengeAlgorithm.SHA256: if (typeof challenge_solution === "string") { - throw new Error("Argon2id challenges do not support a solution as a number"); + throw new Error("Argon2 challenges do not support a solution as a number"); } switch (challenge.strategy) { @@ -254,9 +254,9 @@ export async function validate_challenge(challenge: Challenge, challenge_solutio return validator.exports.validate(algorithmToInt(challenge.algorithm), strategyToInt(challenge.strategy), challenge_ptr, challenge_buf.length, solution_ptr, solution_buf.length, challenge_solution, 0); } - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: if (typeof challenge_solution === "string") { - throw new Error("Argon2id challenges do not support a solution as a number"); + throw new Error("Argon2 challenges do not support a solution as a number"); } switch (challenge.strategy) { diff --git a/packages/widget/src/pow-captcha.ts b/packages/widget/src/pow-captcha.ts index 0bcf6d0..62fd9f4 100644 --- a/packages/widget/src/pow-captcha.ts +++ b/packages/widget/src/pow-captcha.ts @@ -109,6 +109,9 @@ export class PowCaptcha extends LitElement { this.initWorkers(); + this.addEventListener('reset', (ev) => this.reset(ev as CustomEvent)); + this.addEventListener('solve', () => this.solveChallenge()); + switch (this.auto) { case 'onload': this.solveChallenge(); @@ -139,6 +142,19 @@ export class PowCaptcha extends LitElement { } } + reset(ev: CustomEvent) { + this.challengejson = JSON.stringify(ev.detail.challenge); + this.challengeData = null; + this.status = 'unsolved'; + this.solution = ''; + + console.log("received reset event"); + + this.fetchChallenge(); + + console.log(this.challengeData); + } + getCurrentWorkingNonce() { return Atomics.load(new Uint32Array(this.sab), 0); } @@ -177,10 +193,6 @@ export class PowCaptcha extends LitElement { this.solverWorkers.push(new ChallengeWorker()); } - const atomics_view = new Int32Array(this.sab); - Atomics.store(atomics_view, 0, 0); - Atomics.store(atomics_view, 1, 0); - let wasm_module = await get_wasm_module(); let worker_promises: Promise[] = []; for (let i = 0; i < this.solverWorkers.length; i++) { @@ -296,11 +308,11 @@ export class PowCaptcha extends LitElement { break; } break; - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: switch (request.strategy) { case ChallengeStrategy.LeadingZeroes: worker.postMessage({ - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.LeadingZeroes, salt: request.salt, difficulty: request.difficulty, @@ -308,7 +320,7 @@ export class PowCaptcha extends LitElement { break; case ChallengeStrategy.TargetNumber: worker.postMessage({ - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.TargetNumber, target: request.target, salt: request.salt, @@ -354,22 +366,6 @@ export class PowCaptcha extends LitElement { let request: ChallengeSolveRequest; - // switch (this.challengeData.strategy) { - // case ChallengeStrategy.LeadingZeroes: - // request = { - // strategy: ChallengeStrategy.LeadingZeroes, - // salt: this.challengeData.salt, - // difficulty: this.challengeData.difficulty, - // }; - // break; - // case ChallengeStrategy.TargetNumber: - // request = { - // strategy: ChallengeStrategy.TargetNumber, - // target: this.challengeData.target, - // salt: this.challengeData.salt, - // }; - // break; - // } switch (this.challengeData.algorithm) { case ChallengeAlgorithm.SHA256: switch (this.challengeData.strategy) { @@ -391,11 +387,11 @@ export class PowCaptcha extends LitElement { break; } break; - case ChallengeAlgorithm.Argon2id: + case ChallengeAlgorithm.Argon2: switch (this.challengeData.strategy) { case ChallengeStrategy.LeadingZeroes: request = { - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.LeadingZeroes, salt: this.challengeData.salt, difficulty: this.challengeData.difficulty, @@ -403,7 +399,7 @@ export class PowCaptcha extends LitElement { break; case ChallengeStrategy.TargetNumber: request = { - algorithm: ChallengeAlgorithm.Argon2id, + algorithm: ChallengeAlgorithm.Argon2, strategy: ChallengeStrategy.TargetNumber, target: this.challengeData.target, salt: this.challengeData.salt, diff --git a/packages/widget/src/solver-worker.ts b/packages/widget/src/solver-worker.ts index 35bb9aa..f2da531 100644 --- a/packages/widget/src/solver-worker.ts +++ b/packages/widget/src/solver-worker.ts @@ -68,6 +68,7 @@ onmessage = async (event: MessageEvent) => { }; switch (event.data.algorithm) { + case ChallengeAlgorithm.Argon2: case ChallengeAlgorithm.SHA256: switch (event.data.strategy) { case ChallengeStrategy.LeadingZeroes: @@ -84,22 +85,6 @@ onmessage = async (event: MessageEvent) => { break; } break; - case ChallengeAlgorithm.Argon2id: - switch (event.data.strategy) { - case ChallengeStrategy.LeadingZeroes: - // @ts-ignore - params.strategy = ChallengeStrategy.LeadingZeroes; - // @ts-ignore - params.difficulty = event.data.difficulty; - break; - case ChallengeStrategy.TargetNumber: - // @ts-ignore - params.strategy = ChallengeStrategy.TargetNumber; - // @ts-ignore - params.target = event.data.target; - break; - } - break; case ChallengeAlgorithm.kCTF: // @ts-ignore params.strategy = ChallengeStrategy.Null; @@ -111,6 +96,7 @@ onmessage = async (event: MessageEvent) => { solution = solve(solver, params as SolveParams); if (event.data.algorithm !== ChallengeAlgorithm.kCTF) { + console.log(Atomics.load(atomic_nonce!, 0)); solution = Atomics.load(atomic_solution!, 0); } } catch (error: any) { diff --git a/packages/widget/src/types/worker.ts b/packages/widget/src/types/worker.ts index c076f29..6601908 100644 --- a/packages/widget/src/types/worker.ts +++ b/packages/widget/src/types/worker.ts @@ -13,7 +13,7 @@ interface WorkerInitRequest { } interface ChallengeLeadingZeroesSolveRequest { - algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; + algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.LeadingZeroes; salt: string; difficulty: number; @@ -24,7 +24,7 @@ interface WorkerChallengeLeadingZeroesSolveRequest extends ChallengeLeadingZeroe } interface ChallengeTargetNumberSolveRequest { - algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; + algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2; strategy: ChallengeStrategy.TargetNumber; target: string; salt: string; diff --git a/solver/src/algorithms/argon2.zig b/solver/src/algorithms/argon2.zig index c62d9ab..8d9089f 100644 --- a/solver/src/algorithms/argon2.zig +++ b/solver/src/algorithms/argon2.zig @@ -2,16 +2,16 @@ const std = @import("std"); const Allocator = std.mem.Allocator; var argon2_params = std.crypto.pwhash.argon2.Params{ - .t = 4, // time cost - .m = 256, // memory cost (in KiB) - .p = 1, // parallelism (this doesnt do anything because we are targeting wasm, and we do multithreading differently anyways) + .t = 3, // time cost + .m = 8192, // memory cost (in KiB) + .p = 1, // parallelism }; const dk_len: usize = 32; // 16 or 32 byte key pub fn hash(allocator: Allocator, challenge: []const u8, nonce: []const u8) ![]u8 { const derived = try allocator.alloc(u8, dk_len); - try std.crypto.pwhash.argon2.kdf(allocator, derived, nonce, challenge, argon2_params, .argon2id); + try std.crypto.pwhash.argon2.kdf(allocator, derived, nonce, challenge, argon2_params, .argon2d); return derived; } diff --git a/solver/src/algorithms/kctf.zig b/solver/src/algorithms/kctf.zig index 5273f2a..09c6cbb 100644 --- a/solver/src/algorithms/kctf.zig +++ b/solver/src/algorithms/kctf.zig @@ -77,18 +77,12 @@ pub const Challenge = struct { const salt_str = challenge; const salt_bytes_len = try std.base64.standard.Decoder.calcSizeForSlice(salt_str); - std.log.info("salt_bytes_len: {d}\n", .{salt_bytes_len}); - const salt_bytes = try allocator.alloc(u8, salt_bytes_len); defer allocator.free(salt_bytes); try std.base64.standard.Decoder.decode(salt_bytes, salt_str); - std.log.info("decoded salt: {any}\n", .{salt_bytes}); - const usize_salt_bytes: []align(1) usize = std.mem.bytesAsSlice(usize, salt_bytes); - // TODO: the bytes are being read in as little endian, but need to be read in as big endian - std.log.info("usize_salt_bytes: {any}\n", .{usize_salt_bytes}); try salt.ensureCapacity(usize_salt_bytes.len); @memcpy(salt.limbs[0..usize_salt_bytes.len], usize_salt_bytes); salt.setLen(usize_salt_bytes.len); @@ -118,22 +112,16 @@ pub const Challenge = struct { pub fn solve(self: *Self, allocator: Allocator) ![]u8 { for (0..self.difficulty) |_| { - std.log.info("Solving challenge with difficulty {d}\n", .{self.difficulty}); for (0..1277) |_| { try square_mod(&self.salt); } try self.salt.bitXor(&self.salt, &managed_one.?); } - std.log.info("solved challenge: {any}\n", .{self}); - return try self.encode(allocator); } pub fn verify(self: *Self, allocator: Allocator, solution: *Challenge) !bool { - std.log.info("{d}", .{self.difficulty}); - std.log.info("{any} vs {any}\n", .{ self, solution }); - if (managed_one == null) { managed_one = try Int.init(allocator); try managed_one.?.set(1); @@ -145,8 +133,6 @@ pub const Challenge = struct { try square_mod(&solution.salt); } - std.log.info("{any} vs {any}\n", .{ self, solution }); - // I'm like 99.999% sure this can NEVER happen, but its how the solution that I translated from did it so that's // how I will do it if (self.salt.eql(solution.salt)) { diff --git a/solver/src/solver.zig b/solver/src/solver.zig index 5801c7a..6e686f9 100644 --- a/solver/src/solver.zig +++ b/solver/src/solver.zig @@ -54,9 +54,6 @@ export fn free(ptr: ?*anyopaque, byte_count: usize) void { /// /// If a target is not needed for the strategy, target_ptr and target_len should be 0. export fn solve(algorithm: algorithms.Algorithm, strategy: algorithms.Strategy, salt_ptr: [*]u8, salt_len: usize, difficulty: usize, target_ptr: [*]u8, target_len: usize) isize { - std.log.info("Solve called with difficulty {d}\n", .{difficulty}); - std.log.info("Using algorithm {s} and strategy {s}\n", .{ @tagName(algorithm), @tagName(strategy) }); - switch (algorithm) { algorithms.Algorithm.sha256 => return solve_argon2_or_sha256(salt_ptr, salt_len, difficulty, algorithm, strategy, target_ptr, target_len), algorithms.Algorithm.argon2 => return solve_argon2_or_sha256(salt_ptr, salt_len, difficulty, algorithm, strategy, target_ptr, target_len), @@ -95,10 +92,8 @@ fn solve_argon2_or_sha256(salt_ptr: [*]u8, salt_len: usize, difficulty: usize, a return -1; } - // const max_nonce_iterations: u64 = 1_000_000_000; - const max_nonce_iterations: u64 = 100_000; - - std.log.info("Solve called with salt {s}\n", .{salt_slice}); + const max_nonce_iterations: u64 = 1_000_000_000; + // const max_nonce_iterations: u64 = 100_000; // 64 + 9 digits for nonce since the max nonce is 999_999_999 (not 1 billion since nonce < max_nonce_iterations) var input_buffer: []u8 = allocator.alloc(u8, salt_len + 9) catch { @@ -131,22 +126,16 @@ fn solve_argon2_or_sha256(salt_ptr: [*]u8, salt_len: usize, difficulty: usize, a return -1; }; } else { - input = input_buffer[0 .. salt_len + nonce_str.len]; - } - - const hash_hex_slice = algorithms.SHA256.hash(allocator, input) catch { - std.log.err("Error hashing key", .{}); - return -1; - }; - - if (algorithm == .argon2) { - allocator.free(input); + input = algorithms.SHA256.hash(allocator, input_buffer[0 .. salt_len + nonce_str.len]) catch { + std.log.err("Error hashing salt", .{}); + return -1; + }; } switch (strategy) { .leading_zeros => { - _ = hex_encoder.encode(hash_hex_slice); - allocator.free(hash_hex_slice); + _ = hex_encoder.encode(input); + allocator.free(input); if (hex_encoder.countZeroes(difficulty)) { // Found a solution! if (__cmpxchg_solution(-1, nonce) == -1) { @@ -159,8 +148,8 @@ fn solve_argon2_or_sha256(salt_ptr: [*]u8, salt_len: usize, difficulty: usize, a } }, .target_number => { - const hex = hex_encoder.encode(hash_hex_slice); - allocator.free(hash_hex_slice); + const hex = hex_encoder.encode(input); + allocator.free(input); if (std.mem.eql(u8, hex, target_slice.?)) { // Found a solution! if (__cmpxchg_solution(-1, nonce) == -1) { @@ -191,22 +180,17 @@ fn solve_kctf(value_ptr: [*]u8, value_len: usize, difficulty: usize) isize { const challenge_slice = value_ptr[0..value_len]; - std.log.info("Solve called with challenge {s}\n", .{challenge_slice}); - const challenge = algorithms.kCTF.Challenge.from_string(allocator, challenge_slice, difficulty) catch |err| { std.log.info("Error decoding challenge: {s}\n", .{@errorName(err)}); return -1; }; defer challenge.destroy(allocator); - std.log.info("decoded challenge {any}\n", .{challenge}); const solution = challenge.solve(allocator) catch |err| { std.log.info("Error solving challenge: {s}\n", .{@errorName(err)}); return -1; }; - std.log.info("Solution: {s}\n", .{solution}); - const output_ptr = allocator.alloc(u8, solution.len + 4) catch return 0; var output_slice = output_ptr[0 .. solution.len + 2]; diff --git a/solver/src/validator.zig b/solver/src/validator.zig index dbef391..5465f26 100644 --- a/solver/src/validator.zig +++ b/solver/src/validator.zig @@ -74,24 +74,22 @@ fn validate_argon2_or_sha256(challenge_ptr: [*]u8, challenge_len: usize, nonce: var input: []u8 = undefined; if (algorithm == .argon2) { input = algorithms.Argon2.hash(allocator, input_slice[0..challenge_len], input_slice[challenge_len .. challenge_len + nonce_slice.len]) catch return false; - defer allocator.free(input); } else { - input = input_slice[0 .. challenge_len + nonce_slice.len]; + input = algorithms.SHA256.hash(allocator, input_slice[0 .. challenge_len + nonce_slice.len]) catch return false; } + defer allocator.free(input); var hex_encoder = utils.HexEncoder{}; - const hash_hex_slice = algorithms.SHA256.hash(allocator, input) catch return false; - defer allocator.free(hash_hex_slice); switch (strategy) { .leading_zeros => { - _ = hex_encoder.encode(hash_hex_slice); + _ = hex_encoder.encode(input); if (hex_encoder.countZeroes(difficulty)) { return true; } }, .target_number => { - if (std.mem.eql(u8, hex_encoder.encode(hash_hex_slice), target_slice.?)) { + if (std.mem.eql(u8, hex_encoder.encode(input), target_slice.?)) { return true; } }, @@ -105,18 +103,13 @@ fn validate_kctf(challenge_ptr: [*]u8, challenge_len: usize, solution_ptr: [*]u8 const challenge_buf = challenge_ptr[0..challenge_len]; const solution_buf = solution_ptr[0..solution_len]; - std.log.info("Validate called with challenge {s} and solution {s}\n", .{ challenge_buf, solution_buf }); - const challenge = algorithms.kCTF.Challenge.from_string(allocator, challenge_buf, difficulty) catch return false; - std.log.info("decoded challenge {any}\n", .{challenge}); const solution = algorithms.kCTF.Challenge.from_string(allocator, solution_buf, difficulty) catch return false; defer { challenge.destroy(allocator); solution.destroy(allocator); } - std.log.info("decoded challenge and solution\n", .{}); - const is_valid = challenge.verify(allocator, solution) catch return false; return is_valid; @@ -137,10 +130,7 @@ export fn hash(challenge_ptr: [*]u8, challenge_len: usize, nonce_ptr: [*]u8, non hash_slice = algorithms.SHA256.hash(allocator, input_slice[0 .. challenge_len + nonce_len]) catch return 0; }, algorithms.Algorithm.argon2 => { - const argon_key = algorithms.Argon2.hash(allocator, challenge, nonce) catch return 0; - defer allocator.free(argon_key); - - hash_slice = algorithms.SHA256.hash(allocator, argon_key) catch return 0; + hash_slice = algorithms.Argon2.hash(allocator, challenge, nonce) catch return 0; }, else => return 0, }