feat: Replace aCAPTCHA with official ALTCHA (altcha.org) Proof-of-Work web component widget

This commit is contained in:
Richard
2026-07-31 11:39:29 +02:00
parent 6ef74c7cf4
commit 83c782c469
417 changed files with 100543 additions and 317 deletions
+310
View File
@@ -0,0 +1,310 @@
import Mutex from "./mutex";
import {
type IDataType,
type IEmbeddedWasm,
decodeBase64,
getDigestHex,
getUInt8Buffer,
hexStringEqualsUInt8,
writeHexToUInt8,
} from "./util";
export const MAX_HEAP = 16 * 1024;
const WASM_FUNC_HASH_LENGTH = 4;
const wasmMutex = new Mutex();
type ThenArg<T> = T extends Promise<infer U>
? U
: // biome-ignore lint/suspicious/noExplicitAny: TS quirks
T extends (...args: any[]) => Promise<infer V>
? V
: T;
export type IHasher = {
/**
* Initializes hash state to default value
*/
init: () => IHasher;
/**
* Updates the hash content with the given data
*/
update: (data: IDataType) => IHasher;
/**
* Calculates the hash of all of the data passed to be hashed with hash.update().
* Defaults to hexadecimal string
* @param outputType If outputType is "binary", it returns Uint8Array. Otherwise it
* returns hexadecimal string
*/
digest: {
(outputType: "binary"): Uint8Array;
(outputType?: "hex"): string;
};
/**
* Save the current internal state of the hasher for later resumption with load().
* Cannot be called before .init() or after .digest()
*
* Note that this state can include arbitrary information about the value being hashed (e.g.
* could include N plaintext bytes from the value), so needs to be treated as being as
* sensitive as the input value itself.
*/
save: () => Uint8Array;
/**
* Resume a state that was created by save(). If this state was not created by a
* compatible build of hash-wasm, an exception will be thrown.
*/
load: (state: Uint8Array) => IHasher;
/**
* Block size in bytes
*/
blockSize: number;
/**
* Digest size in bytes
*/
digestSize: number;
};
const wasmModuleCache = new Map<string, Promise<WebAssembly.Module>>();
export async function WASMInterface(binary: IEmbeddedWasm, hashLength: number) {
let wasmInstance = null;
let memoryView: Uint8Array = null;
let initialized = false;
if (typeof WebAssembly === "undefined") {
throw new Error("WebAssembly is not supported in this environment!");
}
const writeMemory = (data: Uint8Array, offset = 0) => {
memoryView.set(data, offset);
};
const getMemory = () => memoryView;
const getExports = () => wasmInstance.exports;
const setMemorySize = (totalSize: number) => {
wasmInstance.exports.Hash_SetMemorySize(totalSize);
const arrayOffset: number = wasmInstance.exports.Hash_GetBuffer();
const memoryBuffer = wasmInstance.exports.memory.buffer;
memoryView = new Uint8Array(memoryBuffer, arrayOffset, totalSize);
};
const getStateSize = () => {
const view = new DataView(wasmInstance.exports.memory.buffer);
const stateSize = view.getUint32(wasmInstance.exports.STATE_SIZE, true);
return stateSize;
};
const loadWASMPromise = wasmMutex.dispatch(async () => {
if (!wasmModuleCache.has(binary.name)) {
const asm = decodeBase64(binary.data);
const promise = WebAssembly.compile(asm);
wasmModuleCache.set(binary.name, promise);
}
const module = await wasmModuleCache.get(binary.name);
wasmInstance = await WebAssembly.instantiate(module, {
// env: {
// emscripten_memcpy_big: (dest, src, num) => {
// const memoryBuffer = wasmInstance.exports.memory.buffer;
// const memView = new Uint8Array(memoryBuffer, 0);
// memView.set(memView.subarray(src, src + num), dest);
// },
// print_memory: (offset, len) => {
// const memoryBuffer = wasmInstance.exports.memory.buffer;
// const memView = new Uint8Array(memoryBuffer, 0);
// console.log('print_int32', memView.subarray(offset, offset + len));
// },
// },
});
// wasmInstance.exports._start();
});
const setupInterface = async () => {
if (!wasmInstance) {
await loadWASMPromise;
}
const arrayOffset: number = wasmInstance.exports.Hash_GetBuffer();
const memoryBuffer = wasmInstance.exports.memory.buffer;
memoryView = new Uint8Array(memoryBuffer, arrayOffset, MAX_HEAP);
};
const init = (bits: number = null) => {
initialized = true;
wasmInstance.exports.Hash_Init(bits);
};
const updateUInt8Array = (data: Uint8Array): void => {
let read = 0;
while (read < data.length) {
const chunk = data.subarray(read, read + MAX_HEAP);
read += chunk.length;
memoryView.set(chunk);
wasmInstance.exports.Hash_Update(chunk.length);
}
};
const update = (data: IDataType) => {
if (!initialized) {
throw new Error("update() called before init()");
}
const Uint8Buffer = getUInt8Buffer(data);
updateUInt8Array(Uint8Buffer);
};
const digestChars = new Uint8Array(hashLength * 2);
const digest = (
outputType: "hex" | "binary",
padding: number = null,
): Uint8Array | string => {
if (!initialized) {
throw new Error("digest() called before init()");
}
initialized = false;
wasmInstance.exports.Hash_Final(padding);
if (outputType === "binary") {
// the data is copied to allow GC of the original memory object
return memoryView.slice(0, hashLength);
}
return getDigestHex(digestChars, memoryView, hashLength);
};
const save = (): Uint8Array => {
if (!initialized) {
throw new Error(
"save() can only be called after init() and before digest()",
);
}
const stateOffset: number = wasmInstance.exports.Hash_GetState();
const stateLength: number = getStateSize();
const memoryBuffer = wasmInstance.exports.memory.buffer;
const internalState = new Uint8Array(
memoryBuffer,
stateOffset,
stateLength,
);
// prefix is 4 bytes from SHA1 hash of the WASM binary
// it is used to detect incompatible internal states between different versions of hash-wasm
const prefixedState = new Uint8Array(WASM_FUNC_HASH_LENGTH + stateLength);
writeHexToUInt8(prefixedState, binary.hash);
prefixedState.set(internalState, WASM_FUNC_HASH_LENGTH);
return prefixedState;
};
const load = (state: Uint8Array) => {
if (!(state instanceof Uint8Array)) {
throw new Error("load() expects an Uint8Array generated by save()");
}
const stateOffset: number = wasmInstance.exports.Hash_GetState();
const stateLength: number = getStateSize();
const overallLength: number = WASM_FUNC_HASH_LENGTH + stateLength;
const memoryBuffer = wasmInstance.exports.memory.buffer;
if (state.length !== overallLength) {
throw new Error(
`Bad state length (expected ${overallLength} bytes, got ${state.length})`,
);
}
if (
!hexStringEqualsUInt8(
binary.hash,
state.subarray(0, WASM_FUNC_HASH_LENGTH),
)
) {
throw new Error(
"This state was written by an incompatible hash implementation",
);
}
const internalState = state.subarray(WASM_FUNC_HASH_LENGTH);
new Uint8Array(memoryBuffer, stateOffset, stateLength).set(internalState);
initialized = true;
};
const isDataShort = (data: IDataType) => {
if (typeof data === "string") {
// worst case is 4 bytes / char
return data.length < MAX_HEAP / 4;
}
return data.byteLength < MAX_HEAP;
};
let canSimplify: (data: IDataType, initParam?: number) => boolean =
isDataShort;
switch (binary.name) {
case "argon2":
case "scrypt":
canSimplify = () => true;
break;
case "blake2b":
case "blake2s":
// if there is a key at blake2 then cannot simplify
canSimplify = (data, initParam) => initParam <= 512 && isDataShort(data);
break;
case "blake3":
// if there is a key at blake3 then cannot simplify
canSimplify = (data, initParam) => initParam === 0 && isDataShort(data);
break;
case "xxhash64": // cannot simplify
case "xxhash3":
case "xxhash128":
case "crc64":
canSimplify = () => false;
break;
default:
break;
}
// shorthand for (init + update + digest) for better performance
const calculate = (
data: IDataType,
initParam = null,
digestParam = null,
): string => {
if (!canSimplify(data, initParam)) {
init(initParam);
update(data);
return digest("hex", digestParam) as string;
}
const buffer = getUInt8Buffer(data);
memoryView.set(buffer);
wasmInstance.exports.Hash_Calculate(buffer.length, initParam, digestParam);
return getDigestHex(digestChars, memoryView, hashLength);
};
await setupInterface();
return {
getMemory,
writeMemory,
getExports,
setMemorySize,
init,
update,
digest,
save,
load,
calculate,
hashLength,
};
}
export type IWASMInterface = ThenArg<ReturnType<typeof WASMInterface>>;
+64
View File
@@ -0,0 +1,64 @@
import wasmJson from "../wasm/adler32.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates Adler-32 hash. The resulting 32-bit hash is stored in
* network byte order (big-endian).
*
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function adler32(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 4).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new Adler-32 hash instance
*/
export function createAdler32(): Promise<IHasher> {
return WASMInterface(wasmJson, 4).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 4,
digestSize: 4,
};
return obj;
});
}
+396
View File
@@ -0,0 +1,396 @@
import wasmJson from "../wasm/argon2.wasm.json";
import { type IHasher, WASMInterface } from "./WASMInterface";
import { createBLAKE2b } from "./blake2b";
import {
type IDataType,
decodeBase64,
encodeBase64,
getDecodeBase64Length,
getDigestHex,
getUInt8Buffer,
writeHexToUInt8,
} from "./util";
export interface IArgon2Options {
/**
* Password (or message) to be hashed
*/
password: IDataType;
/**
* Salt (usually containing random bytes)
*/
salt: IDataType;
/**
* Secret for keyed hashing
*/
secret?: IDataType;
/**
* Number of iterations to perform
*/
iterations: number;
/**
* Degree of parallelism
*/
parallelism: number;
/**
* Amount of memory to be used in kibibytes (1024 bytes)
*/
memorySize: number;
/**
* Output size in bytes
*/
hashLength: number;
/**
* Desired output type. Defaults to 'hex'
*/
outputType?: "hex" | "binary" | "encoded";
}
interface IArgon2OptionsExtended extends IArgon2Options {
hashType: "i" | "d" | "id";
}
function encodeResult(
salt: Uint8Array,
options: IArgon2OptionsExtended,
res: Uint8Array,
): string {
const parameters = [
`m=${options.memorySize}`,
`t=${options.iterations}`,
`p=${options.parallelism}`,
].join(",");
return `$argon2${options.hashType}$v=19$${parameters}$${encodeBase64(
salt,
false,
)}$${encodeBase64(res, false)}`;
}
const uint32View = new DataView(new ArrayBuffer(4));
function int32LE(x: number): Uint8Array {
uint32View.setInt32(0, x, true);
return new Uint8Array(uint32View.buffer);
}
async function hashFunc(
blake512: IHasher,
buf: Uint8Array,
len: number,
): Promise<Uint8Array> {
if (len <= 64) {
const blake = await createBLAKE2b(len * 8);
blake.update(int32LE(len));
blake.update(buf);
return blake.digest("binary");
}
const r = Math.ceil(len / 32) - 2;
const ret = new Uint8Array(len);
blake512.init();
blake512.update(int32LE(len));
blake512.update(buf);
let vp = blake512.digest("binary");
ret.set(vp.subarray(0, 32), 0);
for (let i = 1; i < r; i++) {
blake512.init();
blake512.update(vp);
vp = blake512.digest("binary");
ret.set(vp.subarray(0, 32), i * 32);
}
const partialBytesNeeded = len - 32 * r;
let blakeSmall: IHasher;
if (partialBytesNeeded === 64) {
blakeSmall = blake512;
blakeSmall.init();
} else {
blakeSmall = await createBLAKE2b(partialBytesNeeded * 8);
}
blakeSmall.update(vp);
vp = blakeSmall.digest("binary");
ret.set(vp.subarray(0, partialBytesNeeded), r * 32);
return ret;
}
function getHashType(type: IArgon2OptionsExtended["hashType"]): number {
switch (type) {
case "d":
return 0;
case "i":
return 1;
default:
return 2;
}
}
async function argon2Internal(
options: IArgon2OptionsExtended,
): Promise<string | Uint8Array> {
const { parallelism, iterations, hashLength } = options;
const password = getUInt8Buffer(options.password);
const salt = getUInt8Buffer(options.salt);
const version = 0x13;
const hashType = getHashType(options.hashType);
const { memorySize } = options; // in KB
const secret = getUInt8Buffer(options.secret ?? "");
const [argon2Interface, blake512] = await Promise.all([
WASMInterface(wasmJson, 1024),
createBLAKE2b(512),
]);
// last block is for storing the init vector
argon2Interface.setMemorySize(memorySize * 1024 + 1024);
const initVector = new Uint8Array(24);
const initVectorView = new DataView(initVector.buffer);
initVectorView.setInt32(0, parallelism, true);
initVectorView.setInt32(4, hashLength, true);
initVectorView.setInt32(8, memorySize, true);
initVectorView.setInt32(12, iterations, true);
initVectorView.setInt32(16, version, true);
initVectorView.setInt32(20, hashType, true);
argon2Interface.writeMemory(initVector, memorySize * 1024);
blake512.init();
blake512.update(initVector);
blake512.update(int32LE(password.length));
blake512.update(password);
blake512.update(int32LE(salt.length));
blake512.update(salt);
blake512.update(int32LE(secret.length));
blake512.update(secret);
blake512.update(int32LE(0)); // associatedData length + associatedData
const segments = Math.floor(memorySize / (parallelism * 4)); // length of each lane
const lanes = segments * 4;
const param = new Uint8Array(72);
const H0 = blake512.digest("binary");
param.set(H0);
for (let lane = 0; lane < parallelism; lane++) {
param.set(int32LE(0), 64);
param.set(int32LE(lane), 68);
let position = lane * lanes;
let chunk = await hashFunc(blake512, param, 1024);
argon2Interface.writeMemory(chunk, position * 1024);
position += 1;
param.set(int32LE(1), 64);
chunk = await hashFunc(blake512, param, 1024);
argon2Interface.writeMemory(chunk, position * 1024);
}
const C = new Uint8Array(1024);
writeHexToUInt8(C, argon2Interface.calculate(new Uint8Array([]), memorySize));
const res = await hashFunc(blake512, C, hashLength);
if (options.outputType === "hex") {
const digestChars = new Uint8Array(hashLength * 2);
return getDigestHex(digestChars, res, hashLength);
}
if (options.outputType === "encoded") {
return encodeResult(salt, options, res);
}
// return binary format
return res;
}
const validateOptions = (options: IArgon2Options) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (!options.password) {
throw new Error("Password must be specified");
}
options.password = getUInt8Buffer(options.password);
if (options.password.length < 1) {
throw new Error("Password must be specified");
}
if (!options.salt) {
throw new Error("Salt must be specified");
}
options.salt = getUInt8Buffer(options.salt);
if (options.salt.length < 8) {
throw new Error("Salt should be at least 8 bytes long");
}
options.secret = getUInt8Buffer(options.secret ?? "");
if (!Number.isInteger(options.iterations) || options.iterations < 1) {
throw new Error("Iterations should be a positive number");
}
if (!Number.isInteger(options.parallelism) || options.parallelism < 1) {
throw new Error("Parallelism should be a positive number");
}
if (!Number.isInteger(options.hashLength) || options.hashLength < 4) {
throw new Error("Hash length should be at least 4 bytes.");
}
if (!Number.isInteger(options.memorySize)) {
throw new Error("Memory size should be specified.");
}
if (options.memorySize < 8 * options.parallelism) {
throw new Error("Memory size should be at least 8 * parallelism.");
}
if (options.outputType === undefined) {
options.outputType = "hex";
}
if (!["hex", "binary", "encoded"].includes(options.outputType)) {
throw new Error(
`Insupported output type ${options.outputType}. Valid values: ['hex', 'binary', 'encoded']`,
);
}
};
interface IArgon2OptionsBinary {
outputType: "binary";
}
type Argon2ReturnType<T> = T extends IArgon2OptionsBinary ? Uint8Array : string;
/**
* Calculates hash using the argon2i password-hashing function
* @returns Computed hash
*/
export async function argon2i<T extends IArgon2Options>(
options: T,
): Promise<Argon2ReturnType<T>> {
validateOptions(options);
return argon2Internal({
...options,
hashType: "i",
}) as Promise<Argon2ReturnType<T>>;
}
/**
* Calculates hash using the argon2id password-hashing function
* @returns Computed hash
*/
export async function argon2id<T extends IArgon2Options>(
options: T,
): Promise<Argon2ReturnType<T>> {
validateOptions(options);
return argon2Internal({
...options,
hashType: "id",
}) as Promise<Argon2ReturnType<T>>;
}
/**
* Calculates hash using the argon2d password-hashing function
* @returns Computed hash
*/
export async function argon2d<T extends IArgon2Options>(
options: T,
): Promise<Argon2ReturnType<T>> {
validateOptions(options);
return argon2Internal({
...options,
hashType: "d",
}) as Promise<Argon2ReturnType<T>>;
}
export interface Argon2VerifyOptions {
/**
* Password to be verified
*/
password: IDataType;
/**
* Secret used on hash creation
*/
secret?: IDataType;
/**
* A previously generated argon2 hash in the 'encoded' output format
*/
hash: string;
}
const getHashParameters = (
password: IDataType,
encoded: string,
secret?: IDataType,
): IArgon2OptionsExtended => {
const regex =
/^\$argon2(id|i|d)\$v=([0-9]+)\$((?:[mtp]=[0-9]+,){2}[mtp]=[0-9]+)\$([A-Za-z0-9+/]+)\$([A-Za-z0-9+/]+)$/;
const match = encoded.match(regex);
if (!match) {
throw new Error("Invalid hash");
}
const [, hashType, version, parameters, salt, hash] = match;
if (version !== "19") {
throw new Error(`Unsupported version: ${version}`);
}
const parsedParameters: Partial<IArgon2Options> = {};
const paramMap = { m: "memorySize", p: "parallelism", t: "iterations" };
for (const x of parameters.split(",")) {
const [n, v] = x.split("=");
parsedParameters[paramMap[n]] = Number(v);
}
return {
...parsedParameters,
password,
secret,
hashType: hashType as IArgon2OptionsExtended["hashType"],
salt: decodeBase64(salt),
hashLength: getDecodeBase64Length(hash),
outputType: "encoded",
} as IArgon2OptionsExtended;
};
const validateVerifyOptions = (options: Argon2VerifyOptions) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (options.hash === undefined || typeof options.hash !== "string") {
throw new Error("Hash should be specified");
}
};
/**
* Verifies password using the argon2 password-hashing function
* @returns True if the encoded hash matches the password
*/
export async function argon2Verify(
options: Argon2VerifyOptions,
): Promise<boolean> {
validateVerifyOptions(options);
const params = getHashParameters(
options.password,
options.hash,
options.secret,
);
validateOptions(params);
const hashStart = options.hash.lastIndexOf("$") + 1;
const result = (await argon2Internal(params)) as string;
return result.substring(hashStart) === options.hash.substring(hashStart);
}
+186
View File
@@ -0,0 +1,186 @@
import wasmJson from "../wasm/bcrypt.wasm.json";
import { WASMInterface } from "./WASMInterface";
import {
type IDataType,
getDigestHex,
getUInt8Buffer,
intArrayToString,
} from "./util";
export interface BcryptOptions {
/**
* Password to be hashed
*/
password: IDataType;
/**
* Salt (16 bytes long - usually containing random bytes)
*/
salt: IDataType;
/**
* Number of iterations to perform (4 - 31)
*/
costFactor: number;
/**
* Desired output type. Defaults to 'encoded'
*/
outputType?: "hex" | "binary" | "encoded";
}
async function bcryptInternal(
options: BcryptOptions,
): Promise<string | Uint8Array> {
const { costFactor, password, salt } = options;
const bcryptInterface = await WASMInterface(wasmJson, 0);
bcryptInterface.writeMemory(getUInt8Buffer(salt), 0);
const passwordBuffer = getUInt8Buffer(password);
bcryptInterface.writeMemory(passwordBuffer, 16);
const shouldEncode = options.outputType === "encoded" ? 1 : 0;
bcryptInterface
.getExports()
.bcrypt(passwordBuffer.length, costFactor, shouldEncode);
const memory = bcryptInterface.getMemory();
if (options.outputType === "encoded") {
return intArrayToString(memory, 60);
}
if (options.outputType === "hex") {
const digestChars = new Uint8Array(24 * 2);
return getDigestHex(digestChars, memory, 24);
}
// return binary format
// the data is copied to allow GC of the original memory buffer
return memory.slice(0, 24);
}
const validateOptions = (options: BcryptOptions) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (
!Number.isInteger(options.costFactor) ||
options.costFactor < 4 ||
options.costFactor > 31
) {
throw new Error("Cost factor should be a number between 4 and 31");
}
options.password = getUInt8Buffer(options.password);
if (options.password.length < 1) {
throw new Error("Password should be at least 1 byte long");
}
if (options.password.length > 72) {
throw new Error("Password should be at most 72 bytes long");
}
options.salt = getUInt8Buffer(options.salt);
if (options.salt.length !== 16) {
throw new Error("Salt should be 16 bytes long");
}
if (options.outputType === undefined) {
options.outputType = "encoded";
}
if (!["hex", "binary", "encoded"].includes(options.outputType)) {
throw new Error(
`Insupported output type ${options.outputType}. Valid values: ['hex', 'binary', 'encoded']`,
);
}
};
interface IBcryptOptionsBinary {
outputType: "binary";
}
type BcryptReturnType<T> = T extends IBcryptOptionsBinary ? Uint8Array : string;
/**
* Calculates hash using the bcrypt password-hashing function
* @returns Computed hash
*/
export async function bcrypt<T extends BcryptOptions>(
options: T,
): Promise<BcryptReturnType<T>> {
validateOptions(options);
return bcryptInternal(options) as Promise<BcryptReturnType<T>>;
}
export interface BcryptVerifyOptions {
/**
* Password to be verified
*/
password: IDataType;
/**
* A previously generated bcrypt hash in the 'encoded' output format
*/
hash: string;
}
const validateHashCharacters = (hash: string): boolean => {
if (!/^\$2[axyb]\$[0-3][0-9]\$[./A-Za-z0-9]{53}$/.test(hash)) {
return false;
}
if (hash[4] === "0" && Number(hash[5]) < 4) {
return false;
}
if (hash[4] === "3" && Number(hash[5]) > 1) {
return false;
}
return true;
};
const validateVerifyOptions = (options: BcryptVerifyOptions) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (options.hash === undefined || typeof options.hash !== "string") {
throw new Error("Hash should be specified");
}
if (options.hash.length !== 60) {
throw new Error("Hash should be 60 bytes long");
}
if (!validateHashCharacters(options.hash)) {
throw new Error("Invalid hash");
}
options.password = getUInt8Buffer(options.password);
if (options.password.length < 1) {
throw new Error("Password should be at least 1 byte long");
}
if (options.password.length > 72) {
throw new Error("Password should be at most 72 bytes long");
}
};
/**
* Verifies password using bcrypt password-hashing function
* @returns True if the encoded hash matches the password
*/
export async function bcryptVerify(
options: BcryptVerifyOptions,
): Promise<boolean> {
validateVerifyOptions(options);
const { hash, password } = options;
const bcryptInterface = await WASMInterface(wasmJson, 0);
bcryptInterface.writeMemory(getUInt8Buffer(hash), 0);
const passwordBuffer = getUInt8Buffer(password);
bcryptInterface.writeMemory(passwordBuffer, 60);
return !!bcryptInterface.getExports().bcrypt_verify(passwordBuffer.length);
}
+135
View File
@@ -0,0 +1,135 @@
import wasmJson from "../wasm/blake2b.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import { type IDataType, getUInt8Buffer } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateBits(bits: number) {
if (!Number.isInteger(bits) || bits < 8 || bits > 512 || bits % 8 !== 0) {
return new Error("Invalid variant! Valid values: 8, 16, ..., 512");
}
return null;
}
function getInitParam(outputBits, keyBits) {
return outputBits | (keyBits << 16);
}
/**
* Calculates BLAKE2b hash
* @param data Input data (string, Buffer or TypedArray)
* @param bits Number of output bits, which has to be a number
* divisible by 8, between 8 and 512. Defaults to 512.
* @param key Optional key (string, Buffer or TypedArray). Maximum length is 64 bytes.
* @returns Computed hash as a hexadecimal string
*/
export function blake2b(
data: IDataType,
bits = 512,
key: IDataType = null,
): Promise<string> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = bits;
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length > 64) {
return Promise.reject(new Error("Max key length is 64 bytes"));
}
initParam = getInitParam(bits, keyBuffer.length);
}
const hashLength = bits / 8;
if (wasmCache === null || wasmCache.hashLength !== hashLength) {
return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => {
wasmCache = wasm;
if (initParam > 512) {
wasmCache.writeMemory(keyBuffer);
}
return wasmCache.calculate(data, initParam);
});
}
try {
if (initParam > 512) {
wasmCache.writeMemory(keyBuffer);
}
const hash = wasmCache.calculate(data, initParam);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new BLAKE2b hash instance
* @param bits Number of output bits, which has to be a number
* divisible by 8, between 8 and 512. Defaults to 512.
* @param key Optional key (string, Buffer or TypedArray). Maximum length is 64 bytes.
*/
export function createBLAKE2b(
bits = 512,
key: IDataType = null,
): Promise<IHasher> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = bits;
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length > 64) {
return Promise.reject(new Error("Max key length is 64 bytes"));
}
initParam = getInitParam(bits, keyBuffer.length);
}
const outputSize = bits / 8;
return WASMInterface(wasmJson, outputSize).then((wasm) => {
if (initParam > 512) {
wasm.writeMemory(keyBuffer);
}
wasm.init(initParam);
const obj: IHasher = {
init:
initParam > 512
? () => {
wasm.writeMemory(keyBuffer);
wasm.init(initParam);
return obj;
}
: () => {
wasm.init(initParam);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 128,
digestSize: outputSize,
};
return obj;
});
}
+135
View File
@@ -0,0 +1,135 @@
import wasmJson from "../wasm/blake2s.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import { type IDataType, getUInt8Buffer } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateBits(bits: number) {
if (!Number.isInteger(bits) || bits < 8 || bits > 256 || bits % 8 !== 0) {
return new Error("Invalid variant! Valid values: 8, 16, ..., 256");
}
return null;
}
function getInitParam(outputBits, keyBits) {
return outputBits | (keyBits << 16);
}
/**
* Calculates BLAKE2s hash
* @param data Input data (string, Buffer or TypedArray)
* @param bits Number of output bits, which has to be a number
* divisible by 8, between 8 and 256. Defaults to 256.
* @param key Optional key (string, Buffer or TypedArray). Maximum length is 32 bytes.
* @returns Computed hash as a hexadecimal string
*/
export function blake2s(
data: IDataType,
bits = 256,
key: IDataType = null,
): Promise<string> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = bits;
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length > 32) {
return Promise.reject(new Error("Max key length is 32 bytes"));
}
initParam = getInitParam(bits, keyBuffer.length);
}
const hashLength = bits / 8;
if (wasmCache === null || wasmCache.hashLength !== hashLength) {
return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => {
wasmCache = wasm;
if (initParam > 512) {
wasmCache.writeMemory(keyBuffer);
}
return wasmCache.calculate(data, initParam);
});
}
try {
if (initParam > 512) {
wasmCache.writeMemory(keyBuffer);
}
const hash = wasmCache.calculate(data, initParam);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new BLAKE2s hash instance
* @param bits Number of output bits, which has to be a number
* divisible by 8, between 8 and 256. Defaults to 256.
* @param key Optional key (string, Buffer or TypedArray). Maximum length is 32 bytes.
*/
export function createBLAKE2s(
bits = 256,
key: IDataType = null,
): Promise<IHasher> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = bits;
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length > 32) {
return Promise.reject(new Error("Max key length is 32 bytes"));
}
initParam = getInitParam(bits, keyBuffer.length);
}
const outputSize = bits / 8;
return WASMInterface(wasmJson, outputSize).then((wasm) => {
if (initParam > 512) {
wasm.writeMemory(keyBuffer);
}
wasm.init(initParam);
const obj: IHasher = {
init:
initParam > 512
? () => {
wasm.writeMemory(keyBuffer);
wasm.init(initParam);
return obj;
}
: () => {
wasm.init(initParam);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: outputSize,
};
return obj;
});
}
+133
View File
@@ -0,0 +1,133 @@
import wasmJson from "../wasm/blake3.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import { type IDataType, getUInt8Buffer } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateBits(bits: number) {
if (!Number.isInteger(bits) || bits < 8 || bits % 8 !== 0) {
return new Error("Invalid variant! Valid values: 8, 16, ...");
}
return null;
}
/**
* Calculates BLAKE3 hash
* @param data Input data (string, Buffer or TypedArray)
* @param bits Number of output bits, which has to be a number
* divisible by 8. Defaults to 256.
* @param key Optional key (string, Buffer or TypedArray). Length should be 32 bytes.
* @returns Computed hash as a hexadecimal string
*/
export function blake3(
data: IDataType,
bits = 256,
key: IDataType = null,
): Promise<string> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = 0; // key is empty by default
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length !== 32) {
return Promise.reject(new Error("Key length must be exactly 32 bytes"));
}
initParam = 32;
}
const hashLength = bits / 8;
const digestParam = hashLength;
if (wasmCache === null || wasmCache.hashLength !== hashLength) {
return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => {
wasmCache = wasm;
if (initParam === 32) {
wasmCache.writeMemory(keyBuffer);
}
return wasmCache.calculate(data, initParam, digestParam);
});
}
try {
if (initParam === 32) {
wasmCache.writeMemory(keyBuffer);
}
const hash = wasmCache.calculate(data, initParam, digestParam);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new BLAKE3 hash instance
* @param bits Number of output bits, which has to be a number
* divisible by 8. Defaults to 256.
* @param key Optional key (string, Buffer or TypedArray). Length should be 32 bytes.
*/
export function createBLAKE3(
bits = 256,
key: IDataType = null,
): Promise<IHasher> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
let keyBuffer = null;
let initParam = 0; // key is empty by default
if (key !== null) {
keyBuffer = getUInt8Buffer(key);
if (keyBuffer.length !== 32) {
return Promise.reject(new Error("Key length must be exactly 32 bytes"));
}
initParam = 32;
}
const outputSize = bits / 8;
const digestParam = outputSize;
return WASMInterface(wasmJson, outputSize).then((wasm) => {
if (initParam === 32) {
wasm.writeMemory(keyBuffer);
}
wasm.init(initParam);
const obj: IHasher = {
init:
initParam === 32
? () => {
wasm.writeMemory(keyBuffer);
wasm.init(initParam);
return obj;
}
: () => {
wasm.init(initParam);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType, digestParam) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: outputSize,
};
return obj;
});
}
+82
View File
@@ -0,0 +1,82 @@
import wasmJson from "../wasm/crc32.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validatePoly(poly: number) {
if (!Number.isInteger(poly) || poly < 0 || poly > 0xffffffff) {
return new Error("Polynomial must be a valid 32-bit long unsigned integer");
}
return null;
}
/**
* Calculates CRC-32 hash
* @param data Input data (string, Buffer or TypedArray)
* @param polynomial Input polynomial (defaults to 0xedb88320, for CRC32C use 0x82f63b78)
* @returns Computed hash as a hexadecimal string
*/
export function crc32(
data: IDataType,
polynomial = 0xedb88320,
): Promise<string> {
if (validatePoly(polynomial)) {
return Promise.reject(validatePoly(polynomial));
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 4).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, polynomial);
});
}
try {
const hash = wasmCache.calculate(data, polynomial);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new CRC-32 hash instance
* @param polynomial Input polynomial (defaults to 0xedb88320, for CRC32C use 0x82f63b78)
*/
export function createCRC32(polynomial = 0xedb88320): Promise<IHasher> {
if (validatePoly(polynomial)) {
return Promise.reject(validatePoly(polynomial));
}
return WASMInterface(wasmJson, 4).then((wasm) => {
wasm.init(polynomial);
const obj: IHasher = {
init: () => {
wasm.init(polynomial);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 4,
digestSize: 4,
};
return obj;
});
}
+110
View File
@@ -0,0 +1,110 @@
import wasmJson from "../wasm/crc64.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
const polyBuffer = new Uint8Array(8);
function parsePoly(poly: string) {
const errText = "Polynomial must be provided as a 16 char long hex string";
if (typeof poly !== "string" || poly.length !== 16) {
return { hi: 0, lo: 0, err: new Error(errText) };
}
const hi = Number(`0x${poly.slice(0, 8)}`);
const lo = Number(`0x${poly.slice(8)}`);
if (Number.isNaN(hi) || Number.isNaN(lo)) {
return { hi, lo, err: new Error(errText) };
}
return { hi, lo, err: null };
}
function writePoly(arr: ArrayBuffer, lo: number, hi: number) {
// write in little-endian format
const buffer = new DataView(arr);
buffer.setUint32(0, lo, true);
buffer.setUint32(4, hi, true);
}
/**
* Calculates CRC-64 hash
* @param data Input data (string, Buffer or TypedArray)
* @param polynomial Input polynomial (defaults to 'c96c5795d7870f42' - ECMA)
* @returns Computed hash as a hexadecimal string
*/
export function crc64(
data: IDataType,
polynomial = "c96c5795d7870f42",
): Promise<string> {
const { hi, lo, err } = parsePoly(polynomial);
if (err !== null) {
return Promise.reject(err);
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 8).then((wasm) => {
wasmCache = wasm;
writePoly(polyBuffer.buffer, lo, hi);
wasmCache.writeMemory(polyBuffer);
return wasmCache.calculate(data);
});
}
try {
writePoly(polyBuffer.buffer, lo, hi);
wasmCache.writeMemory(polyBuffer);
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new CRC-64 hash instance
* @param polynomial Input polynomial (defaults to 'c96c5795d7870f42' - ECMA)
*/
export function createCRC64(polynomial = "c96c5795d7870f42"): Promise<IHasher> {
const { hi, lo, err } = parsePoly(polynomial);
if (err !== null) {
return Promise.reject(err);
}
return WASMInterface(wasmJson, 8).then((wasm) => {
const instanceBuffer = new Uint8Array(8);
writePoly(instanceBuffer.buffer, lo, hi);
wasm.writeMemory(instanceBuffer);
wasm.init();
const obj: IHasher = {
init: () => {
wasm.writeMemory(instanceBuffer);
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 8,
digestSize: 8,
};
return obj;
});
}
+86
View File
@@ -0,0 +1,86 @@
import type { IHasher } from "./WASMInterface";
import { type IDataType, getUInt8Buffer } from "./util";
function calculateKeyBuffer(hasher: IHasher, key: IDataType): Uint8Array {
const { blockSize } = hasher;
const buf = getUInt8Buffer(key);
if (buf.length > blockSize) {
hasher.update(buf);
const uintArr = hasher.digest("binary");
hasher.init();
return uintArr;
}
return new Uint8Array(buf.buffer, buf.byteOffset, buf.length);
}
function calculateHmac(hasher: IHasher, key: IDataType): IHasher {
hasher.init();
const { blockSize } = hasher;
const keyBuf = calculateKeyBuffer(hasher, key);
const keyBuffer = new Uint8Array(blockSize);
keyBuffer.set(keyBuf);
const opad = new Uint8Array(blockSize);
for (let i = 0; i < blockSize; i++) {
const v = keyBuffer[i];
opad[i] = v ^ 0x5c;
keyBuffer[i] = v ^ 0x36;
}
hasher.update(keyBuffer);
const obj: IHasher = {
init: () => {
hasher.init();
hasher.update(keyBuffer);
return obj;
},
update: (data: IDataType) => {
hasher.update(data);
return obj;
},
digest: ((outputType) => {
const uintArr = hasher.digest("binary");
hasher.init();
hasher.update(opad);
hasher.update(uintArr);
return hasher.digest(outputType);
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
}) as any,
save: () => {
throw new Error("save() not supported");
},
load: () => {
throw new Error("load() not supported");
},
blockSize: hasher.blockSize,
digestSize: hasher.digestSize,
};
return obj;
}
/**
* Calculates HMAC hash
* @param hash Hash algorithm to use. It has to be the return value of a function like createSHA1()
* @param key Key (string, Buffer or TypedArray)
*/
export function createHMAC(
hash: Promise<IHasher>,
key: IDataType,
): Promise<IHasher> {
if (!hash || !hash.then) {
throw new Error(
'Invalid hash function is provided! Usage: createHMAC(createMD5(), "key").',
);
}
return hash.then((hasher) => calculateHmac(hasher, key));
}
+30
View File
@@ -0,0 +1,30 @@
export * from "./adler32";
export * from "./argon2";
export * from "./blake2b";
export * from "./blake2s";
export * from "./blake3";
export * from "./crc32";
export * from "./crc64";
export * from "./md4";
export * from "./md5";
export * from "./sha1";
export * from "./sha3";
export * from "./keccak";
export * from "./sha224";
export * from "./sha256";
export * from "./sha384";
export * from "./sha512";
export * from "./xxhash32";
export * from "./xxhash64";
export * from "./xxhash3";
export * from "./xxhash128";
export * from "./ripemd160";
export * from "./hmac";
export * from "./pbkdf2";
export * from "./scrypt";
export * from "./bcrypt";
export * from "./whirlpool";
export * from "./sm3";
export type { IDataType } from "./util";
export type { IHasher } from "./WASMInterface";
+88
View File
@@ -0,0 +1,88 @@
import wasmJson from "../wasm/sha3.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
type IValidBits = 224 | 256 | 384 | 512;
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateBits(bits: IValidBits) {
if (![224, 256, 384, 512].includes(bits)) {
return new Error("Invalid variant! Valid values: 224, 256, 384, 512");
}
return null;
}
/**
* Calculates Keccak hash
* @param data Input data (string, Buffer or TypedArray)
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
* @returns Computed hash as a hexadecimal string
*/
export function keccak(
data: IDataType,
bits: IValidBits = 512,
): Promise<string> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
const hashLength = bits / 8;
if (wasmCache === null || wasmCache.hashLength !== hashLength) {
return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, bits, 0x01);
});
}
try {
const hash = wasmCache.calculate(data, bits, 0x01);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new Keccak hash instance
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
*/
export function createKeccak(bits: IValidBits = 512): Promise<IHasher> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
const outputSize = bits / 8;
return WASMInterface(wasmJson, outputSize).then((wasm) => {
wasm.init(bits);
const obj: IHasher = {
init: () => {
wasm.init(bits);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType, 0x01) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 200 - 2 * outputSize,
digestSize: outputSize,
};
return obj;
});
}
+14
View File
@@ -0,0 +1,14 @@
import { type IWASMInterface, WASMInterface } from "./WASMInterface";
import type Mutex from "./mutex";
import type { IEmbeddedWasm } from "./util";
export default async function lockedCreate(
mutex: Mutex,
binary: IEmbeddedWasm,
hashLength: number,
): Promise<IWASMInterface> {
const unlock = await mutex.lock();
const wasm = await WASMInterface(binary, hashLength);
unlock();
return wasm;
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/md4.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates MD4 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function md4(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 16).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new MD4 hash instance
*/
export function createMD4(): Promise<IHasher> {
return WASMInterface(wasmJson, 16).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 16,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/md5.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates MD5 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function md5(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 16).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new MD5 hash instance
*/
export function createMD5(): Promise<IHasher> {
return WASMInterface(wasmJson, 16).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 16,
};
return obj;
});
}
+24
View File
@@ -0,0 +1,24 @@
class Mutex {
private mutex = Promise.resolve();
lock(): PromiseLike<() => void> {
let begin: (unlock: () => void) => void = () => {};
this.mutex = this.mutex.then(() => new Promise(begin));
return new Promise((res) => {
begin = res;
});
}
async dispatch<T>(fn: () => PromiseLike<T>): Promise<T> {
const unlock = await this.lock();
try {
return await Promise.resolve(fn());
} finally {
unlock();
}
}
}
export default Mutex;
+138
View File
@@ -0,0 +1,138 @@
import type { IHasher } from "./WASMInterface";
import { createHMAC } from "./hmac";
import { type IDataType, getDigestHex, getUInt8Buffer } from "./util";
export interface IPBKDF2Options {
/**
* Password (or message) to be hashed
*/
password: IDataType;
/**
* Salt (usually containing random bytes)
*/
salt: IDataType;
/**
* Number of iterations to perform
*/
iterations: number;
/**
* Output size in bytes
*/
hashLength: number;
/**
* Hash algorithm to use. It has to be the return value of a function like createSHA1()
*/
hashFunction: Promise<IHasher>;
/**
* Desired output type. Defaults to 'hex'
*/
outputType?: "hex" | "binary";
}
async function calculatePBKDF2(
digest: IHasher,
salt: IDataType,
iterations: number,
hashLength: number,
outputType?: "hex" | "binary",
): Promise<Uint8Array | string> {
const DK = new Uint8Array(hashLength);
const block1 = new Uint8Array(salt.length + 4);
const block1View = new DataView(block1.buffer);
const saltBuffer = getUInt8Buffer(salt);
const saltUIntBuffer = new Uint8Array(
saltBuffer.buffer,
saltBuffer.byteOffset,
saltBuffer.length,
);
block1.set(saltUIntBuffer);
let destPos = 0;
const hLen = digest.digestSize;
const l = Math.ceil(hashLength / hLen);
let T: Uint8Array = null;
let U: Uint8Array = null;
for (let i = 1; i <= l; i++) {
block1View.setUint32(salt.length, i);
digest.init();
digest.update(block1);
T = digest.digest("binary");
U = T.slice();
for (let j = 1; j < iterations; j++) {
digest.init();
digest.update(U);
U = digest.digest("binary");
for (let k = 0; k < hLen; k++) {
T[k] ^= U[k];
}
}
DK.set(T.subarray(0, hashLength - destPos), destPos);
destPos += hLen;
}
if (outputType === "binary") {
return DK;
}
const digestChars = new Uint8Array(hashLength * 2);
return getDigestHex(digestChars, DK, hashLength);
}
const validateOptions = (options: IPBKDF2Options) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (!options.hashFunction || !options.hashFunction.then) {
throw new Error(
'Invalid hash function is provided! Usage: pbkdf2("password", "salt", 1000, 32, createSHA1()).',
);
}
if (!Number.isInteger(options.iterations) || options.iterations < 1) {
throw new Error("Iterations should be a positive number");
}
if (!Number.isInteger(options.hashLength) || options.hashLength < 1) {
throw new Error("Hash length should be a positive number");
}
if (options.outputType === undefined) {
options.outputType = "hex";
}
if (!["hex", "binary"].includes(options.outputType)) {
throw new Error(
`Insupported output type ${options.outputType}. Valid values: ['hex', 'binary']`,
);
}
};
interface IPBKDF2OptionsBinary {
outputType: "binary";
}
type PBKDF2ReturnType<T> = T extends IPBKDF2OptionsBinary ? Uint8Array : string;
/**
* Generates a new PBKDF2 hash for the supplied password
*/
export async function pbkdf2<T extends IPBKDF2Options>(
options: T,
): Promise<PBKDF2ReturnType<T>> {
validateOptions(options);
const hmac = await createHMAC(options.hashFunction, options.password);
return calculatePBKDF2(
hmac,
options.salt,
options.iterations,
options.hashLength,
options.outputType,
) as Promise<PBKDF2ReturnType<T>>;
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/ripemd160.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates RIPEMD-160 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function ripemd160(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 20).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new RIPEMD-160 hash instance
*/
export function createRIPEMD160(): Promise<IHasher> {
return WASMInterface(wasmJson, 20).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 20,
};
return obj;
});
}
+141
View File
@@ -0,0 +1,141 @@
import wasmJson from "../wasm/scrypt.wasm.json";
import { WASMInterface } from "./WASMInterface";
import { pbkdf2 } from "./pbkdf2";
import { createSHA256 } from "./sha256";
import { type IDataType, getDigestHex } from "./util";
export interface ScryptOptions {
/**
* Password (or message) to be hashed
*/
password: IDataType;
/**
* Salt (usually containing random bytes)
*/
salt: IDataType;
/**
* CPU / memory cost - must be a power of 2 (e.g. 1024)
*/
costFactor: number;
/**
* Block size (8 is commonly used)
*/
blockSize: number;
/**
* Degree of parallelism
*/
parallelism: number;
/**
* Output size in bytes
*/
hashLength: number;
/**
* Output data type. Defaults to hexadecimal string
*/
outputType?: "hex" | "binary";
}
async function scryptInternal(
options: ScryptOptions,
): Promise<string | Uint8Array> {
const { costFactor, blockSize, parallelism, hashLength } = options;
const SHA256Hasher = createSHA256();
const blockData = await pbkdf2({
password: options.password,
salt: options.salt,
iterations: 1,
hashLength: 128 * blockSize * parallelism,
hashFunction: SHA256Hasher,
outputType: "binary",
});
const scryptInterface = await WASMInterface(wasmJson, 0);
// last block is for storing the temporary vectors
const VSize = 128 * blockSize * costFactor;
const XYSize = 256 * blockSize;
scryptInterface.setMemorySize(blockData.length + VSize + XYSize);
scryptInterface.writeMemory(blockData, 0);
// mix blocks
scryptInterface.getExports().scrypt(blockSize, costFactor, parallelism);
const expensiveSalt = scryptInterface
.getMemory()
.subarray(0, 128 * blockSize * parallelism);
const outputData = await pbkdf2({
password: options.password,
salt: expensiveSalt,
iterations: 1,
hashLength,
hashFunction: SHA256Hasher,
outputType: "binary",
});
if (options.outputType === "hex") {
const digestChars = new Uint8Array(hashLength * 2);
return getDigestHex(digestChars, outputData, hashLength);
}
// return binary format
return outputData;
}
const isPowerOfTwo = (v: number): boolean => v && !(v & (v - 1));
const validateOptions = (options: ScryptOptions) => {
if (!options || typeof options !== "object") {
throw new Error("Invalid options parameter. It requires an object.");
}
if (!Number.isInteger(options.blockSize) || options.blockSize < 1) {
throw new Error("Block size should be a positive number");
}
if (
!Number.isInteger(options.costFactor) ||
options.costFactor < 2 ||
!isPowerOfTwo(options.costFactor)
) {
throw new Error("Cost factor should be a power of 2, greater than 1");
}
if (!Number.isInteger(options.parallelism) || options.parallelism < 1) {
throw new Error("Parallelism should be a positive number");
}
if (!Number.isInteger(options.hashLength) || options.hashLength < 1) {
throw new Error("Hash length should be a positive number.");
}
if (options.outputType === undefined) {
options.outputType = "hex";
}
if (!["hex", "binary"].includes(options.outputType)) {
throw new Error(
`Insupported output type ${options.outputType}. Valid values: ['hex', 'binary']`,
);
}
};
interface IScryptOptionsBinary {
outputType: "binary";
}
type ScryptReturnType<T> = T extends IScryptOptionsBinary ? Uint8Array : string;
/**
* Calculates hash using the scrypt password-based key derivation function
* @returns Computed hash as a hexadecimal string or as
* Uint8Array depending on the outputType option
*/
export async function scrypt<T extends ScryptOptions>(
options: T,
): Promise<ScryptReturnType<T>> {
validateOptions(options);
return scryptInternal(options) as Promise<ScryptReturnType<T>>;
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sha1.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SHA-1 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sha1(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 20).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-1 hash instance
*/
export function createSHA1(): Promise<IHasher> {
return WASMInterface(wasmJson, 20).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 20,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sha256.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SHA-2 (SHA-224) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sha224(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 28).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, 224);
});
}
try {
const hash = wasmCache.calculate(data, 224);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-2 (SHA-224) hash instance
*/
export function createSHA224(): Promise<IHasher> {
return WASMInterface(wasmJson, 28).then((wasm) => {
wasm.init(224);
const obj: IHasher = {
init: () => {
wasm.init(224);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 28,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sha256.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SHA-2 (SHA-256) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sha256(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 32).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, 256);
});
}
try {
const hash = wasmCache.calculate(data, 256);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-2 (SHA-256) hash instance
*/
export function createSHA256(): Promise<IHasher> {
return WASMInterface(wasmJson, 32).then((wasm) => {
wasm.init(256);
const obj: IHasher = {
init: () => {
wasm.init(256);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 32,
};
return obj;
});
}
+84
View File
@@ -0,0 +1,84 @@
import wasmJson from "../wasm/sha3.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
type IValidBits = 224 | 256 | 384 | 512;
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateBits(bits: IValidBits) {
if (![224, 256, 384, 512].includes(bits)) {
return new Error("Invalid variant! Valid values: 224, 256, 384, 512");
}
return null;
}
/**
* Calculates SHA-3 hash
* @param data Input data (string, Buffer or TypedArray)
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
* @returns Computed hash as a hexadecimal string
*/
export function sha3(data: IDataType, bits: IValidBits = 512): Promise<string> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
const hashLength = bits / 8;
if (wasmCache === null || wasmCache.hashLength !== hashLength) {
return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, bits, 0x06);
});
}
try {
const hash = wasmCache.calculate(data, bits, 0x06);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-3 hash instance
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
*/
export function createSHA3(bits: IValidBits = 512): Promise<IHasher> {
if (validateBits(bits)) {
return Promise.reject(validateBits(bits));
}
const outputSize = bits / 8;
return WASMInterface(wasmJson, outputSize).then((wasm) => {
wasm.init(bits);
const obj: IHasher = {
init: () => {
wasm.init(bits);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType, 0x06) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 200 - 2 * outputSize,
digestSize: outputSize,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sha512.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SHA-2 (SHA-384) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sha384(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 48).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, 384);
});
}
try {
const hash = wasmCache.calculate(data, 384);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-2 (SHA-384) hash instance
*/
export function createSHA384(): Promise<IHasher> {
return WASMInterface(wasmJson, 48).then((wasm) => {
wasm.init(384);
const obj: IHasher = {
init: () => {
wasm.init(384);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 128,
digestSize: 48,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sha512.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SHA-2 (SHA-512) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sha512(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 64).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, 512);
});
}
try {
const hash = wasmCache.calculate(data, 512);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SHA-2 (SHA-512) hash instance
*/
export function createSHA512(): Promise<IHasher> {
return WASMInterface(wasmJson, 64).then((wasm) => {
wasm.init(512);
const obj: IHasher = {
init: () => {
wasm.init(512);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 128,
digestSize: 64,
};
return obj;
});
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/sm3.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates SM3 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function sm3(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 32).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new SM3 hash instance
*/
export function createSM3(): Promise<IHasher> {
return WASMInterface(wasmJson, 32).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 32,
};
return obj;
});
}
+191
View File
@@ -0,0 +1,191 @@
function getGlobal() {
if (typeof globalThis !== "undefined") return globalThis;
if (typeof self !== "undefined") return self;
if (typeof window !== "undefined") return window;
return global;
}
const globalObject = getGlobal();
const nodeBuffer = globalObject.Buffer ?? null;
const textEncoder = globalObject.TextEncoder
? new globalObject.TextEncoder()
: null;
export type ITypedArray = Uint8Array | Uint16Array | Uint32Array;
export type IDataType = string | Buffer | ITypedArray;
export type IEmbeddedWasm = { name: string; data: string; hash: string };
export function intArrayToString(arr: Uint8Array, len: number): string {
return String.fromCharCode(...arr.subarray(0, len));
}
function hexCharCodesToInt(a: number, b: number): number {
return (
(((a & 0xf) + ((a >> 6) | ((a >> 3) & 0x8))) << 4) |
((b & 0xf) + ((b >> 6) | ((b >> 3) & 0x8)))
);
}
export function writeHexToUInt8(buf: Uint8Array, str: string) {
const size = str.length >> 1;
for (let i = 0; i < size; i++) {
const index = i << 1;
buf[i] = hexCharCodesToInt(
str.charCodeAt(index),
str.charCodeAt(index + 1),
);
}
}
export function hexStringEqualsUInt8(str: string, buf: Uint8Array): boolean {
if (str.length !== buf.length * 2) {
return false;
}
for (let i = 0; i < buf.length; i++) {
const strIndex = i << 1;
if (
buf[i] !==
hexCharCodesToInt(str.charCodeAt(strIndex), str.charCodeAt(strIndex + 1))
) {
return false;
}
}
return true;
}
const alpha = "a".charCodeAt(0) - 10;
const digit = "0".charCodeAt(0);
export function getDigestHex(
tmpBuffer: Uint8Array,
input: Uint8Array,
hashLength: number,
): string {
let p = 0;
for (let i = 0; i < hashLength; i++) {
let nibble = input[i] >>> 4;
tmpBuffer[p++] = nibble > 9 ? nibble + alpha : nibble + digit;
nibble = input[i] & 0xf;
tmpBuffer[p++] = nibble > 9 ? nibble + alpha : nibble + digit;
}
return String.fromCharCode.apply(null, tmpBuffer);
}
export const getUInt8Buffer =
nodeBuffer !== null
? (data: IDataType): Uint8Array => {
if (typeof data === "string") {
const buf = nodeBuffer.from(data, "utf8");
return new Uint8Array(buf.buffer, buf.byteOffset, buf.length);
}
if (nodeBuffer.isBuffer(data)) {
return new Uint8Array(data.buffer, data.byteOffset, data.length);
}
if (ArrayBuffer.isView(data)) {
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
throw new Error("Invalid data type!");
}
: (data: IDataType): Uint8Array => {
if (typeof data === "string") {
return textEncoder.encode(data);
}
if (ArrayBuffer.isView(data)) {
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
}
throw new Error("Invalid data type!");
};
const base64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const base64Lookup = new Uint8Array(256);
for (let i = 0; i < base64Chars.length; i++) {
base64Lookup[base64Chars.charCodeAt(i)] = i;
}
export function encodeBase64(data: Uint8Array, pad = true): string {
const len = data.length;
const extraBytes = len % 3;
const parts = [];
const len2 = len - extraBytes;
for (let i = 0; i < len2; i += 3) {
const tmp =
((data[i] << 16) & 0xff0000) +
((data[i + 1] << 8) & 0xff00) +
(data[i + 2] & 0xff);
const triplet =
base64Chars.charAt((tmp >> 18) & 0x3f) +
base64Chars.charAt((tmp >> 12) & 0x3f) +
base64Chars.charAt((tmp >> 6) & 0x3f) +
base64Chars.charAt(tmp & 0x3f);
parts.push(triplet);
}
if (extraBytes === 1) {
const tmp = data[len - 1];
const a = base64Chars.charAt(tmp >> 2);
const b = base64Chars.charAt((tmp << 4) & 0x3f);
parts.push(`${a}${b}`);
if (pad) {
parts.push("==");
}
} else if (extraBytes === 2) {
const tmp = (data[len - 2] << 8) + data[len - 1];
const a = base64Chars.charAt(tmp >> 10);
const b = base64Chars.charAt((tmp >> 4) & 0x3f);
const c = base64Chars.charAt((tmp << 2) & 0x3f);
parts.push(`${a}${b}${c}`);
if (pad) {
parts.push("=");
}
}
return parts.join("");
}
export function getDecodeBase64Length(data: string): number {
let bufferLength = Math.floor(data.length * 0.75);
const len = data.length;
if (data[len - 1] === "=") {
bufferLength -= 1;
if (data[len - 2] === "=") {
bufferLength -= 1;
}
}
return bufferLength;
}
export function decodeBase64(data: string): Uint8Array {
const bufferLength = getDecodeBase64Length(data);
const len = data.length;
const bytes = new Uint8Array(bufferLength);
let p = 0;
for (let i = 0; i < len; i += 4) {
const encoded1 = base64Lookup[data.charCodeAt(i)];
const encoded2 = base64Lookup[data.charCodeAt(i + 1)];
const encoded3 = base64Lookup[data.charCodeAt(i + 2)];
const encoded4 = base64Lookup[data.charCodeAt(i + 3)];
bytes[p] = (encoded1 << 2) | (encoded2 >> 4);
p += 1;
bytes[p] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
p += 1;
bytes[p] = ((encoded3 & 3) << 6) | (encoded4 & 63);
p += 1;
}
return bytes;
}
+62
View File
@@ -0,0 +1,62 @@
import wasmJson from "../wasm/whirlpool.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
/**
* Calculates Whirlpool hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export function whirlpool(data: IDataType): Promise<string> {
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 64).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data);
});
}
try {
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new Whirlpool hash instance
*/
export function createWhirlpool(): Promise<IHasher> {
return WASMInterface(wasmJson, 64).then((wasm) => {
wasm.init();
const obj: IHasher = {
init: () => {
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 64,
digestSize: 64,
};
return obj;
});
}
+115
View File
@@ -0,0 +1,115 @@
import wasmJson from "../wasm/xxhash128.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
const seedBuffer = new Uint8Array(8);
function validateSeed(seed: number) {
if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) {
return new Error(
"Seed must be given as two valid 32-bit long unsigned integers (lo + high).",
);
}
return null;
}
function writeSeed(arr: ArrayBuffer, low: number, high: number) {
// write in little-endian format
const buffer = new DataView(arr);
buffer.setUint32(0, low, true);
buffer.setUint32(4, high, true);
}
/**
* Calculates xxHash128 hash
* @param data Input data (string, Buffer or TypedArray)
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @returns Computed hash as a hexadecimal string
*/
export function xxhash128(
data: IDataType,
seedLow = 0,
seedHigh = 0,
): Promise<string> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 16).then((wasm) => {
wasmCache = wasm;
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
return wasmCache.calculate(data);
});
}
try {
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new xxHash128 hash instance
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
*/
export function createXXHash128(seedLow = 0, seedHigh = 0): Promise<IHasher> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
return WASMInterface(wasmJson, 16).then((wasm) => {
const instanceBuffer = new Uint8Array(8);
writeSeed(instanceBuffer.buffer, seedLow, seedHigh);
wasm.writeMemory(instanceBuffer);
wasm.init();
const obj: IHasher = {
init: () => {
wasm.writeMemory(instanceBuffer);
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 512,
digestSize: 16,
};
return obj;
});
}
+115
View File
@@ -0,0 +1,115 @@
import wasmJson from "../wasm/xxhash3.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
const seedBuffer = new Uint8Array(8);
function validateSeed(seed: number) {
if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) {
return new Error(
"Seed must be given as two valid 32-bit long unsigned integers (lo + high).",
);
}
return null;
}
function writeSeed(arr: ArrayBuffer, low: number, high: number) {
// write in little-endian format
const buffer = new DataView(arr);
buffer.setUint32(0, low, true);
buffer.setUint32(4, high, true);
}
/**
* Calculates xxHash3 hash
* @param data Input data (string, Buffer or TypedArray)
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @returns Computed hash as a hexadecimal string
*/
export function xxhash3(
data: IDataType,
seedLow = 0,
seedHigh = 0,
): Promise<string> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 8).then((wasm) => {
wasmCache = wasm;
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
return wasmCache.calculate(data);
});
}
try {
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new xxHash3 hash instance
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
*/
export function createXXHash3(seedLow = 0, seedHigh = 0): Promise<IHasher> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
return WASMInterface(wasmJson, 8).then((wasm) => {
const instanceBuffer = new Uint8Array(8);
writeSeed(instanceBuffer.buffer, seedLow, seedHigh);
wasm.writeMemory(instanceBuffer);
wasm.init();
const obj: IHasher = {
init: () => {
wasm.writeMemory(instanceBuffer);
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 512,
digestSize: 8,
};
return obj;
});
}
+79
View File
@@ -0,0 +1,79 @@
import wasmJson from "../wasm/xxhash32.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
function validateSeed(seed: number) {
if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) {
return new Error("Seed must be a valid 32-bit long unsigned integer.");
}
return null;
}
/**
* Calculates xxHash32 hash
* @param data Input data (string, Buffer or TypedArray)
* @param seed Number used to initialize the internal state of the algorithm (defaults to 0)
* @returns Computed hash as a hexadecimal string
*/
export function xxhash32(data: IDataType, seed = 0): Promise<string> {
if (validateSeed(seed)) {
return Promise.reject(validateSeed(seed));
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 4).then((wasm) => {
wasmCache = wasm;
return wasmCache.calculate(data, seed);
});
}
try {
const hash = wasmCache.calculate(data, seed);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new xxHash32 hash instance
* @param data Input data (string, Buffer or TypedArray)
* @param seed Number used to initialize the internal state of the algorithm (defaults to 0)
*/
export function createXXHash32(seed = 0): Promise<IHasher> {
if (validateSeed(seed)) {
return Promise.reject(validateSeed(seed));
}
return WASMInterface(wasmJson, 4).then((wasm) => {
wasm.init(seed);
const obj: IHasher = {
init: () => {
wasm.init(seed);
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 16,
digestSize: 4,
};
return obj;
});
}
+115
View File
@@ -0,0 +1,115 @@
import wasmJson from "../wasm/xxhash64.wasm.json";
import {
type IHasher,
type IWASMInterface,
WASMInterface,
} from "./WASMInterface";
import lockedCreate from "./lockedCreate";
import Mutex from "./mutex";
import type { IDataType } from "./util";
const mutex = new Mutex();
let wasmCache: IWASMInterface = null;
const seedBuffer = new Uint8Array(8);
function validateSeed(seed: number) {
if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) {
return new Error(
"Seed must be given as two valid 32-bit long unsigned integers (lo + high).",
);
}
return null;
}
function writeSeed(arr: ArrayBuffer, low: number, high: number) {
// write in little-endian format
const buffer = new DataView(arr);
buffer.setUint32(0, low, true);
buffer.setUint32(4, high, true);
}
/**
* Calculates xxHash64 hash
* @param data Input data (string, Buffer or TypedArray)
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @returns Computed hash as a hexadecimal string
*/
export function xxhash64(
data: IDataType,
seedLow = 0,
seedHigh = 0,
): Promise<string> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
if (wasmCache === null) {
return lockedCreate(mutex, wasmJson, 8).then((wasm) => {
wasmCache = wasm;
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
return wasmCache.calculate(data);
});
}
try {
writeSeed(seedBuffer.buffer, seedLow, seedHigh);
wasmCache.writeMemory(seedBuffer);
const hash = wasmCache.calculate(data);
return Promise.resolve(hash);
} catch (err) {
return Promise.reject(err);
}
}
/**
* Creates a new xxHash64 hash instance
* @param seedLow Lower 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
* @param seedHigh Higher 32 bits of the number used to
* initialize the internal state of the algorithm (defaults to 0)
*/
export function createXXHash64(seedLow = 0, seedHigh = 0): Promise<IHasher> {
if (validateSeed(seedLow)) {
return Promise.reject(validateSeed(seedLow));
}
if (validateSeed(seedHigh)) {
return Promise.reject(validateSeed(seedHigh));
}
return WASMInterface(wasmJson, 8).then((wasm) => {
const instanceBuffer = new Uint8Array(8);
writeSeed(instanceBuffer.buffer, seedLow, seedHigh);
wasm.writeMemory(instanceBuffer);
wasm.init();
const obj: IHasher = {
init: () => {
wasm.writeMemory(instanceBuffer);
wasm.init();
return obj;
},
update: (data) => {
wasm.update(data);
return obj;
},
// biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type
digest: (outputType) => wasm.digest(outputType) as any,
save: () => wasm.save(),
load: (data) => {
wasm.load(data);
return obj;
},
blockSize: 32,
digestSize: 8,
};
return obj;
});
}