further benchmarking stuff

This commit is contained in:
Zoe
2025-11-28 14:53:06 -06:00
parent e16383e9b9
commit d0f4936b84
25 changed files with 441 additions and 739 deletions

View File

@@ -0,0 +1,187 @@
<script setup lang="ts">
let { data: challengeData } = await useFetch('/api/pow/challenge');
let { data: powData } = await useFetch('/api/pow');
if (!challengeData || !powData) {
throw createError({
statusCode: 500,
message: 'Failed to fetch data',
});
}
const algorithms = {
"argon2": {
name: 'argon2',
label: 'Argon2',
strategies: ['leading_zeroes', 'target_number'],
},
"kctf": {
name: 'kctf',
label: 'kCTF',
strategies: ['null'],
},
"sha256": {
name: 'sha256',
label: 'SHA256',
strategies: ['leading_zeroes', 'target_number'],
},
};
async function refresh() {
challengeData.value = await $fetch('/api/pow/challenge');
powData.value = await $fetch('/api/pow');
resetCaptcha();
}
function resetCaptcha() {
document.querySelector("pow-captcha")!.dispatchEvent(new CustomEvent('reset', {
detail: {
challenge: challengeData.value.challenge,
}
}));
}
let bench_results = ref([])
let start = ref(0);
let continue_bench: Promise<void> | null = null;
async function bench() {
for (let algorithm_name in algorithms) {
let algorithm = algorithms[algorithm_name as "sha256" | "argon2" | "kctf"];
for (let strategy of algorithm.strategies) {
for (let i = 1; i <= 3; i++) {
// reduce statistical anomolies by running tests multiple times and averaging after tha fact
for (let j = 0; j < 5; j++) {
let difficulty;
switch (strategy) {
case 'leading_zeroes':
difficulty = i;
break;
case 'null':
case 'target_number':
// these tests scale linearly, so to try to match the
// complexity of leading_zeroes, we grow the difficulty
// exponentially
difficulty = Math.pow(16, i);
break;
}
await changeAlgorithm(algorithm_name);
if (strategy !== 'null') {
await changeStrategy(strategy);
}
await $fetch('/api/pow/difficulty', {
method: 'PUT',
body: JSON.stringify({
difficulty: difficulty,
}),
})
// sleep for 300ms
await new Promise((resolve) => setTimeout(resolve, 750));
await refresh();
continue_bench = new Promise((resolve) => {
document.querySelector("pow-captcha")!.addEventListener('impost:solved', () => {
resolve();
});
});
start.value = performance.now();
document.querySelector("pow-captcha")!.dispatchEvent(new Event('solve'));
await continue_bench;
let end = performance.now();
const data = {
algorithm: algorithm_name,
cores: navigator.hardwareConcurrency,
strategy: strategy,
difficulty: difficulty,
time: end - start.value,
};
const should_scroll = document.documentElement.scrollTop + document.documentElement.clientHeight >= document.documentElement.scrollHeight;
bench_results.value.push(data);
if (should_scroll) {
document.documentElement.scrollTop = document.documentElement.scrollHeight;
}
}
}
}
}
}
function solved(ev: CustomEvent) {
console.log("Solved:", ev.detail.solution);
}
async function changeAlgorithmEV(ev: Event) {
changeAlgorithm(ev.target.value);
refresh();
}
async function changeAlgorithm(algorithm: string) {
await $fetch('/api/pow/algorithm', {
method: 'PUT',
body: JSON.stringify({
algorithm: algorithm
}),
});
}
async function changeStrategyEV(ev: Event) {
changeStrategy(ev.target.value);
refresh();
}
async function changeStrategy(strategy: string) {
await $fetch('/api/pow/strategy', {
method: 'PUT',
body: JSON.stringify({
strategy: strategy
}),
});
}
</script>
<template>
<pow-captcha challengeUrl="/api/pow" :challengejson="JSON.stringify(challengeData!.challenge)"
@impost:solved="solved" />
<div class="flex flex-row gap-4">
<div class="flex flex-row gap-2" v-for="algorithm in algorithms" :key="algorithm.label">
<input type="radio" name="algorithm" @change="changeAlgorithmEV" :value="algorithm.name"
:id="algorithm.name" :checked="powData!.algorithm === algorithm.name"> <label :for="algorithm.name">{{
algorithm.label
}}</label>
</div>
</div>
<div v-if="algorithms[powData!.algorithm].strategies.length > 1 && powData!.algorithm === algorithms[powData!.algorithm].name"
class="flex flex-row gap-4">
<div class="flex flex-row gap-2" v-for="strategy in algorithms[powData!.algorithm].strategies">
<input type="radio" name="strategy" @change="changeStrategyEV" :value="strategy" :id="strategy"
:checked="powData!.strategy === strategy"> <label :for="strategy">{{ strategy }}</label>
</div>
</div>
<input type="button" value="Start benchmark" @click="bench" />
<div v-if="bench_results.length > 0">
<table>
<thead>
<tr>
<th>Algorithm</th>
<th>Strategy</th>
<th>Difficulty</th>
<th>Time (ms)</th>
</tr>
</thead>
<tbody>
<tr v-for="result in bench_results" :key="result.algorithm + result.strategy + result.difficulty">
<td>{{ algorithms[result.algorithm].label }}</td>
<td>{{ result.strategy }}</td>
<td>{{ result.difficulty }}</td>
<td>{{ result.time }}</td>
</tr>
</tbody>
</table>
</div>
</template>

View File

@@ -1,411 +0,0 @@
<script setup lang="ts">
import { type WorkerRequest, type SolutionMessage, WorkerResponseType, WorkerMessageType, ChallengeStrategy, type Challenge } from '~/types/pow';
import WASMSolverUrl from "~/utils/solver.wasm?url";
import ChallengeWorker from '~/utils/worker?worker';
let shared_atomics = new SharedArrayBuffer(12);
let workers: Worker[] = [];
let workers_initialized = false;
if (import.meta.client) {
try {
// const num_workers = 1;
const num_workers = navigator.hardwareConcurrency;
for (let i = 0; i < num_workers; i++) {
workers.push(new ChallengeWorker());
}
} catch (error: any) {
console.error("Failed to create worker:", error);
}
}
let autoSolve: Ref<boolean> = ref(false);
watch(autoSolve, async () => {
if (autoSolve.value) {
while (autoSolve.value) {
if (solving.value) {
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
await getChallenge();
await solveChallenge();
if (!autoSolve.value) {
break;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
}
});
let total_solved: Ref<number> = ref(0);
let total_solving_for: Ref<number> = ref(0);
function pluralize(value: number, string: string) {
return value === 1 ? string : `${string}s`;
}
let challenge_loading: Ref<boolean> = ref(false);
let challenge_loading_indicator: Ref<string> = ref('');
let challenge: Ref<Challenge | null> = ref(null);
let nonce: Ref<string | null> = ref(null);
let solved: Ref<boolean> = ref(false);
let solving_for: Ref<string> = ref('0');
const number_formatter = new Intl.NumberFormat('en-US', {
notation: 'compact',
compactDisplay: 'short',
maximumFractionDigits: 2,
});
// the hashrate of all the runs
let hashrate_array: Ref<Array<number>> = ref([]);
let hashrate = computed(() => {
if (hashrate_array.value.length === 0) {
return 0;
}
return hashrate_array.value.reduce((a, b) => a + b, 0) / hashrate_array.value.length;
});
let solving = ref(false);
let solveTime: Ref<number> = ref(0);
let solveTimeout: Ref<any | null> = ref(null);
let challenge_error: Ref<string | null> = ref(null);
let difficulty: Ref<number> = ref(0);
let { data } = await useFetch('/api/pow/difficulty');
if (data.value) {
difficulty.value = data.value.difficulty;
}
const MESSAGE_TIMEOUT = 3000;
async function getChallenge() {
challenge_error.value = null;
challenge_loading_indicator.value = '';
challenge.value = null;
nonce.value = null;
challenge_loading.value = true;
const spinners = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let spinner_index = 0;
const loading_interval = setInterval(() => {
challenge_loading_indicator.value = spinners[spinner_index]!;
spinner_index = (spinner_index + 1) % spinners.length;
console.log(spinners[spinner_index]);
}, 100);
try {
const new_challenge = await $fetch('/api/pow/challenge');
challenge.value = new_challenge.challenge;
} catch (error: any) {
console.error("Failed to get challenge:", error);
challenge_error.value = `Failed to get challenge: ${error.message}`;
} finally {
challenge_loading.value = false;
clearInterval(loading_interval);
}
}
async function initWorkers() {
if (workers_initialized) {
throw createError("Workers already initialized");
}
workers_initialized = true;
const module = await WebAssembly.compileStreaming(fetch(WASMSolverUrl));
const atomics_view = new Int32Array(shared_atomics);
Atomics.store(atomics_view, 0, 0);
Atomics.store(atomics_view, 1, 0);
console.debug(`Initializing ${workers.length} workers`);
let worker_promises: Promise<void>[] = [];
for (let i = 0; i < workers.length; i++) {
const worker = workers[i]!;
worker_promises.push(new Promise<void>((resolve, reject) => {
const message_handler = (event: MessageEvent<SolutionMessage>) => {
if (event.data.type === WorkerResponseType.Error) {
console.error("Worker error:", event.data.error);
reject(event.data.error);
}
if (event.data.type === WorkerResponseType.Ok) {
resolve();
}
reject(new Error("Unexpected message from worker"));
};
const error_handler = (error: ErrorEvent) => {
console.error("Worker error:", error);
reject(error);
};
worker.addEventListener('message', message_handler);
worker.addEventListener('error', error_handler);
worker.postMessage({
type: WorkerMessageType.Init,
module: module,
sab: shared_atomics,
} as WorkerRequest);
}));
}
const timeoutMs = 10 * 1000;
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Function timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
await Promise.race([
Promise.all(worker_promises),
timeoutPromise,
]);
console.log("All workers initialized");
}
async function getChallengeSolution(worker: Worker, request: { strategy: ChallengeStrategy.LeadingZeroes, target: string, difficulty: number } | { strategy: ChallengeStrategy.TargetNumber, target: string, salt: string }): Promise<SolutionMessage> {
return new Promise<SolutionMessage>((resolve, reject) => {
const message_handler = (event: MessageEvent<SolutionMessage>) => {
worker.removeEventListener('message', message_handler);
worker.removeEventListener('error', error_handler);
resolve(event.data);
};
const error_handler = (error: ErrorEvent) => {
worker.removeEventListener('error', error_handler);
worker.removeEventListener('message', message_handler);
console.error("Worker error:", error);
reject(error);
};
worker.addEventListener('message', message_handler);
worker.addEventListener('error', error_handler);
switch (request.strategy) {
case ChallengeStrategy.LeadingZeroes:
worker.postMessage({
strategy: ChallengeStrategy.LeadingZeroes,
target: request.target,
difficulty: request.difficulty,
} as WorkerRequest);
break;
case ChallengeStrategy.TargetNumber:
worker.postMessage({
strategy: ChallengeStrategy.TargetNumber,
target: request.target,
salt: request.salt,
} as WorkerRequest);
break;
}
});
}
async function solveChallenge() {
if (!challenge.value?.target) {
return;
}
if (!workers_initialized) {
try {
await initWorkers();
} catch (error: any) {
console.error("Failed to initialize workers:", error);
return;
}
}
const atomics_view = new Int32Array(shared_atomics);
Atomics.store(atomics_view, 0, 0);
Atomics.store(atomics_view, 1, -1);
solved.value = false;
challenge_error.value = null;
solving.value = true;
solveTime.value = 0;
solving_for.value = '0';
let startTime = performance.now();
let solving_for_interval = setInterval(() => {
solving_for.value = ((performance.now() - startTime) / 1000).toFixed(1);
}, 100);
function cleanup() {
clearTimeout(solveTimeout.value);
solveTimeout.value = setTimeout(() => {
solveTime.value = 0;
}, MESSAGE_TIMEOUT);
solved.value = false;
solving.value = false;
clearInterval(solving_for_interval);
}
try {
let request: { strategy: ChallengeStrategy.LeadingZeroes, target: string, difficulty: number } | { strategy: ChallengeStrategy.TargetNumber, target: string, salt: string };
switch (challenge.value.strategy) {
case ChallengeStrategy.LeadingZeroes:
request = {
strategy: ChallengeStrategy.LeadingZeroes,
target: challenge.value.target,
difficulty: challenge.value.difficulty,
};
break;
case ChallengeStrategy.TargetNumber:
request = {
strategy: ChallengeStrategy.TargetNumber,
target: challenge.value.target,
salt: challenge.value.salt,
};
break;
}
let worker_promises: Promise<SolutionMessage>[] = [];
for (let worker of workers) {
// dispatch to all workers, func is async so it will not block
worker_promises.push(getChallengeSolution(worker, request));
}
let solution = await Promise.race(worker_promises);
if (solution.type === WorkerResponseType.Error) {
throw createError(solution.error);
}
if (solution.type === WorkerResponseType.Ok) {
throw createError("spurious solution");
}
console.log(shared_atomics.slice(8, 12));
nonce.value = Atomics.load(atomics_view, 1).toString();
solveTime.value = Math.floor(performance.now() - startTime);
total_solved.value += 1;
total_solving_for.value += solveTime.value;
// since nonce is the number of iterations we have completed, we can divide that by how long it took in second
// to get H/s
hashrate_array.value.push(+Atomics.load(atomics_view, 1) / (solveTime.value / 1000));
clearTimeout(solveTimeout.value);
await $fetch('/api/pow/challenge', {
method: 'POST',
body: {
challenge: challenge.value.target,
nonce: nonce.value,
}
});
solved.value = true;
solveTimeout.value = setTimeout(() => {
solveTime.value = 0;
solved.value = false;
}, MESSAGE_TIMEOUT);
switch (challenge.value.strategy) {
case ChallengeStrategy.LeadingZeroes:
console.debug("Solved challenge with difficulty", challenge.value.difficulty, "in " + solveTime.value + "ms");
break;
case ChallengeStrategy.TargetNumber:
console.debug("Solved challenge with salt", challenge.value.salt, "in " + solveTime.value + "ms");
break;
}
solving.value = false;
clearInterval(solving_for_interval);
} catch (error: any) {
challenge_error.value = `Failed to solve challenge: ${error.message}`;
console.error(error);
cleanup();
}
}
async function setDifficulty(difficulty: number) {
const response = await $fetch('/api/pow/difficulty', {
method: 'PUT',
body: {
difficulty,
}
});
console.log(response);
}
</script>
<template>
<div class="flex justify-between" v-if="hashrate_array.length !== 0">
<span>Your average Hashrate: {{ number_formatter.format(hashrate) }} H/s</span>
<span>You have solved {{ total_solved }} {{ pluralize(total_solved, "challenge") }} in
{{ number_formatter.format(total_solving_for / 1000) }}s</span>
<span>Your Hashrate on the last challenge: {{ number_formatter.format(hashrate_array.at(-1)!) }} H/s</span>
</div>
<p v-else>You have not solved any challenges yet</p>
<p>Challenge: <span v-if="challenge_loading">{{ challenge_loading_indicator }}</span>
<span v-else>{{ challenge }}</span>
</p>
<p>Nonce: {{ nonce }}</p>
<button @click="getChallenge()" :disabled="solving || challenge_loading">
Get Challenge
</button>
<button @click="solveChallenge()" :disabled="solving || challenge === null || nonce !== null">
<span v-if="!solving">Solve Challenge</span>
<span v-else>Solving challenge for {{ solving_for }}s...</span>
</button>
<div>
<div v-if="solveTime && !challenge_error" class="min-h-[1rem]">
<span v-if="solved">Challenge solved in {{ solveTime }}ms!</span>
<span v-else>Validating solution...</span>
</div>
<div v-else-if="challenge_error">{{ challenge_error }}</div>
<p v-else class="empty-p"><!-- Empty so there is no content shift when there is text or isnt --></p>
</div>
<div class="flex flex-row w-fit">
<label for="autoSolve">Auto solve</label>
<input class="w-min" type="checkbox" v-model="autoSolve" id="autoSolve"></input>
</div>
<div class="flex flex-col w-fit">
<label for="difficulty">Difficulty</label>
<input class="w-min" type="number" min="1" max="64" v-model="difficulty" @change="setDifficulty(difficulty)"
id="difficulty"></input>
</div>
</template>
<style scoped>
button {
min-width: 140px;
padding: 0.25rem 0.375rem;
}
.empty-p {
margin: 0;
&::after {
content: "-";
visibility: hidden;
}
}
</style>

View File

@@ -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)]}`;
}

View File

@@ -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<WorkerRequest>) => {
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);
};

View File

@@ -1,4 +1,4 @@
algorithm = "argon2id" algorithm = "argon2"
strategy = "target_number" strategy = "target_number"
[leading_zeroes] [leading_zeroes]

View File

@@ -0,0 +1,7 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
return {
algorithm: config.algorithm
}
})

View File

@@ -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}`
};
});

