Initial commit

Once again a weird place to commit, I have already done a lot of work, but I am just bad at using git, okay.
This commit is contained in:
Zoe
2025-11-17 16:12:26 +00:00
commit cfab3d0b8f
58 changed files with 18689 additions and 0 deletions

31
solver/src/hasher.zig Normal file
View File

@@ -0,0 +1,31 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
var argon2_params = std.crypto.pwhash.argon2.Params{
.t = 4, // time cost
.m = 256, // memory cost (in KiB)
.p = 1, // parallelism (this doesnt do anything because we are targeting wasm, and we do multithreading differently anyways)
};
const dk_len: usize = 32; // 16 or 32 byte key
var derived: [dk_len]u8 = undefined;
var buffer_hash_hex: [64]u8 = undefined;
fn bytesToHex(bytes: []const u8, output: []u8) void {
const hex_chars = "0123456789abcdef";
var i: usize = 0;
while (i < bytes.len) : (i += 1) {
output[i * 2] = hex_chars[(bytes[i] >> 4)];
output[i * 2 + 1] = hex_chars[bytes[i] & 0x0F];
}
}
pub fn hash(allocator: Allocator, challenge: []const u8, nonce: []const u8) ![]u8 {
try std.crypto.pwhash.argon2.kdf(allocator, &derived, nonce, challenge, argon2_params, .argon2id);
var hash_bytes: [32]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash(&derived, @ptrCast(hash_bytes[0..].ptr), .{});
bytesToHex(&hash_bytes, &buffer_hash_hex);
return buffer_hash_hex[0..];
}

7
solver/src/kctf.zig Normal file
View File

@@ -0,0 +1,7 @@
// A PoW algorithm based on google's kCTF scheme
// https://google.github.io/kctf/
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const math = std.math;

159
solver/src/solver.zig Normal file
View File

@@ -0,0 +1,159 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const hasher = @import("hasher.zig");
// var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
// var allocator = gpa.allocator();
var allocator = std.heap.wasm_allocator;
extern fn __get_solution() i32;
extern fn __set_solution(value: i32) void;
extern fn __cmpxchg_solution(old: i32, new: i32) i32;
extern fn __fetch_add_nonce(value: i32) i32;
// fn log(comptime fmt: []const u8, args: anytype) void {
// const formatted = std.fmt.allocPrint(allocator, fmt, args) catch return;
// __log(@intFromPtr(formatted.ptr), formatted.len);
// allocator.free(formatted);
// }
export fn malloc(byte_count: usize) ?*u8 {
const ptr = allocator.alloc(u8, byte_count) catch return null;
return @ptrCast(ptr.ptr);
}
export fn free(ptr: ?*anyopaque, byte_count: usize) void {
if (ptr) |p| {
const cast_ptr: [*]u8 = @ptrCast(p);
allocator.free(cast_ptr[0..byte_count]);
}
}
const SolveError = enum(u32) {
InvalidDifficulty = 1,
InvalidNonce = 2,
NoSolution = 3,
OutOfMemory = 4,
};
var solve_error: ?SolveError = null;
export fn get_solve_error() u32 {
if (solve_error) |err| {
return @intFromEnum(err);
}
return 0;
}
// returns nonce on success, -1 on failure
// to get the error, call get_solve_error
export fn solve_leaading_zeroes_challenge(challenge_ptr: [*]u8, challenge_len: usize, difficulty: u32) i32 {
solve_error = null;
const challenge_slice = challenge_ptr[0..challenge_len];
if (difficulty < 1 or difficulty > 64) {
solve_error = SolveError.InvalidDifficulty;
return -1;
}
var target_prefix_buffer: [64]u8 = @splat('0');
const target_prefix = target_prefix_buffer[0..difficulty];
const max_nonce_iterations: u64 = 1_000_000_000;
// 64 + 9 digits for nonce since the max nonce is 999_999_999 (not 1 billion since nonce < max_nonce_iterations)
var input_buffer: []u8 = allocator.alloc(u8, challenge_len + 9) catch {
// log("Failed to allocate memory for challenge\n", .{});
solve_error = SolveError.OutOfMemory;
return -1;
};
// dont leak memory :pepega:
defer allocator.free(input_buffer);
@memcpy(input_buffer[0..challenge_len], challenge_slice);
var nonce = __fetch_add_nonce(1);
while (nonce < max_nonce_iterations) : (nonce = __fetch_add_nonce(1)) {
if (__get_solution() != -1) {
// solution has already been found, no point in continuing
return 0;
}
const nonce_str = std.fmt.bufPrint(input_buffer[challenge_len..], "{d}", .{nonce}) catch {
solve_error = SolveError.InvalidNonce;
return -1;
};
const hash_hex_slice = hasher.hash(allocator, input_buffer[0..challenge_len], input_buffer[challenge_len .. challenge_len + nonce_str.len]) catch {
solve_error = SolveError.OutOfMemory;
return -1;
};
if (std.mem.startsWith(u8, hash_hex_slice, target_prefix)) {
// Found a solution!
if (__cmpxchg_solution(-1, nonce) == -1) {
// we found a solution, and we are the first to do so
return nonce;
} else {
// we found a solution, but we are not the first to do so
return 0;
}
}
}
solve_error = SolveError.NoSolution;
return -1;
}
export fn solve_target_number_challenge(target_ptr: [*]u8, target_len: usize, salt_ptr: [*]u8, salt_len: usize) i32 {
solve_error = null;
const target_slice = target_ptr[0..target_len];
const salt_slice = salt_ptr[0..salt_len];
// TODO: take in max number
const max_nonce_iterations: usize = 1_000_000_000;
const max_digits = std.math.log10(max_nonce_iterations);
var input_buffer: []u8 = allocator.alloc(u8, salt_len + max_digits) catch {
solve_error = SolveError.OutOfMemory;
return -1;
};
defer allocator.free(input_buffer);
@memcpy(input_buffer[0..salt_len], salt_slice);
var nonce = __fetch_add_nonce(1);
while (nonce < max_nonce_iterations) : (nonce = __fetch_add_nonce(1)) {
if (__get_solution() != -1) {
return 0;
}
const nonce_str = std.fmt.bufPrint(input_buffer[salt_len..], "{d}", .{nonce}) catch {
solve_error = SolveError.InvalidNonce;
return -1;
};
const hash_hex_slice = hasher.hash(allocator, input_buffer[0..salt_len], input_buffer[salt_len .. salt_len + nonce_str.len]) catch {
solve_error = SolveError.OutOfMemory;
return -1;
};
if (std.mem.eql(u8, target_slice, hash_hex_slice)) {
// Found a solution!
if (__cmpxchg_solution(-1, nonce) == -1) {
// we found a solution, and we are the first to do so
return nonce;
} else {
// we found a solution, but we are not the first to do so
return 0;
}
}
}
solve_error = SolveError.NoSolution;
return -1;
}

