feat: Replace aCAPTCHA with official ALTCHA (altcha.org) Proof-of-Work web component widget
This commit is contained in:
+310
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user