View File

@@ -27,11 +27,11 @@ export default defineEventHandler(async () => {
break; break;
} }
break; break;
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
switch (config.strategy) { switch (config.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
challenge_config = { challenge_config = {
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.LeadingZeroes, strategy: ChallengeStrategy.LeadingZeroes,
difficulty: config.leading_zeroes.difficulty, difficulty: config.leading_zeroes.difficulty,
parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS }, parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS },
@@ -39,7 +39,7 @@ export default defineEventHandler(async () => {
break; break;
case ChallengeStrategy.TargetNumber: case ChallengeStrategy.TargetNumber:
challenge_config = { challenge_config = {
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.TargetNumber, strategy: ChallengeStrategy.TargetNumber,
difficulty: config.target_number.max_number, difficulty: config.target_number.max_number,
parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS }, parameters: { expires_at: Date.now() + CHALLENGE_TIMEOUT_MS },

View File

@@ -5,7 +5,7 @@ import { outstandingChallenges } from '~~/server/utils/pow';
const challengeSchema = z.object({ const challengeSchema = z.object({
salt: z.string(), 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()), solution: z.string().or(z.number()),
}) })

View File

@@ -1,9 +1,14 @@
import { defineEventHandler } from 'h3' import { defineEventHandler } from 'h3'
import { ChallengeStrategy } from '@impost/lib'; import { ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib';
export default defineEventHandler((event) => { export default defineEventHandler((event) => {
let difficulty: number; let difficulty: number;
console.log("CONFIG", config);
switch (config.algorithm) {
case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2:
switch (config.strategy) { switch (config.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
difficulty = config.leading_zeroes.difficulty!; difficulty = config.leading_zeroes.difficulty!;
@@ -12,6 +17,11 @@ export default defineEventHandler((event) => {
difficulty = config.target_number.max_number!; difficulty = config.target_number.max_number!;
break; break;
} }
break;
case ChallengeAlgorithm.kCTF:
difficulty = config.kctf.difficulty!;
break;
}
return { return {
difficulty difficulty

View File

@@ -1,11 +1,14 @@
import { defineEventHandler } from 'h3' import { defineEventHandler } from 'h3'
import { ChallengeStrategy } from '@impost/lib'; import { ChallengeStrategy, ChallengeAlgorithm } from '@impost/lib';
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const body = await readBody(event) const body = await readBody(event)
let difficulty = body.difficulty; let difficulty = body.difficulty;
switch (config.algorithm) {
case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2:
switch (config.strategy) { switch (config.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
if (!difficulty || difficulty < 1 || difficulty > 64) { if (!difficulty || difficulty < 1 || difficulty > 64) {
@@ -28,8 +31,13 @@ export default defineEventHandler(async (event) => {
config.target_number.max_number = difficulty; config.target_number.max_number = difficulty;
break; break;
} }
break;
case ChallengeAlgorithm.kCTF:
config.kctf.difficulty = difficulty;
break;
}
return { return {
message: 'Challenge difficulty set' message: `Challenge difficulty set to ${difficulty}`
}; };
}); });

View File

@@ -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,
}
})

View File

@@ -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,
}
});

View File

@@ -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}`
};
});

View File

@@ -20,16 +20,16 @@ const SHA256Schema = z.discriminatedUnion("strategy", [
}), }),
]); ]);
const Argon2idSchema = z.discriminatedUnion("strategy", [ const Argon2Schema = z.discriminatedUnion("strategy", [
z.object({ z.object({
algorithm: z.literal(ChallengeAlgorithm.Argon2id), algorithm: z.literal(ChallengeAlgorithm.Argon2),
strategy: z.literal(ChallengeStrategy.LeadingZeroes), strategy: z.literal(ChallengeStrategy.LeadingZeroes),
leading_zeroes: z.object({ leading_zeroes: z.object({
difficulty: z.number().int().min(1).max(64), difficulty: z.number().int().min(1).max(64),
}), }),
}), }),
z.object({ z.object({
algorithm: z.literal(ChallengeAlgorithm.Argon2id), algorithm: z.literal(ChallengeAlgorithm.Argon2),
strategy: z.literal(ChallengeStrategy.TargetNumber), strategy: z.literal(ChallengeStrategy.TargetNumber),
target_number: z.object({ target_number: z.object({
max_number: z.number().int().min(1).max(100_000), 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<typeof Config>; export type Config = z.infer<typeof Config>;

View File

@@ -2,7 +2,7 @@ import { UUID } from "uuidv7";
export enum ChallengeAlgorithm { export enum ChallengeAlgorithm {
SHA256 = "sha256", SHA256 = "sha256",
Argon2id = "argon2id", Argon2 = "argon2",
kCTF = "kctf", kCTF = "kctf",
} }
@@ -10,7 +10,7 @@ export function algorithmToInt(algorithm: ChallengeAlgorithm): number {
switch (algorithm) { switch (algorithm) {
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
return 0; return 0;
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
return 1; return 1;
case ChallengeAlgorithm.kCTF: case ChallengeAlgorithm.kCTF:
return 2; 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 // In this case, the client will repeatedly hash a number with has until it
// finds a hash thaat starts with *difficulty* leading zeroes // finds a hash thaat starts with *difficulty* leading zeroes
export interface ChallengeLeadingZeroes { export interface ChallengeLeadingZeroes {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.LeadingZeroes; strategy: ChallengeStrategy.LeadingZeroes;
salt: string; // random string salt: string; // random string
difficulty: number; difficulty: number;
@@ -46,7 +46,7 @@ export interface ChallengeLeadingZeroes {
// In this case, the server generates a random number, and the client will hash // 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 // the salt (a random string) + a random number until it finds a hash that is equal to challenge
export interface ChallengeTargetNumber { export interface ChallengeTargetNumber {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.TargetNumber; strategy: ChallengeStrategy.TargetNumber;
salt: string; // random string salt: string; // random string
target: string; // hash of salt + random number target: string; // hash of salt + random number

View File

@@ -31,14 +31,14 @@ export async function init_solver(env: SolverEnv, module: WebAssembly.Module): P
} }
type Argon2LeadingZeroesParams = { type Argon2LeadingZeroesParams = {
name: ChallengeAlgorithm.Argon2id; name: ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.LeadingZeroes; strategy: ChallengeStrategy.LeadingZeroes;
salt: string; salt: string;
difficulty: number; difficulty: number;
}; };
type Argon2TargetNumberParams = { type Argon2TargetNumberParams = {
name: ChallengeAlgorithm.Argon2id; name: ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.TargetNumber; strategy: ChallengeStrategy.TargetNumber;
salt: string; salt: string;
target: string; target: string;
@@ -90,7 +90,7 @@ export function solve(solver: SolverModule, algorithm: SolveParams): string | nu
let ret: string | number; let ret: string | number;
switch (algorithm.name) { switch (algorithm.name) {
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
switch (algorithm.strategy) { switch (algorithm.strategy) {
case ChallengeStrategy.LeadingZeroes: { case ChallengeStrategy.LeadingZeroes: {
ret = solver.exports.solve(algorithmToInt(algorithm.name), strategyToInt(ChallengeStrategy.LeadingZeroes), salt_ptr, salt_buf.length, algorithm.difficulty, 0, 0); ret = solver.exports.solve(algorithmToInt(algorithm.name), strategyToInt(ChallengeStrategy.LeadingZeroes), salt_ptr, salt_buf.length, algorithm.difficulty, 0, 0);

View File

@@ -31,7 +31,7 @@ export interface SHA256ChallengeConfig {
} }
export interface Argon2ChallengeConfig { export interface Argon2ChallengeConfig {
algorithm: ChallengeAlgorithm.Argon2id; algorithm: ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.LeadingZeroes | ChallengeStrategy.TargetNumber; strategy: ChallengeStrategy.LeadingZeroes | ChallengeStrategy.TargetNumber;
difficulty: number; difficulty: number;
parameters: Object; parameters: Object;
@@ -69,8 +69,8 @@ async function encode_challenge(inner_challenge: InnerChallenge, parameters: Obj
} }
break; break;
} }
case ChallengeAlgorithm.Argon2id: { case ChallengeAlgorithm.Argon2: {
challenge.algorithm = ChallengeAlgorithm.Argon2id; challenge.algorithm = ChallengeAlgorithm.Argon2;
challenge.salt = inner_challenge.salt; challenge.salt = inner_challenge.salt;
switch (inner_challenge.strategy) { switch (inner_challenge.strategy) {
case ChallengeStrategy.LeadingZeroes: { case ChallengeStrategy.LeadingZeroes: {
@@ -130,7 +130,7 @@ export async function generate_challenge(config: ChallengeConfig): Promise<Chall
let parameters_str: string; let parameters_str: string;
switch (config.algorithm) { switch (config.algorithm) {
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
switch (config.strategy) { switch (config.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
if (config.difficulty < 1 || config.difficulty > 64) { if (config.difficulty < 1 || config.difficulty > 64) {
@@ -235,7 +235,7 @@ export async function validate_challenge(challenge: Challenge, challenge_solutio
switch (challenge.algorithm) { switch (challenge.algorithm) {
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
if (typeof challenge_solution === "string") { 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) { 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); 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") { 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) { switch (challenge.strategy) {

View File

@@ -109,6 +109,9 @@ export class PowCaptcha extends LitElement {
this.initWorkers(); this.initWorkers();
this.addEventListener('reset', (ev) => this.reset(ev as CustomEvent));
this.addEventListener('solve', () => this.solveChallenge());
switch (this.auto) { switch (this.auto) {
case 'onload': case 'onload':
this.solveChallenge(); 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() { getCurrentWorkingNonce() {
return Atomics.load(new Uint32Array(this.sab), 0); return Atomics.load(new Uint32Array(this.sab), 0);
} }
@@ -177,10 +193,6 @@ export class PowCaptcha extends LitElement {
this.solverWorkers.push(new ChallengeWorker()); 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 wasm_module = await get_wasm_module();
let worker_promises: Promise<void>[] = []; let worker_promises: Promise<void>[] = [];
for (let i = 0; i < this.solverWorkers.length; i++) { for (let i = 0; i < this.solverWorkers.length; i++) {
@@ -296,11 +308,11 @@ export class PowCaptcha extends LitElement {
break; break;
} }
break; break;
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
switch (request.strategy) { switch (request.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
worker.postMessage({ worker.postMessage({
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.LeadingZeroes, strategy: ChallengeStrategy.LeadingZeroes,
salt: request.salt, salt: request.salt,
difficulty: request.difficulty, difficulty: request.difficulty,
@@ -308,7 +320,7 @@ export class PowCaptcha extends LitElement {
break; break;
case ChallengeStrategy.TargetNumber: case ChallengeStrategy.TargetNumber:
worker.postMessage({ worker.postMessage({
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.TargetNumber, strategy: ChallengeStrategy.TargetNumber,
target: request.target, target: request.target,
salt: request.salt, salt: request.salt,
@@ -354,22 +366,6 @@ export class PowCaptcha extends LitElement {
let request: ChallengeSolveRequest; 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) { switch (this.challengeData.algorithm) {
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
switch (this.challengeData.strategy) { switch (this.challengeData.strategy) {
@@ -391,11 +387,11 @@ export class PowCaptcha extends LitElement {
break; break;
} }
break; break;
case ChallengeAlgorithm.Argon2id: case ChallengeAlgorithm.Argon2:
switch (this.challengeData.strategy) { switch (this.challengeData.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
request = { request = {
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.LeadingZeroes, strategy: ChallengeStrategy.LeadingZeroes,
salt: this.challengeData.salt, salt: this.challengeData.salt,
difficulty: this.challengeData.difficulty, difficulty: this.challengeData.difficulty,
@@ -403,7 +399,7 @@ export class PowCaptcha extends LitElement {
break; break;
case ChallengeStrategy.TargetNumber: case ChallengeStrategy.TargetNumber:
request = { request = {
algorithm: ChallengeAlgorithm.Argon2id, algorithm: ChallengeAlgorithm.Argon2,
strategy: ChallengeStrategy.TargetNumber, strategy: ChallengeStrategy.TargetNumber,
target: this.challengeData.target, target: this.challengeData.target,
salt: this.challengeData.salt, salt: this.challengeData.salt,

View File

@@ -68,6 +68,7 @@ onmessage = async (event: MessageEvent<WorkerRequest>) => {
}; };
switch (event.data.algorithm) { switch (event.data.algorithm) {
case ChallengeAlgorithm.Argon2:
case ChallengeAlgorithm.SHA256: case ChallengeAlgorithm.SHA256:
switch (event.data.strategy) { switch (event.data.strategy) {
case ChallengeStrategy.LeadingZeroes: case ChallengeStrategy.LeadingZeroes:
@@ -84,22 +85,6 @@ onmessage = async (event: MessageEvent<WorkerRequest>) => {
break; break;
} }
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: case ChallengeAlgorithm.kCTF:
// @ts-ignore // @ts-ignore
params.strategy = ChallengeStrategy.Null; params.strategy = ChallengeStrategy.Null;
@@ -111,6 +96,7 @@ onmessage = async (event: MessageEvent<WorkerRequest>) => {
solution = solve(solver, params as SolveParams); solution = solve(solver, params as SolveParams);
if (event.data.algorithm !== ChallengeAlgorithm.kCTF) { if (event.data.algorithm !== ChallengeAlgorithm.kCTF) {
console.log(Atomics.load(atomic_nonce!, 0));
solution = Atomics.load(atomic_solution!, 0); solution = Atomics.load(atomic_solution!, 0);
} }
} catch (error: any) { } catch (error: any) {

View File

@@ -13,7 +13,7 @@ interface WorkerInitRequest {
} }
interface ChallengeLeadingZeroesSolveRequest { interface ChallengeLeadingZeroesSolveRequest {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.LeadingZeroes; strategy: ChallengeStrategy.LeadingZeroes;
salt: string; salt: string;
difficulty: number; difficulty: number;
@@ -24,7 +24,7 @@ interface WorkerChallengeLeadingZeroesSolveRequest extends ChallengeLeadingZeroe
} }
interface ChallengeTargetNumberSolveRequest { interface ChallengeTargetNumberSolveRequest {
algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2id; algorithm: ChallengeAlgorithm.SHA256 | ChallengeAlgorithm.Argon2;
strategy: ChallengeStrategy.TargetNumber; strategy: ChallengeStrategy.TargetNumber;
target: string; target: string;
salt: string; salt: string;

View File

@@ -2,16 +2,16 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
var argon2_params = std.crypto.pwhash.argon2.Params{ var argon2_params = std.crypto.pwhash.argon2.Params{
.t = 4, // time cost .t = 3, // time cost
.m = 256, // memory cost (in KiB) .m = 8192, // memory cost (in KiB)
.p = 1, // parallelism (this doesnt do anything because we are targeting wasm, and we do multithreading differently anyways) .p = 1, // parallelism
}; };
const dk_len: usize = 32; // 16 or 32 byte key const dk_len: usize = 32; // 16 or 32 byte key
pub fn hash(allocator: Allocator, challenge: []const u8, nonce: []const u8) ![]u8 { pub fn hash(allocator: Allocator, challenge: []const u8, nonce: []const u8) ![]u8 {
const derived = try allocator.alloc(u8, dk_len); 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; return derived;
} }

View File

@@ -77,18 +77,12 @@ pub const Challenge = struct {
const salt_str = challenge; const salt_str = challenge;
const salt_bytes_len = try std.base64.standard.Decoder.calcSizeForSlice(salt_str); 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); const salt_bytes = try allocator.alloc(u8, salt_bytes_len);
defer allocator.free(salt_bytes); defer allocator.free(salt_bytes);
try std.base64.standard.Decoder.decode(salt_bytes, salt_str); 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); 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); try salt.ensureCapacity(usize_salt_bytes.len);
@memcpy(salt.limbs[0..usize_salt_bytes.len], usize_salt_bytes); @memcpy(salt.limbs[0..usize_salt_bytes.len], usize_salt_bytes);
salt.setLen(usize_salt_bytes.len); salt.setLen(usize_salt_bytes.len);
@@ -118,22 +112,16 @@ pub const Challenge = struct {
pub fn solve(self: *Self, allocator: Allocator) ![]u8 { pub fn solve(self: *Self, allocator: Allocator) ![]u8 {
for (0..self.difficulty) |_| { for (0..self.difficulty) |_| {
std.log.info("Solving challenge with difficulty {d}\n", .{self.difficulty});
for (0..1277) |_| { for (0..1277) |_| {
try square_mod(&self.salt); try square_mod(&self.salt);
} }
try self.salt.bitXor(&self.salt, &managed_one.?); try self.salt.bitXor(&self.salt, &managed_one.?);
} }
std.log.info("solved challenge: {any}\n", .{self});
return try self.encode(allocator); return try self.encode(allocator);
} }
pub fn verify(self: *Self, allocator: Allocator, solution: *Challenge) !bool { 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) { if (managed_one == null) {
managed_one = try Int.init(allocator); managed_one = try Int.init(allocator);
try managed_one.?.set(1); try managed_one.?.set(1);
@@ -145,8 +133,6 @@ pub const Challenge = struct {
try square_mod(&solution.salt); 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 // 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 // how I will do it
if (self.salt.eql(solution.salt)) { if (self.salt.eql(solution.salt)) {

View File

@@ -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. /// 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 { 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) { switch (algorithm) {
algorithms.Algorithm.sha256 => return solve_argon2_or_sha256(salt_ptr, salt_len, difficulty, algorithm, strategy, target_ptr, target_len), 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), 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; return -1;
} }
// const max_nonce_iterations: u64 = 1_000_000_000; const max_nonce_iterations: u64 = 1_000_000_000;
const max_nonce_iterations: u64 = 100_000; // const max_nonce_iterations: u64 = 100_000;
std.log.info("Solve called with salt {s}\n", .{salt_slice});
// 64 + 9 digits for nonce since the max nonce is 999_999_999 (not 1 billion since nonce < max_nonce_iterations) // 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 { 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; return -1;
}; };
} else { } else {
input = input_buffer[0 .. salt_len + nonce_str.len]; input = algorithms.SHA256.hash(allocator, input_buffer[0 .. salt_len + nonce_str.len]) catch {
} std.log.err("Error hashing salt", .{});
const hash_hex_slice = algorithms.SHA256.hash(allocator, input) catch {
std.log.err("Error hashing key", .{});
return -1; return -1;
}; };
if (algorithm == .argon2) {
allocator.free(input);
} }
switch (strategy) { switch (strategy) {
.leading_zeros => { .leading_zeros => {
_ = hex_encoder.encode(hash_hex_slice); _ = hex_encoder.encode(input);
allocator.free(hash_hex_slice); allocator.free(input);
if (hex_encoder.countZeroes(difficulty)) { if (hex_encoder.countZeroes(difficulty)) {
// Found a solution! // Found a solution!
if (__cmpxchg_solution(-1, nonce) == -1) { 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 => { .target_number => {
const hex = hex_encoder.encode(hash_hex_slice); const hex = hex_encoder.encode(input);
allocator.free(hash_hex_slice); allocator.free(input);
if (std.mem.eql(u8, hex, target_slice.?)) { if (std.mem.eql(u8, hex, target_slice.?)) {
// Found a solution! // Found a solution!
if (__cmpxchg_solution(-1, nonce) == -1) { 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]; 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| { const challenge = algorithms.kCTF.Challenge.from_string(allocator, challenge_slice, difficulty) catch |err| {
std.log.info("Error decoding challenge: {s}\n", .{@errorName(err)}); std.log.info("Error decoding challenge: {s}\n", .{@errorName(err)});
return -1; return -1;
}; };
defer challenge.destroy(allocator); defer challenge.destroy(allocator);
std.log.info("decoded challenge {any}\n", .{challenge});
const solution = challenge.solve(allocator) catch |err| { const solution = challenge.solve(allocator) catch |err| {
std.log.info("Error solving challenge: {s}\n", .{@errorName(err)}); std.log.info("Error solving challenge: {s}\n", .{@errorName(err)});
return -1; return -1;
}; };
std.log.info("Solution: {s}\n", .{solution});
const output_ptr = allocator.alloc(u8, solution.len + 4) catch return 0; const output_ptr = allocator.alloc(u8, solution.len + 4) catch return 0;
var output_slice = output_ptr[0 .. solution.len + 2]; var output_slice = output_ptr[0 .. solution.len + 2];

View File

@@ -74,24 +74,22 @@ fn validate_argon2_or_sha256(challenge_ptr: [*]u8, challenge_len: usize, nonce:
var input: []u8 = undefined; var input: []u8 = undefined;
if (algorithm == .argon2) { 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; 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 { } 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{}; 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) { switch (strategy) {
.leading_zeros => { .leading_zeros => {
_ = hex_encoder.encode(hash_hex_slice); _ = hex_encoder.encode(input);
if (hex_encoder.countZeroes(difficulty)) { if (hex_encoder.countZeroes(difficulty)) {
return true; return true;
} }
}, },
.target_number => { .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; 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 challenge_buf = challenge_ptr[0..challenge_len];
const solution_buf = solution_ptr[0..solution_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; 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; const solution = algorithms.kCTF.Challenge.from_string(allocator, solution_buf, difficulty) catch return false;
defer { defer {
challenge.destroy(allocator); challenge.destroy(allocator);
solution.destroy(allocator); solution.destroy(allocator);
} }
std.log.info("decoded challenge and solution\n", .{});
const is_valid = challenge.verify(allocator, solution) catch return false; const is_valid = challenge.verify(allocator, solution) catch return false;
return is_valid; 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; hash_slice = algorithms.SHA256.hash(allocator, input_slice[0 .. challenge_len + nonce_len]) catch return 0;
}, },
algorithms.Algorithm.argon2 => { algorithms.Algorithm.argon2 => {
const argon_key = algorithms.Argon2.hash(allocator, challenge, nonce) catch return 0; hash_slice = 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;
}, },
else => return 0, else => return 0,
} }