Implement algorithm switching

This commit implements every algorithm I have played with so far. It also allows for you to switch which algorithm you want to use at runtime.
This commit is contained in:
Zoe
2025-11-25 18:09:17 +00:00
parent 570531fe32
commit e16383e9b9
20 changed files with 1262 additions and 476 deletions

View File

@@ -1,44 +1,69 @@
import { UUID } from "uuidv7";
export enum ChallengeAlgorithm {
SHA256 = "sha256",
Argon2id = "argon2id",
kCTF = "kctf",
}
export function algorithmToInt(algorithm: ChallengeAlgorithm): number {
switch (algorithm) {
case ChallengeAlgorithm.SHA256:
return 0;
case ChallengeAlgorithm.Argon2id:
return 1;
case ChallengeAlgorithm.kCTF:
return 2;
}
}
export enum ChallengeStrategy {
kCTF = "kctf",
Null = "null",
LeadingZeroes = "leading_zeroes",
TargetNumber = "target_number",
}
export function strategyToInt(strategy: ChallengeStrategy): number {
switch (strategy) {
case ChallengeStrategy.Null:
return 0;
case ChallengeStrategy.LeadingZeroes:
return 1;
case ChallengeStrategy.TargetNumber:
return 2;
}
}
// 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;
// strategy: ChallengeStrategy.LeadingZeroes;
// salt: string; // random string
// difficulty: number;
// }
export interface ChallengeLeadingZeroes {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.LeadingZeroes;
salt: string; // random string
difficulty: number;
}
// // 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;
// strategy: ChallengeStrategy.TargetNumber;
// salt: string; // random string
// target: string; // hash of salt + random number
// }
// 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;
strategy: ChallengeStrategy.TargetNumber;
salt: string; // random string
target: string; // hash of salt + random number
}
export interface InnerChallengekCTF {
strategy: ChallengeStrategy.kCTF;
algorithm: ChallengeAlgorithm.kCTF;
salt: UUID; // UUIDv7
difficulty: number;
}
export interface ChallengekCTF {
strategy: ChallengeStrategy.kCTF;
challenge: string;
algorithm: ChallengeAlgorithm.kCTF;
salt: string;
difficulty: number;
}
export type InnerChallenge = InnerChallengekCTF;
export type InnerChallenge = InnerChallengekCTF | ChallengeLeadingZeroes | ChallengeTargetNumber;
export type Challenge = ChallengekCTF;
export type Challenge = ChallengekCTF | ChallengeLeadingZeroes | ChallengeTargetNumber;

View File