81
solver/src/validator.zig Normal file
View File

@@ -0,0 +1,81 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const hasher = @import("hasher.zig");
var allocator = std.heap.wasm_allocator;
export fn malloc(byte_count: usize) ?*u8 {
const ptr = allocator.alloc(u8, byte_count) catch return null;
return @ptrCast(ptr.ptr);
}
export fn free(ptr: ?*anyopaque, byte_count: usize) void {
if (ptr) |p| {
const cast_ptr: [*]u8 = @ptrCast(p);
allocator.free(cast_ptr[0..byte_count]);
}
}
fn bytesToHex(bytes: []const u8, buf: []u8) void {
const hex_chars = "0123456789abcdef";
var i: usize = 0;
while (i < bytes.len) : (i += 1) {
buf[i * 2] = hex_chars[(bytes[i] >> 4)];
buf[i * 2 + 1] = hex_chars[bytes[i] & 0x0F];
}
}
export fn validate_leading_zeroes_challenge(challenge_ptr: [*]u8, challenge_len: usize, nonce_ptr: [*]u8, nonce_len: usize, difficulty: u32) i32 {
const challenge_slice = challenge_ptr[0..challenge_len];
const nonce_slice = nonce_ptr[0..nonce_len];
if (difficulty < 1 or difficulty > 64) {
return -1;
}
var target_prefix_buffer: [64]u8 = @splat('0');
const target_prefix = target_prefix_buffer[0..difficulty];
const hash_hex_slice = hasher.hash(allocator, challenge_slice, nonce_slice) catch return -2;
if (!std.mem.startsWith(u8, hash_hex_slice, target_prefix)) {
return -3;
}
return 0;
}
export fn validate_target_number_challenge(target_ptr: [*]u8, target_len: usize, nonce_ptr: [*]u8, nonce_len: usize, salt_ptr: [*]u8, salt_len: usize) i32 {
const target_slice = target_ptr[0..target_len];
const salt_slice = salt_ptr[0..salt_len];
const nonce_slice = nonce_ptr[0..nonce_len];
const hash_hex_slice = hasher.hash(allocator, salt_slice, nonce_slice) catch return -2;
if (!std.mem.eql(u8, target_slice, hash_hex_slice)) {
return -3;
}
return 0;
}
export fn hash(challenge_ptr: [*]u8, challenge_len: usize, nonce_ptr: [*]u8, nonce_len: usize) u64 {
const challenge = challenge_ptr[0..challenge_len];
const nonce = nonce_ptr[0..nonce_len];
const hash_slice = hasher.hash(allocator, challenge, nonce) catch return 0;
// bs to get the compiler to not whine about hash_slice.len being a u5 annd thus cannot be shifted by 32
var ret: u64 = hash_slice.len;
ret <<= 32;
ret |= @intFromPtr(hash_slice.ptr);
return ret;
}
// pub fn main() void {
// const challenge = "4d7220e22a1ea588fea60000ab8874194e4c6ffd71077adbae915826c73dbf48";
// const nonce = "4302";
// const difficulty = 3;
// std.log.info("{d}", .{validate_challenge(@constCast(challenge[0..].ptr), challenge.len, @constCast(nonce[0..].ptr), nonce.len, difficulty)});
// }