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:
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user