@@ -1,12 +1,10 @@
import { ChallengeAlgorithm, ChallengeStrategy, algorithmToInt, strategyToInt } from "./index";
import WASMSolverUrl from '../../../solver/zig-out/bin/solver.wasm?url&inline';
type WasmExports = Record<string, Function> & {
"malloc": (byte_count: number) => number | null;
"free": (ptr: number | null, byte_count: number) => void;
// "solve_leaading_zeroes_challenge": (challenge_ptr: number, challenge_len: number, difficulty: number) => number;
// "solve_target_number_challenge": (challenge_ptr: number, challenge_len: number, target_ptr: number, target_len:
// number) => number;
"solve": (value_ptr: number, value_len: number) => number,
"solve": (algorithm: number, strategy: number, salt_ptr: number, salt_len: number, difficulty: number, target_ptr: number, target_len: number) => number,
"memory": WebAssembly.Memory;
}
@@ -32,35 +30,100 @@ export async function init_solver(env: SolverEnv, module: WebAssembly.Module): P
}) as unknown as SolverModule;
}
export function solve(solver: SolverModule, challenge: string): string {
console.log(challenge);
type Argon2LeadingZeroesParams = {
name: ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.LeadingZeroes;
salt: string;
difficulty: number;
};
type Argon2TargetNumberParams = {
name: ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.TargetNumber;
salt: string;
target: string;
};
type Argon2Params = Argon2LeadingZeroesParams | Argon2TargetNumberParams;
type SHA256LeadingZeroesParams = {
name: ChallengeAlgorithm.SHA256;
strategy: ChallengeStrategy.LeadingZeroes;
salt: string;
difficulty: number;
};
type SHA256TargetNumberParams = {
name: ChallengeAlgorithm.SHA256;
strategy: ChallengeStrategy.TargetNumber;
salt: string;
target: string;
};
type SHA256Params = SHA256LeadingZeroesParams | SHA256TargetNumberParams;
type KCTFParams = {
name: ChallengeAlgorithm.kCTF;
strategy: ChallengeStrategy.Null;
salt: string;
difficulty: number;
};
export type SolveParams = Argon2Params | SHA256Params | KCTFParams;
export function solve(solver: SolverModule, algorithm: SolveParams): string | number {
if (algorithm.name === ChallengeAlgorithm.kCTF) {
algorithm.salt = algorithm.salt.split("?")[0];
}
const encoder = new TextEncoder();
const challenge_buf = encoder.encode(challenge);
let salt_buf = encoder.encode(algorithm.salt);
const challenge_ptr = solver.exports.malloc(challenge_buf.length);
if (challenge_ptr === 0 || challenge_ptr === null) {
let salt_ptr = solver.exports.malloc(salt_buf.length);
if (salt_ptr === 0 || salt_ptr === null) {
throw new Error("Failed to allocate memory for challenge string");
}
const memory = new Uint8Array(solver.exports.memory.buffer);
memory.set(challenge_buf, challenge_ptr);
let memory = new Uint8Array(solver.exports.memory.buffer);
memory.set(salt_buf, salt_ptr);
const ret = solver.exports.solve(challenge_ptr, challenge_buf.length);
let ret: string | number;
switch (algorithm.name) {
case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2id:
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);
break;
}
case ChallengeStrategy.TargetNumber: {
const target_buf = encoder.encode(algorithm.target);
const target_ptr = solver.exports.malloc(target_buf.length);
if (target_ptr === 0 || target_ptr === null) {
throw new Error("Failed to allocate memory for target string");
}
console.log("RET", ret);
memory = new Uint8Array(solver.exports.memory.buffer);
memory.set(target_buf, target_ptr);
if (ret <= 0) {
throw new Error("Failed to solve challenge");
ret = solver.exports.solve(algorithmToInt(algorithm.name), strategyToInt(ChallengeStrategy.TargetNumber), salt_ptr, salt_buf.length, 0, target_ptr, target_buf.length);
break;
}
}
break;
case ChallengeAlgorithm.kCTF:
const solution_ptr = solver.exports.solve(algorithmToInt(ChallengeAlgorithm.kCTF), strategyToInt(ChallengeStrategy.Null), salt_ptr, salt_buf.length, algorithm.difficulty, 0, 0);
if (solution_ptr <= 0) {
throw new Error("Failed to solve challenge");
}
const length = new DataView(solver.exports.memory.buffer, solution_ptr, 2).getUint16(0, true);
ret = new TextDecoder().decode(solver.exports.memory.buffer.slice(solution_ptr + 2, solution_ptr + 2 + length));
solver.exports.free(solution_ptr, 2 + length);
break;
}
const length = new DataView(solver.exports.memory.buffer, ret, 2).getUint16(0, true);
const solution = new TextDecoder().decode(solver.exports.memory.buffer.slice(ret + 2, ret + 2 + length));
console.log("SOLUTION", solution);
console.log("LENGTH", length);
solver.exports.free(ret, 2 + length);
return solution;
return ret;
}

View File

