Skip to content

Instantly share code, notes, and snippets.

@theoparis
Created April 28, 2023 09:45
Show Gist options
  • Save theoparis/23ff33fe720d21a5bfa0d67365883c19 to your computer and use it in GitHub Desktop.
Save theoparis/23ff33fe720d21a5bfa0d67365883c19 to your computer and use it in GitHub Desktop.
thash zig + bun backup
const modes = {
argon2d: 0,
argon2i: 1,
argon2id: 2,
};
export type Mode = keyof typeof modes;
export type WasmExports = {
memory: { buffer: ArrayBuffer };
hptr: () => number;
cad: () => void;
free: (ptr: number, size: number) => void;
sad: (ptr: number, size: number) => void;
alloc: (size: number) => number;
ssecret: (ptr: number, size: number) => void;
csecret: () => void;
hash_password: (
ptr: number,
size: number,
mode: number,
time: number,
memory: number,
) => boolean;
verify_password: (
ptr: number,
size: number,
hashPtr: number,
hashSize: number,
) => boolean;
};
export type HashOptions = {
t?: number;
m?: number;
mode?: Mode;
};
const utf8_encode = TextEncoder.prototype.encode.bind(new TextEncoder());
const utf8_decode = TextDecoder.prototype.decode.bind(new TextDecoder());
import { readFile } from "fs/promises";
export const load = async () => {
const file = await readFile(new URL("thash.wasm", import.meta.url), "buffer");
const module = new WebAssembly.Module(file as unknown as BufferSource);
const instance = new WebAssembly.Instance(module, {
wasi_snapshot_preview1: {
fd_write() {
throw null;
},
random_get(ptr: number, size: number) {
if (!wasm) throw new Error("WebAssembly module not loaded");
return crypto.getRandomValues(
new Uint8Array(wasm.memory.buffer, ptr, size),
);
},
},
});
const wasm = instance.exports as unknown as WasmExports;
const hptr = wasm.hptr();
const presets = Object.assign(Object.create(null), {
argon2i: Object.assign(Object.create(null), {
moderate: { t: 6, m: 134217728 / 1024 },
sensitive: { t: 8, m: 536870912 / 1024 },
interactive: { t: 4, m: 33554432 / 1024 },
}),
argon2id: Object.assign(Object.create(null), {
moderate: { t: 3, m: 67108864 / 1024 },
sensitive: { t: 4, m: 1073741824 / 1024 },
interactive: { t: 2, m: 67108864 / 1024 },
}),
});
// TODO: extract core functions into @theoparis/twasm
const u8 = (buf: BufferSource | number) => {
if (buf instanceof Uint8Array) return buf;
if (ArrayBuffer.isView(buf))
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
if (buf instanceof ArrayBuffer || buf instanceof SharedArrayBuffer)
return new Uint8Array(buf);
throw new TypeError("expected a buffer");
};
const ad = (ad: number) => {
if (!wasm) throw new Error("WebAssembly module not loaded");
if (null == ad) return wasm.cad();
const buf = "string" !== typeof ad ? u8(ad) : utf8_encode(ad);
const ptr = wasm.alloc(buf.length);
new Uint8Array(wasm.memory.buffer, ptr, buf.length).set(buf);
wasm.sad(ptr, buf.length);
};
const secret = (secret: string) => {
if (!wasm) throw new Error("WebAssembly module not loaded");
if (null == secret) return wasm.csecret();
const buf = "string" !== typeof secret ? u8(secret) : utf8_encode(secret);
const ptr = wasm.alloc(buf.length);
new Uint8Array(wasm.memory.buffer, ptr, buf.length).set(buf);
wasm.ssecret(ptr, buf.length);
};
const hashPassword = (pswd: string, options?: HashOptions) => {
if (!wasm) throw new Error("WebAssembly module not loaded");
const buf = "string" !== typeof pswd ? u8(pswd) : utf8_encode(pswd);
const ptr = wasm.alloc(buf.length);
new Uint8Array(wasm.memory.buffer, ptr, buf.length).set(buf);
try {
if (
!wasm.hash_password(
ptr,
buf.length,
modes[options?.mode ?? "argon2id"] ?? modes.argon2id,
options?.t ?? presets.argon2i.moderate.t,
options?.m ?? presets.argon2i.moderate.m,
)
)
return null;
return utf8_decode(new Uint8Array(wasm.memory.buffer, hptr, 128));
} finally {
wasm.free(ptr, buf.length);
}
};
const verifyPassword = (pswd: string, hash: string) => {
if (!wasm) throw new Error("WebAssembly module not loaded");
const passwordBuffer =
"string" !== typeof pswd ? u8(pswd) : utf8_encode(pswd);
const passwordPointer = wasm.alloc(passwordBuffer.length);
new Uint8Array(
wasm.memory.buffer,
passwordPointer,
passwordBuffer.length,
).set(passwordBuffer);
const hashBuffer = "string" !== typeof hash ? u8(hash) : utf8_encode(hash);
const hashPointer = wasm.alloc(hashBuffer.length);
new Uint8Array(wasm.memory.buffer, hashPointer, hashBuffer.length).set(
hashBuffer,
);
try {
if (
!wasm.verify_password(
passwordPointer,
passwordBuffer.length,
hashPointer,
hashBuffer.length,
)
)
return false;
return true;
} finally {
wasm.free(passwordPointer, passwordBuffer.length);
}
};
return {
hashPassword,
verifyPassword,
};
};
const std = @import("std");
const builtin = @import("builtin");
var ad: ?[]const u8 = null;
var out: [128]u8 = undefined;
const allocator = std.heap.page_allocator;
var secret: ?[]const u8 = null;
export fn hptr() *[128]u8 {
return &out;
}
export fn cad() void {
if (ad) |s| allocator.free(s);
ad = null;
}
export fn free(ptr: [*]u8, size: usize) void {
allocator.free(ptr[0..size]);
}
export fn csecret() void {
if (secret) |s| allocator.free(s);
secret = null;
}
export fn sad(ptr: [*]u8, size: usize) void {
if (ad) |s| allocator.free(s);
ad = ptr[0..size];
}
export fn alloc(size: usize) ?[*]u8 {
return (allocator.alloc(u8, size) catch return null).ptr;
}
export fn ssecret(ptr: [*]u8, size: usize) void {
if (secret) |s| allocator.free(s);
secret = ptr[0..size];
}
export fn hash_password(ptr: [*]const u8, size: usize, mode: u8, time: u32, memory: u32) bool {
_ = std.crypto.pwhash.argon2.strHash(ptr[0..size], .{
.allocator = allocator,
.mode = @intToEnum(std.crypto.pwhash.argon2.Mode, mode),
.params = .{
.p = 1,
.ad = ad,
.t = time,
.m = memory,
.secret = secret,
},
}, &out) catch return false;
return true;
}
export fn verify_password(
password_ptr: [*]const u8,
password_size: usize,
hash_ptr: [*]const u8,
hash_size: usize,
) bool {
_ = std.crypto.pwhash.argon2.strVerify(
hash_ptr[0..hash_size],
password_ptr[0..password_size],
.{
.allocator = allocator,
},
) catch return false;
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment