Clean up code. Reorganize files. Port stuff from other branches. + more
This turns the project into a monorepo using pnpm workspaces, dramatically simplifying the build process. It also fixes a lot of bugs and just generally makes the codebase a lot cleaner.
This commit is contained in:
2
packages/solver/.gitignore
vendored
Normal file
2
packages/solver/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.zig-cache/
|
||||
zig-out/
|
||||
46
packages/solver/build.zig
Normal file
46
packages/solver/build.zig
Normal file
@@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// solver
|
||||
const solver_mod = b.addModule("solver", .{
|
||||
.root_source_file = b.path("src/solver.zig"),
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
|
||||
const solver_exe = b.addExecutable(.{
|
||||
.name = "solver",
|
||||
.root_module = solver_mod,
|
||||
});
|
||||
|
||||
if (target.result.cpu.arch == .wasm32) {
|
||||
solver_exe.entry = .disabled;
|
||||
solver_exe.rdynamic = true;
|
||||
solver_exe.link_gc_sections = true;
|
||||
solver_exe.lto = .full;
|
||||
}
|
||||
|
||||
b.installArtifact(solver_exe);
|
||||
|
||||
// validator
|
||||
const validator_mod = b.addModule("validator", .{
|
||||
.root_source_file = b.path("src/validator.zig"),
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
|
||||
const validator_exe = b.addExecutable(.{
|
||||
.name = "validator",
|
||||
.root_module = validator_mod,
|
||||
});
|
||||
|
||||
if (target.result.cpu.arch == .wasm32) {
|
||||
validator_exe.entry = .disabled;
|
||||
validator_exe.rdynamic = true;
|
||||
}
|
||||
|
||||
b.installArtifact(validator_exe);
|
||||
}
|
||||
11
packages/solver/package.json
Normal file
11
packages/solver/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "solver",
|
||||
"description": "Zig WASM POW solver, not an actual node package, just using this so that pnpm will build it for me",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "pnpm run build:wasm && pnpm run minify",
|
||||
"build:wasm": "zig build --release=fast -Dtarget=wasm32-freestanding -Dcpu=generic+bulk_memory+bulk_memory_opt+simd128+tail_call",
|
||||
"minify": "wasm-opt --strip-debug --strip-dwarf -O4 -o zig-out/bin/solver.wasm zig-out/bin/solver.wasm && wasm-opt --strip-debug --strip-dwarf -O4 -o zig-out/bin/validator.wasm zig-out/bin/validator.wasm"
|
||||
}
|
||||
}
|
||||
18
packages/solver/src/argon2.zig
Normal file
18
packages/solver/src/argon2.zig
Normal file
@@ -0,0 +1,18 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
var argon2_params = std.crypto.pwhash.argon2.Params{
|
||||
.t = 3, // time cost
|
||||
.m = 8192, // 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;
|
||||
|
||||
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);
|
||||
|
||||
return derived[0..];
|
||||
}
|
||||
155
packages/solver/src/solver.zig
Normal file
155
packages/solver/src/solver.zig
Normal file
@@ -0,0 +1,155 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const utils = @import("utils.zig");
|
||||
|
||||
const argon2 = @import("argon2.zig");
|
||||
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||
var allocator = gpa.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;
|
||||
extern fn __log(str_ptr: usize, str_len: usize) void;
|
||||
|
||||
fn log(comptime level: std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime fmt: []const u8, args: anytype) void {
|
||||
if (comptime builtin.target.cpu.arch != .wasm32) {
|
||||
std.log.defaultLog(level, scope, fmt, args);
|
||||
return;
|
||||
}
|
||||
|
||||
const log_level_str = switch (level) {
|
||||
.err => "Error: ",
|
||||
.warn => "Warning: ",
|
||||
.info => "Info: ",
|
||||
.debug => "Debug: ",
|
||||
};
|
||||
|
||||
const formatted = std.fmt.allocPrint(allocator, fmt, args) catch return;
|
||||
const log_str = std.fmt.allocPrint(allocator, "{s}{s}", .{ log_level_str, formatted }) catch return;
|
||||
allocator.free(formatted);
|
||||
__log(@intFromPtr(log_str.ptr), log_str.len);
|
||||
allocator.free(log_str);
|
||||
}
|
||||
|
||||
pub const std_options: std.Options = .{ .logFn = log };
|
||||
var hex_encoder = utils.HexEncoder{};
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
const challenge_slice = challenge_ptr[0..challenge_len];
|
||||
|
||||
if (difficulty < 1 or difficulty > 64) {
|
||||
std.log.err("Invalid difficulty for leading zeroes\n", .{});
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 {
|
||||
std.log.err("Failed to allocate memory for challenge\n", .{});
|
||||
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 {
|
||||
std.log.err("Failed to allocate memory for nonce\n", .{});
|
||||
return -1;
|
||||
};
|
||||
|
||||
const argon2_key = argon2.hash(allocator, input_buffer[0..challenge_len], input_buffer[challenge_len .. challenge_len + nonce_str.len]) catch {
|
||||
std.log.err("Failed to hash argon2 key\n", .{});
|
||||
return -1;
|
||||
};
|
||||
|
||||
_ = hex_encoder.encode(argon2_key);
|
||||
if (!hex_encoder.countZeroes(difficulty)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
export fn solve_target_number_challenge(target_ptr: [*]u8, target_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];
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
return -1;
|
||||
};
|
||||
|
||||
const argon2_key = argon2.hash(allocator, input_buffer[0..salt_len], input_buffer[salt_len .. salt_len + nonce_str.len]) catch {
|
||||
return -1;
|
||||
};
|
||||
|
||||
const hex_slice = hex_encoder.encode(argon2_key);
|
||||
|
||||
if (std.mem.eql(u8, target_slice, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
43
packages/solver/src/utils.zig
Normal file
43
packages/solver/src/utils.zig
Normal file
@@ -0,0 +1,43 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const HexEncoder = struct {
|
||||
scratch: [64]u8 = undefined,
|
||||
scratch_set: bool = false,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn encode(self: *Self, bytes: []const u8) []u8 {
|
||||
self.scratch_set = true;
|
||||
|
||||
const hex_chars = "0123456789abcdef";
|
||||
var i: usize = 0;
|
||||
while (i < bytes.len) : (i += 1) {
|
||||
self.scratch[i * 2] = hex_chars[(bytes[i] >> 4)];
|
||||
self.scratch[i * 2 + 1] = hex_chars[bytes[i] & 0x0F];
|
||||
}
|
||||
|
||||
return &self.scratch;
|
||||
}
|
||||
|
||||
// counts the number of leading hexidecimal zeroes in the scratch buffer
|
||||
// which is set by encode
|
||||
pub fn countZeroes(self: *Self, zeroes: usize) bool {
|
||||
if (!self.scratch_set) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (zeroes > 64 or zeroes == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < zeroes) : (i += 1) {
|
||||
if (self.scratch[i] != '0') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
76
packages/solver/src/validator.zig
Normal file
76
packages/solver/src/validator.zig
Normal file
@@ -0,0 +1,76 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const argon2 = @import("argon2.zig");
|
||||
const utils = @import("utils.zig");
|
||||
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
|
||||
var allocator = gpa.allocator();
|
||||
|
||||
var hex_encoder = utils.HexEncoder{};
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const argon2_key = argon2.hash(allocator, challenge_slice, nonce_slice) catch return -2;
|
||||
|
||||
_ = hex_encoder.encode(argon2_key);
|
||||
if (!hex_encoder.countZeroes(difficulty)) {
|
||||
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 argon2_key = argon2.hash(allocator, salt_slice, nonce_slice) catch return -2;
|
||||
const hex_slice = hex_encoder.encode(argon2_key);
|
||||
|
||||
if (!std.mem.eql(u8, target_slice, 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 argon2_key = argon2.hash(allocator, challenge, nonce) catch return 0;
|
||||
const hex_slice = hex_encoder.encode(argon2_key);
|
||||
|
||||
// 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 = hex_slice.len;
|
||||
ret <<= 32;
|
||||
ret |= @intFromPtr(hex_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)});
|
||||
// }
|
||||
Reference in New Issue
Block a user