@@ -1,11 +1,12 @@
import { ChallengeStrategy, type Challenge, type InnerChallenge } from '.';
import { ChallengeStrategy, type Challenge, type InnerChallenge, ChallengeAlgorithm, algorithmToInt, strategyToInt } from '.';
import WASMValidatorUrl from '../../../solver/zig-out/bin/validator.wasm?url&inline';
import { uuidv7obj } from 'uuidv7';
type WasmExports = Record<string, Function> & {
"malloc": (byte_count: number) => number | null;
"free": (ptr: number | null, byte_count: number) => void;
"validate": (challenge_ptr: number, challenge_len: number, solution_ptr: number, solution_len: number) => boolean;
"validate": (algorithm: number, strategy: number, challenge_ptr: number, challenge_len: number, solution_ptr: number, solution_len: number, nonce: number, difficulty: number) => boolean;
"hash": (challenge_ptr: number, challebge_len: number, nonce_ptr: number, nonce_len: number, algorithm: number) => bigint;
"memory": WebAssembly.Memory;
}
@@ -22,39 +23,91 @@ function array_to_base64(buffer: ArrayBufferLike): string {
return btoa(binary);
}
export interface kCTFChallengeConfig {
parameters: Object;
strategy: ChallengeStrategy.kCTF;
export interface SHA256ChallengeConfig {
algorithm: ChallengeAlgorithm.SHA256;
strategy: ChallengeStrategy.LeadingZeroes | ChallengeStrategy.TargetNumber;
difficulty: number;
parameters: Object;
}
export type ChallengeConfig = kCTFChallengeConfig;
export interface Argon2ChallengeConfig {
algorithm: ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.LeadingZeroes | ChallengeStrategy.TargetNumber;
difficulty: number;
parameters: Object;
}
const VERSION = "s";
export interface kCTFChallengeConfig {
algorithm: ChallengeAlgorithm.kCTF;
difficulty: number;
parameters: Object;
}
async function encode_challenge(challenge: InnerChallenge, parameters: Object = {}): Promise<string> {
if (challenge.strategy !== ChallengeStrategy.kCTF) {
throw new Error("Unsupported challenge strategy");
export type ChallengeConfig = kCTFChallengeConfig | SHA256ChallengeConfig | Argon2ChallengeConfig;
async function encode_challenge(inner_challenge: InnerChallenge, parameters: Object = {}): Promise<Challenge> {
let challenge: Challenge = {} as Challenge;
switch (inner_challenge.algorithm) {
case ChallengeAlgorithm.SHA256: {
challenge.algorithm = ChallengeAlgorithm.SHA256;
challenge.salt = inner_challenge.salt;
switch (inner_challenge.strategy) {
case ChallengeStrategy.LeadingZeroes: {
// @ts-ignore
challenge.strategy = ChallengeStrategy.LeadingZeroes;
// @ts-ignore
challenge.difficulty = inner_challenge.difficulty;
break;
}
case ChallengeStrategy.TargetNumber: {
// @ts-ignore
challenge.strategy = ChallengeStrategy.TargetNumber;
// @ts-ignore
challenge.target = inner_challenge.target;
break;
}
}
break;
}
case ChallengeAlgorithm.Argon2id: {
challenge.algorithm = ChallengeAlgorithm.Argon2id;
challenge.salt = inner_challenge.salt;
switch (inner_challenge.strategy) {
case ChallengeStrategy.LeadingZeroes: {
// @ts-ignore
challenge.strategy = ChallengeStrategy.LeadingZeroes;
// @ts-ignore
challenge.difficulty = inner_challenge.difficulty;
break;
}
case ChallengeStrategy.TargetNumber: {
// @ts-ignore
challenge.strategy = ChallengeStrategy.TargetNumber;
// @ts-ignore
challenge.target = inner_challenge.target;
break;
}
}
break;
}
case ChallengeAlgorithm.kCTF: {
// @ts-ignore
challenge.difficulty = inner_challenge.difficulty;
challenge.algorithm = ChallengeAlgorithm.kCTF;
challenge.salt = array_to_base64(inner_challenge.salt.bytes.buffer);
break;
}
}
if (challenge.difficulty < 1) {
throw new Error("Difficulty must be at least 1");
}
const difficulty_buf = new Uint8Array(4);
const view = new DataView(difficulty_buf.buffer);
view.setUint32(0, challenge.difficulty, false);
const difficulty_str = array_to_base64(difficulty_buf.buffer);
const salt_str = array_to_base64(challenge.salt.bytes.buffer);
// the parameters str is expected to be sliced out of the challenge via the widget before it sends it to the wasm solver.
let parameters_str = Object.entries(parameters).map(([key, value]) => `${key}=${value}`).join("&");
if (parameters_str.length > 0) {
parameters_str = "?" + parameters_str;
}
return `${VERSION}.${difficulty_str}.${salt_str}${parameters_str}`;
challenge.salt = challenge.salt + parameters_str;
return challenge;
}
export async function generate_challenge(config: ChallengeConfig): Promise<Challenge | null> {
@@ -62,43 +115,180 @@ export async function generate_challenge(config: ChallengeConfig): Promise<Chall
throw new Error("Difficulty must be at least 1");
}
const challenge: InnerChallenge = {
strategy: ChallengeStrategy.kCTF,
salt: uuidv7obj(),
difficulty: config.difficulty,
const validator = (await WebAssembly.instantiateStreaming(fetch(WASMValidatorUrl), {
env: {
__log: (str_ptr: number, str_len: number) => console.log(new TextDecoder().decode(new Uint8Array(validator.exports.memory.buffer, str_ptr, str_len))),
}
})).instance as unknown as ValidatorModule;
const encoder = new TextEncoder();
var inner_challenge: InnerChallenge = {
algorithm: config.algorithm,
} as InnerChallenge;
let salt = `${array_to_base64(crypto.getRandomValues(new Uint8Array(32)).buffer)}`;
let parameters_str: string;
switch (config.algorithm) {
case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2id:
switch (config.strategy) {
case ChallengeStrategy.LeadingZeroes:
if (config.difficulty < 1 || config.difficulty > 64) {
throw new Error("Invalid difficulty for leading zeroes strategy");
}
// @ts-ignore
inner_challenge.strategy = ChallengeStrategy.LeadingZeroes;
// @ts-ignore
inner_challenge.difficulty = config.difficulty;
parameters_str = Object.entries(config.parameters).map(([key, value]) => `${key}=${value}`).join("&");
if (parameters_str.length > 0) {
parameters_str = "?" + parameters_str;
}
inner_challenge.salt = salt + parameters_str;
config.parameters = {};
break;
case ChallengeStrategy.TargetNumber:
if (config.difficulty < 1) {
throw new Error("Difficulty must be at least 1");
}
// @ts-ignore
inner_challenge.strategy = ChallengeStrategy.TargetNumber;
parameters_str = Object.entries(config.parameters).map(([key, value]) => `${key}=${value}`).join("&");
if (parameters_str.length > 0) {
parameters_str = "?" + parameters_str;
}
inner_challenge.salt = salt + parameters_str;
config.parameters = {};
const random_number = Math.floor(Math.random() * config.difficulty).toString();
console.log("RANDOM NUMBER", random_number);
const challenge_buf = encoder.encode(inner_challenge.salt + random_number);
const challenge_ptr = validator.exports.malloc(challenge_buf.length);
if (challenge_ptr === 0 || challenge_ptr === null) {
console.error("Failed to allocate memory for challenge string");
return null;
}
const memory = new Uint8Array(validator.exports.memory.buffer);
memory.set(challenge_buf, challenge_ptr);
const challenge_len = inner_challenge.salt.length;
const nonce_ptr = challenge_ptr + challenge_len;
const nonce_len = challenge_buf.length - challenge_len;
const target = validator.exports.hash(challenge_ptr, challenge_len, nonce_ptr, nonce_len, algorithmToInt(inner_challenge.algorithm));
const target_len = Number((target >> 32n) & 0xFFFFFFFFn);
const target_ptr = Number(target & 0xFFFFFFFFn);
const target_buf = new Uint8Array(validator.exports.memory.buffer, target_ptr, target_len);
// @ts-ignore
inner_challenge.target = new TextDecoder().decode(target_buf);
// @ts-ignore
console.log("TARGET", inner_challenge.target);
validator.exports.free(challenge_ptr, challenge_len + random_number.length);
validator.exports.free(target_ptr, target_len);
break;
}
break;
case ChallengeAlgorithm.kCTF:
if (config.difficulty < 1) {
throw new Error("Difficulty must be at least 1");
}
inner_challenge.salt = uuidv7obj();
// @ts-ignore
inner_challenge.difficulty = config.difficulty;
break;
}
return {
strategy: ChallengeStrategy.kCTF,
challenge: await encode_challenge(challenge),
};
return await encode_challenge(inner_challenge, config.parameters);
}
export async function validate_challenge(challenge: Challenge, challenge_solution: string): Promise<boolean> {
export async function validate_challenge(challenge: Challenge, challenge_solution: string | number): Promise<boolean> {
const validator = (await WebAssembly.instantiateStreaming(fetch(WASMValidatorUrl), {
env: {
__log: (str_ptr: number, str_len: number) => console.log(new TextDecoder().decode(new Uint8Array(validator.exports.memory.buffer, str_ptr, str_len))),
}
})).instance as unknown as ValidatorModule
const encoder = new TextEncoder();
const challenge_buf = encoder.encode(challenge.challenge);
const solution_buf = encoder.encode(challenge_solution);
if (challenge.algorithm === ChallengeAlgorithm.kCTF) {
challenge.salt = challenge.salt.split("?")[0];
}
const challenge_buf = encoder.encode(challenge.salt);
const challenge_ptr = validator.exports.malloc(challenge_buf.length);
const solution_ptr = validator.exports.malloc(solution_buf.length);
if (challenge_ptr === 0 || challenge_ptr === null || solution_ptr === 0 || solution_ptr === null) {
if (challenge_ptr === 0 || challenge_ptr === null) {
console.error("Failed to allocate memory for challenge string");
return false;
}
const memory = new Uint8Array(validator.exports.memory.buffer);
memory.set(challenge_buf, challenge_ptr);
memory.set(solution_buf, solution_ptr);
const is_valid = validator.exports.validate(challenge_ptr, challenge.challenge.length, solution_ptr, challenge_solution.length);
validator.exports.free(challenge_ptr, challenge.challenge.length);
validator.exports.free(solution_ptr, challenge_solution.length);
switch (challenge.algorithm) {
case ChallengeAlgorithm.SHA256:
if (typeof challenge_solution === "string") {
throw new Error("Argon2id challenges do not support a solution as a number");
}
return is_valid;
switch (challenge.strategy) {
case ChallengeStrategy.LeadingZeroes:
return validator.exports.validate(algorithmToInt(challenge.algorithm), strategyToInt(challenge.strategy), challenge_ptr, challenge_buf.length, 0, 0, challenge_solution, challenge.difficulty);
case ChallengeStrategy.TargetNumber:
const solution_buf = encoder.encode(challenge.target);
const solution_ptr = validator.exports.malloc(solution_buf.length);
if (solution_ptr === 0 || solution_ptr === null) {
console.error("Failed to allocate memory for challenge string");
return false;
}
const memory = new Uint8Array(validator.exports.memory.buffer);
memory.set(solution_buf, solution_ptr);
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:
if (typeof challenge_solution === "string") {
throw new Error("Argon2id challenges do not support a solution as a number");
}
switch (challenge.strategy) {
case ChallengeStrategy.LeadingZeroes:
return validator.exports.validate(algorithmToInt(challenge.algorithm), strategyToInt(challenge.strategy), challenge_ptr, challenge_buf.length, 0, 0, challenge_solution, challenge.difficulty);
case ChallengeStrategy.TargetNumber:
const solution_buf = encoder.encode(challenge.target);
const solution_ptr = validator.exports.malloc(solution_buf.length);
if (solution_ptr === 0 || solution_ptr === null) {
console.error("Failed to allocate memory for challenge string");
return false;
}
const memory = new Uint8Array(validator.exports.memory.buffer);
memory.set(solution_buf, solution_ptr);
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.kCTF:
if (typeof challenge_solution === "number") {
throw new Error("KCTF challenges do not support a solution as a number");
}
const solution_buf = encoder.encode(challenge_solution);
const solution_ptr = validator.exports.malloc(solution_buf.length);
if (solution_ptr === 0 || solution_ptr === null) {
console.error("Failed to allocate memory for challenge string");
return false;
}
const memory = new Uint8Array(validator.exports.memory.buffer);
memory.set(solution_buf, solution_ptr);
return validator.exports.validate(algorithmToInt(challenge.algorithm), 0, challenge_ptr, challenge_buf.length, solution_ptr, solution_buf.length, 0, challenge.difficulty);
}
}

View File

@@ -1,7 +1,7 @@
import { LitElement, html, css, isServer, type PropertyValues } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { type ChallengeSolveRequest, type SolutionMessage, WorkerMessageType, type WorkerRequest, WorkerResponseType } from './types/worker';
import { type Challenge, ChallengeStrategy } from '@impost/lib';
import { type Challenge, ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib';
import { get_wasm_module } from '@impost/lib/solver';
import ChallengeWorker from './solver-worker?worker&inline';
@@ -172,7 +172,7 @@ export class PowCaptcha extends LitElement {
async initWorkers() {
this.solverWorkers = [];
const num_workers = 1;
const num_workers = navigator.hardwareConcurrency || 4;
for (let i = 0; i < num_workers; i++) {
this.solverWorkers.push(new ChallengeWorker());
}
@@ -275,11 +275,52 @@ export class PowCaptcha extends LitElement {
// } as WorkerRequest);
// break;
// }
switch (request.strategy) {
case ChallengeStrategy.kCTF:
switch (request.algorithm) {
case ChallengeAlgorithm.SHA256:
switch (request.strategy) {
case ChallengeStrategy.LeadingZeroes:
worker.postMessage({
algorithm: ChallengeAlgorithm.SHA256,
strategy: ChallengeStrategy.LeadingZeroes,
salt: request.salt,
difficulty: request.difficulty,
} as WorkerRequest);
break;
case ChallengeStrategy.TargetNumber:
worker.postMessage({
algorithm: ChallengeAlgorithm.SHA256,
strategy: ChallengeStrategy.TargetNumber,
target: request.target,
salt: request.salt,
} as WorkerRequest);
break;
}
break;
case ChallengeAlgorithm.Argon2id:
switch (request.strategy) {
case ChallengeStrategy.LeadingZeroes:
worker.postMessage({
algorithm: ChallengeAlgorithm.Argon2id,
strategy: ChallengeStrategy.LeadingZeroes,
salt: request.salt,
difficulty: request.difficulty,
} as WorkerRequest);
break;
case ChallengeStrategy.TargetNumber:
worker.postMessage({
algorithm: ChallengeAlgorithm.Argon2id,
strategy: ChallengeStrategy.TargetNumber,
target: request.target,
salt: request.salt,
} as WorkerRequest);
break;
}
break;
case ChallengeAlgorithm.kCTF:
worker.postMessage({
strategy: ChallengeStrategy.kCTF,
challenge: request.challenge,
algorithm: ChallengeAlgorithm.kCTF,
salt: request.salt,
difficulty: request.difficulty,
} as WorkerRequest);
break;
}
@@ -329,11 +370,52 @@ export class PowCaptcha extends LitElement {
// };
// break;
// }
switch (this.challengeData.strategy) {
case ChallengeStrategy.kCTF:
switch (this.challengeData.algorithm) {
case ChallengeAlgorithm.SHA256:
switch (this.challengeData.strategy) {
case ChallengeStrategy.LeadingZeroes:
request = {
algorithm: ChallengeAlgorithm.SHA256,
strategy: ChallengeStrategy.LeadingZeroes,
salt: this.challengeData.salt,
difficulty: this.challengeData.difficulty,
};
break;
case ChallengeStrategy.TargetNumber:
request = {
algorithm: ChallengeAlgorithm.SHA256,
strategy: ChallengeStrategy.TargetNumber,
target: this.challengeData.target,
salt: this.challengeData.salt,
};
break;
}
break;
case ChallengeAlgorithm.Argon2id:
switch (this.challengeData.strategy) {
case ChallengeStrategy.LeadingZeroes:
request = {
algorithm: ChallengeAlgorithm.Argon2id,
strategy: ChallengeStrategy.LeadingZeroes,
salt: this.challengeData.salt,
difficulty: this.challengeData.difficulty,
};
break;
case ChallengeStrategy.TargetNumber:
request = {
algorithm: ChallengeAlgorithm.Argon2id,
strategy: ChallengeStrategy.TargetNumber,
target: this.challengeData.target,
salt: this.challengeData.salt,
};
break;
}
break;
case ChallengeAlgorithm.kCTF:
request = {
strategy: ChallengeStrategy.kCTF,
challenge: this.challengeData.challenge,
algorithm: ChallengeAlgorithm.kCTF,
salt: this.challengeData.salt,
difficulty: this.challengeData.difficulty,
};
break;
}
@@ -346,9 +428,13 @@ export class PowCaptcha extends LitElement {
// TODO: We need to do a better job of tracking solvers, so if one worker
// errors out, we only error out if all workers have errored out.
let worker_promises: Promise<SolutionMessage>[] = [];
for (let worker of this.solverWorkers) {
// dispatch to all workers, func is async so it will not block
worker_promises.push(this.issueChallengeToWorker(worker, request));
if (request.algorithm === ChallengeAlgorithm.kCTF) {
worker_promises.push(this.issueChallengeToWorker(this.solverWorkers[0], request));
} else {
for (let worker of this.solverWorkers) {
// dispatch to all workers, func is async so it will not block
worker_promises.push(this.issueChallengeToWorker(worker, request));
}
}
let solution = await Promise.race(worker_promises);
@@ -370,7 +456,7 @@ export class PowCaptcha extends LitElement {
await fetch(`${this.challengeUrl}/challenge`, {
method: 'POST',
body: JSON.stringify({
challenge: this.challengeData.challenge,
salt: this.challengeData.salt,
solution: solution.solution,
}),
headers: {
@@ -382,7 +468,7 @@ export class PowCaptcha extends LitElement {
this.dispatchEvent(new CustomEvent('impost:solved', {
detail: {
challenge: this.challengeData.challenge,
salt: this.challengeData.salt,
solution: solution.solution,
},
bubbles: true,

View File

@@ -8,7 +8,8 @@ import {
WorkerResponseType,
} from "./types/worker";
import { type SolverModule, init_solver, solve } from '@impost/lib/solver';
import { type SolverModule, init_solver, solve, type SolveParams } from '@impost/lib/solver';
import { ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib';
let solver: SolverModule | null = null;
@@ -59,9 +60,59 @@ onmessage = async (event: MessageEvent<WorkerRequest>) => {
return;
}
let solution: string;
let solution: string | number;
try {
solution = solve(solver, event.data.challenge);
let params = {
name: event.data.algorithm,
salt: event.data.salt,
};
switch (event.data.algorithm) {
case ChallengeAlgorithm.SHA256:
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.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;
// @ts-ignore
params.difficulty = event.data.difficulty;
break;
}
solution = solve(solver, params as SolveParams);
if (event.data.algorithm !== ChallengeAlgorithm.kCTF) {
solution = Atomics.load(atomic_solution!, 0);
}
} catch (error: any) {
postMessage({
type: WorkerResponseType.Error,
@@ -70,6 +121,7 @@ onmessage = async (event: MessageEvent<WorkerRequest>) => {
return;
}
postMessage({
type: WorkerResponseType.Solution,
solution,

View File

@@ -1,4 +1,4 @@
import { ChallengeStrategy } from "@impost/lib";
import { ChallengeAlgorithm, ChallengeStrategy } from "@impost/lib";
export enum WorkerMessageType {
Init = "init",
@@ -12,37 +12,40 @@ interface WorkerInitRequest {
sab: SharedArrayBuffer;
}
// interface ChallengeLeadingZeroesSolveRequest {
// strategy: ChallengeStrategy.LeadingZeroes;
// salt: string;
// difficulty: number;
// }
interface ChallengeLeadingZeroesSolveRequest {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.LeadingZeroes;
salt: string;
difficulty: number;
}
// interface WorkerChallengeLeadingZeroesSolveRequest extends ChallengeLeadingZeroesSolveRequest {
// type: WorkerMessageType.Challenge;
// }
interface WorkerChallengeLeadingZeroesSolveRequest extends ChallengeLeadingZeroesSolveRequest {
type: WorkerMessageType.Challenge;
}
// interface ChallengeTargetNumberSolveRequest {
// strategy: ChallengeStrategy.TargetNumber;
// target: string;
// salt: string;
// }
interface ChallengeTargetNumberSolveRequest {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id;
strategy: ChallengeStrategy.TargetNumber;
target: string;
salt: string;
}
// interface WorkerChallengeTargetNumberSolveRequest extends ChallengeTargetNumberSolveRequest {
// type: WorkerMessageType.Challenge;
// }
interface WorkerChallengeTargetNumberSolveRequest extends ChallengeTargetNumberSolveRequest {
type: WorkerMessageType.Challenge;
}
interface ChallengekCTFSolveRequest {
strategy: ChallengeStrategy.kCTF;
challenge: string;
algorithm: ChallengeAlgorithm.kCTF;
salt: string;
difficulty: number;
}
interface WorkerChallengekCTFSolveRequest extends ChallengekCTFSolveRequest {
type: WorkerMessageType.Challenge;
}
export type ChallengeSolveRequest = ChallengekCTFSolveRequest;
type WorkerChallengeSolveRequest = WorkerChallengekCTFSolveRequest;
export type ChallengeSolveRequest = ChallengekCTFSolveRequest | ChallengeLeadingZeroesSolveRequest | ChallengeTargetNumberSolveRequest;
type WorkerChallengeSolveRequest = WorkerChallengekCTFSolveRequest | WorkerChallengeLeadingZeroesSolveRequest | WorkerChallengeTargetNumberSolveRequest;
export type WorkerRequest = WorkerInitRequest | WorkerChallengeSolveRequest;
@@ -59,7 +62,7 @@ interface ErrorMessageResponse {
interface SolutionMessageResponse {
type: WorkerResponseType.Solution;
solution: string;
solution: string | number;
}
interface InitOkMessageResponse {