This commit is contained in:
gr
2026-11-04 10:14:23 +08:00
parent f7f323462c
commit b7c3a962dd
759 changed files with 74649 additions and 4421 deletions
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates SHA-2 (SHA-256) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function sha256(data: IDataType): Promise<string>;
/**
* Creates a new SHA-2 (SHA-256) hash instance
*/
export declare function createSHA256(): Promise<IHasher>;
@@ -0,0 +1,38 @@
MIT License
Copyright (c) 2020 Dani Biró
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Embedded C implementations might use other, similarly permissive licenses.
Check the beginning of the files from the /src directory.
Special thank you to the authors of original C algorithms:
- Alexander Peslyak <solar@openwall.com>
- Aleksey Kravchenko <rhash.admin@gmail.com>
- Colin Percival
- Stephan Brumme <create@stephan-brumme.com>
- Steve Reid <steve@edmweb.com>
- Samuel Neves <sneves@dei.uc.pt>
- Solar Designer <solar@openwall.com>
- Project Nayuki
- ARM Limited
- Yanbo Li dreamfly281@gmail.com, goldboar@163.comYanbo Li
- Mark Adler
- Yann Collet
@@ -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;
});
}
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates MD5 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function md5(data: IDataType): Promise<string>;
/**
* Creates a new MD5 hash instance
*/
export declare function createMD5(): Promise<IHasher>;
@@ -0,0 +1,37 @@
import type { IHasher } from "./WASMInterface";
import { type IDataType } 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";
}
interface IPBKDF2OptionsBinary {
outputType: "binary";
}
type PBKDF2ReturnType<T> = T extends IPBKDF2OptionsBinary ? Uint8Array : string;
/**
* Generates a new PBKDF2 hash for the supplied password
*/
export declare function pbkdf2<T extends IPBKDF2Options>(options: T): Promise<PBKDF2ReturnType<T>>;
export {};
@@ -0,0 +1,42 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"overrides": [
{
"include": ["test/**"],
"linter": {
"rules": {
"suspicious": {
"noExplicitAny": "off"
}
}
}
}
]
}
@@ -0,0 +1,332 @@
/* sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak).
* based on the
* The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011
* by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche
*
* Copyright (c) 2013, Aleksey Kravchenko <rhash.admin@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define NumberOfRounds 24
#define sha3_max_permutation_size 25
#define sha3_max_rate_in_qwords 24
#define I64(x) x##ULL
#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n))))
struct SHA3_CTX {
/* 1600 bits algorithm hashing state */
uint64_t hash[sha3_max_permutation_size];
/* 1536-bit buffer for leftovers */
uint64_t message[sha3_max_rate_in_qwords];
/* count of bytes in the message[] buffer */
unsigned rest;
/* size of a message block processed at once */
unsigned block_size;
};
struct SHA3_CTX sctx;
struct SHA3_CTX* ctx = &sctx;
/* SHA3 (Keccak) constants for 24 rounds */
static uint64_t keccak_round_constants[NumberOfRounds] = {
I64(0x0000000000000001), I64(0x0000000000008082), I64(0x800000000000808A), I64(0x8000000080008000),
I64(0x000000000000808B), I64(0x0000000080000001), I64(0x8000000080008081), I64(0x8000000000008009),
I64(0x000000000000008A), I64(0x0000000000000088), I64(0x0000000080008009), I64(0x000000008000000A),
I64(0x000000008000808B), I64(0x800000000000008B), I64(0x8000000000008089), I64(0x8000000000008003),
I64(0x8000000000008002), I64(0x8000000000000080), I64(0x000000000000800A), I64(0x800000008000000A),
I64(0x8000000080008081), I64(0x8000000000008080), I64(0x0000000080000001), I64(0x8000000080008008)
};
/* Initializing a sha3 context for given number of output bits */
WASM_EXPORT
void Hash_Init(uint32_t bits) {
/* NB: The Keccak capacity parameter = bits * 2 */
uint32_t rate = 1600 - bits * 2;
for(int i = 0; i < sha3_max_permutation_size; i++) {
ctx->hash[i] = 0;
}
for(int i = 0; i < sha3_max_rate_in_qwords; i++) {
ctx->message[i] = 0;
}
ctx->rest = 0;
ctx->block_size = rate / 8;
}
#define XORED_A(i) A[(i)] ^ A[(i) + 5] ^ A[(i) + 10] ^ A[(i) + 15] ^ A[(i) + 20]
#define THETA_STEP(i) \
A[(i)] ^= D[(i)]; \
A[(i) + 5] ^= D[(i)]; \
A[(i) + 10] ^= D[(i)]; \
A[(i) + 15] ^= D[(i)]; \
A[(i) + 20] ^= D[(i)]
/* Keccak theta() transformation */
static void keccak_theta(uint64_t* A) {
uint64_t D[5];
D[0] = ROTL64(XORED_A(1), 1) ^ XORED_A(4);
D[1] = ROTL64(XORED_A(2), 1) ^ XORED_A(0);
D[2] = ROTL64(XORED_A(3), 1) ^ XORED_A(1);
D[3] = ROTL64(XORED_A(4), 1) ^ XORED_A(2);
D[4] = ROTL64(XORED_A(0), 1) ^ XORED_A(3);
THETA_STEP(0);
THETA_STEP(1);
THETA_STEP(2);
THETA_STEP(3);
THETA_STEP(4);
}
/* Keccak pi() transformation */
static void keccak_pi(uint64_t* A) {
uint64_t A1;
A1 = A[1];
A[1] = A[6];
A[6] = A[9];
A[9] = A[22];
A[22] = A[14];
A[14] = A[20];
A[20] = A[2];
A[2] = A[12];
A[12] = A[13];
A[13] = A[19];
A[19] = A[23];
A[23] = A[15];
A[15] = A[4];
A[4] = A[24];
A[24] = A[21];
A[21] = A[8];
A[8] = A[16];
A[16] = A[5];
A[5] = A[3];
A[3] = A[18];
A[18] = A[17];
A[17] = A[11];
A[11] = A[7];
A[7] = A[10];
A[10] = A1;
/* note: A[ 0] is left as is */
}
#define CHI_STEP(i) \
A0 = A[0 + (i)]; \
A1 = A[1 + (i)]; \
A[0 + (i)] ^= ~A1 & A[2 + (i)]; \
A[1 + (i)] ^= ~A[2 + (i)] & A[3 + (i)]; \
A[2 + (i)] ^= ~A[3 + (i)] & A[4 + (i)]; \
A[3 + (i)] ^= ~A[4 + (i)] & A0; \
A[4 + (i)] ^= ~A0 & A1
/* Keccak chi() transformation */
static void keccak_chi(uint64_t* A) {
uint64_t A0, A1;
CHI_STEP(0);
CHI_STEP(5);
CHI_STEP(10);
CHI_STEP(15);
CHI_STEP(20);
}
static void sha3_permutation(uint64_t* state) {
for (int round = 0; round < NumberOfRounds; round++) {
keccak_theta(state);
/* apply Keccak rho() transformation */
state[ 1] = ROTL64(state[ 1], 1);
state[ 2] = ROTL64(state[ 2], 62);
state[ 3] = ROTL64(state[ 3], 28);
state[ 4] = ROTL64(state[ 4], 27);
state[ 5] = ROTL64(state[ 5], 36);
state[ 6] = ROTL64(state[ 6], 44);
state[ 7] = ROTL64(state[ 7], 6);
state[ 8] = ROTL64(state[ 8], 55);
state[ 9] = ROTL64(state[ 9], 20);
state[10] = ROTL64(state[10], 3);
state[11] = ROTL64(state[11], 10);
state[12] = ROTL64(state[12], 43);
state[13] = ROTL64(state[13], 25);
state[14] = ROTL64(state[14], 39);
state[15] = ROTL64(state[15], 41);
state[16] = ROTL64(state[16], 45);
state[17] = ROTL64(state[17], 15);
state[18] = ROTL64(state[18], 21);
state[19] = ROTL64(state[19], 8);
state[20] = ROTL64(state[20], 18);
state[21] = ROTL64(state[21], 2);
state[22] = ROTL64(state[22], 61);
state[23] = ROTL64(state[23], 56);
state[24] = ROTL64(state[24], 14);
keccak_pi(state);
keccak_chi(state);
/* apply iota(state, round) */
*state ^= keccak_round_constants[round];
}
}
/**
* The core transformation. Process the specified block of data.
*
* @param hash the algorithm state
* @param block the message block to process
* @param block_size the size of the processed block in bytes
*/
static void sha3_process_block(
uint64_t hash[25], const uint64_t* block, uint32_t block_size
) {
/* expanded loop */
hash[0] ^= block[0];
hash[1] ^= block[1];
hash[2] ^= block[2];
hash[3] ^= block[3];
hash[4] ^= block[4];
hash[5] ^= block[5];
hash[6] ^= block[6];
hash[7] ^= block[7];
hash[8] ^= block[8];
/* if not sha3-512 */
if (block_size > 72) {
hash[9] ^= block[9];
hash[10] ^= block[10];
hash[11] ^= block[11];
hash[12] ^= block[12];
/* if not sha3-384 */
if (block_size > 104) {
hash[13] ^= block[13];
hash[14] ^= block[14];
hash[15] ^= block[15];
hash[16] ^= block[16];
/* if not sha3-256 */
if (block_size > 136) {
hash[17] ^= block[17];
#ifdef FULL_SHA3_FAMILY_SUPPORT
/* if not sha3-224 */
if (block_size > 144) {
hash[18] ^= block[18];
hash[19] ^= block[19];
hash[20] ^= block[20];
hash[21] ^= block[21];
hash[22] ^= block[22];
hash[23] ^= block[23];
hash[24] ^= block[24];
}
#endif
}
}
}
/* make a permutation of the hash */
sha3_permutation(hash);
}
#define SHA3_FINALIZED 0x80000000
/**
* Calculate message hash.
* Can be called repeatedly with chunks of the message to be hashed.
*
* @param msg message chunk
* @param size length of the message chunk
*/
WASM_EXPORT
void Hash_Update(uint32_t size) {
const uint8_t* msg = main_buffer;
uint32_t index = (uint32_t)ctx->rest;
uint32_t block_size = (uint32_t)ctx->block_size;
if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */
ctx->rest = (unsigned)((ctx->rest + size) % block_size);
/* fill partial block */
if (index) {
uint32_t left = block_size - index;
uint32_t end = size < left ? size : left;
uint8_t* msg_pointer = (uint8_t*)ctx->message + index;
for (uint32_t i = 0; i < end; i++) {
msg_pointer[i] = msg[i];
}
if (size < left) return;
/* process partial block */
sha3_process_block(ctx->hash, ctx->message, block_size);
msg += left;
size -= left;
}
while (size >= block_size) {
uint64_t* aligned_message_block = (uint64_t*)msg;
sha3_process_block(ctx->hash, aligned_message_block, block_size);
msg += block_size;
size -= block_size;
}
if (size) {
/* save leftovers */
uint8_t* msg_pointer = (uint8_t*)ctx->message;
for (uint8_t i = 0; i < size; i++) {
msg_pointer[i] = msg[i];
}
}
}
/**
* Store calculated hash into the given array.
*/
WASM_EXPORT
void Hash_Final(uint8_t padding) {
uint32_t digest_length = 100 - ctx->block_size / 2;
const uint32_t block_size = ctx->block_size;
if (!(ctx->rest & SHA3_FINALIZED)) {
/* clear the rest of the data queue */
int8_t* start = (int8_t*)ctx->message + ctx->rest;
for (int i = 0; i < block_size - ctx->rest; i++) {
start[i] = 0;
}
((int8_t*)ctx->message)[ctx->rest] |= padding;
((int8_t*)ctx->message)[block_size - 1] |= 0x80;
/* process final block */
sha3_process_block(ctx->hash, ctx->message, block_size);
ctx->rest = SHA3_FINALIZED; /* mark context as finalized */
}
uint32_t* array32 = (uint32_t*)main_buffer;
uint32_t* hash32 = (uint32_t*)ctx->hash;
for (uint32_t i = 0; i < digest_length / 4; i++) {
array32[i] = hash32[i];
}
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(*ctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) ctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t initParam, uint8_t finalParam) {
Hash_Init(initParam);
Hash_Update(length);
Hash_Final(finalParam);
}
@@ -0,0 +1,18 @@
import { type IHasher } from "./WASMInterface";
import { type IDataType } from "./util";
/**
* 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 declare function blake2s(data: IDataType, bits?: number, key?: IDataType): Promise<string>;
/**
* 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 declare function createBLAKE2s(bits?: number, key?: IDataType): Promise<IHasher>;
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES6",
"module": "ES6",
"lib": ["es2017", "es7", "es6", "dom"],
"baseUrl": ".",
"strict": false,
"esModuleInterop": true,
"downlevelIteration": true,
"moduleResolution": "node",
"resolveJsonModule": true
},
"include": [
"lib",
]
}
@@ -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;
});
}
@@ -0,0 +1,18 @@
import { type IHasher } from "./WASMInterface";
import { type IDataType } from "./util";
/**
* 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 declare function blake2b(data: IDataType, bits?: number, key?: IDataType): Promise<string>;
/**
* 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 declare function createBLAKE2b(bits?: number, key?: IDataType): Promise<IHasher>;
@@ -0,0 +1,192 @@
/*
SHA-1 in C
By Steve Reid <steve@edmweb.com>
100% Public Domain
Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
/* blk0() and blk() perform the initial expand. */
/* I got the idea of expanding during the round function from SSLeay */
#define blk0(i) \
(block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \
(rol(block->l[i], 8) & 0x00FF00FF))
#define blk(i) \
(block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \
block->l[(i + 8) & 15] ^ \
block->l[(i + 2) & 15] ^ \
block->l[i & 15], \
1))
struct SHA1_CTX {
uint32_t state[5];
uint32_t count[2];
uint8_t buffer[64];
};
struct SHA1_CTX sctx;
struct SHA1_CTX* context = &sctx;
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
#define R0(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R1(v, w, x, y, z, i) \
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
w = rol(w, 30);
#define R2(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
w = rol(w, 30);
#define R3(v, w, x, y, z, i) \
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
w = rol(w, 30);
#define R4(v, w, x, y, z, i) \
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
w = rol(w, 30);
/* Hash a single 512-bit block. This is the core of the algorithm. */
void SHA1Transform(uint32_t state[5], const uint8_t buffer[64]) {
uint32_t a, b, c, d, e;
typedef union {
uint8_t c[64];
uint32_t l[16];
uint64_t ll[8];
} CHAR64LONG16;
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
#pragma clang loop unroll(full)
for (uint8_t i = 0; i < 8; i++) {
block->ll[i] = *(uint64_t*)&buffer[i * 8];
}
/* Copy context->state[] to working vars */
a = state[0];
b = state[1];
c = state[2];
d = state[3];
e = state[4];
/* 4 rounds of 20 operations each. Loop unrolled. */
R0(a, b, c, d, e, 0); R0(e, a, b, c, d, 1); R0(d, e, a, b, c, 2); R0(c, d, e, a, b, 3);
R0(b, c, d, e, a, 4); R0(a, b, c, d, e, 5); R0(e, a, b, c, d, 6); R0(d, e, a, b, c, 7);
R0(c, d, e, a, b, 8); R0(b, c, d, e, a, 9); R0(a, b, c, d, e, 10); R0(e, a, b, c, d, 11);
R0(d, e, a, b, c, 12); R0(c, d, e, a, b, 13); R0(b, c, d, e, a, 14); R0(a, b, c, d, e, 15);
R1(e, a, b, c, d, 16); R1(d, e, a, b, c, 17); R1(c, d, e, a, b, 18); R1(b, c, d, e, a, 19);
R2(a, b, c, d, e, 20); R2(e, a, b, c, d, 21); R2(d, e, a, b, c, 22); R2(c, d, e, a, b, 23);
R2(b, c, d, e, a, 24); R2(a, b, c, d, e, 25); R2(e, a, b, c, d, 26); R2(d, e, a, b, c, 27);
R2(c, d, e, a, b, 28); R2(b, c, d, e, a, 29); R2(a, b, c, d, e, 30); R2(e, a, b, c, d, 31);
R2(d, e, a, b, c, 32); R2(c, d, e, a, b, 33); R2(b, c, d, e, a, 34); R2(a, b, c, d, e, 35);
R2(e, a, b, c, d, 36); R2(d, e, a, b, c, 37); R2(c, d, e, a, b, 38); R2(b, c, d, e, a, 39);
R3(a, b, c, d, e, 40); R3(e, a, b, c, d, 41); R3(d, e, a, b, c, 42); R3(c, d, e, a, b, 43);
R3(b, c, d, e, a, 44); R3(a, b, c, d, e, 45); R3(e, a, b, c, d, 46); R3(d, e, a, b, c, 47);
R3(c, d, e, a, b, 48); R3(b, c, d, e, a, 49); R3(a, b, c, d, e, 50); R3(e, a, b, c, d, 51);
R3(d, e, a, b, c, 52); R3(c, d, e, a, b, 53); R3(b, c, d, e, a, 54); R3(a, b, c, d, e, 55);
R3(e, a, b, c, d, 56); R3(d, e, a, b, c, 57); R3(c, d, e, a, b, 58); R3(b, c, d, e, a, 59);
R4(a, b, c, d, e, 60); R4(e, a, b, c, d, 61); R4(d, e, a, b, c, 62); R4(c, d, e, a, b, 63);
R4(b, c, d, e, a, 64); R4(a, b, c, d, e, 65); R4(e, a, b, c, d, 66); R4(d, e, a, b, c, 67);
R4(c, d, e, a, b, 68); R4(b, c, d, e, a, 69); R4(a, b, c, d, e, 70); R4(e, a, b, c, d, 71);
R4(d, e, a, b, c, 72); R4(c, d, e, a, b, 73); R4(b, c, d, e, a, 74); R4(a, b, c, d, e, 75);
R4(e, a, b, c, d, 76); R4(d, e, a, b, c, 77); R4(c, d, e, a, b, 78); R4(b, c, d, e, a, 79);
/* Add the working vars back into context.state[] */
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
state[4] += e;
}
/* SHA1Init - Initialize new context */
WASM_EXPORT
void Hash_Init() {
context->state[0] = 0x67452301;
context->state[1] = 0xEFCDAB89;
context->state[2] = 0x98BADCFE;
context->state[3] = 0x10325476;
context->state[4] = 0xC3D2E1F0;
context->count[0] = context->count[1] = 0;
}
void SHA1Update(const uint8_t* data, uint32_t len) {
uint32_t i;
uint32_t j = context->count[0];
if ((context->count[0] += len << 3) < j) {
context->count[1]++;
}
context->count[1] += (len >> 29);
j = (j >> 3) & 63;
if ((j + len) > 63) {
uint8_t end = i = 64 - j;
for (uint8_t z = 0; z < end; z++) {
context->buffer[j + z] = data[z];
}
SHA1Transform(context->state, context->buffer);
for (; i + 63 < len; i += 64) {
SHA1Transform(context->state, &data[i]);
}
j = 0;
} else {
i = 0;
}
for (uint8_t z = 0; z < len - i; z++) {
context->buffer[j + z] = data[i + z];
}
}
WASM_EXPORT
void Hash_Update(uint32_t len) {
SHA1Update(main_buffer, len);
}
/* Add padding and return the message digest. */
WASM_EXPORT
void Hash_Final() {
uint8_t* result = main_buffer;
uint8_t finalcount[8];
uint8_t c;
for (uint8_t i = 0; i < 8; i++) {
finalcount[i] = (uint8_t)(
(context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255);
}
c = 0200;
SHA1Update(&c, 1);
while ((context->count[0] & 504) != 448) {
c = 0000;
SHA1Update(&c, 1);
}
SHA1Update(finalcount, 8); /* Should cause a SHA1Transform() */
for (uint8_t i = 0; i < 20; i++) {
result[i] =
(uint8_t)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
}
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(*context);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) context;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length) {
Hash_Init();
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,9 @@
module.exports = {
roots: [
'<rootDir>/test',
],
transform: {
'^.+\\.ts$': 'ts-jest',
},
cacheDirectory: '<rootDir>/.jest-cache',
};
@@ -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;
});
}
@@ -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;
});
}
@@ -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;
});
}
@@ -0,0 +1,237 @@
/*
Based on Golang's Argon2 implementation from crypto package
Written for hash-wasm by Dani Biró
*/
#include "hash-wasm.h"
#define BYTES_PER_PAGE 65536
uint8_t *B = NULL;
uint64_t B_size = 0;
WASM_EXPORT
int8_t Hash_SetMemorySize(uint32_t total_bytes) {
uint32_t bytes_required = total_bytes - B_size;
if (bytes_required > 0) {
uint32_t blocks = bytes_required / BYTES_PER_PAGE;
if (blocks * BYTES_PER_PAGE < bytes_required) {
blocks += 1;
}
if (__builtin_wasm_memory_grow(0, blocks) == -1) {
return -1;
}
B_size += blocks * BYTES_PER_PAGE;
}
return 0;
}
WASM_EXPORT
uint8_t *Hash_GetBuffer() {
if (B == NULL) {
// start of new memory
B = (uint8_t *)(__builtin_wasm_memory_size(0) * BYTES_PER_PAGE);
if (Hash_SetMemorySize(512 * 1024) == -1) { // always preallocate 16kb to not cause problems with the other hashes
return NULL;
}
}
return B;
}
static __inline__ uint64_t rotr64(const uint64_t w, const unsigned c) {
return (w >> c) | (w << (64 - c));
}
#define G(a, b, c, d) \
do { \
a = a + b + 2 * (a & 0xFFFFFFFF) * (b & 0xFFFFFFFF); \
d = rotr64(d ^ a, 32); \
c = c + d + 2 * (c & 0xFFFFFFFF) * (d & 0xFFFFFFFF); \
b = rotr64(b ^ c, 24); \
a = a + b + 2 * (a & 0xFFFFFFFF) * (b & 0xFFFFFFFF); \
d = rotr64(d ^ a, 16); \
c = c + d + 2 * (c & 0xFFFFFFFF) * (d & 0xFFFFFFFF); \
b = rotr64(b ^ c, 63); \
} while (0)
void P(
uint64_t *a0, uint64_t *a1, uint64_t *a2, uint64_t *a3,
uint64_t *a4, uint64_t *a5, uint64_t *a6, uint64_t *a7,
uint64_t *a8, uint64_t *a9, uint64_t *a10, uint64_t *a11,
uint64_t *a12, uint64_t *a13, uint64_t *a14, uint64_t *a15
) {
G(*a0, *a4, *a8, *a12);
G(*a1, *a5, *a9, *a13);
G(*a2, *a6, *a10, *a14);
G(*a3, *a7, *a11, *a15);
G(*a0, *a5, *a10, *a15);
G(*a1, *a6, *a11, *a12);
G(*a2, *a7, *a8, *a13);
G(*a3, *a4, *a9, *a14);
}
uint32_t indexAlpha(
uint64_t rand, uint32_t lanes, uint32_t segments,
uint32_t parallelism, uint32_t k, uint32_t slice,
uint32_t lane, uint32_t index
) {
uint32_t rlane = ((uint32_t)(rand >> 32)) % parallelism;
if (k == 0 && slice == 0) {
rlane = lane;
}
uint32_t max = segments * 3;
uint32_t start = ((slice + 1) % 4) * segments;
if (lane == rlane) {
max += index;
}
if (k == 0) {
max = slice * segments;
start = 0;
if (slice == 0 || lane == rlane) {
max += index;
}
}
if (index == 0 || lane == rlane) {
max--;
}
uint64_t phi = rand & 0xFFFFFFFF;
phi = phi * phi >> 32;
phi = phi * max >> 32;
uint32_t ri = (start + max - 1 - phi) % (uint64_t)lanes;
return rlane * lanes + ri;
}
uint64_t t[128];
void block(uint64_t *z, uint64_t *a, uint64_t *b, int32_t xor) {
#pragma clang loop unroll(full)
for (int i = 0; i < 128; i++) {
t[i] = a[i] ^ b[i];
}
#pragma clang loop unroll(full)
for (int i = 0; i < 128; i += 16) {
P(
&t[i], &t[i + 1], &t[i + 2], &t[i + 3], &t[i + 4], &t[i + 5], &t[i + 6], &t[i + 7],
&t[i + 8], &t[i + 9], &t[i + 10], &t[i + 11], &t[i + 12], &t[i + 13], &t[i + 14], &t[i + 15]
);
}
#pragma clang loop unroll(full)
for (int i = 0; i < 16; i += 2) {
P(
&t[i], &t[i + 1], &t[i + 16], &t[i + 17], &t[i + 32], &t[i + 33], &t[i + 48], &t[i + 49],
&t[i + 64], &t[i + 65], &t[i + 80], &t[i + 81], &t[i + 96], &t[i + 97], &t[i + 112], &t[i + 113]
);
}
if (xor) {
for (int i = 0; i < 128; i++) {
z[i] ^= a[i] ^ b[i] ^ t[i];
}
} else {
for (int i = 0; i < 128; i++) {
z[i] = a[i] ^ b[i] ^ t[i];
}
}
}
uint64_t addresses[128];
uint64_t zero[128];
uint64_t in[128];
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t memorySize) {
uint32_t *initVector = (uint32_t *)(B + 1024 * memorySize);
uint32_t parallelism = initVector[0];
uint32_t hashLength = initVector[1];
uint32_t memorySize2 = initVector[2];
uint32_t iterations = initVector[3];
uint32_t version = initVector[4];
uint32_t hashType = initVector[5];
if (memorySize2 != memorySize) {
return;
}
uint32_t segments = memorySize / (parallelism * 4);
memorySize = segments * parallelism * 4;
uint32_t lanes = segments * 4;
in[3] = memorySize;
in[4] = iterations;
in[5] = hashType;
for (uint32_t k = 0; k < iterations; k++) {
in[0] = k;
for (uint8_t slice = 0; slice < 4; slice++) {
in[2] = slice;
for (uint32_t lane = 0; lane < parallelism; lane++) {
in[1] = lane;
in[6] = 0;
uint32_t index = 0;
if (k == 0 && slice == 0) {
index = 2;
if (hashType == 1 || hashType == 2) {
in[6]++;
block(addresses, in, zero, 0);
block(addresses, addresses, zero, 0);
}
}
uint32_t offset = lane * lanes + slice * segments + index;
while (index < segments) {
uint32_t prev = offset - 1;
if (index == 0 && slice == 0) {
prev += lanes;
}
uint64_t rand;
if (hashType == 1 || (hashType == 2 && k == 0 && slice < 2)) {
if (index % 128 == 0) {
in[6]++;
block(addresses, in, zero, 0);
block(addresses, addresses, zero, 0);
}
rand = addresses[index % 128];
} else {
rand = *(uint64_t *)(B + prev * 1024);
}
uint32_t newOffset = indexAlpha(rand, lanes, segments, parallelism, k, slice, lane, index);
block(
(uint64_t *)&B[offset * 1024],
(uint64_t *)&B[prev * 1024],
(uint64_t *)&B[newOffset * 1024],
1
);
index++;
offset++;
}
}
}
}
uint32_t destIndex = (memorySize - 1) * 1024;
for (uint32_t lane = 0; lane < parallelism - 1; lane++) {
uint32_t sourceIndex = (lane * lanes + lanes - 1) * 1024;
for (uint32_t i = 0; i < 1024; i += 8) {
*(uint64_t *)&B[destIndex + i] ^= *(uint64_t *)&B[sourceIndex + i];
}
}
for (uint16_t i = 0; i < 1024; i += 8) {
*(uint64_t *)&B[i] = *(uint64_t *)&B[destIndex + i];
}
}
@@ -0,0 +1,20 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function xxhash64(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>;
/**
* 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 declare function createXXHash64(seedLow?: number, seedHigh?: number): Promise<IHasher>;
@@ -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);
}
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates SHA-2 (SHA-512) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function sha512(data: IDataType): Promise<string>;
/**
* Creates a new SHA-2 (SHA-512) hash instance
*/
export declare function createSHA512(): Promise<IHasher>;
@@ -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;
});
}
@@ -0,0 +1,543 @@
# hash-wasm
[![npm package](https://img.shields.io/npm/v/hash-wasm.svg)](http://npmjs.org/package/hash-wasm)
[![Bundle size](https://badgen.net/bundlephobia/minzip/hash-wasm)](https://bundlephobia.com/result?p=hash-wasm)
[![codecov](https://codecov.io/gh/Daninet/hash-wasm/branch/master/graph/badge.svg)](https://codecov.io/gh/Daninet/hash-wasm)
[![Build status](https://github.com/Daninet/hash-wasm/workflows/Build%20&%20publish/badge.svg?branch=master)](https://github.com/Daninet/hash-wasm/actions)
[![JSDelivr downloads](https://data.jsdelivr.com/v1/package/npm/hash-wasm/badge)](https://www.jsdelivr.com/package/npm/hash-wasm)
Hash-WASM is a ⚡lightning fast⚡ hash function library for browsers and Node.js.
It is using hand-tuned WebAssembly binaries to calculate the hash faster than other libraries.
# Supported algorithms
| Name | Bundle size (gzipped) |
| ---------------------------------------------- | --------------------- |
| Adler-32 | 3 kB |
| Argon2: Argon2d, Argon2i, Argon2id (v1.3) | 11 kB |
| bcrypt | 11 kB |
| BLAKE2b | 6 kB |
| BLAKE2s | 5 kB |
| BLAKE3 | 9 kB |
| CRC32 | 3 kB |
| CRC64 | 4 kB |
| HMAC | - |
| MD4 | 4 kB |
| MD5 | 4 kB |
| PBKDF2 | - |
| RIPEMD-160 | 5 kB |
| scrypt | 10 kB |
| SHA-1 | 5 kB |
| SHA-2: SHA-224, SHA-256 | 7 kB |
| SHA-2: SHA-384, SHA-512 | 8 kB |
| SHA-3: SHA3-224, SHA3-256, SHA3-384, SHA3-512 | 4 kB |
| Keccak-224, Keccak-256, Keccak-384, Keccak-512 | 4 kB |
| SM3 | 4 kB |
| Whirlpool | 6 kB |
| xxHash32 | 3 kB |
| xxHash64 | 4 kB |
| xxHash3 | 7 kB |
| xxHash128 | 8 kB |
# Features
- A lot faster than other JS / WASM implementations (see [benchmarks](#benchmark) below)
- It's lightweight. See the table above
- Compiled from heavily optimized algorithms written in C
- Supports all modern browsers, Node.js and Deno
- Supports large data streams
- Supports UTF-8 strings and typed arrays
- Supports chunked input streams
- Modular architecture (the algorithms are compiled into individual WASM binaries)
- WASM modules are bundled as base64 strings (no problems with linking)
- Supports tree shaking (Webpack only bundles the hash algorithms you use)
- Works without Webpack or other bundlers
- Includes TypeScript type definitions
- Works in Web Workers
- Zero dependencies
- Supports concurrent hash calculations with multiple states
- Supports saving and loading the internal state of the hash (segmented hashing and rewinding)
- [Unit tests](https://github.com/Daninet/hash-wasm/tree/master/test) for all algorithms
- 100% open source & transparent [build process](https://github.com/Daninet/hash-wasm/actions)
- Easy to use, Promise-based API
# Installation
```
npm i hash-wasm
```
It can also be used directly from HTML (via [jsDelivr](https://www.jsdelivr.com/package/npm/hash-wasm)):
```html
<!-- load all algortihms into the global `hashwasm` variable -->
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4"></script>
<!-- load individual algortihms into the global `hashwasm` variable -->
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4/dist/md5.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hash-wasm@4/dist/hmac.umd.min.js"></script>
```
# Examples
### Demo apps
[Hash calculator](https://3w4be.csb.app/) - [source code](https://codesandbox.io/s/hash-wasm-3w4be?file=/src/App.tsx)
[MD5 file hasher using HTML5 File API](https://stackoverflow.com/a/63287199/6251760)
### Usage with the shorthand form
It is the easiest and the fastest way to calculate hashes. Use it when the input buffer is already in the memory.
```javascript
import { md5, sha1, sha512, sha3 } from "hash-wasm";
async function run() {
console.log("MD5:", await md5("demo"));
const int8Buffer = new Uint8Array([0, 1, 2, 3]);
console.log("SHA1:", await sha1(int8Buffer));
console.log("SHA512:", await sha512(int8Buffer));
const int32Buffer = new Uint32Array([1056, 641]);
console.log("SHA3-256:", await sha3(int32Buffer, 256));
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### Advanced usage with streaming input
createXXXX() functions create new WASM instances with separate states, which can be used to calculate multiple hashes paralelly. They are slower compared to shorthand functions like md5(), which reuse the same WASM instance and state to do multiple calculations. For this reason, the shorthand form is always preferred when the data is already in the memory.
For the best performance, avoid calling createXXXX() functions in loops. When calculating multiple hashes sequentially, the init() function can be used to reset the internal state between runs. It is faster than creating new instances with createXXXX().
```javascript
import { createSHA1 } from "hash-wasm";
async function run() {
const sha1 = await createSHA1();
sha1.init();
while (hasMoreData()) {
const chunk = readChunk();
sha1.update(chunk);
}
const hash = sha1.digest("binary"); // returns Uint8Array
console.log("SHA1:", hash);
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### Hashing passwords with Argon2
The recommended process for choosing the parameters can be found here: https://tools.ietf.org/html/draft-irtf-cfrg-argon2-04#section-4
```javascript
import { argon2id, argon2Verify } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await argon2id({
password: "pass",
salt, // salt is a buffer containing random bytes
parallelism: 1,
iterations: 256,
memorySize: 512, // use 512KB memory
hashLength: 32, // output size = 32 bytes
outputType: "encoded", // return standard encoded string containing parameters needed to verify the key
});
console.log("Derived key:", key);
const isValid = await argon2Verify({
password: "pass",
hash: key,
});
console.log(isValid ? "Valid password" : "Invalid password");
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### Hashing passwords with bcrypt
```javascript
import { bcrypt, bcryptVerify } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await bcrypt({
password: "pass",
salt, // salt is a buffer containing 16 random bytes
costFactor: 11,
outputType: "encoded", // return standard encoded string containing parameters needed to verify the key
});
console.log("Derived key:", key);
const isValid = await bcryptVerify({
password: "pass",
hash: key,
});
console.log(isValid ? "Valid password" : "Invalid password");
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### Calculating HMAC
All supported hash functions can be used to calculate HMAC. For the best performance, avoid calling createXXXX() in loops (see `Advanced usage with streaming input` section above)
```javascript
import { createHMAC, createSHA3 } from "hash-wasm";
async function run() {
const hashFunc = createSHA3(224); // SHA3-224
const hmac = await createHMAC(hashFunc, "key");
const fruits = ["apple", "raspberry", "watermelon"];
console.log("Input:", fruits);
const codes = fruits.map((data) => {
hmac.init();
hmac.update(data);
return hmac.digest();
});
console.log("HMAC:", codes);
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### Calculating PBKDF2
All supported hash functions can be used to calculate PBKDF2. For the best performance, avoid calling createXXXX() in loops (see `Advanced usage with streaming input` section above)
```javascript
import { pbkdf2, createSHA1 } from "hash-wasm";
async function run() {
const salt = new Uint8Array(16);
window.crypto.getRandomValues(salt);
const key = await pbkdf2({
password: "password",
salt,
iterations: 1000,
hashLength: 32,
hashFunction: createSHA1(),
outputType: "hex",
});
console.log("Derived key:", key);
}
run();
```
_\* See [String encoding pitfalls](#string-encoding-pitfalls)_
_\*\* See [API reference](#api)_
### String encoding pitfalls
You should be aware that there may be multiple UTF-8 representations of a given string:
```js
"\u00fc"; // encodes the ü character
"u\u0308"; // also encodes the ü character
"\u00fc" === "u\u0308"; // false
"ü" === "ü"; // false
```
All algorithms defined in this library depend on the binary representation of the input string. Thus, it's highly recommended to normalize your strings before passing it to hash-wasm. You can use the `normalize()` built-in String function to archive this:
```js
"\u00fc".normalize() === "u\u0308".normalize(); // true
const te = new TextEncoder();
te.encode("u\u0308"); // Uint8Array(3) [117, 204, 136]
te.encode("\u00fc"); // Uint8Array(2) [195, 188]
te.encode("u\u0308".normalize("NFKC")); // Uint8Array(2) [195, 188]
te.encode("\u00fc".normalize("NFKC")); // Uint8Array(2) [195, 188]
```
You can read more about this issue here: https://en.wikipedia.org/wiki/Unicode_equivalence
### Resumable hashing
You can save the current internal state of the hash using the `.save()` function. This state may be written to disk or stored elsewhere in memory.
You can then use the `.load(state)` function to reload that state into a new instance of the hash, or back into the same instance.
This allows you to span the work of hashing a file across multiple processes (e.g. in environments with limited execution times like AWS Lambda, where large jobs need to be split across multiple invocations), or rewind the hash to an earlier point in the stream. For example, the first process could:
```js
// first process starts hashing
const md5 = await createMD5();
md5.init();
md5.update("Hello, ");
const state = md5.save(); // save this state
// second process resumes hashing from the stored state
const md5 = await createMD5();
md5.load(state);
md5.update("world!");
console.log(md5.digest()); // Prints 6cd3556deb0da54bca060b4c39479839 = md5("Hello, world!")
```
_Note that both the saving and loading processes must be running compatible versions of the hash function (i.e. the hash function hasn't changed between the versions of hash-wasm used in the saving and loading processes). If the saved state is incompatible, `load()` will throw an exception._
_The saved state can contain information about the input, including plaintext input bytes, so from a security perspective it must be treated with the same care as the input data itself._
<br/>
# Browser support
<br/>
| Chrome | Safari | Firefox | Edge | IE | Node.js | Deno |
| ------ | ------ | ------- | ---- | ------------- | ------- | ---- |
| 57+ | 11+ | 53+ | 16+ | Not supported | 8+ | 1+ |
<br/>
# Benchmark
You can make your own measurements here: [link](https://daninet.github.io/hash-wasm-benchmark/)
Two scenarios were measured:
- throughput with the short form (input size = 32 bytes)
- throughput with the short form (input size = 1MB)
Results:
| MD5 | throughput (32 bytes) | throughput (1MB) |
| --------------------------- | --------------------- | ---------------- |
| **hash-wasm 4.10.0** | **110.52 MB/s** | **850.31 MB/s** |
| spark-md5 3.0.2 (from npm) | 38.87 MB/s | 171.73 MB/s |
| md5-wasm 2.0.0 (from npm) | 37.36 MB/s | 131.77 MB/s |
| crypto-js 4.1.1 (from npm) | 9.30 MB/s | 46.71 MB/s |
| node-forge 1.3.1 (from npm) | 18.23 MB/s | 28.94 MB/s |
| md5 2.3.0 (from npm) | 14.50 MB/s | 21.65 MB/s |
#
| SHA1 | throughput (32 bytes) | throughput (1MB) |
| --------------------------- | --------------------- | ---------------- |
| **hash-wasm 4.10.0** | **83.80 MB/s** | **798.19 MB/s** |
| jsSHA 3.3.1 (from npm) | 34.93 MB/s | 78.12 MB/s |
| crypto-js 4.1.1 (from npm) | 9.50 MB/s | 69.02 MB/s |
| node-forge 1.3.1 (from npm) | 17.02 MB/s | 32.00 MB/s |
| sha1 1.1.1 (from npm) | 14.68 MB/s | 24.24 MB/s |
#
| SHA256 | throughput (32 bytes) | throughput (1MB) |
| ----------------------------- | --------------------- | ---------------- |
| **hash-wasm 4.10.0** | **63.99 MB/s** | **426.16 MB/s** |
| sha256-wasm 2.2.2 (from npm) | 20.37 MB/s | 308.39 MB/s |
| noble-hashes 1.3.2 (from npm) | 24.73 MB/s | 110.02 MB/s |
| crypto-js 4.1.1 (from npm) | 8.99 MB/s | 65.17 MB/s |
| jsSHA 3.3.1 (from npm) | 25.64 MB/s | 57.98 MB/s |
| node-forge 1.3.1 (from npm) | 13.93 MB/s | 28.19 MB/s |
#
| SHA3-512 | throughput (32 bytes) | throughput (1MB) |
| ----------------------------- | --------------------- | ---------------- |
| **hash-wasm 4.10.0** | **38.06 MB/s** | **234.40 MB/s** |
| sha3-wasm 1.0.0 (from npm) | 15.44 MB/s | 101.51 MB/s |
| noble-hashes 1.3.2 (from npm) | 5.74 MB/s | 14.19 MB/s |
| sha3 2.1.4 (from npm) | 3.80 MB/s | 10.73 MB/s |
| jsSHA 3.2.0 (from npm) | 2.08 MB/s | 3.82 MB/s |
#
| XXHash64 | throughput (32 bytes) | throughput (1MB) |
| ---------------------------- | --------------------- | ------------------ |
| **hash-wasm 4.10.0** | **101.66 MB/s** | **15 989 MB/s** |
| xxhash-wasm 1.0.2 (from npm) | 47.58 MB/s | 15 929 MB/s |
| xxhashjs 0.2.2 (from npm) | 0.92 MB/s | 42.26 MB/s |
#
| PBKDF2-SHA512 - 1000 iterations | operations per second (16 bytes) |
| ------------------------------- | -------------------------------- |
| **hash-wasm 4.10.0** | **588 ops** |
| noble-hashes 1.3.2 (from npm) | 395 ops |
| pbkdf2 3.1.2 (from npm) | 83 ops |
| crypto-js 4.1.1 (from npm) | 29 ops |
#
| Argon2id (m=512, t=8, p=1) | operations per second (16 bytes) |
| -------------------------------- | -------------------------------- |
| **hash-wasm 4.10.0** | **438 ops** |
| argon2-browser 1.18.0 (from npm) | 213 ops |
| argon2-wasm-pro 1.1.0 (from npm) | 203 ops |
| argon2-wasm 0.9.0 (from npm) | 195 ops |
<br/>
\* These measurements were made with `Chrome v131` on a Ryzen 9 7900X desktop CPU.
# API
```ts
type IDataType = string | Buffer | Uint8Array | Uint16Array | Uint32Array;
// all functions return hash in hex format
adler32(data: IDataType): Promise<string>
blake2b(data: IDataType, bits?: number, key?: IDataType): Promise<string> // default is 512 bits
blake2s(data: IDataType, bits?: number, key?: IDataType): Promise<string> // default is 256 bits
blake3(data: IDataType, bits?: number, key?: IDataType): Promise<string> // default is 256 bits
crc32(data: IDataType, polynomial?: number): Promise<string> // default polynomial is 0xedb88320, for CRC32C use 0x82f63b78
crc64(data: IDataType, polynomial?: string): Promise<string> // default polynomial is 'c96c5795d7870f42' (ECMA)
keccak(data: IDataType, bits?: 224 | 256 | 384 | 512): Promise<string> // default is 512 bits
md4(data: IDataType): Promise<string>
md5(data: IDataType): Promise<string>
ripemd160(data: IDataType): Promise<string>
sha1(data: IDataType): Promise<string>
sha224(data: IDataType): Promise<string>
sha256(data: IDataType): Promise<string>
sha3(data: IDataType, bits?: 224 | 256 | 384 | 512): Promise<string> // default is 512 bits
sha384(data: IDataType): Promise<string>
sha512(data: IDataType): Promise<string>
sm3(data: IDataType): Promise<string>
whirlpool(data: IDataType): Promise<string>
xxhash32(data: IDataType, seed?: number): Promise<string>
xxhash64(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
xxhash3(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
xxhash128(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>
interface IHasher {
init: () => IHasher;
update: (data: IDataType) => IHasher;
digest: (outputType: 'hex' | 'binary') => string | Uint8Array; // by default returns hex string
save: () => Uint8Array; // returns the internal state for later resumption
load: (state: Uint8Array) => IHasher; // loads a previously saved internal state
blockSize: number; // in bytes
digestSize: number; // in bytes
}
createAdler32(): Promise<IHasher>
createBLAKE2b(bits?: number, key?: IDataType): Promise<IHasher> // default is 512 bits
createBLAKE2s(bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
createBLAKE3(bits?: number, key?: IDataType): Promise<IHasher> // default is 256 bits
createCRC32(polynomial?: number): Promise<IHasher> // default polynomial is 0xedb88320, for CRC32C use 0x82f63b78
createCRC64(polynomial?: number): Promise<IHasher> // default polynomial is 'c96c5795d7870f42' (ECMA)
createKeccak(bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
createMD4(): Promise<IHasher>
createMD5(): Promise<IHasher>
createRIPEMD160(): Promise<IHasher>
createSHA1(): Promise<IHasher>
createSHA224(): Promise<IHasher>
createSHA256(): Promise<IHasher>
createSHA3(bits?: 224 | 256 | 384 | 512): Promise<IHasher> // default is 512 bits
createSHA384(): Promise<IHasher>
createSHA512(): Promise<IHasher>
createSM3(): Promise<IHasher>
createWhirlpool(): Promise<IHasher>
createXXHash32(seed: number): Promise<IHasher>
createXXHash64(seedLow: number, seedHigh: number): Promise<IHasher>
createXXHash3(seedLow: number, seedHigh: number): Promise<IHasher>
createXXHash128(seedLow: number, seedHigh: number): Promise<IHasher>
createHMAC(hashFunction: Promise<IHasher>, key: IDataType): Promise<IHasher>
pbkdf2({
password: IDataType, // password (or message) to be hashed
salt: IDataType, // salt (usually containing random bytes)
iterations: number, // number of iterations to perform
hashLength: number, // output size in bytes
hashFunction: Promise<IHasher>, // the return value of a function like createSHA1()
outputType?: 'hex' | 'binary', // by default returns hex string
}): Promise<string | Uint8Array>
scrypt({
password: IDataType, // password (or message) to be hashed
salt: IDataType, // salt (usually containing random bytes)
costFactor: number, // CPU/memory cost - must be a power of 2 (e.g. 1024)
blockSize: number, // block size parameter (8 is commonly used)
parallelism: number, // degree of parallelism
hashLength: number, // output size in bytes
outputType?: 'hex' | 'binary', // by default returns hex string
}): Promise<string | Uint8Array>
interface IArgon2Options {
password: IDataType; // password (or message) to be hashed
salt: IDataType; // salt (usually containing random bytes)
secret?: IDataType; // secret for keyed hashing
iterations: number; // number of iterations to perform
parallelism: number; // degree of parallelism
memorySize: number; // amount of memory to be used in kibibytes (1024 bytes)
hashLength: number; // output size in bytes
outputType?: 'hex' | 'binary' | 'encoded'; // by default returns hex string
}
argon2i(options: IArgon2Options): Promise<string | Uint8Array>
argon2d(options: IArgon2Options): Promise<string | Uint8Array>
argon2id(options: IArgon2Options): Promise<string | Uint8Array>
argon2Verify({
password: IDataType, // password
secret?: IDataType, // secret used on hash creation
hash: string, // encoded hash
}): Promise<boolean>
bcrypt({
password: IDataType, // password
salt: IDataType, // salt (16 bytes long - usually containing random bytes)
costFactor: number, // number of iterations to perform (4 - 31)
outputType?: 'hex' | 'binary' | 'encoded', // by default returns encoded string
}): Promise<string | Uint8Array>
bcryptVerify({
password: IDataType, // password
hash: string, // encoded hash
}): Promise<boolean>
```
# Future plans
- Add more well-known algorithms
- Write a polyfill which keeps bundle sizes low and enables running binaries containing newer WASM instructions
- Use WebAssembly Bulk Memory Operations
- Use WebAssembly SIMD instructions (expecting a 10-20% performance increase)
- Enable multithreading where it's possible (like at Argon2)
@@ -0,0 +1,15 @@
export type ITypedArray = Uint8Array | Uint16Array | Uint32Array;
export type IDataType = string | Buffer | ITypedArray;
export type IEmbeddedWasm = {
name: string;
data: string;
hash: string;
};
export declare function intArrayToString(arr: Uint8Array, len: number): string;
export declare function writeHexToUInt8(buf: Uint8Array, str: string): void;
export declare function hexStringEqualsUInt8(str: string, buf: Uint8Array): boolean;
export declare function getDigestHex(tmpBuffer: Uint8Array, input: Uint8Array, hashLength: number): string;
export declare const getUInt8Buffer: (data: IDataType) => Uint8Array;
export declare function encodeBase64(data: Uint8Array, pad?: boolean): string;
export declare function getDecodeBase64Length(data: string): number;
export declare function decodeBase64(data: string): Uint8Array;
@@ -0,0 +1,20 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function xxhash128(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>;
/**
* 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 declare function createXXHash128(seedLow?: number, seedHigh?: number): Promise<IHasher>;
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates MD4 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function md4(data: IDataType): Promise<string>;
/**
* Creates a new MD4 hash instance
*/
export declare function createMD4(): Promise<IHasher>;
@@ -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;
}
@@ -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;
});
}
@@ -0,0 +1,16 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
type IValidBits = 224 | 256 | 384 | 512;
/**
* 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 declare function sha3(data: IDataType, bits?: IValidBits): Promise<string>;
/**
* Creates a new SHA-3 hash instance
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
*/
export declare function createSHA3(bits?: IValidBits): Promise<IHasher>;
export {};
@@ -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;
}
@@ -0,0 +1,298 @@
/*
BLAKE2 reference source code package - reference C implementations
Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the
terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
your option. The terms of these licenses can be found at:
- CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
- OpenSSL license : https://www.openssl.org/source/license.html
- Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0
More information about the BLAKE2 hash function can be found at
https://blake2.net.
Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define BLAKE2_PACKED(x) x __attribute__((packed))
enum blake2b_constant {
BLAKE2B_BLOCKBYTES = 128,
BLAKE2B_OUTBYTES = 64,
BLAKE2B_KEYBYTES = 64,
BLAKE2B_SALTBYTES = 16,
BLAKE2B_PERSONALBYTES = 16
};
typedef struct blake2b_state__ {
uint64_t h[8];
uint64_t t[2];
uint64_t f[2];
uint8_t buf[BLAKE2B_BLOCKBYTES];
int buflen;
int outlen;
uint8_t last_node;
} blake2b_state;
blake2b_state S[1];
BLAKE2_PACKED(struct blake2b_param__ {
uint8_t digest_length; /* 1 */
uint8_t key_length; /* 2 */
uint8_t fanout; /* 3 */
uint8_t depth; /* 4 */
uint32_t leaf_length; /* 8 */
uint32_t node_offset; /* 12 */
uint32_t xof_length; /* 16 */
uint8_t node_depth; /* 17 */
uint8_t inner_length; /* 18 */
uint8_t reserved[14]; /* 32 */
uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */
uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */
});
typedef struct blake2b_param__ blake2b_param;
blake2b_param P[1];
static __inline__ uint64_t load64(const void *src) {
return *(uint64_t *)src;
}
static __inline__ void store64(void *dst, uint64_t w) {
*(uint64_t *)dst = w;
}
static __inline__ uint64_t rotr64(const uint64_t w, const unsigned c) {
return (w >> c) | (w << (64 - c));
}
static const uint64_t blake2b_IV[8] = {
0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL,
0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL,
0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
};
static const uint8_t blake2b_sigma[12][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 },
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }
};
static __inline__ void blake2b_set_lastnode() { S->f[1] = (uint64_t)-1; }
/* Some helper functions, not necessarily useful */
static __inline__ int blake2b_is_lastblock() { return S->f[0] != 0; }
static __inline__ void blake2b_set_lastblock() {
if (S->last_node) blake2b_set_lastnode();
S->f[0] = (uint64_t)-1;
}
static __inline__ void blake2b_increment_counter(const uint64_t inc) {
S->t[0] += inc;
S->t[1] += (S->t[0] < inc);
}
#define G(r, i, a, b, c, d) \
do { \
a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \
d = rotr64(d ^ a, 32); \
c = c + d; \
b = rotr64(b ^ c, 24); \
a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \
d = rotr64(d ^ a, 16); \
c = c + d; \
b = rotr64(b ^ c, 63); \
} while (0)
static void round(uint32_t r, uint64_t m[16], uint64_t v[16]) {
G(r, 0, v[0], v[4], v[8], v[12]);
G(r, 1, v[1], v[5], v[9], v[13]);
G(r, 2, v[2], v[6], v[10], v[14]);
G(r, 3, v[3], v[7], v[11], v[15]);
G(r, 4, v[0], v[5], v[10], v[15]);
G(r, 5, v[1], v[6], v[11], v[12]);
G(r, 6, v[2], v[7], v[8], v[13]);
G(r, 7, v[3], v[4], v[9], v[14]);
};
static void blake2b_compress(const uint8_t block[BLAKE2B_BLOCKBYTES]) {
uint64_t m[16];
uint64_t v[16];
#pragma clang loop unroll(full)
for (int i = 0; i < 16; ++i) {
m[i] = load64(block + i * sizeof(m[i]));
}
#pragma clang loop unroll(full)
for (int i = 0; i < 8; ++i) {
v[i] = S->h[i];
}
v[8] = blake2b_IV[0];
v[9] = blake2b_IV[1];
v[10] = blake2b_IV[2];
v[11] = blake2b_IV[3];
v[12] = blake2b_IV[4] ^ S->t[0];
v[13] = blake2b_IV[5] ^ S->t[1];
v[14] = blake2b_IV[6] ^ S->f[0];
v[15] = blake2b_IV[7] ^ S->f[1];
#pragma clang loop unroll(full)
for (int i = 0; i < 12; ++i) {
round(i, m, v);
}
#pragma clang loop unroll(full)
for (int i = 0; i < 8; ++i) {
S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];
}
}
#undef G
void blake2b_update(const void *pin, int inlen) {
const unsigned char *in = (const unsigned char *)pin;
if (inlen > 0) {
int left = S->buflen;
int fill = BLAKE2B_BLOCKBYTES - left;
if (inlen > fill) {
S->buflen = 0;
/* Fill buffer */
for (uint8_t i = 0; i < fill; i++) {
S->buf[left + i] = in[i];
}
blake2b_increment_counter(BLAKE2B_BLOCKBYTES);
blake2b_compress(S->buf); /* Compress */
in += fill;
inlen -= fill;
while (inlen > BLAKE2B_BLOCKBYTES) {
blake2b_increment_counter(BLAKE2B_BLOCKBYTES);
blake2b_compress(in);
in += BLAKE2B_BLOCKBYTES;
inlen -= BLAKE2B_BLOCKBYTES;
}
}
for (uint8_t i = 0; i < inlen; i++) {
S->buf[S->buflen + i] = in[i];
}
S->buflen += inlen;
}
}
WASM_EXPORT
void Hash_Final() {
int outlen = S->outlen;
uint8_t buffer[BLAKE2B_OUTBYTES] = {0};
if (blake2b_is_lastblock()) {
return;
}
blake2b_increment_counter(S->buflen);
blake2b_set_lastblock();
for (int i = 0; i < BLAKE2B_BLOCKBYTES - S->buflen; i++) { /* Padding */
(S->buf + S->buflen)[i] = 0;
}
blake2b_compress(S->buf);
for (int i = 0; i < 8; ++i) {
/* Output full hash to temp buffer */
store64(buffer + sizeof(S->h[i]) * i, S->h[i]);
}
for (uint8_t i = 0; i < S->outlen; i++) {
main_buffer[i] = buffer[i];
}
}
static void blake2b_init0() {
memset(S, 0, sizeof(blake2b_state));
for (int i = 0; i < 8; ++i) {
S->h[i] = blake2b_IV[i];
}
}
/* init xors IV with input parameter block */
void blake2b_init_param() {
const uint8_t *p = (const uint8_t *)(P);
int i;
blake2b_init0();
/* IV XOR ParamBlock */
for (i = 0; i < 8; ++i) {
S->h[i] ^= load64(p + sizeof(S->h[i]) * i);
}
S->outlen = P->digest_length;
}
void blake2b_init_key(int outlen, const uint8_t *key, int keylen) {
P->digest_length = (uint8_t)outlen;
P->key_length = (uint8_t)keylen;
P->fanout = 1;
P->depth = 1;
// P->leaf_length = 0;
// P->node_offset = 0;
// P->xof_length = 0;
// P->node_depth = 0;
// P->inner_length = 0;
// memset(P->reserved, 0, sizeof(P->reserved));
// memset(P->salt, 0, sizeof(P->salt));
// memset(P->personal, 0, sizeof(P->personal));
blake2b_init_param();
if (keylen > 0) {
uint8_t block[BLAKE2B_BLOCKBYTES];
memset128(block, 0);
for (uint8_t i = 0; i < keylen; i++) {
block[i] = key[i];
}
blake2b_update(block, BLAKE2B_BLOCKBYTES);
}
}
WASM_EXPORT
void Hash_Init(uint32_t bits) {
int outlen = bits & 0xFFFF;
int keylen = bits >> 16;
blake2b_init_key(outlen / 8, main_buffer, keylen);
}
WASM_EXPORT
void Hash_Update(uint32_t size) {
blake2b_update(main_buffer, size);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(S);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) S;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t initParam) {
Hash_Init(initParam);
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,183 @@
// //////////////////////////////////////////////////////////
// xxhash32.h
// Copyright (c) 2016 Stephan Brumme. All rights reserved.
// see http://create.stephan-brumme.com/disclaimer.html
//
// XXHash (32 bit), based on Yann Collet's descriptions, see
// http://cyan4973.github.io/xxHash/
//
// Modified for hash-wasm by Dani Biró
//
#define WITH_BUFFER
#include "hash-wasm.h"
#define bswap32 __builtin_bswap32
static const uint32_t Prime1 = 2654435761U;
static const uint32_t Prime2 = 2246822519U;
static const uint32_t Prime3 = 3266489917U;
static const uint32_t Prime4 = 668265263U;
static const uint32_t Prime5 = 374761393U;
// temporarily store up to 15 bytes between multiple add() calls
#define MAX_BUFFER_SIZE (15 + 1)
// internal state and temporary buffer
struct XXHash32_CTX {
uint32_t state[4]; // state[2] == seed if totalLength < MAX_BUFFER_SIZE
unsigned char buffer[MAX_BUFFER_SIZE];
unsigned int bufferSize;
uint64_t totalLength;
};
static struct XXHash32_CTX sctx;
// rotate bits, should compile to a single CPU instruction (ROL)
static inline uint32_t rotateLeft(uint32_t x, unsigned char bits) {
return (x << bits) | (x >> (32 - bits));
}
// process a block of 4x4 bytes, this is the main part of the XXHash32
// algorithm
static inline void process(
const void* data, uint32_t* state0, uint32_t* state1,
uint32_t* state2, uint32_t* state3
) {
const uint32_t* block = (const uint32_t*)data;
*state0 = rotateLeft(*state0 + block[0] * Prime2, 13) * Prime1;
*state1 = rotateLeft(*state1 + block[1] * Prime2, 13) * Prime1;
*state2 = rotateLeft(*state2 + block[2] * Prime2, 13) * Prime1;
*state3 = rotateLeft(*state3 + block[3] * Prime2, 13) * Prime1;
}
// create new XXHash (32 bit)
/** @param seed your seed value, even zero is a valid seed and e.g. used by LZ4
* **/
WASM_EXPORT
void Hash_Init(uint32_t seed) {
sctx.state[0] = seed + Prime1 + Prime2;
sctx.state[1] = seed + Prime2;
sctx.state[2] = seed;
sctx.state[3] = seed - Prime1;
sctx.bufferSize = 0;
sctx.totalLength = 0;
}
// add a chunk of bytes
/** @param length number of bytes
@return false if parameters are invalid / zero **/
WASM_EXPORT
void Hash_Update(uint32_t length) {
const void* input = main_buffer;
// no data ?
if (!input || length == 0) return;
sctx.totalLength += length;
// byte-wise access
const unsigned char* data = (const unsigned char*)input;
// unprocessed old data plus new data still fit in temporary buffer ?
if (sctx.bufferSize + length < MAX_BUFFER_SIZE) {
// just add new data
while (length-- > 0) {
sctx.buffer[sctx.bufferSize++] = *data++;
}
return;
}
// point beyond last byte
const unsigned char* stop = data + length;
const unsigned char* stopBlock = stop - MAX_BUFFER_SIZE;
// some data left from previous update ?
if (sctx.bufferSize > 0) {
// make sure temporary buffer is full (16 bytes)
while (sctx.bufferSize < MAX_BUFFER_SIZE) {
sctx.buffer[sctx.bufferSize++] = *data++;
}
// process these 16 bytes (4x4)
process(sctx.buffer, &sctx.state[0], &sctx.state[1], &sctx.state[2], &sctx.state[3]);
}
// copying state to local variables helps optimizer A LOT
uint32_t s0 = sctx.state[0], s1 = sctx.state[1], s2 = sctx.state[2], s3 = sctx.state[3];
// 16 bytes at once
while (data <= stopBlock) {
// local variables s0..s3 instead of state[0]..state[3] are much faster
process(data, &s0, &s1, &s2, &s3);
data += 16;
}
// copy back
sctx.state[0] = s0;
sctx.state[1] = s1;
sctx.state[2] = s2;
sctx.state[3] = s3;
// copy remainder to temporary buffer
sctx.bufferSize = stop - data;
for (unsigned int i = 0; i < sctx.bufferSize; i++) {
sctx.buffer[i] = data[i];
}
}
// get current hash
/** @return 32 bit XXHash **/
WASM_EXPORT
void Hash_Final() {
uint32_t result = (uint32_t)sctx.totalLength;
// fold 128 bit state into one single 32 bit value
if (sctx.totalLength >= MAX_BUFFER_SIZE) {
result += rotateLeft(sctx.state[0], 1) +
rotateLeft(sctx.state[1], 7) +
rotateLeft(sctx.state[2], 12) +
rotateLeft(sctx.state[3], 18);
} else {
// internal state wasn't set in add(), therefore original seed is still
// stored in state2
result += sctx.state[2] + Prime5;
}
// process remaining bytes in temporary buffer
const unsigned char* data = sctx.buffer;
// point beyond last byte
const unsigned char* stop = data + sctx.bufferSize;
// at least 4 bytes left ? => eat 4 bytes per step
for (; data + 4 <= stop; data += 4) {
result = rotateLeft(result + *(uint32_t*)data * Prime3, 17) * Prime4;
}
// take care of remaining 0..3 bytes, eat 1 byte per step
while (data != stop) {
result = rotateLeft(result + (*data++) * Prime5, 11) * Prime1;
}
// mix bits
result ^= result >> 15;
result *= Prime2;
result ^= result >> 13;
result *= Prime3;
result ^= result >> 16;
result = bswap32(result);
memcpy32(main_buffer, &result);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(sctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) &sctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t initParam) {
Hash_Init(initParam);
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,231 @@
/*******************************************************************************
* SM3 function implementation
* Copyright 2016 Yanbo Li dreamfly281@gmail.com, goldboar@163.com
* MIT License
*
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define SM3_DIGEST_LEN 32
#define u8 uint8_t
#define u32 uint32_t
struct sm3_ctx {
u32 total[2];
u32 state[8];
u8 buffer[64];
};
#define S(x,n) ((x << n) | (x >> (32 - n)))
#define P0(x) (x ^ S(x, 9) ^ S(x,17))
#define P1(x) (x ^ S(x,15) ^ S(x,23))
#define PW(t) \
( \
temp = W[t - 16] ^ W[t - 9] ^ (S(W[t - 3], 15)), \
P1(temp) ^ W[t - 6] ^ (S(W[t - 13], 7)) \
)
#define FF1(x,y,z) (x ^ y ^ z)
#define FF2(x,y,z) ((x & y) | (x & z) | (y & z))
#define GG1(x,y,z) (x ^ y ^ z)
#define GG2(x,y,z) ((x & y) | ((~x) & z))
#define T1 0x79cc4519
#define T2 0x7a879d8a
#define bswap_32(x) __builtin_bswap32(x)
void sm3_init(struct sm3_ctx *ctx) {
ctx->total[0] = 0;
ctx->total[1] = 0;
ctx->state[0] = 0x7380166f;
ctx->state[1] = 0x4914b2b9;
ctx->state[2] = 0x172442d7;
ctx->state[3] = 0xda8a0600;
ctx->state[4] = 0xa96f30bc;
ctx->state[5] = 0x163138aa;
ctx->state[6] = 0xe38dee4d;
ctx->state[7] = 0xb0fb0e4e;
}
static void sm3_process(struct sm3_ctx *ctx, const u8 data[64]) {
u32 temp, W[68], WP[64], A, B, C, D, E, F, G, H, SS1, SS2, TT1, TT2;
int j, k;
#pragma clang loop unroll(full)
for (int i = 0; i < 16; i++) {
W[i] = bswap_32(((u32 *) data)[i]);
}
W[16] = PW(16);
W[17] = PW(17);
W[18] = PW(18);
W[19] = PW(19);
A = ctx->state[0];
B = ctx->state[1];
C = ctx->state[2];
D = ctx->state[3];
E = ctx->state[4];
F = ctx->state[5];
G = ctx->state[6];
H = ctx->state[7];
// #pragma clang loop unroll(full)
for (int i = 0; i < 16; i++) {
WP[i] = W[i] ^ W[i+4];
SS1 = S(A, 12) + E + S(T1, i);
SS1 = S(SS1, 7);
SS2 = SS1 ^ S(A, 12);
TT1 = FF1(A, B, C) + D + SS2 + WP[i];
TT2 = GG1(E, F, G) + H + SS1 + W[i];
D = C;
C = S(B,9);
B = A;
A = TT1;
H = G;
G = S(F,19);
F = E;
E = P0(TT2);
}
// #pragma clang loop unroll(full)
for (int i = 16; i < 64; i++) {
k = i + 4;
W[k] = PW(k);
WP[i] = W[i] ^ W[i + 4];
j = i % 32;
SS1 = S(A, 12) + E + S(T2, j);
SS1 = S(SS1, 7);
SS2 = SS1 ^ S(A, 12);
TT1 = FF2(A, B, C) + D + SS2 + WP[i];
TT2 = GG2(E, F, G) + H + SS1 + W[i];
D = C;
C = S(B, 9);
B = A;
A = TT1;
H = G;
G = S(F, 19);
F = E;
E = P0(TT2);
}
ctx->state[0] ^= A;
ctx->state[1] ^= B;
ctx->state[2] ^= C;
ctx->state[3] ^= D;
ctx->state[4] ^= E;
ctx->state[5] ^= F;
ctx->state[6] ^= G;
ctx->state[7] ^= H;
}
static void sm3_update(struct sm3_ctx *ctx, const u8 *msg, u32 len) {
u32 left, fill;
if (!len) {
return;
}
left = ctx->total[0] & 0x3F;
fill = 64 - left;
ctx->total[0] += len;
ctx->total[0] &= 0xFFFFFFFF;
if (ctx->total[0] < len) {
ctx->total[1]++;
}
if (left && (len >= fill)) {
memcpy((void *)(ctx->buffer + left), (void *)msg, fill);
sm3_process(ctx, ctx->buffer);
len -= fill;
msg += fill;
left = 0;
}
while (len >= 64) {
sm3_process(ctx, msg);
len -= 64;
msg += 64;
}
if (len) {
memcpy((void *)(ctx->buffer + left), (void *)msg, len);
}
}
static u8 sm3_padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static void sm3_finish(struct sm3_ctx *ctx, u8 digest[32]) {
u32 last, padn;
u32 high, low;
u8 msglen[8];
high = (ctx->total[0] >> 29)
| (ctx->total[1] << 3);
low = (ctx->total[0] << 3);
((u32 *)msglen)[0] = bswap_32(high);
((u32 *)msglen)[1] = bswap_32(low);
last = ctx->total[0] & 0x3F;
padn = (last < 56 ) ? (56 - last) : (120 - last);
sm3_update(ctx, sm3_padding, padn);
sm3_update(ctx, msglen, 8);
for (int i = 0; i < 8; i++) {
((u32 *)digest)[i] = bswap_32(ctx->state[i]);
}
}
struct sm3_ctx ctx;
WASM_EXPORT
void Hash_Init() {
sm3_init(&ctx);
}
WASM_EXPORT
void Hash_Update(uint32_t size) {
sm3_update(&ctx, main_buffer, size);
}
WASM_EXPORT
void Hash_Final() {
sm3_finish(&ctx, main_buffer);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(ctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) &ctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length) {
Hash_Init();
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,86 @@
// Based on crc32.c implementation of Stephan Brumme
// Modified for hash-wasm by Dani Biró
#include <stdint.h>
#define WITH_BUFFER
#include "hash-wasm.h"
#define bswap_64(x) __builtin_bswap64(x)
alignas(128) static uint64_t crc64_lookup[8][256] = {0};
void init_lut(uint64_t polynomial) {
for (int i = 0; i < 256; ++i) {
uint64_t crc = i;
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (-(int64_t)(crc & 1) & polynomial);
}
crc64_lookup[0][i] = crc;
}
for (int i = 1; i < 256; ++i) {
uint64_t lv = crc64_lookup[0][i];
for (int j = 1; j < 8; ++j) {
lv = (lv >> 8) ^ crc64_lookup[0][lv & 255];
crc64_lookup[j][i] = lv;
}
}
}
uint64_t crc64_lut_initialized_to = 0;
uint64_t previous_crc64 = 0;
WASM_EXPORT
void Hash_Init() {
// polynomial is at the memory object
uint64_t polynomial = *((uint64_t *)main_buffer);
if (crc64_lut_initialized_to != polynomial) {
init_lut(polynomial);
crc64_lut_initialized_to = polynomial;
}
previous_crc64 = 0;
}
WASM_EXPORT
void Hash_Update(uint32_t length) {
const uint8_t *data = main_buffer;
uint64_t crc = ~previous_crc64; // same as previous_crc64 ^ 0xFFFFFFFF
const uint64_t *current = (const uint64_t *)data;
// process eight bytes at once (Slicing-by-8)
while (length >= 8) {
uint64_t val = *current++ ^ crc;
crc = crc64_lookup[0][(val >> 56)] ^ crc64_lookup[1][(val >> 48) & 0xFF] ^
crc64_lookup[2][(val >> 40) & 0xFF] ^
crc64_lookup[3][(val >> 32) & 0xFF] ^
crc64_lookup[4][(val >> 24) & 0xFF] ^
crc64_lookup[5][(val >> 16) & 0xFF] ^
crc64_lookup[6][(val >> 8) & 0xFF] ^ crc64_lookup[7][val & 0xFF];
length -= 8;
}
const uint8_t *currentChar = (const uint8_t *)current;
// remaining 1 to 7 bytes (standard algorithm)
while (length-- != 0) {
crc = (crc >> 8) ^ crc64_lookup[0][(crc & 0xFF) ^ *currentChar++];
}
previous_crc64 = ~crc;
}
WASM_EXPORT
void Hash_Final() { ((uint64_t *)main_buffer)[0] = bswap_64(previous_crc64); }
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(previous_crc64);
WASM_EXPORT
uint8_t *Hash_GetState() { return (uint8_t *)&previous_crc64; }
WASM_EXPORT
void Hash_Calculate() { return; }
@@ -0,0 +1,295 @@
/* sha256.c - an implementation of SHA-256/224 hash functions
* based on FIPS 180-3 (Federal Information Processing Standart).
*
* Copyright (c) 2010, Aleksey Kravchenko <rhash.admin@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define sha256_block_size 64
#define sha256_hash_size 32
#define sha224_hash_size 28
#define ROTR32(dword, n) ((dword) >> (n) ^ ((dword) << (32 - (n))))
#define bswap_32(x) __builtin_bswap32(x)
struct sha256_ctx {
uint32_t message[16]; /* 512-bit buffer for leftovers */
uint64_t length; /* number of processed bytes */
uint32_t hash[8]; /* 256-bit algorithm internal hashing state */
uint32_t digest_length; /* length of the algorithm digest in bytes */
};
struct sha256_ctx sctx;
struct sha256_ctx* ctx = &sctx;
/* SHA-224 and SHA-256 constants for 64 rounds. These words represent
* the first 32 bits of the fractional parts of the cube
* roots of the first 64 prime numbers. */
static const uint32_t rhash_k256[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
/* The SHA256/224 functions defined by FIPS 180-3, 4.1.2 */
/* Optimized version of Ch(x,y,z)=((x & y) | (~x & z)) */
#define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
/* Optimized version of Maj(x,y,z)=((x & y) ^ (x & z) ^ (y & z)) */
#define Maj(x, y, z) (((x) & (y)) ^ ((z) & ((x) ^ (y))))
#define Sigma0(x) (ROTR32((x), 2) ^ ROTR32((x), 13) ^ ROTR32((x), 22))
#define Sigma1(x) (ROTR32((x), 6) ^ ROTR32((x), 11) ^ ROTR32((x), 25))
#define sigma0(x) (ROTR32((x), 7) ^ ROTR32((x), 18) ^ ((x) >> 3))
#define sigma1(x) (ROTR32((x), 17) ^ ROTR32((x), 19) ^ ((x) >> 10))
/* Recalculate element n-th of circular buffer W using formula
* W[n] = sigma1(W[n - 2]) + W[n - 7] + sigma0(W[n - 15]) + W[n - 16]; */
#define RECALCULATE_W(W, n) \
(W[n] += \
(sigma1(W[(n - 2) & 15]) + W[(n - 7) & 15] + sigma0(W[(n - 15) & 15])))
#define ROUND(a, b, c, d, e, f, g, h, k, data) \
{ \
uint32_t T1 = h + Sigma1(e) + Ch(e, f, g) + k + (data); \
d += T1, h = T1 + Sigma0(a) + Maj(a, b, c); \
}
#define ROUND_1_16(a, b, c, d, e, f, g, h, n) \
ROUND(a, b, c, d, e, f, g, h, rhash_k256[n], W[n] = bswap_32(block[n]))
#define ROUND_17_64(a, b, c, d, e, f, g, h, n) \
ROUND(a, b, c, d, e, f, g, h, k[n], RECALCULATE_W(W, n))
/**
* Initialize context before calculaing hash.
*
*/
void sha256_init() {
/* Initial values. These words were obtained by taking the first 32
* bits of the fractional parts of the square roots of the first
* eight prime numbers. */
static const uint32_t SHA256_H0[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
ctx->length = 0;
ctx->digest_length = sha256_hash_size;
/* initialize algorithm state */
#pragma clang loop unroll(full)
for (uint8_t i = 0; i < 8; i += 2) {
*(uint64_t*)&ctx->hash[i] = *(uint64_t*)&SHA256_H0[i];
}
}
/**
* Initialize context before calculaing hash.
*
*/
void sha224_init() {
/* Initial values from FIPS 180-3. These words were obtained by taking
* bits from 33th to 64th of the fractional parts of the square
* roots of ninth through sixteenth prime numbers. */
static const uint32_t SHA224_H0[8] = {
0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939,
0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4
};
ctx->length = 0;
ctx->digest_length = sha224_hash_size;
#pragma clang loop unroll(full)
for (uint8_t i = 0; i < 8; i += 2) {
*(uint64_t*)&ctx->hash[i] = *(uint64_t*)&SHA224_H0[i];
}
}
WASM_EXPORT
void Hash_Init(uint32_t bits) {
if (bits == 224) {
sha224_init();
} else {
sha256_init();
}
}
/**
* The core transformation. Process a 512-bit block.
*
* @param hash algorithm state
* @param block the message block to process
*/
static void sha256_process_block(uint32_t hash[8], uint32_t block[16]) {
uint32_t A, B, C, D, E, F, G, H;
uint32_t W[16];
const uint32_t* k;
int i;
A = hash[0], B = hash[1], C = hash[2], D = hash[3];
E = hash[4], F = hash[5], G = hash[6], H = hash[7];
/* Compute SHA using alternate Method: FIPS 180-3 6.1.3 */
ROUND_1_16(A, B, C, D, E, F, G, H, 0);
ROUND_1_16(H, A, B, C, D, E, F, G, 1);
ROUND_1_16(G, H, A, B, C, D, E, F, 2);
ROUND_1_16(F, G, H, A, B, C, D, E, 3);
ROUND_1_16(E, F, G, H, A, B, C, D, 4);
ROUND_1_16(D, E, F, G, H, A, B, C, 5);
ROUND_1_16(C, D, E, F, G, H, A, B, 6);
ROUND_1_16(B, C, D, E, F, G, H, A, 7);
ROUND_1_16(A, B, C, D, E, F, G, H, 8);
ROUND_1_16(H, A, B, C, D, E, F, G, 9);
ROUND_1_16(G, H, A, B, C, D, E, F, 10);
ROUND_1_16(F, G, H, A, B, C, D, E, 11);
ROUND_1_16(E, F, G, H, A, B, C, D, 12);
ROUND_1_16(D, E, F, G, H, A, B, C, 13);
ROUND_1_16(C, D, E, F, G, H, A, B, 14);
ROUND_1_16(B, C, D, E, F, G, H, A, 15);
#pragma clang loop unroll(full)
for (i = 16, k = &rhash_k256[16]; i < 64; i += 16, k += 16) {
ROUND_17_64(A, B, C, D, E, F, G, H, 0);
ROUND_17_64(H, A, B, C, D, E, F, G, 1);
ROUND_17_64(G, H, A, B, C, D, E, F, 2);
ROUND_17_64(F, G, H, A, B, C, D, E, 3);
ROUND_17_64(E, F, G, H, A, B, C, D, 4);
ROUND_17_64(D, E, F, G, H, A, B, C, 5);
ROUND_17_64(C, D, E, F, G, H, A, B, 6);
ROUND_17_64(B, C, D, E, F, G, H, A, 7);
ROUND_17_64(A, B, C, D, E, F, G, H, 8);
ROUND_17_64(H, A, B, C, D, E, F, G, 9);
ROUND_17_64(G, H, A, B, C, D, E, F, 10);
ROUND_17_64(F, G, H, A, B, C, D, E, 11);
ROUND_17_64(E, F, G, H, A, B, C, D, 12);
ROUND_17_64(D, E, F, G, H, A, B, C, 13);
ROUND_17_64(C, D, E, F, G, H, A, B, 14);
ROUND_17_64(B, C, D, E, F, G, H, A, 15);
}
hash[0] += A, hash[1] += B, hash[2] += C, hash[3] += D;
hash[4] += E, hash[5] += F, hash[6] += G, hash[7] += H;
}
/**
* Calculate message hash.
* Can be called repeatedly with chunks of the message to be hashed.
*
* @param size length of the message chunk
*/
WASM_EXPORT
void Hash_Update(uint32_t size) {
const uint8_t* msg = main_buffer;
uint32_t index = (uint32_t)ctx->length & 63;
ctx->length += size;
/* fill partial block */
if (index) {
uint32_t left = sha256_block_size - index;
uint32_t end = size < left ? size : left;
uint8_t* message8 = (uint8_t*)ctx->message;
for (uint8_t i = 0; i < end; i++) {
*(message8 + index + i) = msg[i];
}
if (size < left) return;
/* process partial block */
sha256_process_block(ctx->hash, (uint32_t*)ctx->message);
msg += left;
size -= left;
}
while (size >= sha256_block_size) {
uint32_t* aligned_message_block = (uint32_t*)msg;
sha256_process_block(ctx->hash, aligned_message_block);
msg += sha256_block_size;
size -= sha256_block_size;
}
if (size) {
/* save leftovers */
for (uint8_t i = 0; i < size; i++) {
*(((uint8_t*)ctx->message) + i) = msg[i];
}
}
}
/**
* Store calculated hash into the given array.
*
*/
WASM_EXPORT
void Hash_Final() {
uint32_t index = ((uint32_t)ctx->length & 63) >> 2;
uint32_t shift = ((uint32_t)ctx->length & 3) * 8;
/* pad message and run for last block */
/* append the byte 0x80 to the message */
ctx->message[index] &= ~(0xFFFFFFFFu << shift);
ctx->message[index++] ^= 0x80u << shift;
/* if no room left in the message to store 64-bit message length */
if (index > 14) {
/* then fill the rest with zeros and process it */
while (index < 16) {
ctx->message[index++] = 0;
}
sha256_process_block(ctx->hash, ctx->message);
index = 0;
}
while (index < 14) {
ctx->message[index++] = 0;
}
ctx->message[14] = bswap_32((uint32_t)(ctx->length >> 29));
ctx->message[15] = bswap_32((uint32_t)(ctx->length << 3));
sha256_process_block(ctx->hash, ctx->message);
#pragma clang loop unroll(full)
for (int32_t i = 7; i >= 0; i--) {
ctx->hash[i] = bswap_32(ctx->hash[i]);
}
for (uint8_t i = 0; i < ctx->digest_length; i++) {
main_buffer[i] = *(((uint8_t*)ctx->hash) + i);
}
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(*ctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) ctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t initParam) {
Hash_Init(initParam);
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,199 @@
// //////////////////////////////////////////////////////////
// xxhash64.h
// Copyright (c) 2016 Stephan Brumme. All rights reserved.
// see http://create.stephan-brumme.com/disclaimer.html
//
// XXHash (64 bit), based on Yann Collet's descriptions, see
// http://cyan4973.github.io/xxHash/
//
// Modified for hash-wasm by Dani Biró
//
#define WITH_BUFFER
#include "hash-wasm.h"
#define bswap64 __builtin_bswap64
const uint64_t Prime1 = 11400714785074694791ULL;
const uint64_t Prime2 = 14029467366897019727ULL;
const uint64_t Prime3 = 1609587929392839161ULL;
const uint64_t Prime4 = 9650029242287828579ULL;
const uint64_t Prime5 = 2870177450012600261ULL;
// temporarily store up to 31 bytes between multiple add() calls
#define MAX_BUFFER_SIZE (31 + 1)
struct XXHash64_CTX {
uint64_t state[4];
unsigned char buffer[MAX_BUFFER_SIZE];
unsigned int bufferSize;
uint64_t totalLength;
};
static struct XXHash64_CTX sctx;
// rotate bits, should compile to a single CPU instruction (ROL)
static inline uint64_t rotateLeft(uint64_t x, unsigned char bits) {
return (x << bits) | (x >> (64 - bits));
}
// process a single 64 bit value
static inline uint64_t processSingle(uint64_t previous, uint64_t input) {
return rotateLeft(previous + input * Prime2, 31) * Prime1;
}
// process a block of 4x4 bytes, this is the main part of the XXHash32
// algorithm
static inline void process(
const void* data, uint64_t* state0, uint64_t* state1,
uint64_t* state2, uint64_t* state3
) {
const uint64_t* block = (const uint64_t*)data;
*state0 = processSingle(*state0, block[0]);
*state1 = processSingle(*state1, block[1]);
*state2 = processSingle(*state2, block[2]);
*state3 = processSingle(*state3, block[3]);
}
WASM_EXPORT
void Hash_Init() {
// seed is at the memory object
uint64_t seed = *((uint64_t*)main_buffer);
sctx.state[0] = seed + Prime1 + Prime2;
sctx.state[1] = seed + Prime2;
sctx.state[2] = seed;
sctx.state[3] = seed - Prime1;
sctx.bufferSize = 0;
sctx.totalLength = 0;
}
// add a chunk of bytes
/** @param length number of bytes
@return false if parameters are invalid / zero **/
WASM_EXPORT
void Hash_Update(uint32_t length) {
const void* input = main_buffer;
// no data ?
if (length == 0) return;
sctx.totalLength += length;
// byte-wise access
const unsigned char* data = (const unsigned char*)input;
// unprocessed old data plus new data still fit in temporary buffer ?
if (sctx.bufferSize + length < MAX_BUFFER_SIZE) {
// just add new data
while (length-- > 0) {
sctx.buffer[sctx.bufferSize++] = *data++;
}
return;
}
// point beyond last byte
const unsigned char* stop = data + length;
const unsigned char* stopBlock = stop - MAX_BUFFER_SIZE;
// some data left from previous update ?
if (sctx.bufferSize > 0) {
// make sure temporary buffer is full (16 bytes)
while (sctx.bufferSize < MAX_BUFFER_SIZE) {
sctx.buffer[sctx.bufferSize++] = *data++;
}
// process these 32 bytes (4x8)
process(sctx.buffer, &sctx.state[0], &sctx.state[1], &sctx.state[2], &sctx.state[3]);
}
// copying state to local variables helps optimizer A LOT
uint64_t s0 = sctx.state[0], s1 = sctx.state[1], s2 = sctx.state[2], s3 = sctx.state[3];
// 32 bytes at once
while (data <= stopBlock) {
// local variables s0..s3 instead of state[0]..state[3] are much faster
process(data, &s0, &s1, &s2, &s3);
data += 32;
}
// copy back
sctx.state[0] = s0;
sctx.state[1] = s1;
sctx.state[2] = s2;
sctx.state[3] = s3;
// copy remainder to temporary buffer
sctx.bufferSize = stop - data;
for (unsigned int i = 0; i < sctx.bufferSize; i++) {
sctx.buffer[i] = data[i];
}
return;
}
/// get current hash
WASM_EXPORT
void Hash_Final() {
// fold 256 bit state into one single 64 bit value
uint64_t result;
if (sctx.totalLength >= MAX_BUFFER_SIZE) {
result = rotateLeft(sctx.state[0], 1) + rotateLeft(sctx.state[1], 7) +
rotateLeft(sctx.state[2], 12) + rotateLeft(sctx.state[3], 18);
result = (result ^ processSingle(0, sctx.state[0])) * Prime1 + Prime4;
result = (result ^ processSingle(0, sctx.state[1])) * Prime1 + Prime4;
result = (result ^ processSingle(0, sctx.state[2])) * Prime1 + Prime4;
result = (result ^ processSingle(0, sctx.state[3])) * Prime1 + Prime4;
} else {
// internal state wasn't set in add(), therefore original seed is still
// stored in state2
result = sctx.state[2] + Prime5;
}
result += sctx.totalLength;
// process remaining bytes in temporary buffer
const unsigned char* data = sctx.buffer;
// point beyond last byte
const unsigned char* stop = data + sctx.bufferSize;
// at least 8 bytes left ? => eat 8 bytes per step
for (; data + 8 <= stop; data += 8) {
result =
rotateLeft(result ^ processSingle(0, *(uint64_t*)data), 27) * Prime1 +
Prime4;
}
// 4 bytes left ? => eat those
if (data + 4 <= stop) {
result =
rotateLeft(result ^ (*(uint32_t*)data) * Prime1, 23) * Prime2 + Prime3;
data += 4;
}
// take care of remaining 0..3 bytes, eat 1 byte per step
while (data != stop) {
result = rotateLeft(result ^ (*data++) * Prime5, 11) * Prime1;
}
// mix bits
result ^= result >> 33;
result *= Prime2;
result ^= result >> 29;
result *= Prime3;
result ^= result >> 32;
result = bswap64(result);
memcpy64(main_buffer, &result);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(sctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) &sctx;
}
WASM_EXPORT
void Hash_Calculate() {
return;
}
@@ -0,0 +1,187 @@
## 4.12.0 (November 19, 2024)
- **BREAKING CHANGE**: crc32c() and createCRC32C() were removed. You can now do the same thing with crc32() and createCRC32() through setting the polynomial parameter to 0x82F63B78
- Added CRC-64 hash function
- Exported IDataType, IHasher types from index.ts
- Update benchmarks
- Update dependencies, including Clang
- Migrate from ESLint to Biome and fix linting errors
## 4.11.0 (November 13, 2023)
- Added `secret` parameter to Argon2 (by [@malobre](https://github.com/malobre))
## 4.10.0 (October 15, 2023)
- Add sideEffects: false to package.json
- Update dependencies, including Clang
## 4.9.0 (July 22, 2021)
- Add CRC32C
- Update dependencies, including Clang
## 4.8.0 (June 27, 2021)
- Optimized Adler-32 for WASM
- Add xxhash3() and xxhash128() hash functions
- Slightly faster xxhash32() and xxhash64()
## 4.7.0 (June 5, 2021)
- Add Adler-32 algorithm
- New feature: saving and loading the internal state of the hash (segmented hashing and rewinding)
- Faster builds using GNU Make
- Fix alignment of the main buffer (might result in better performance on some platforms)
- Updated dependencies
- _(Special thanks to Nicholas Sherlock for his contributions)_
## 4.6.0 (March 26, 2021)
- Smaller CRC32 binary (now it computes the lookup table at the first run)
- Less RAM required for the WASM instances: it only allocates 128kB of RAM instead of 256kB
- Update dependencies
## 4.5.0 (March 6, 2021)
- Add BLAKE3 algorithm
- Performance improvements at BLAKE2
- Update dependencies
- Update benchmarks
## 4.4.1 (November 22, 2020)
- Add JSDoc annotations
## 4.4.0 (November 8, 2020)
- Add Whirlpool and SM3 algorithms
- Fix block size at blake algorithms
- Switch compiler from Emscripten to Clang
- Fix cached seed invalidation at xxhash64
- Added wasm binary hashes to build log
- Create minified bundles from each algorithm
## 4.3.0 (October 10, 2020)
- Add blake2s algorithm
- Add bundle sizes to README
## 4.2.2 (September 19, 2020)
- Add bcrypt algorithm
- Add argon2Validate() function
## 4.1.0 (September 5, 2020)
- Add scrypt algorithm
- Improve type signiture of key derivation functions. Now the return type depends on the parameters
- Update benchmarks, add relative performance
- Add section to README about string encoding pitfalls
## 4.0.1 (August 29, 2020)
- ~~**BREAKING CHANGE**: All APIs were converted to **synchronous** functions, so you don't need to use `await` or `.then()` anymore.~~ It turned out that Chrome does not allow synchronous WebAssembly instantiation of binaries larger than 4KB. So this change was reverted.
- **BREAKING CHANGE**: `argon2()` function was renamed to `argon2i()`, `argon2d()` and `argon2id()`.
- **BREAKING CHANGE**: `pbkdf2()` function requires now an object containing all parameters
- Update dependencies
## 3.7.2 (August 15, 2020)
- Add more badges to README
- Update dependencies
- Increase code coverage
- Consistent coding style in C files
## 3.7.1 (August 2, 2020)
- Allow returning Uint8Array from digest()
- Allow operation chaining in createXXXX() response
- Throw an error when an IHasher instance is reused without init() call.
- Add "binary" and "encoded" output types to argon2
- Update benchmark results
- Remove all dependencies on Buffer object in standalone mode
## 3.7.0 (August 1, 2020)
- Added Argon2
- Support hash lengths shorter than 16 bytes in Blake2b
## 3.6.0 (July 25, 2020)
- **Breaking change**: Drop support for String() objects (normal string literals are still supported of course)
- Node.js buffer polyfills are not needed anymore
## 3.5.0 (July 19, 2020)
- Added BLAKE2b algorithm
- Fixed a bug, where sha3() and keccak() gave incorrect results when the hash length was changed
## 3.4.1 (June 27, 2020)
- Add PBKDF2 example and benchmark results
## 3.4.0 (June 27, 2020)
- Add PBKDF2 algorithm
## 3.3.0 (June 22, 2020)
- Add RIPEMD-160 algorithm
## 3.2.1 (June 5, 2020)
- Fix buffer.copy() error in browsers
## 3.2.0 (June 5, 2020)
- Added HMAC
- Better examples in readme
- Updated dependencies
## 3.1.2 (May 27, 2020)
- Fixed example code in readme
- Better description and keywords
## 3.1.1 (May 25, 2020)
- ~3x faster when hashing small chunks of data (one WASM function call instead of three)
- Updated benchmark results in readme
## 3.0.1 (May 24, 2020)
- Updated benchmark results in readme
- Use new web-based tool for benchmarking
## 3.0.0 (May 24, 2020)
- Improved performance in smaller input buffers
## 2.6.0 (April 18, 2020)
- Allow concurrent hash calculations with multiple states
- Fix WebAssembly detection
- Cache WebAssembly modules
- Fix crash when starting many parallel hash calculations
- Allocate less memory for WASM modules
## 2.5.1 (April 13, 2020)
- Add React.js demo app
## 2.5.0 (April 13, 2020)
- Add xxhash32 and xxhash64
- Add Keccak
- Bugfixes
## 2.1.0 (April 12, 2020)
- Use faster SHA256 and SHA512 implementations
- Allocate less memory in WASM modules
- Clean up C source codes
## 2.0.0 (April 11, 2020)
- Initial release
@@ -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;
});
}
@@ -0,0 +1,16 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
type IValidBits = 224 | 256 | 384 | 512;
/**
* 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 declare function keccak(data: IDataType, bits?: IValidBits): Promise<string>;
/**
* Creates a new Keccak hash instance
* @param bits Number of output bits. Valid values: 224, 256, 384, 512
*/
export declare function createKeccak(bits?: IValidBits): Promise<IHasher>;
export {};
@@ -0,0 +1,112 @@
import typescript from "@rollup/plugin-typescript";
import json from "@rollup/plugin-json";
import { terser } from "rollup-plugin-terser";
// import gzipPlugin from 'rollup-plugin-gzip';
import license from "rollup-plugin-license";
const ALGORITHMS = [
"adler32",
"argon2",
"bcrypt",
"blake2b",
"blake2s",
"blake3",
"crc32",
"crc64",
"hmac",
"keccak",
"md4",
"md5",
"pbkdf2",
"ripemd160",
"scrypt",
"sha1",
"sha3",
"sha224",
"sha256",
"sha384",
"sha512",
"sm3",
"whirlpool",
"xxhash32",
"xxhash64",
"xxhash3",
"xxhash128",
];
const TERSER_CONFIG = {
output: {
comments: false,
},
};
const LICENSE_CONFIG = {
banner: {
commentStyle: "ignored",
content: `hash-wasm (https://www.npmjs.com/package/hash-wasm)
(c) Dani Biro
@license MIT`,
},
};
const MAIN_BUNDLE_CONFIG = {
input: "lib/index.ts",
output: [
{
file: "dist/index.umd.js",
name: "hashwasm",
format: "umd",
},
{
file: "dist/index.esm.js",
format: "es",
},
],
plugins: [json(), typescript(), license(LICENSE_CONFIG)],
};
const MINIFIED_MAIN_BUNDLE_CONFIG = {
input: "lib/index.ts",
output: [
{
file: "dist/index.umd.min.js",
name: "hashwasm",
format: "umd",
},
{
file: "dist/index.esm.min.js",
format: "es",
},
],
plugins: [
json(),
typescript(),
terser(TERSER_CONFIG),
license(LICENSE_CONFIG),
],
};
const INDIVIDUAL_BUNDLE_CONFIG = (algorithm) => ({
input: `lib/${algorithm}.ts`,
output: [
{
file: `dist/${algorithm}.umd.min.js`,
name: "hashwasm",
format: "umd",
extend: true,
},
],
plugins: [
json(),
typescript(),
terser(TERSER_CONFIG),
license(LICENSE_CONFIG),
// gzipPlugin(),
],
});
export default [
MAIN_BUNDLE_CONFIG,
MINIFIED_MAIN_BUNDLE_CONFIG,
...ALGORITHMS.map(INDIVIDUAL_BUNDLE_CONFIG),
];
@@ -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;
});
}
@@ -0,0 +1,18 @@
import { type IHasher } from "./WASMInterface";
import { type IDataType } from "./util";
/**
* 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 declare function blake3(data: IDataType, bits?: number, key?: IDataType): Promise<string>;
/**
* 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 declare function createBLAKE3(bits?: number, key?: IDataType): Promise<IHasher>;
@@ -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;
});
}
@@ -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;
});
}
@@ -0,0 +1,14 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function crc32(data: IDataType, polynomial?: number): Promise<string>;
/**
* Creates a new CRC-32 hash instance
* @param polynomial Input polynomial (defaults to 0xedb88320, for CRC32C use 0x82f63b78)
*/
export declare function createCRC32(polynomial?: number): Promise<IHasher>;
@@ -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>>;
}
@@ -0,0 +1,290 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD4 Message-Digest Algorithm (RFC 1320).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md4
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* (This is a heavily cut-down "BSD license".)
*
* This differs from Colin Plumb's older public domain implementation in that
* no exactly 32-bit integer data type is required (any 32-bit or wider
* unsigned integer data type will do), there's no compile-time endianness
* configuration, and the function prototypes match OpenSSL's. No code from
* Colin Plumb's implementation has been reused; this comment merely compares
* the properties of the two independent implementations.
*
* The primary goals of this implementation are portability and ease of use.
* It is meant to be fast, but not as fast as possible. Some known
* optimizations are not included to reduce source code size and avoid
* compile-time configuration.
*
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
struct MD4_CTX {
uint32_t lo, hi;
uint32_t a, b, c, d;
uint8_t buffer[64];
uint32_t block[16];
};
struct MD4_CTX sctx;
struct MD4_CTX *ctx = &sctx;
/*
* The basic MD4 functions.
*
* F and G are optimized compared to their RFC 1320 definitions, with the
* optimization for F borrowed from Colin Plumb's MD5 implementation.
*/
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
/*
* The MD4 transformation for all three rounds.
*/
#define STEP(f, a, b, c, d, x, s) \
(a) += f((b), (c), (d)) + (x); \
(a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s))));
/*
* SET reads 4 input bytes in little-endian byte order and stores them in a
* properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned memory
* accesses is just an optimization. Nothing will break if it fails to detect
* a suitable architecture.
*
* Unfortunately, this optimization may be a C strict aliasing rules violation
* if the caller's data buffer has effective type that cannot be aliased by
* uint32_t. In practice, this problem may occur if these MD4 routines are
* inlined into a calling function, or with future and dangerously advanced
* link-time optimizations. For the time being, keeping these MD4 routines in
* their own translation unit avoids the problem.
*/
#define SET(n) (*(uint32_t *)&ptr[(n)*4])
#define GET(n) SET(n)
/*
* This processes one or more 64-byte data blocks, but does NOT update the bit
* counters. There are no alignment requirements.
*/
static const void *body(const void *data, uint32_t size) {
const uint8_t *ptr;
uint32_t a, b, c, d;
uint32_t saved_a, saved_b, saved_c, saved_d;
const uint32_t ac1 = 0x5a827999, ac2 = 0x6ed9eba1;
ptr = (const uint8_t *)data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
STEP(F, a, b, c, d, SET(0), 3)
STEP(F, d, a, b, c, SET(1), 7)
STEP(F, c, d, a, b, SET(2), 11)
STEP(F, b, c, d, a, SET(3), 19)
STEP(F, a, b, c, d, SET(4), 3)
STEP(F, d, a, b, c, SET(5), 7)
STEP(F, c, d, a, b, SET(6), 11)
STEP(F, b, c, d, a, SET(7), 19)
STEP(F, a, b, c, d, SET(8), 3)
STEP(F, d, a, b, c, SET(9), 7)
STEP(F, c, d, a, b, SET(10), 11)
STEP(F, b, c, d, a, SET(11), 19)
STEP(F, a, b, c, d, SET(12), 3)
STEP(F, d, a, b, c, SET(13), 7)
STEP(F, c, d, a, b, SET(14), 11)
STEP(F, b, c, d, a, SET(15), 19)
/* Round 2 */
STEP(G, a, b, c, d, GET(0) + ac1, 3)
STEP(G, d, a, b, c, GET(4) + ac1, 5)
STEP(G, c, d, a, b, GET(8) + ac1, 9)
STEP(G, b, c, d, a, GET(12) + ac1, 13)
STEP(G, a, b, c, d, GET(1) + ac1, 3)
STEP(G, d, a, b, c, GET(5) + ac1, 5)
STEP(G, c, d, a, b, GET(9) + ac1, 9)
STEP(G, b, c, d, a, GET(13) + ac1, 13)
STEP(G, a, b, c, d, GET(2) + ac1, 3)
STEP(G, d, a, b, c, GET(6) + ac1, 5)
STEP(G, c, d, a, b, GET(10) + ac1, 9)
STEP(G, b, c, d, a, GET(14) + ac1, 13)
STEP(G, a, b, c, d, GET(3) + ac1, 3)
STEP(G, d, a, b, c, GET(7) + ac1, 5)
STEP(G, c, d, a, b, GET(11) + ac1, 9)
STEP(G, b, c, d, a, GET(15) + ac1, 13)
/* Round 3 */
STEP(H, a, b, c, d, GET(0) + ac2, 3)
STEP(H, d, a, b, c, GET(8) + ac2, 9)
STEP(H, c, d, a, b, GET(4) + ac2, 11)
STEP(H, b, c, d, a, GET(12) + ac2, 15)
STEP(H, a, b, c, d, GET(2) + ac2, 3)
STEP(H, d, a, b, c, GET(10) + ac2, 9)
STEP(H, c, d, a, b, GET(6) + ac2, 11)
STEP(H, b, c, d, a, GET(14) + ac2, 15)
STEP(H, a, b, c, d, GET(1) + ac2, 3)
STEP(H, d, a, b, c, GET(9) + ac2, 9)
STEP(H, c, d, a, b, GET(5) + ac2, 11)
STEP(H, b, c, d, a, GET(13) + ac2, 15)
STEP(H, a, b, c, d, GET(3) + ac2, 3)
STEP(H, d, a, b, c, GET(11) + ac2, 9)
STEP(H, c, d, a, b, GET(7) + ac2, 11)
STEP(H, b, c, d, a, GET(15) + ac2, 15)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
WASM_EXPORT
void Hash_Init() {
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
WASM_EXPORT
void Hash_Update(uint32_t size) {
const uint8_t *data = main_buffer;
uint32_t saved_lo;
uint32_t used, available;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) {
ctx->hi++;
}
ctx->hi += size >> 29;
used = saved_lo & 0x3f;
if (used) {
available = 64 - used;
if (size < available) {
for (uint32_t i = 0; i < size; i++) {
ctx->buffer[used + i] = data[i];
}
return;
}
for (uint32_t i = 0; i < available; i++) {
ctx->buffer[used + i] = data[i];
}
data = (const uint8_t *)data + available;
size -= available;
body(ctx->buffer, 64);
}
if (size >= 64) {
data = body(data, size & ~(uint32_t)0x3f);
size &= 0x3f;
}
for (uint32_t i = 0; i < size; i++) {
ctx->buffer[i] = data[i];
}
}
#define OUT(dst, src) \
(dst)[0] = (uint8_t)(src); \
(dst)[1] = (uint8_t)((src) >> 8); \
(dst)[2] = (uint8_t)((src) >> 16); \
(dst)[3] = (uint8_t)((src) >> 24);
WASM_EXPORT
void Hash_Final() {
uint8_t *result = main_buffer;
uint32_t used, available;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
available = 64 - used;
if (available < 8) {
for (int i = 0; i < available; i++) {
ctx->buffer[used + i] = 0;
}
body(ctx->buffer, 64);
used = 0;
available = 64;
}
for (int i = available - 9; i >= 0; i--) {
ctx->buffer[used + i] = 0;
}
ctx->lo <<= 3;
OUT(&ctx->buffer[56], ctx->lo)
OUT(&ctx->buffer[60], ctx->hi)
body(ctx->buffer, 64);
OUT(&result[0], ctx->a)
OUT(&result[4], ctx->b)
OUT(&result[8], ctx->c)
OUT(&result[12], ctx->d)
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(*ctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) ctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length) {
Hash_Init();
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates SHA-1 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function sha1(data: IDataType): Promise<string>;
/**
* Creates a new SHA-1 hash instance
*/
export declare function createSHA1(): Promise<IHasher>;
@@ -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;
});
}
@@ -0,0 +1,100 @@
{
"name": "hash-wasm",
"version": "4.12.0",
"description": "Lightning fast hash functions for browsers and Node.js using hand-tuned WebAssembly binaries (MD4, MD5, SHA-1, SHA-2, SHA-3, Keccak, BLAKE2, BLAKE3, PBKDF2, Argon2, bcrypt, scrypt, Adler-32, CRC32, CRC32C, RIPEMD-160, HMAC, xxHash, SM3, Whirlpool)",
"main": "dist/index.umd.js",
"module": "dist/index.esm.js",
"types": "dist/lib/index.d.ts",
"scripts": {
"build": "sh -c ./scripts/build.sh",
"lint": "npx @biomejs/biome check lib test",
"prepublishOnly": "sh -c ./scripts/build.sh",
"test": "node --expose-gc ./node_modules/.bin/jest --coverage --logHeapUsage",
"webpack": "node webpack/webpack.js"
},
"sideEffects": false,
"repository": {
"type": "git",
"url": "git+https://github.com/Daninet/hash-wasm.git"
},
"keywords": [
"hash",
"wasm",
"webassembly",
"md5",
"adler-32",
"crc32",
"crc64",
"sha-1",
"sha-2",
"sha-3",
"xxhash",
"keccak",
"ripemd",
"hmac",
"pbkdf2",
"blake2",
"blake2b",
"blake2s",
"blake3",
"fast",
"hashing",
"browser",
"key",
"derivation",
"password",
"md4",
"sha",
"sha1",
"sha2",
"sha224",
"sha-224",
"sha256",
"sha-256",
"sha384",
"sha-384",
"sha512",
"sha-512",
"sha3",
"sha3-224",
"sha3-256",
"sha3-384",
"sha3-512",
"xxhash32",
"xxhash64",
"ripemd160",
"argon2",
"argon2i",
"argon2d",
"argon2id",
"scrypt",
"bcrypt",
"sm3",
"whirlpool"
],
"bugs": {
"url": "https://github.com/Daninet/hash-wasm/issues"
},
"homepage": "https://github.com/Daninet/hash-wasm#readme",
"author": "Dani Biró <danibiro93@gmail.com> (https://danibiro.com)",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-typescript": "^12.1.1",
"@types/estree": "^1.0.6",
"@types/jest": "^29.5.14",
"@types/node": "^22.9.0",
"binaryen": "^120.0.0",
"jest": "^29.7.0",
"rollup": "^4.27.3",
"rollup-plugin-gzip": "^4.0.1",
"rollup-plugin-license": "^3.5.3",
"rollup-plugin-terser": "^7.0.2",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typescript": "^5.6.3"
}
}
@@ -0,0 +1,4 @@
import { type IWASMInterface } from "./WASMInterface";
import type Mutex from "./mutex";
import type { IEmbeddedWasm } from "./util";
export default function lockedCreate(mutex: Mutex, binary: IEmbeddedWasm, hashLength: number): Promise<IWASMInterface>;
@@ -0,0 +1,311 @@
/*
* This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
* MD5 Message-Digest Algorithm (RFC 1321).
*
* Homepage:
* http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
*
* Author:
* Alexander Peslyak, better known as Solar Designer <solar at openwall.com>
*
* This software was written by Alexander Peslyak in 2001. No copyright is
* claimed, and the software is hereby placed in the public domain.
* In case this attempt to disclaim copyright and place the software in the
* public domain is deemed null and void, then the software is
* Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
* general public under the following terms:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* There's ABSOLUTELY NO WARRANTY, express or implied.
*
* (This is a heavily cut-down "BSD license".)
*
* This differs from Colin Plumb's older public domain implementation in that
* no exactly 32-bit integer data type is required (any 32-bit or wider
* unsigned integer data type will do), there's no compile-time endianness
* configuration, and the function prototypes match OpenSSL's. No code from
* Colin Plumb's implementation has been reused; this comment merely compares
* the properties of the two independent implementations.
*
* The primary goals of this implementation are portability and ease of use.
* It is meant to be fast, but not as fast as possible. Some known
* optimizations are not included to reduce source code size and avoid
* compile-time configuration.
*
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
struct MD5_CTX {
uint32_t lo, hi;
uint32_t a, b, c, d;
uint8_t buffer[64];
uint32_t block[16];
};
struct MD5_CTX sctx;
struct MD5_CTX *ctx = &sctx;
/*
* The basic MD5 functions.
*
* F and G are optimized compared to their RFC 1321 definitions for
* architectures that lack an AND-NOT instruction, just like in Colin Plumb's
* implementation.
*/
#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
#define H(x, y, z) (((x) ^ (y)) ^ (z))
#define H2(x, y, z) ((x) ^ ((y) ^ (z)))
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
/*
* The MD5 transformation for all four rounds.
*/
#define STEP(f, a, b, c, d, x, t, s) \
(a) += f((b), (c), (d)) + (x) + (t); \
(a) = (((a) << (s)) | (((a)&0xffffffff) >> (32 - (s)))); \
(a) += (b);
/*
* SET reads 4 input bytes in little-endian byte order and stores them in a
* properly aligned word in host byte order.
*
* The check for little-endian architectures that tolerate unaligned memory
* accesses is just an optimization. Nothing will break if it fails to detect
* a suitable architecture.
*
* Unfortunately, this optimization may be a C strict aliasing rules violation
* if the caller's data buffer has effective type that cannot be aliased by
* uint32_t. In practice, this problem may occur if these MD5 routines are
* inlined into a calling function, or with future and dangerously advanced
* link-time optimizations. For the time being, keeping these MD5 routines in
* their own translation unit avoids the problem.
*/
#define SET(n) (*(uint32_t *)&ptr[(n)*4])
#define GET(n) SET(n)
/*
* This processes one or more 64-byte data blocks, but does NOT update the bit
* counters. There are no alignment requirements.
*/
static const void *body(const void *data, uint32_t size) {
const uint8_t *ptr;
uint32_t a, b, c, d;
uint32_t saved_a, saved_b, saved_c, saved_d;
ptr = (const uint8_t *)data;
a = ctx->a;
b = ctx->b;
c = ctx->c;
d = ctx->d;
do {
saved_a = a;
saved_b = b;
saved_c = c;
saved_d = d;
/* Round 1 */
STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7)
STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12)
STEP(F, c, d, a, b, SET(2), 0x242070db, 17)
STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22)
STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7)
STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12)
STEP(F, c, d, a, b, SET(6), 0xa8304613, 17)
STEP(F, b, c, d, a, SET(7), 0xfd469501, 22)
STEP(F, a, b, c, d, SET(8), 0x698098d8, 7)
STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12)
STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17)
STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22)
STEP(F, a, b, c, d, SET(12), 0x6b901122, 7)
STEP(F, d, a, b, c, SET(13), 0xfd987193, 12)
STEP(F, c, d, a, b, SET(14), 0xa679438e, 17)
STEP(F, b, c, d, a, SET(15), 0x49b40821, 22)
/* Round 2 */
STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5)
STEP(G, d, a, b, c, GET(6), 0xc040b340, 9)
STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14)
STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20)
STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5)
STEP(G, d, a, b, c, GET(10), 0x02441453, 9)
STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14)
STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20)
STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5)
STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9)
STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14)
STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20)
STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5)
STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9)
STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14)
STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20)
/* Round 3 */
STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4)
STEP(H2, d, a, b, c, GET(8), 0x8771f681, 11)
STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16)
STEP(H2, b, c, d, a, GET(14), 0xfde5380c, 23)
STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4)
STEP(H2, d, a, b, c, GET(4), 0x4bdecfa9, 11)
STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16)
STEP(H2, b, c, d, a, GET(10), 0xbebfbc70, 23)
STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4)
STEP(H2, d, a, b, c, GET(0), 0xeaa127fa, 11)
STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16)
STEP(H2, b, c, d, a, GET(6), 0x04881d05, 23)
STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4)
STEP(H2, d, a, b, c, GET(12), 0xe6db99e5, 11)
STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16)
STEP(H2, b, c, d, a, GET(2), 0xc4ac5665, 23)
/* Round 4 */
STEP(I, a, b, c, d, GET(0), 0xf4292244, 6)
STEP(I, d, a, b, c, GET(7), 0x432aff97, 10)
STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15)
STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21)
STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6)
STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10)
STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15)
STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21)
STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6)
STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10)
STEP(I, c, d, a, b, GET(6), 0xa3014314, 15)
STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21)
STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6)
STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10)
STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15)
STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21)
a += saved_a;
b += saved_b;
c += saved_c;
d += saved_d;
ptr += 64;
} while (size -= 64);
ctx->a = a;
ctx->b = b;
ctx->c = c;
ctx->d = d;
return ptr;
}
WASM_EXPORT
void Hash_Init() {
ctx->a = 0x67452301;
ctx->b = 0xefcdab89;
ctx->c = 0x98badcfe;
ctx->d = 0x10325476;
ctx->lo = 0;
ctx->hi = 0;
}
WASM_EXPORT
void Hash_Update(uint32_t size) {
const uint8_t *data = main_buffer;
uint32_t saved_lo;
uint32_t used, available;
saved_lo = ctx->lo;
if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) {
ctx->hi++;
}
ctx->hi += size >> 29;
used = saved_lo & 0x3f;
if (used) {
available = 64 - used;
if (size < available) {
for (uint32_t i = 0; i < size; i++) {
ctx->buffer[used + i] = data[i];
}
return;
}
for (uint32_t i = 0; i < available; i++) {
ctx->buffer[used + i] = data[i];
}
data = (const uint8_t *)data + available;
size -= available;
body(ctx->buffer, 64);
}
if (size >= 64) {
data = body(data, size & ~(uint32_t)0x3f);
size &= 0x3f;
}
for (uint32_t i = 0; i < size; i++) {
ctx->buffer[i] = data[i];
}
}
#define OUT(dst, src) \
(dst)[0] = (uint8_t)(src); \
(dst)[1] = (uint8_t)((src) >> 8); \
(dst)[2] = (uint8_t)((src) >> 16); \
(dst)[3] = (uint8_t)((src) >> 24);
WASM_EXPORT
void Hash_Final() {
uint8_t *result = main_buffer;
uint32_t used, available;
used = ctx->lo & 0x3f;
ctx->buffer[used++] = 0x80;
available = 64 - used;
if (available < 8) {
for (int i = 0; i < available; i++) {
ctx->buffer[used + i] = 0;
}
body(ctx->buffer, 64);
used = 0;
available = 64;
}
for (int i = available - 9; i >= 0; i--) {
ctx->buffer[used + i] = 0;
}
ctx->lo <<= 3;
OUT(&ctx->buffer[56], ctx->lo)
OUT(&ctx->buffer[60], ctx->hi)
body(ctx->buffer, 64);
OUT(&result[0], ctx->a)
OUT(&result[4], ctx->b)
OUT(&result[8], ctx->c)
OUT(&result[12], ctx->d)
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(*ctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) ctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length) {
Hash_Init();
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,15 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function xxhash32(data: IDataType, seed?: number): Promise<string>;
/**
* 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 declare function createXXHash32(seed?: number): Promise<IHasher>;
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates Whirlpool hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function whirlpool(data: IDataType): Promise<string>;
/**
* Creates a new Whirlpool hash instance
*/
export declare function createWhirlpool(): Promise<IHasher>;
@@ -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;
});
}
@@ -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;
});
}
@@ -0,0 +1,42 @@
import { type IDataType } 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";
}
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 declare function scrypt<T extends ScryptOptions>(options: T): Promise<ScryptReturnType<T>>;
export {};
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates SHA-2 (SHA-224) hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function sha224(data: IDataType): Promise<string>;
/**
* Creates a new SHA-2 (SHA-224) hash instance
*/
export declare function createSHA224(): Promise<IHasher>;
@@ -0,0 +1,29 @@
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";
@@ -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>>;
@@ -0,0 +1,284 @@
/*
BLAKE2 reference source code package - reference C implementations
Copyright 2012, Samuel Neves <sneves@dei.uc.pt>. You may use this under the
terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at
your option. The terms of these licenses can be found at:
- CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0
- OpenSSL license : https://www.openssl.org/source/license.html
- Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0
More information about the BLAKE2 hash function can be found at
https://blake2.net.
Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define BLAKE2_PACKED(x) x __attribute__((packed))
enum blake2s_constant {
BLAKE2S_BLOCKBYTES = 64,
BLAKE2S_OUTBYTES = 32,
BLAKE2S_KEYBYTES = 32,
BLAKE2S_SALTBYTES = 8,
BLAKE2S_PERSONALBYTES = 8
};
typedef struct blake2s_state__ {
uint32_t h[8];
uint32_t t[2];
uint32_t f[2];
uint8_t buf[BLAKE2S_BLOCKBYTES];
int buflen;
int outlen;
uint8_t last_node;
} blake2s_state;
blake2s_state S[1];
BLAKE2_PACKED(struct blake2s_param__ {
uint8_t digest_length; /* 1 */
uint8_t key_length; /* 2 */
uint8_t fanout; /* 3 */
uint8_t depth; /* 4 */
uint32_t leaf_length; /* 8 */
uint32_t node_offset; /* 12 */
uint16_t xof_length; /* 14 */
uint8_t node_depth; /* 15 */
uint8_t inner_length; /* 16 */
uint8_t salt[BLAKE2S_SALTBYTES]; /* 24 */
uint8_t personal[BLAKE2S_PERSONALBYTES]; /* 32 */
});
typedef struct blake2s_param__ blake2s_param;
blake2s_param P[1];
static __inline__ uint32_t load32(const void *src) {
return *(uint32_t *)src;
}
static __inline__ void store32(void *dst, uint32_t w) {
*(uint32_t *)dst = w;
}
static __inline__ uint64_t rotr32(const uint32_t w, const unsigned c) {
return (w >> c) | (w << (32 - c));
}
static const uint32_t blake2s_IV[8] = {
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
};
static const uint8_t blake2s_sigma[10][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } ,
{ 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } ,
{ 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } ,
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } ,
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } ,
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } ,
{ 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } ,
{ 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } ,
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } ,
{ 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 }
};
static __inline__ void blake2s_set_lastnode() { S->f[1] = (uint32_t)-1; }
/* Some helper functions, not necessarily useful */
static __inline__ int blake2s_is_lastblock() { return S->f[0] != 0; }
static __inline__ void blake2s_set_lastblock() {
if (S->last_node) blake2s_set_lastnode();
S->f[0] = (uint32_t)-1;
}
static __inline__ void blake2s_increment_counter(const uint32_t inc) {
S->t[0] += inc;
S->t[1] += (S->t[0] < inc);
}
#define G(r, i, a, b, c, d) \
do { \
a = a + b + m[blake2s_sigma[r][2 * i + 0]]; \
d = rotr32(d ^ a, 16); \
c = c + d; \
b = rotr32(b ^ c, 12); \
a = a + b + m[blake2s_sigma[r][2 * i + 1]]; \
d = rotr32(d ^ a, 8); \
c = c + d; \
b = rotr32(b ^ c, 7); \
} while (0)
static void round(uint32_t r, uint32_t m[16], uint32_t v[16]) {
G(r, 0, v[0], v[4], v[8], v[12]);
G(r, 1, v[1], v[5], v[9], v[13]);
G(r, 2, v[2], v[6], v[10], v[14]);
G(r, 3, v[3], v[7], v[11], v[15]);
G(r, 4, v[0], v[5], v[10], v[15]);
G(r, 5, v[1], v[6], v[11], v[12]);
G(r, 6, v[2], v[7], v[8], v[13]);
G(r, 7, v[3], v[4], v[9], v[14]);
}
static void blake2s_compress(const uint8_t block[BLAKE2S_BLOCKBYTES]) {
uint32_t m[16];
uint32_t v[16];
memcpy64(m, block);
memcpy32(v, S->h);
uint64_t* v64 = (uint64_t*)v;
uint64_t* blake2s_IV64 = (uint64_t*)blake2s_IV;
uint64_t* st64 = (uint64_t*)S->t;
uint64_t* sf64 = (uint64_t*)S->f;
v64[4] = blake2s_IV64[0];
v64[5] = blake2s_IV64[1];
v64[6] = blake2s_IV64[2] ^ st64[0];
v64[7] = blake2s_IV64[3] ^ sf64[0];
#pragma clang loop unroll(full)
for (int i = 0; i < 10; ++i) {
round(i, m, v);
}
uint64_t* sh64 = (uint64_t*)S->h;
#pragma clang loop unroll(full)
for (int i = 0; i < 4; ++i) {
sh64[i] = sh64[i] ^ v64[i] ^ v64[i + 4];
}
}
#undef G
void blake2s_update(const void *pin, int inlen) {
const unsigned char *in = (const unsigned char *)pin;
if (inlen > 0) {
int left = S->buflen;
int fill = BLAKE2S_BLOCKBYTES - left;
if (inlen > fill) {
S->buflen = 0;
/* Fill buffer */
memcpy(&S->buf[left], in, fill);
blake2s_increment_counter(BLAKE2S_BLOCKBYTES);
blake2s_compress(S->buf); /* Compress */
in += fill;
inlen -= fill;
while (inlen > BLAKE2S_BLOCKBYTES) {
blake2s_increment_counter(BLAKE2S_BLOCKBYTES);
blake2s_compress(in);
in += BLAKE2S_BLOCKBYTES;
inlen -= BLAKE2S_BLOCKBYTES;
}
}
memcpy(&S->buf[S->buflen], in, inlen);
S->buflen += inlen;
}
}
WASM_EXPORT
void Hash_Final() {
int outlen = S->outlen;
uint8_t buffer[BLAKE2S_OUTBYTES] = {0};
if (blake2s_is_lastblock()) {
return;
}
blake2s_increment_counter(S->buflen);
blake2s_set_lastblock();
for (int i = 0; i < BLAKE2S_BLOCKBYTES - S->buflen; i++) { /* Padding */
(S->buf + S->buflen)[i] = 0;
}
blake2s_compress(S->buf);
for (int i = 0; i < 8; ++i) {
/* Output full hash to temp buffer */
store32(buffer + sizeof(S->h[i]) * i, S->h[i]);
}
for (uint8_t i = 0; i < S->outlen; i++) {
main_buffer[i] = buffer[i];
}
}
static void blake2s_init0() {
for (int i = 0; i < sizeof(blake2s_state); i++) {
((uint8_t*)S)[i] = 0;
}
for (int i = 0; i < 8; ++i) {
S->h[i] = blake2s_IV[i];
}
}
/* init xors IV with input parameter block */
void blake2s_init_param() {
const uint8_t *p = (const uint8_t *)(P);
blake2s_init0();
/* IV XOR ParamBlock */
for (int i = 0; i < 8; ++i) {
S->h[i] ^= load32(p + sizeof(S->h[i]) * i);
}
S->outlen = P->digest_length;
}
void blake2s_init_key(int outlen, const uint8_t *key, int keylen) {
P->digest_length = (uint8_t)outlen;
P->key_length = (uint8_t)keylen;
P->fanout = 1;
P->depth = 1;
// P->leaf_length = 0;
// P->node_offset = 0;
// P->xof_length = 0;
// P->node_depth = 0;
// P->inner_length = 0;
// memset(P->reserved, 0, sizeof(P->reserved));
// memset(P->salt, 0, sizeof(P->salt));
// memset(P->personal, 0, sizeof(P->personal));
blake2s_init_param();
if (keylen > 0) {
uint8_t block[BLAKE2S_BLOCKBYTES] = { 0 };
for (uint8_t i = 0; i < keylen; i++) {
block[i] = key[i];
}
blake2s_update(block, BLAKE2S_BLOCKBYTES);
}
}
WASM_EXPORT
void Hash_Init(uint32_t bits) {
int outlen = bits & 0xFFFF;
int keylen = bits >> 16;
blake2s_init_key(outlen / 8, main_buffer, keylen);
}
WASM_EXPORT
void Hash_Update(uint32_t size) {
blake2s_update(main_buffer, size);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(S);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) S;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length, uint32_t initParam) {
Hash_Init(initParam);
Hash_Update(length);
Hash_Final();
}
@@ -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;
});
}
@@ -0,0 +1,132 @@
#include <stdint.h>
#include <stdalign.h>
#ifndef NULL
#define NULL 0
#endif
#ifdef _MSC_VER
#define WASM_EXPORT
#define __inline__
#else
#define WASM_EXPORT __attribute__((visibility("default")))
#endif
#ifdef WITH_BUFFER
#define MAIN_BUFFER_SIZE 16 * 1024
alignas(128) uint8_t main_buffer[MAIN_BUFFER_SIZE];
WASM_EXPORT
uint8_t *Hash_GetBuffer() {
return main_buffer;
}
#endif
// Sometimes LLVM emits these functions during the optimization step
// even with -nostdlib -fno-builtin flags
static __inline__ void* memcpy(void* dst, const void* src, uint32_t cnt) {
uint8_t *destination = dst;
const uint8_t *source = src;
while (cnt) {
*(destination++)= *(source++);
--cnt;
}
return dst;
}
static __inline__ void* memset(void* dst, const uint8_t value, uint32_t cnt) {
uint8_t *p = dst;
while (cnt--) {
*p++ = value;
}
return dst;
}
static __inline__ void* memcpy2(void* dst, const void* src, uint32_t cnt) {
uint64_t *destination64 = dst;
const uint64_t *source64 = src;
while (cnt >= 8) {
*(destination64++)= *(source64++);
cnt -= 8;
}
uint8_t *destination = (uint8_t*)destination64;
const uint8_t *source = (uint8_t*)source64;
while (cnt) {
*(destination++)= *(source++);
--cnt;
}
return dst;
}
static __inline__ void memcpy16(void* dst, const void* src) {
uint64_t* dst64 = (uint64_t*)dst;
uint64_t* src64 = (uint64_t*)src;
dst64[0] = src64[0];
dst64[1] = src64[1];
}
static __inline__ void memcpy32(void* dst, const void* src) {
uint64_t* dst64 = (uint64_t*)dst;
uint64_t* src64 = (uint64_t*)src;
#pragma clang loop unroll(full)
for (int i = 0; i < 4; i++) {
dst64[i] = src64[i];
}
}
static __inline__ void memcpy64(void* dst, const void* src) {
uint64_t* dst64 = (uint64_t*)dst;
uint64_t* src64 = (uint64_t*)src;
#pragma clang loop unroll(full)
for (int i = 0; i < 8; i++) {
dst64[i] = src64[i];
}
}
static __inline__ uint64_t widen8to64(const uint8_t value) {
return value | (value << 8) | (value << 16) | (value << 24);
}
static __inline__ void memset16(void* dst, const uint8_t value) {
uint64_t val = widen8to64(value);
uint64_t* dst64 = (uint64_t*)dst;
dst64[0] = val;
dst64[1] = val;
}
static __inline__ void memset32(void* dst, const uint8_t value) {
uint64_t val = widen8to64(value);
uint64_t* dst64 = (uint64_t*)dst;
#pragma clang loop unroll(full)
for (int i = 0; i < 4; i++) {
dst64[i] = val;
}
}
static __inline__ void memset64(void* dst, const uint8_t value) {
uint64_t val = widen8to64(value);
uint64_t* dst64 = (uint64_t*)dst;
#pragma clang loop unroll(full)
for (int i = 0; i < 8; i++) {
dst64[i] = val;
}
}
static __inline__ void memset128(void* dst, const uint8_t value) {
uint64_t val = widen8to64(value);
uint64_t* dst64 = (uint64_t*)dst;
#pragma clang loop unroll(full)
for (int i = 0; i < 16; i++) {
dst64[i] = val;
}
}
@@ -0,0 +1,44 @@
import { type IDataType } 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";
}
interface IBcryptOptionsBinary {
outputType: "binary";
}
type BcryptReturnType<T> = T extends IBcryptOptionsBinary ? Uint8Array : string;
/**
* Calculates hash using the bcrypt password-hashing function
* @returns Computed hash
*/
export declare function bcrypt<T extends BcryptOptions>(options: T): Promise<BcryptReturnType<T>>;
export interface BcryptVerifyOptions {
/**
* Password to be verified
*/
password: IDataType;
/**
* A previously generated bcrypt hash in the 'encoded' output format
*/
hash: string;
}
/**
* Verifies password using bcrypt password-hashing function
* @returns True if the encoded hash matches the password
*/
export declare function bcryptVerify(options: BcryptVerifyOptions): Promise<boolean>;
export {};
@@ -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>>;
}
@@ -0,0 +1,20 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function xxhash3(data: IDataType, seedLow?: number, seedHigh?: number): Promise<string>;
/**
* 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 declare function createXXHash3(seedLow?: number, seedHigh?: number): Promise<IHasher>;
@@ -0,0 +1,60 @@
import { type IDataType, type IEmbeddedWasm } from "./util";
export declare const MAX_HEAP: number;
type ThenArg<T> = T extends Promise<infer U> ? U : 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;
};
export declare function WASMInterface(binary: IEmbeddedWasm, hashLength: number): Promise<{
getMemory: () => Uint8Array;
writeMemory: (data: Uint8Array, offset?: number) => void;
getExports: () => any;
setMemorySize: (totalSize: number) => void;
init: (bits?: number) => void;
update: (data: IDataType) => void;
digest: (outputType: "hex" | "binary", padding?: number) => Uint8Array | string;
save: () => Uint8Array;
load: (state: Uint8Array) => void;
calculate: (data: IDataType, initParam?: any, digestParam?: any) => string;
hashLength: number;
}>;
export type IWASMInterface = ThenArg<ReturnType<typeof WASMInterface>>;
export {};
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates RIPEMD-160 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function ripemd160(data: IDataType): Promise<string>;
/**
* Creates a new RIPEMD-160 hash instance
*/
export declare function createRIPEMD160(): Promise<IHasher>;
@@ -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;
});
}
@@ -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;
});
}
@@ -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;
});
}
@@ -0,0 +1,871 @@
/*
* xxHash - Extremely Fast Hash algorithm
* Header File
* Copyright (C) 2012-2020 Yann Collet
*
* BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* You can contact the author at:
* - xxHash homepage: https://www.xxhash.com
* - xxHash source repository: https://github.com/Cyan4973/xxHash
*
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include <stddef.h>
#include "hash-wasm.h"
typedef uint8_t xxh_u8;
typedef uint32_t XXH32_hash_t;
typedef XXH32_hash_t xxh_u32;
typedef uint64_t XXH64_hash_t;
typedef XXH64_hash_t xxh_u64;
typedef struct {
XXH64_hash_t low64; /*!< `value & 0xFFFFFFFFFFFFFFFF` */
XXH64_hash_t high64; /*!< `value >> 64` */
} XXH128_hash_t;
#define XXH_RESTRICT restrict
#define XXH_NO_INLINE static
#define XXH_likely(x) __builtin_expect(x, 1)
#define XXH_unlikely(x) __builtin_expect(x, 0)
#define XXH_swap64 __builtin_bswap64
#define XXH_swap32 __builtin_bswap32
#define XXH_rotl32 __builtin_rotateleft32
#define XXH_rotl64 __builtin_rotateleft64
#define XXH_FORCE_INLINE inline static
#define XXH_ALIGN(n) alignas(n)
#define XXH_ALIGN_MEMBER(align, type) XXH_ALIGN(align) type
#define XXH3_INTERNALBUFFER_SIZE 256
#define XXH3_SECRET_DEFAULT_SIZE 192
#define XXH3_SECRET_SIZE_MIN 136
#define XXH_SECRET_DEFAULT_SIZE 192 /* minimum XXH3_SECRET_SIZE_MIN */
#define XXH_STRIPE_LEN 64
#define XXH_SECRET_CONSUME_RATE \
8 /* nb of secret bytes consumed at each accumulation */
#define XXH_ASSERT(c) ((void)0)
#define XXH_STATIC_ASSERT(c) \
do { \
enum { XXH_sa = 1 / (int)(!!(c)) }; \
} while (0)
#define XXH_ACC_ALIGN 64
#define XXH_ACC_NB (XXH_STRIPE_LEN / sizeof(xxh_u64))
#define XXH_SECRET_MERGEACCS_START 11
#define XXH3_MIDSIZE_MAX 240
#define XXH_SECRET_LASTACC_START 7
#define XXH3_MIDSIZE_STARTOFFSET 3
#define XXH3_MIDSIZE_LASTOFFSET 17
#define XXH_SEC_ALIGN 64
XXH_FORCE_INLINE xxh_u64 XXH_mult32to64(xxh_u64 x, xxh_u64 y) {
return (x & 0xFFFFFFFF) * (y & 0xFFFFFFFF);
}
XXH_FORCE_INLINE xxh_u32 XXH_read32(const void* memPtr) {
return *(const xxh_u32*)memPtr;
}
XXH_FORCE_INLINE xxh_u32 XXH_readLE32(const void* ptr) {
return XXH_read32(ptr);
}
XXH_FORCE_INLINE xxh_u64 XXH_read64(const void* memPtr) {
return *(const xxh_u64*)memPtr;
}
XXH_FORCE_INLINE xxh_u64 XXH_readLE64(const void* ptr) {
return XXH_read64(ptr);
}
XXH_FORCE_INLINE void XXH_writeLE64(void* dst, xxh_u64 v64) {
memcpy64(dst, &v64);
}
XXH_FORCE_INLINE xxh_u64 XXH_xorshift64(xxh_u64 v64, int shift) {
XXH_ASSERT(0 <= shift && shift < 64);
return v64 ^ (v64 >> shift);
}
/*
* This is a stronger avalanche,
* inspired by Pelle Evensen's rrmxmx
* preferable when input has not been previously mixed
*/
XXH_FORCE_INLINE XXH64_hash_t XXH3_rrmxmx(xxh_u64 h64, xxh_u64 len) {
/* this mix is inspired by Pelle Evensen's rrmxmx */
h64 ^= XXH_rotl64(h64, 49) ^ XXH_rotl64(h64, 24);
h64 *= 0x9FB21C651E98DF25ULL;
h64 ^= (h64 >> 35) + len;
h64 *= 0x9FB21C651E98DF25ULL;
return XXH_xorshift64(h64, 28);
}
XXH_FORCE_INLINE XXH128_hash_t XXH_mult64to128(xxh_u64 lhs, xxh_u64 rhs) {
/* First calculate all of the cross products. */
xxh_u64 const lo_lo = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs & 0xFFFFFFFF);
xxh_u64 const hi_lo = XXH_mult32to64(lhs >> 32, rhs & 0xFFFFFFFF);
xxh_u64 const lo_hi = XXH_mult32to64(lhs & 0xFFFFFFFF, rhs >> 32);
xxh_u64 const hi_hi = XXH_mult32to64(lhs >> 32, rhs >> 32);
/* Now add the products together. These will never overflow. */
xxh_u64 const cross = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + lo_hi;
xxh_u64 const upper = (hi_lo >> 32) + (cross >> 32) + hi_hi;
xxh_u64 const lower = (cross << 32) | (lo_lo & 0xFFFFFFFF);
XXH128_hash_t r128;
r128.low64 = lower;
r128.high64 = upper;
return r128;
}
#define XXH_PREFETCH(ptr) (void)(ptr) /* disabled */
typedef XXH64_hash_t (*XXH3_hashLong64_f)(
const void* XXH_RESTRICT, size_t,
XXH64_hash_t,
const xxh_u8* XXH_RESTRICT, size_t
);
#define XXH_PREFETCH_DIST 320
#define XXH_PRIME32_1 0x9E3779B1U /*!< 0b10011110001101110111100110110001 */
#define XXH_PRIME32_2 0x85EBCA77U /*!< 0b10000101111010111100101001110111 */
#define XXH_PRIME32_3 0xC2B2AE3DU /*!< 0b11000010101100101010111000111101 */
#define XXH_PRIME32_4 0x27D4EB2FU /*!< 0b00100111110101001110101100101111 */
#define XXH_PRIME32_5 0x165667B1U /*!< 0b00010110010101100110011110110001 */
#define XXH_PRIME64_1 0x9E3779B185EBCA87ULL
#define XXH_PRIME64_2 0xC2B2AE3D27D4EB4FULL
#define XXH_PRIME64_3 0x165667B19E3779F9ULL
#define XXH_PRIME64_4 0x85EBCA77C2B2AE63ULL
#define XXH_PRIME64_5 0x27D4EB2F165667C5ULL
XXH_ALIGN(64)
static const xxh_u8 XXH3_kSecret[XXH_SECRET_DEFAULT_SIZE] = {
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c,
0xf7, 0x21, 0xad, 0x1c, 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb,
0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f, 0xcb, 0x79, 0xe6, 0x4e,
0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6,
0x81, 0x3a, 0x26, 0x4c, 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb,
0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3, 0x71, 0x64, 0x48, 0x97,
0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7,
0xc7, 0x0b, 0x4f, 0x1d, 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31,
0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64, 0xea, 0xc5, 0xac, 0x83,
0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26,
0x29, 0xd4, 0x68, 0x9e, 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc,
0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce, 0x45, 0xcb, 0x3a, 0x8f,
0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
};
#define XXH3_INIT_ACC \
{ \
XXH_PRIME32_3, XXH_PRIME64_1, XXH_PRIME64_2, XXH_PRIME64_3, XXH_PRIME64_4, \
XXH_PRIME32_2, XXH_PRIME64_5, XXH_PRIME32_1 \
}
struct XXH3_state_s {
XXH_ALIGN_MEMBER(64, XXH64_hash_t acc[8]);
/*!< The 8 accumulators. Similar to `vN` in @ref XXH32_state_s::v1 and @ref
* XXH64_state_s */
XXH_ALIGN_MEMBER(64, unsigned char customSecret[XXH3_SECRET_DEFAULT_SIZE]);
/*!< Used to store a custom secret generated from a seed. */
XXH_ALIGN_MEMBER(64, unsigned char buffer[XXH3_INTERNALBUFFER_SIZE]);
/*!< The internal buffer. @see XXH32_state_s::mem32 */
XXH32_hash_t bufferedSize;
/*!< The amount of memory in @ref buffer, @see XXH32_state_s::memsize */
XXH32_hash_t reserved32;
/*!< Reserved field. Needed for padding on 64-bit. */
size_t nbStripesSoFar;
/*!< Number or stripes processed. */
XXH64_hash_t totalLen;
/*!< Total length hashed. 64-bit even on 32-bit targets. */
size_t nbStripesPerBlock;
/*!< Number of stripes per block. */
size_t secretLimit;
/*!< Size of @ref customSecret or @ref extSecret */
XXH64_hash_t seed;
/*!< Seed for _withSeed variants. Must be zero otherwise, @see
* XXH3_INITSTATE() */
XXH64_hash_t reserved64;
/*!< Reserved field. */
const unsigned char* extSecret;
/*!< Reference to an external secret for the _withSecret variants, NULL
* for other variants. */
/* note: there may be some padding at the end due to alignment on 64 bytes */
}; /* typedef'd to XXH3_state_t */
typedef struct XXH3_state_s XXH3_state_t;
static xxh_u64 XXH64_avalanche(xxh_u64 h64) {
h64 ^= h64 >> 33;
h64 *= XXH_PRIME64_2;
h64 ^= h64 >> 29;
h64 *= XXH_PRIME64_3;
h64 ^= h64 >> 32;
return h64;
}
static xxh_u64 XXH3_mul128_fold64(xxh_u64 lhs, xxh_u64 rhs) {
XXH128_hash_t product = XXH_mult64to128(lhs, rhs);
return product.low64 ^ product.high64;
}
inline xxh_u64 XXH3_mix16B(
const xxh_u8* XXH_RESTRICT input,
const xxh_u8* XXH_RESTRICT secret,
xxh_u64 seed64
) {
xxh_u64 const input_lo = XXH_readLE64(input);
xxh_u64 const input_hi = XXH_readLE64(input + 8);
return XXH3_mul128_fold64(input_lo ^ (XXH_readLE64(secret) + seed64),
input_hi ^ (XXH_readLE64(secret + 8) - seed64));
}
static void XXH3_reset_internal(
XXH3_state_t* statePtr, XXH64_hash_t seed,
const void* secret, size_t secretSize
) {
size_t const initStart = offsetof(XXH3_state_t, bufferedSize);
size_t const initLength = offsetof(XXH3_state_t, nbStripesPerBlock) - initStart;
XXH_ASSERT(offsetof(XXH3_state_t, nbStripesPerBlock) > initStart);
XXH_ASSERT(statePtr != NULL);
/* set members from bufferedSize to nbStripesPerBlock (excluded) to 0 */
memset((char*)statePtr + initStart, 0, initLength);
statePtr->acc[0] = XXH_PRIME32_3;
statePtr->acc[1] = XXH_PRIME64_1;
statePtr->acc[2] = XXH_PRIME64_2;
statePtr->acc[3] = XXH_PRIME64_3;
statePtr->acc[4] = XXH_PRIME64_4;
statePtr->acc[5] = XXH_PRIME32_2;
statePtr->acc[6] = XXH_PRIME64_5;
statePtr->acc[7] = XXH_PRIME32_1;
statePtr->seed = seed;
statePtr->extSecret = (const unsigned char*)secret;
XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
statePtr->secretLimit = secretSize - XXH_STRIPE_LEN;
statePtr->nbStripesPerBlock = statePtr->secretLimit / XXH_SECRET_CONSUME_RATE;
}
static void XXH3_64bits_reset(XXH3_state_t* statePtr) {
XXH3_reset_internal(statePtr, 0, XXH3_kSecret, XXH_SECRET_DEFAULT_SIZE);
}
static void XXH3_initCustomSecret_scalar(
void* XXH_RESTRICT customSecret, xxh_u64 seed64
) {
const xxh_u8* kSecretPtr = XXH3_kSecret;
XXH_STATIC_ASSERT((XXH_SECRET_DEFAULT_SIZE & 15) == 0);
XXH_ASSERT(kSecretPtr == XXH3_kSecret);
int const nbRounds = XXH_SECRET_DEFAULT_SIZE / 16;
for (int i = 0; i < nbRounds; i++) {
/*
* The asm hack causes Clang to assume that kSecretPtr aliases with
* customSecret, and on aarch64, this prevented LDP from merging two
* loads together for free. Putting the loads together before the stores
* properly generates LDP.
*/
xxh_u64 lo = XXH_readLE64(kSecretPtr + 16 * i) + seed64;
xxh_u64 hi = XXH_readLE64(kSecretPtr + 16 * i + 8) - seed64;
XXH_writeLE64((xxh_u8*)customSecret + 16 * i, lo);
XXH_writeLE64((xxh_u8*)customSecret + 16 * i + 8, hi);
}
}
static void XXH3_64bits_reset_withSeed(
XXH3_state_t* statePtr, XXH64_hash_t seed
) {
if (seed == 0) return XXH3_64bits_reset(statePtr);
if (seed != statePtr->seed)
XXH3_initCustomSecret_scalar(statePtr->customSecret, seed);
XXH3_reset_internal(statePtr, seed, NULL, XXH_SECRET_DEFAULT_SIZE);
}
void XXH3_accumulate_512_scalar(
void* XXH_RESTRICT acc,
const void* XXH_RESTRICT input,
const void* XXH_RESTRICT secret
) {
XXH_ALIGN(XXH_ACC_ALIGN)
xxh_u64* const xacc = (xxh_u64*)acc; /* presumed aligned */
const xxh_u8* const xinput = (const xxh_u8*)input; /* no alignment restriction */
const xxh_u8* const xsecret = (const xxh_u8*)secret; /* no alignment restriction */
XXH_ASSERT(((size_t)acc & (XXH_ACC_ALIGN - 1)) == 0);
for (size_t i = 0; i < XXH_ACC_NB; i++) {
xxh_u64 const data_val = XXH_readLE64(xinput + 8 * i);
xxh_u64 const data_key = data_val ^ XXH_readLE64(xsecret + i * 8);
xacc[i ^ 1] += data_val; /* swap adjacent lanes */
xacc[i] += XXH_mult32to64(data_key & 0xFFFFFFFF, data_key >> 32);
}
}
void XXH3_scrambleAcc_scalar(
void* XXH_RESTRICT acc, const void* XXH_RESTRICT secret
) {
XXH_ALIGN(XXH_ACC_ALIGN)
xxh_u64* const xacc = (xxh_u64*)acc; /* presumed aligned */
const xxh_u8* const xsecret = (const xxh_u8*)secret; /* no alignment restriction */
XXH_ASSERT((((size_t)acc) & (XXH_ACC_ALIGN - 1)) == 0);
for (size_t i = 0; i < XXH_ACC_NB; i++) {
xxh_u64 const key64 = XXH_readLE64(xsecret + 8 * i);
xxh_u64 acc64 = xacc[i];
acc64 = XXH_xorshift64(acc64, 47);
acc64 ^= key64;
acc64 *= XXH_PRIME32_1;
xacc[i] = acc64;
}
}
/*
* XXH3_accumulate()
* Loops over XXH3_accumulate_512().
* Assumption: nbStripes will not overflow the secret size
*/
void XXH3_accumulate(
xxh_u64* XXH_RESTRICT acc,
const xxh_u8* XXH_RESTRICT input,
const xxh_u8* XXH_RESTRICT secret, size_t nbStripes
) {
for (size_t n = 0; n < nbStripes; n++) {
const xxh_u8* const in = input + n * XXH_STRIPE_LEN;
XXH_PREFETCH(in + XXH_PREFETCH_DIST);
XXH3_accumulate_512_scalar(acc, in, secret + n * XXH_SECRET_CONSUME_RATE);
}
}
/* Note : when XXH3_consumeStripes() is invoked,
* there must be a guarantee that at least one more byte must be consumed from
* input
* so that the function can blindly consume all stripes using the "normal"
* secret segment */
void XXH3_consumeStripes(
xxh_u64* XXH_RESTRICT acc,
size_t* XXH_RESTRICT nbStripesSoFarPtr,
size_t nbStripesPerBlock,
const xxh_u8* XXH_RESTRICT input,
size_t nbStripes,
const xxh_u8* XXH_RESTRICT secret,
size_t secretLimit
) {
XXH_ASSERT(nbStripes <= nbStripesPerBlock); /* can handle max 1 scramble per invocation */
XXH_ASSERT(*nbStripesSoFarPtr < nbStripesPerBlock);
if (nbStripesPerBlock - *nbStripesSoFarPtr <= nbStripes) {
/* need a scrambling operation */
size_t const nbStripesToEndofBlock = nbStripesPerBlock - *nbStripesSoFarPtr;
size_t const nbStripesAfterBlock = nbStripes - nbStripesToEndofBlock;
XXH3_accumulate(
acc,
input,
secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE,
nbStripesToEndofBlock
);
XXH3_scrambleAcc_scalar(acc, secret + secretLimit);
XXH3_accumulate(
acc,
input + nbStripesToEndofBlock * XXH_STRIPE_LEN,
secret,
nbStripesAfterBlock
);
*nbStripesSoFarPtr = nbStripesAfterBlock;
} else {
XXH3_accumulate(
acc,
input,
secret + nbStripesSoFarPtr[0] * XXH_SECRET_CONSUME_RATE,
nbStripes
);
*nbStripesSoFarPtr += nbStripes;
}
}
void XXH3_update(XXH3_state_t* state, const xxh_u8* input, size_t len) {
{
const xxh_u8* const bEnd = input + len;
const unsigned char* const secret =
(state->extSecret == NULL) ? state->customSecret : state->extSecret;
state->totalLen += len;
XXH_ASSERT(state->bufferedSize <= XXH3_INTERNALBUFFER_SIZE);
if (state->bufferedSize + len <= XXH3_INTERNALBUFFER_SIZE) { /* fill in tmp buffer */
memcpy2(state->buffer + state->bufferedSize, input, len);
state->bufferedSize += (XXH32_hash_t)len;
return;
}
/* total input is now > XXH3_INTERNALBUFFER_SIZE */
#define XXH3_INTERNALBUFFER_STRIPES (XXH3_INTERNALBUFFER_SIZE / XXH_STRIPE_LEN)
XXH_STATIC_ASSERT(XXH3_INTERNALBUFFER_SIZE % XXH_STRIPE_LEN ==
0); /* clean multiple */
/*
* Internal buffer is partially filled (always, except at beginning)
* Complete it, then consume it.
*/
if (state->bufferedSize) {
size_t const loadSize = XXH3_INTERNALBUFFER_SIZE - state->bufferedSize;
memcpy2(state->buffer + state->bufferedSize, input, loadSize);
input += loadSize;
XXH3_consumeStripes(state->acc, &state->nbStripesSoFar,
state->nbStripesPerBlock, state->buffer,
XXH3_INTERNALBUFFER_STRIPES, secret,
state->secretLimit);
state->bufferedSize = 0;
}
XXH_ASSERT(input < bEnd);
/* Consume input by a multiple of internal buffer size */
if (input + XXH3_INTERNALBUFFER_SIZE < bEnd) {
const xxh_u8* const limit = bEnd - XXH3_INTERNALBUFFER_SIZE;
do {
XXH3_consumeStripes(
state->acc, &state->nbStripesSoFar, state->nbStripesPerBlock, input,
XXH3_INTERNALBUFFER_STRIPES, secret, state->secretLimit);
input += XXH3_INTERNALBUFFER_SIZE;
} while (input < limit);
/* for last partial stripe */
memcpy64(state->buffer + sizeof(state->buffer) - XXH_STRIPE_LEN,
input - XXH_STRIPE_LEN);
}
XXH_ASSERT(input < bEnd);
/* Some remaining input (always) : buffer it */
memcpy2(state->buffer, input, (size_t)(bEnd - input));
state->bufferedSize = (XXH32_hash_t)(bEnd - input);
}
return;
}
void XXH3_64bits_update(XXH3_state_t* state, const void* input, size_t len) {
XXH3_update(state, (const xxh_u8*)input, len);
}
xxh_u64 XXH3_mix2Accs(
const xxh_u64* XXH_RESTRICT acc,
const xxh_u8* XXH_RESTRICT secret
) {
return XXH3_mul128_fold64(
acc[0] ^ XXH_readLE64(secret),
acc[1] ^ XXH_readLE64(secret + 8)
);
}
/*
* This is a fast avalanche stage,
* suitable when input bits are already partially mixed
*/
XXH64_hash_t XXH3_avalanche(xxh_u64 h64) {
h64 = XXH_xorshift64(h64, 37);
h64 *= 0x165667919E3779F9ULL;
h64 = XXH_xorshift64(h64, 32);
return h64;
}
XXH64_hash_t XXH3_mergeAccs(
const xxh_u64* XXH_RESTRICT acc,
const xxh_u8* XXH_RESTRICT secret,
xxh_u64 start
) {
xxh_u64 result64 = start;
for (size_t i = 0; i < 4; i++) {
result64 += XXH3_mix2Accs(acc + 2 * i, secret + 16 * i);
}
return XXH3_avalanche(result64);
}
void XXH3_digest_long(
XXH64_hash_t* acc,
const XXH3_state_t* state,
const unsigned char* secret
) {
/*
* Digest on a local copy. This way, the state remains unaltered, and it can
* continue ingesting more input afterwards.
*/
memcpy2(acc, state->acc, sizeof(state->acc));
if (state->bufferedSize >= XXH_STRIPE_LEN) {
size_t const nbStripes = (state->bufferedSize - 1) / XXH_STRIPE_LEN;
size_t nbStripesSoFar = state->nbStripesSoFar;
XXH3_consumeStripes(acc, &nbStripesSoFar, state->nbStripesPerBlock,
state->buffer, nbStripes, secret, state->secretLimit);
/* last stripe */
XXH3_accumulate_512_scalar(
acc, state->buffer + state->bufferedSize - XXH_STRIPE_LEN,
secret + state->secretLimit - XXH_SECRET_LASTACC_START);
} else { /* bufferedSize < XXH_STRIPE_LEN */
xxh_u8 lastStripe[XXH_STRIPE_LEN];
size_t const catchupSize = XXH_STRIPE_LEN - state->bufferedSize;
XXH_ASSERT(state->bufferedSize > 0); /* there is always some input buffered */
memcpy2(lastStripe, state->buffer + sizeof(state->buffer) - catchupSize, catchupSize);
memcpy2(lastStripe + catchupSize, state->buffer, state->bufferedSize);
XXH3_accumulate_512_scalar(
acc,
lastStripe,
secret + state->secretLimit - XXH_SECRET_LASTACC_START
);
}
}
XXH64_hash_t XXH3_len_9to16_64b(
const xxh_u8* input,
size_t len,
const xxh_u8* secret,
XXH64_hash_t seed
) {
XXH_ASSERT(input != NULL);
XXH_ASSERT(secret != NULL);
XXH_ASSERT(8 <= len && len <= 16);
xxh_u64 const bitflip1 =
(XXH_readLE64(secret + 24) ^ XXH_readLE64(secret + 32)) + seed;
xxh_u64 const bitflip2 =
(XXH_readLE64(secret + 40) ^ XXH_readLE64(secret + 48)) - seed;
xxh_u64 const input_lo = XXH_readLE64(input) ^ bitflip1;
xxh_u64 const input_hi = XXH_readLE64(input + len - 8) ^ bitflip2;
xxh_u64 const acc =
len + XXH_swap64(input_lo) + input_hi + XXH3_mul128_fold64(input_lo, input_hi);
return XXH3_avalanche(acc);
}
XXH64_hash_t XXH3_len_4to8_64b(
const xxh_u8* input,
size_t len,
const xxh_u8* secret,
XXH64_hash_t seed
) {
XXH_ASSERT(input != NULL);
XXH_ASSERT(secret != NULL);
XXH_ASSERT(4 <= len && len <= 8);
seed ^= (xxh_u64)XXH_swap32((xxh_u32)seed) << 32;
xxh_u32 const input1 = XXH_readLE32(input);
xxh_u32 const input2 = XXH_readLE32(input + len - 4);
xxh_u64 const bitflip =
(XXH_readLE64(secret + 8) ^ XXH_readLE64(secret + 16)) - seed;
xxh_u64 const input64 = input2 + (((xxh_u64)input1) << 32);
xxh_u64 const keyed = input64 ^ bitflip;
return XXH3_rrmxmx(keyed, len);
}
XXH64_hash_t XXH3_len_1to3_64b(
const xxh_u8* input,
size_t len,
const xxh_u8* secret,
XXH64_hash_t seed
) {
XXH_ASSERT(input != NULL);
XXH_ASSERT(1 <= len && len <= 3);
XXH_ASSERT(secret != NULL);
/*
* len = 1: combined = { input[0], 0x01, input[0], input[0] }
* len = 2: combined = { input[1], 0x02, input[0], input[1] }
* len = 3: combined = { input[2], 0x03, input[0], input[1] }
*/
xxh_u8 const c1 = input[0];
xxh_u8 const c2 = input[len >> 1];
xxh_u8 const c3 = input[len - 1];
xxh_u32 const combined = ((xxh_u32)c1 << 16) | ((xxh_u32)c2 << 24) |
((xxh_u32)c3 << 0) | ((xxh_u32)len << 8);
xxh_u64 const bitflip =
(XXH_readLE32(secret) ^ XXH_readLE32(secret + 4)) + seed;
xxh_u64 const keyed = (xxh_u64)combined ^ bitflip;
return XXH64_avalanche(keyed);
}
XXH64_hash_t XXH3_len_0to16_64b(
const xxh_u8* input,
size_t len,
const xxh_u8* secret,
XXH64_hash_t seed
) {
XXH_ASSERT(len <= 16);
if (XXH_likely(len > 8))
return XXH3_len_9to16_64b(input, len, secret, seed);
if (XXH_likely(len >= 4))
return XXH3_len_4to8_64b(input, len, secret, seed);
if (len) return XXH3_len_1to3_64b(input, len, secret, seed);
return XXH64_avalanche(
seed ^ (XXH_readLE64(secret + 56) ^ XXH_readLE64(secret + 64)));
}
/* For mid range keys, XXH3 uses a Mum-hash variant. */
XXH64_hash_t XXH3_len_17to128_64b(
const xxh_u8* XXH_RESTRICT input,
size_t len,
const xxh_u8* XXH_RESTRICT secret,
size_t secretSize,
XXH64_hash_t seed
) {
XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
(void)secretSize;
XXH_ASSERT(16 < len && len <= 128);
xxh_u64 acc = len * XXH_PRIME64_1;
if (len > 32) {
if (len > 64) {
if (len > 96) {
acc += XXH3_mix16B(input + 48, secret + 96, seed);
acc += XXH3_mix16B(input + len - 64, secret + 112, seed);
}
acc += XXH3_mix16B(input + 32, secret + 64, seed);
acc += XXH3_mix16B(input + len - 48, secret + 80, seed);
}
acc += XXH3_mix16B(input + 16, secret + 32, seed);
acc += XXH3_mix16B(input + len - 32, secret + 48, seed);
}
acc += XXH3_mix16B(input + 0, secret + 0, seed);
acc += XXH3_mix16B(input + len - 16, secret + 16, seed);
return XXH3_avalanche(acc);
}
XXH64_hash_t XXH3_len_129to240_64b(
const xxh_u8* XXH_RESTRICT input, size_t len,
const xxh_u8* XXH_RESTRICT secret,
size_t secretSize, XXH64_hash_t seed
) {
XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
(void)secretSize;
XXH_ASSERT(128 < len && len <= XXH3_MIDSIZE_MAX);
{
xxh_u64 acc = len * XXH_PRIME64_1;
int const nbRounds = (int)len / 16;
int i;
for (i = 0; i < 8; i++) {
acc += XXH3_mix16B(input + (16 * i), secret + (16 * i), seed);
}
acc = XXH3_avalanche(acc);
XXH_ASSERT(nbRounds >= 8);
for (i = 8; i < nbRounds; i++) {
acc +=
XXH3_mix16B(input + (16 * i),
secret + (16 * (i - 8)) + XXH3_MIDSIZE_STARTOFFSET, seed);
}
/* last bytes */
acc += XXH3_mix16B(input + len - 16,
secret + XXH3_SECRET_SIZE_MIN - XXH3_MIDSIZE_LASTOFFSET,
seed);
return XXH3_avalanche(acc);
}
}
void XXH3_hashLong_internal_loop(
xxh_u64* XXH_RESTRICT acc,
const xxh_u8* XXH_RESTRICT input, size_t len,
const xxh_u8* XXH_RESTRICT secret,
size_t secretSize
) {
size_t const nbStripesPerBlock =
(secretSize - XXH_STRIPE_LEN) / XXH_SECRET_CONSUME_RATE;
size_t const block_len = XXH_STRIPE_LEN * nbStripesPerBlock;
size_t const nb_blocks = (len - 1) / block_len;
size_t n;
XXH_ASSERT(secretSize >= XXH3_SECRET_SIZE_MIN);
for (n = 0; n < nb_blocks; n++) {
XXH3_accumulate(acc, input + n * block_len, secret, nbStripesPerBlock);
XXH3_scrambleAcc_scalar(acc, secret + secretSize - XXH_STRIPE_LEN);
}
/* last partial block */
XXH_ASSERT(len > XXH_STRIPE_LEN);
{
size_t const nbStripes =
((len - 1) - (block_len * nb_blocks)) / XXH_STRIPE_LEN;
XXH_ASSERT(nbStripes <= (secretSize / XXH_SECRET_CONSUME_RATE));
XXH3_accumulate(acc, input + nb_blocks * block_len, secret, nbStripes);
/* last stripe */
{
const xxh_u8* const p = input + len - XXH_STRIPE_LEN;
#define XXH_SECRET_LASTACC_START \
7 /* not aligned on 8, last secret is different from acc & scrambler */
XXH3_accumulate_512_scalar(
acc, p,
secret + secretSize - XXH_STRIPE_LEN - XXH_SECRET_LASTACC_START);
}
}
}
XXH64_hash_t XXH3_hashLong_64b_internal(
const void* XXH_RESTRICT input,
size_t len,
const void* XXH_RESTRICT secret,
size_t secretSize
) {
XXH_ALIGN(XXH_ACC_ALIGN) xxh_u64 acc[XXH_ACC_NB] = XXH3_INIT_ACC;
XXH3_hashLong_internal_loop(acc, (const xxh_u8*)input, len,
(const xxh_u8*)secret, secretSize);
/* converge into final hash */
XXH_STATIC_ASSERT(sizeof(acc) == 64);
/* do not align on 8, so that the secret is different from the accumulator */
XXH_ASSERT(secretSize >= sizeof(acc) + XXH_SECRET_MERGEACCS_START);
return XXH3_mergeAccs(acc, (const xxh_u8*)secret + XXH_SECRET_MERGEACCS_START, (xxh_u64)len * XXH_PRIME64_1);
}
XXH64_hash_t XXH3_hashLong_64b_withSeed_internal(
const void* input, size_t len, XXH64_hash_t seed
) {
if (seed == 0)
return XXH3_hashLong_64b_internal(
input, len, XXH3_kSecret, sizeof(XXH3_kSecret)
);
{
XXH_ALIGN(XXH_SEC_ALIGN) xxh_u8 secret[XXH_SECRET_DEFAULT_SIZE];
XXH3_initCustomSecret_scalar(secret, seed);
return XXH3_hashLong_64b_internal(input, len, secret, sizeof(secret));
}
}
/*
* It's important for performance that XXH3_hashLong is not inlined.
*/
XXH64_hash_t XXH3_hashLong_64b_withSeed(
const void* input, size_t len,
XXH64_hash_t seed, const xxh_u8* secret,
size_t secretLen
) {
return XXH3_hashLong_64b_withSeed_internal(input, len, seed);
}
XXH64_hash_t XXH3_hashLong_64b_withSecret(
const void* XXH_RESTRICT input,
size_t len, XXH64_hash_t seed64,
const xxh_u8* XXH_RESTRICT secret,
size_t secretLen
) {
return XXH3_hashLong_64b_internal(input, len, secret, secretLen);
}
XXH64_hash_t XXH3_64bits_internal(
const void* XXH_RESTRICT input, size_t len,
XXH64_hash_t seed64,
const void* XXH_RESTRICT secret,
size_t secretLen,
XXH3_hashLong64_f f_hashLong
) {
XXH_ASSERT(secretLen >= XXH3_SECRET_SIZE_MIN);
/*
* If an action is to be taken if `secretLen` condition is not respected,
* it should be done here.
* For now, it's a contract pre-condition.
* Adding a check and a branch here would cost performance at every hash.
* Also, note that function signature doesn't offer room to return an error.
*/
if (len <= 16)
return XXH3_len_0to16_64b(
(const xxh_u8*)input, len, (const xxh_u8*)secret, seed64
);
if (len <= 128)
return XXH3_len_17to128_64b(
(const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64
);
if (len <= XXH3_MIDSIZE_MAX)
return XXH3_len_129to240_64b(
(const xxh_u8*)input, len, (const xxh_u8*)secret, secretLen, seed64
);
return f_hashLong(input, len, seed64, (const xxh_u8*)secret, secretLen);
}
XXH64_hash_t XXH3_64bits_withSeed(
const void* input, size_t len, XXH64_hash_t seed
) {
return XXH3_64bits_internal(input, len, seed, XXH3_kSecret, sizeof(XXH3_kSecret), XXH3_hashLong_64b_withSeed);
}
XXH64_hash_t XXH3_64bits_withSecret(
const void* input, size_t len, const void* secret, size_t secretSize
) {
return XXH3_64bits_internal(input, len, 0, secret, secretSize, XXH3_hashLong_64b_withSecret);
}
XXH64_hash_t XXH3_64bits_digest(const XXH3_state_t* state) {
const unsigned char* const secret =
(state->extSecret == NULL) ? state->customSecret : state->extSecret;
if (state->totalLen > XXH3_MIDSIZE_MAX) {
XXH_ALIGN(XXH_ACC_ALIGN) XXH64_hash_t acc[XXH_ACC_NB];
XXH3_digest_long(acc, state, secret);
return XXH3_mergeAccs(
acc, secret + XXH_SECRET_MERGEACCS_START,
(xxh_u64)state->totalLen * XXH_PRIME64_1
);
}
/* totalLen <= XXH3_MIDSIZE_MAX: digesting a short input */
if (state->seed)
return XXH3_64bits_withSeed(
state->buffer, (size_t)state->totalLen, state->seed
);
return XXH3_64bits_withSecret(
state->buffer, (size_t)(state->totalLen),
secret, state->secretLimit + XXH_STRIPE_LEN
);
}
static struct XXH3_state_s sctx;
XXH3_state_t* state = &sctx;
WASM_EXPORT
void Hash_Init() {
// seed is at the memory object
uint64_t seed = *((uint64_t*)main_buffer);
XXH3_64bits_reset_withSeed(state, seed);
}
WASM_EXPORT
void Hash_Update(uint32_t length) {
const void* input = main_buffer;
XXH3_64bits_update(state, input, length);
}
WASM_EXPORT
void Hash_Final() {
XXH64_hash_t const result = XXH_swap64(XXH3_64bits_digest(state));
memcpy64(main_buffer, &result);
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(sctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*)&sctx;
}
WASM_EXPORT
void Hash_Calculate() {
return;
}
@@ -0,0 +1,14 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* 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 declare function adler32(data: IDataType): Promise<string>;
/**
* Creates a new Adler-32 hash instance
*/
export declare function createAdler32(): Promise<IHasher>;
@@ -0,0 +1,243 @@
/**
* Whirlpool hash in C
*
* Copyright (c) 2017 Project Nayuki. (MIT License)
* https://www.nayuki.io/page/fast-whirlpool-hash-in-x86-assembly
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*
* Modified for hash-wasm by Dani Biró
*/
#define WITH_BUFFER
#include "hash-wasm.h"
#define BLOCK_LEN 64 // In bytes
#define STATE_LEN 64 // In bytes
struct Whirlpool_CTX {
uint8_t buffer[BLOCK_LEN]; /* buffer of data to hash */
uint8_t hash[STATE_LEN]; /* the hashing state */
uint32_t rem;
uint64_t totalBytes;
};
static struct Whirlpool_CTX sctx;
static void whirlpool_round(uint64_t block[static 8], const uint64_t key[static 8]);
static uint64_t ROUND_CONSTANTS[32] = {
UINT64_C(0x4F01B887E8C62318), UINT64_C(0x52916F79F5D2A636), UINT64_C(0x357B0CA38E9BBC60), UINT64_C(0x57FE4B2EC2D7E01D),
UINT64_C(0xDA4AF09FE5377715), UINT64_C(0x856BA0B10A29C958), UINT64_C(0x67053ECBF4105DBD), UINT64_C(0xD8957DA78B4127E4),
UINT64_C(0x9E4717DD667CEEFB), UINT64_C(0x33835AAD07BF2DCA), UINT64_C(0xD94919C871AA0263), UINT64_C(0xB032269A885BE3F2),
UINT64_C(0x4834CDBE80D50FE9), UINT64_C(0xAE1A68205F907AFF), UINT64_C(0x1273F164229354B4), UINT64_C(0x3D8DA1DBECC30840),
UINT64_C(0x1BD682762BCF0097), UINT64_C(0xEF30F345506AAFB5), UINT64_C(0xC02FBA65EAA2553F), UINT64_C(0x8A0675924DFD1CDE),
UINT64_C(0x96A8D4621F0EE6B2), UINT64_C(0x4C3972845925C5F9), UINT64_C(0x61E2A5D18C38785E), UINT64_C(0x04FCC7431E9C21B3),
UINT64_C(0x247EDFFA0D6D9951), UINT64_C(0xEBB74E8F11CEAB3B), UINT64_C(0xD32C13B9F794813C), UINT64_C(0xA97F445603C46EE7),
UINT64_C(0x6C9D0BDC53C1BB2A), UINT64_C(0xE11489AC46F67431), UINT64_C(0xEDD0B67009693A16), UINT64_C(0x86F85C28A49842CC),
};
// Temporary state that doesn't need to be preserved between calls to _compress()
uint64_t tempState[8];
uint64_t tempBlock[8];
uint64_t rcon[8] = {0};
void whirlpool_compress(uint8_t state[static 64], const uint8_t block[static 64]) {
const int NUM_ROUNDS = 10; // Any number from 0 to 32 is allowed
// Initialization
#pragma clang loop unroll(full)
for (int i = 0; i < 8; i++) {
int j = i << 3;
uint64_t x = *(uint64_t*)(state + j);
uint64_t y = *(uint64_t*)(block + j);
tempState[i] = x;
tempBlock[i] = x ^ y;
}
// Hashing rounds
#pragma clang loop unroll(full)
for (int i = 0; i < NUM_ROUNDS; i++) {
rcon[0] = ROUND_CONSTANTS[i];
whirlpool_round(tempState, rcon);
whirlpool_round(tempBlock, tempState);
}
// Final combining
#pragma clang loop unroll(full)
for (uint8_t i = 0; i < 8; i++) {
((uint64_t*)state)[i] ^= ((uint64_t*)block)[i] ^ tempBlock[i];
}
}
// The combined effect of gamma (SubBytes) and theta (MixRows)
static uint64_t MAGIC_TABLE[256] = {
UINT64_C(0xD83078C018601818), UINT64_C(0x2646AF05238C2323), UINT64_C(0xB891F97EC63FC6C6), UINT64_C(0xFBCD6F13E887E8E8), UINT64_C(0xCB13A14C87268787), UINT64_C(0x116D62A9B8DAB8B8), UINT64_C(0x0902050801040101), UINT64_C(0x0D9E6E424F214F4F),
UINT64_C(0x9B6CEEAD36D83636), UINT64_C(0xFF510459A6A2A6A6), UINT64_C(0x0CB9BDDED26FD2D2), UINT64_C(0x0EF706FBF5F3F5F5), UINT64_C(0x96F280EF79F97979), UINT64_C(0x30DECE5F6FA16F6F), UINT64_C(0x6D3FEFFC917E9191), UINT64_C(0xF8A407AA52555252),
UINT64_C(0x47C0FD27609D6060), UINT64_C(0x35657689BCCABCBC), UINT64_C(0x372BCDAC9B569B9B), UINT64_C(0x8A018C048E028E8E), UINT64_C(0xD25B1571A3B6A3A3), UINT64_C(0x6C183C600C300C0C), UINT64_C(0x84F68AFF7BF17B7B), UINT64_C(0x806AE1B535D43535),
UINT64_C(0xF53A69E81D741D1D), UINT64_C(0xB3DD4753E0A7E0E0), UINT64_C(0x21B3ACF6D77BD7D7), UINT64_C(0x9C99ED5EC22FC2C2), UINT64_C(0x435C966D2EB82E2E), UINT64_C(0x29967A624B314B4B), UINT64_C(0x5DE121A3FEDFFEFE), UINT64_C(0xD5AE168257415757),
UINT64_C(0xBD2A41A815541515), UINT64_C(0xE8EEB69F77C17777), UINT64_C(0x926EEBA537DC3737), UINT64_C(0x9ED7567BE5B3E5E5), UINT64_C(0x1323D98C9F469F9F), UINT64_C(0x23FD17D3F0E7F0F0), UINT64_C(0x20947F6A4A354A4A), UINT64_C(0x44A9959EDA4FDADA),
UINT64_C(0xA2B025FA587D5858), UINT64_C(0xCF8FCA06C903C9C9), UINT64_C(0x7C528D5529A42929), UINT64_C(0x5A1422500A280A0A), UINT64_C(0x507F4FE1B1FEB1B1), UINT64_C(0xC95D1A69A0BAA0A0), UINT64_C(0x14D6DA7F6BB16B6B), UINT64_C(0xD917AB5C852E8585),
UINT64_C(0x3C677381BDCEBDBD), UINT64_C(0x8FBA34D25D695D5D), UINT64_C(0x9020508010401010), UINT64_C(0x07F503F3F4F7F4F4), UINT64_C(0xDD8BC016CB0BCBCB), UINT64_C(0xD37CC6ED3EF83E3E), UINT64_C(0x2D0A112805140505), UINT64_C(0x78CEE61F67816767),
UINT64_C(0x97D55373E4B7E4E4), UINT64_C(0x024EBB25279C2727), UINT64_C(0x7382583241194141), UINT64_C(0xA70B9D2C8B168B8B), UINT64_C(0xF6530151A7A6A7A7), UINT64_C(0xB2FA94CF7DE97D7D), UINT64_C(0x4937FBDC956E9595), UINT64_C(0x56AD9F8ED847D8D8),
UINT64_C(0x70EB308BFBCBFBFB), UINT64_C(0xCDC17123EE9FEEEE), UINT64_C(0xBBF891C77CED7C7C), UINT64_C(0x71CCE31766856666), UINT64_C(0x7BA78EA6DD53DDDD), UINT64_C(0xAF2E4BB8175C1717), UINT64_C(0x458E460247014747), UINT64_C(0x1A21DC849E429E9E),
UINT64_C(0xD489C51ECA0FCACA), UINT64_C(0x585A99752DB42D2D), UINT64_C(0x2E637991BFC6BFBF), UINT64_C(0x3F0E1B38071C0707), UINT64_C(0xAC472301AD8EADAD), UINT64_C(0xB0B42FEA5A755A5A), UINT64_C(0xEF1BB56C83368383), UINT64_C(0xB666FF8533CC3333),
UINT64_C(0x5CC6F23F63916363), UINT64_C(0x12040A1002080202), UINT64_C(0x93493839AA92AAAA), UINT64_C(0xDEE2A8AF71D97171), UINT64_C(0xC68DCF0EC807C8C8), UINT64_C(0xD1327DC819641919), UINT64_C(0x3B92707249394949), UINT64_C(0x5FAF9A86D943D9D9),
UINT64_C(0x31F91DC3F2EFF2F2), UINT64_C(0xA8DB484BE3ABE3E3), UINT64_C(0xB9B62AE25B715B5B), UINT64_C(0xBC0D9234881A8888), UINT64_C(0x3E29C8A49A529A9A), UINT64_C(0x0B4CBE2D26982626), UINT64_C(0xBF64FA8D32C83232), UINT64_C(0x597D4AE9B0FAB0B0),
UINT64_C(0xF2CF6A1BE983E9E9), UINT64_C(0x771E33780F3C0F0F), UINT64_C(0x33B7A6E6D573D5D5), UINT64_C(0xF41DBA74803A8080), UINT64_C(0x27617C99BEC2BEBE), UINT64_C(0xEB87DE26CD13CDCD), UINT64_C(0x8968E4BD34D03434), UINT64_C(0x3290757A483D4848),
UINT64_C(0x54E324ABFFDBFFFF), UINT64_C(0x8DF48FF77AF57A7A), UINT64_C(0x643DEAF4907A9090), UINT64_C(0x9DBE3EC25F615F5F), UINT64_C(0x3D40A01D20802020), UINT64_C(0x0FD0D56768BD6868), UINT64_C(0xCA3472D01A681A1A), UINT64_C(0xB7412C19AE82AEAE),
UINT64_C(0x7D755EC9B4EAB4B4), UINT64_C(0xCEA8199A544D5454), UINT64_C(0x7F3BE5EC93769393), UINT64_C(0x2F44AA0D22882222), UINT64_C(0x63C8E907648D6464), UINT64_C(0x2AFF12DBF1E3F1F1), UINT64_C(0xCCE6A2BF73D17373), UINT64_C(0x82245A9012481212),
UINT64_C(0x7A805D3A401D4040), UINT64_C(0x4810284008200808), UINT64_C(0x959BE856C32BC3C3), UINT64_C(0xDFC57B33EC97ECEC), UINT64_C(0x4DAB9096DB4BDBDB), UINT64_C(0xC05F1F61A1BEA1A1), UINT64_C(0x9107831C8D0E8D8D), UINT64_C(0xC87AC9F53DF43D3D),
UINT64_C(0x5B33F1CC97669797), UINT64_C(0x0000000000000000), UINT64_C(0xF983D436CF1BCFCF), UINT64_C(0x6E5687452BAC2B2B), UINT64_C(0xE1ECB39776C57676), UINT64_C(0xE619B06482328282), UINT64_C(0x28B1A9FED67FD6D6), UINT64_C(0xC33677D81B6C1B1B),
UINT64_C(0x74775BC1B5EEB5B5), UINT64_C(0xBE432911AF86AFAF), UINT64_C(0x1DD4DF776AB56A6A), UINT64_C(0xEAA00DBA505D5050), UINT64_C(0x578A4C1245094545), UINT64_C(0x38FB18CBF3EBF3F3), UINT64_C(0xAD60F09D30C03030), UINT64_C(0xC4C3742BEF9BEFEF),
UINT64_C(0xDA7EC3E53FFC3F3F), UINT64_C(0xC7AA1C9255495555), UINT64_C(0xDB591079A2B2A2A2), UINT64_C(0xE9C96503EA8FEAEA), UINT64_C(0x6ACAEC0F65896565), UINT64_C(0x036968B9BAD2BABA), UINT64_C(0x4A5E93652FBC2F2F), UINT64_C(0x8E9DE74EC027C0C0),
UINT64_C(0x60A181BEDE5FDEDE), UINT64_C(0xFC386CE01C701C1C), UINT64_C(0x46E72EBBFDD3FDFD), UINT64_C(0x1F9A64524D294D4D), UINT64_C(0x7639E0E492729292), UINT64_C(0xFAEABC8F75C97575), UINT64_C(0x360C1E3006180606), UINT64_C(0xAE0998248A128A8A),
UINT64_C(0x4B7940F9B2F2B2B2), UINT64_C(0x85D15963E6BFE6E6), UINT64_C(0x7E1C36700E380E0E), UINT64_C(0xE73E63F81F7C1F1F), UINT64_C(0x55C4F73762956262), UINT64_C(0x3AB5A3EED477D4D4), UINT64_C(0x814D3229A89AA8A8), UINT64_C(0x5231F4C496629696),
UINT64_C(0x62EF3A9BF9C3F9F9), UINT64_C(0xA397F666C533C5C5), UINT64_C(0x104AB13525942525), UINT64_C(0xABB220F259795959), UINT64_C(0xD015AE54842A8484), UINT64_C(0xC5E4A7B772D57272), UINT64_C(0xEC72DDD539E43939), UINT64_C(0x1698615A4C2D4C4C),
UINT64_C(0x94BC3BCA5E655E5E), UINT64_C(0x9FF085E778FD7878), UINT64_C(0xE570D8DD38E03838), UINT64_C(0x980586148C0A8C8C), UINT64_C(0x17BFB2C6D163D1D1), UINT64_C(0xE4570B41A5AEA5A5), UINT64_C(0xA1D94D43E2AFE2E2), UINT64_C(0x4EC2F82F61996161),
UINT64_C(0x427B45F1B3F6B3B3), UINT64_C(0x3442A51521842121), UINT64_C(0x0825D6949C4A9C9C), UINT64_C(0xEE3C66F01E781E1E), UINT64_C(0x6186522243114343), UINT64_C(0xB193FC76C73BC7C7), UINT64_C(0x4FE52BB3FCD7FCFC), UINT64_C(0x2408142004100404),
UINT64_C(0xE3A208B251595151), UINT64_C(0x252FC7BC995E9999), UINT64_C(0x22DAC44F6DA96D6D), UINT64_C(0x651A39680D340D0D), UINT64_C(0x79E93583FACFFAFA), UINT64_C(0x69A384B6DF5BDFDF), UINT64_C(0xA9FC9BD77EE57E7E), UINT64_C(0x1948B43D24902424),
UINT64_C(0xFE76D7C53BEC3B3B), UINT64_C(0x9A4B3D31AB96ABAB), UINT64_C(0xF081D13ECE1FCECE), UINT64_C(0x9922558811441111), UINT64_C(0x8303890C8F068F8F), UINT64_C(0x049C6B4A4E254E4E), UINT64_C(0x667351D1B7E6B7B7), UINT64_C(0xE0CB600BEB8BEBEB),
UINT64_C(0xC178CCFD3CF03C3C), UINT64_C(0xFD1FBF7C813E8181), UINT64_C(0x4035FED4946A9494), UINT64_C(0x1CF30CEBF7FBF7F7), UINT64_C(0x186F67A1B9DEB9B9), UINT64_C(0x8B265F98134C1313), UINT64_C(0x51589C7D2CB02C2C), UINT64_C(0x05BBB8D6D36BD3D3),
UINT64_C(0x8CD35C6BE7BBE7E7), UINT64_C(0x39DCCB576EA56E6E), UINT64_C(0xAA95F36EC437C4C4), UINT64_C(0x1B060F18030C0303), UINT64_C(0xDCAC138A56455656), UINT64_C(0x5E88491A440D4444), UINT64_C(0xA0FE9EDF7FE17F7F), UINT64_C(0x884F3721A99EA9A9),
UINT64_C(0x6754824D2AA82A2A), UINT64_C(0x0A6B6DB1BBD6BBBB), UINT64_C(0x879FE246C123C1C1), UINT64_C(0xF1A602A253515353), UINT64_C(0x72A58BAEDC57DCDC), UINT64_C(0x531627580B2C0B0B), UINT64_C(0x0127D39C9D4E9D9D), UINT64_C(0x2BD8C1476CAD6C6C),
UINT64_C(0xA462F59531C43131), UINT64_C(0xF3E8B98774CD7474), UINT64_C(0x15F109E3F6FFF6F6), UINT64_C(0x4C8C430A46054646), UINT64_C(0xA5452609AC8AACAC), UINT64_C(0xB50F973C891E8989), UINT64_C(0xB42844A014501414), UINT64_C(0xBADF425BE1A3E1E1),
UINT64_C(0xA62C4EB016581616), UINT64_C(0xF774D2CD3AE83A3A), UINT64_C(0x06D2D06F69B96969), UINT64_C(0x41122D4809240909), UINT64_C(0xD7E0ADA770DD7070), UINT64_C(0x6F7154D9B6E2B6B6), UINT64_C(0x1EBDB7CED067D0D0), UINT64_C(0xD6C77E3BED93EDED),
UINT64_C(0xE285DB2ECC17CCCC), UINT64_C(0x6884572A42154242), UINT64_C(0x2C2DC2B4985A9898), UINT64_C(0xED550E49A4AAA4A4), UINT64_C(0x7550885D28A02828), UINT64_C(0x86B831DA5C6D5C5C), UINT64_C(0x6BED3F93F8C7F8F8), UINT64_C(0xC211A44486228686),
};
static void whirlpool_round(uint64_t block[static 8], const uint64_t key[static 8]) {
uint64_t a = block[0];
uint64_t b = block[1];
uint64_t c = block[2];
uint64_t d = block[3];
uint64_t e = block[4];
uint64_t f = block[5];
uint64_t g = block[6];
uint64_t h = block[7];
uint64_t r;
#define ROTR64(x, n) (((0U + (x)) << (64 - (n))) | ((x) >> (n))) // Assumes that x is uint64_t and 0 < n < 64
#define DOROW(i, s, t, u, v, w, x, y, z) \
r = MAGIC_TABLE[(uint8_t)s]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(t >> 8)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(u >> 16)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(v >> 24)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(w >> 32)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(x >> 40)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(y >> 48)]; r = ROTR64(r, 8); \
r ^= MAGIC_TABLE[(uint8_t)(z >> 56)]; r = ROTR64(r, 8); \
block[i] = r ^ key[i];
DOROW(0, a, h, g, f, e, d, c, b)
DOROW(1, b, a, h, g, f, e, d, c)
DOROW(2, c, b, a, h, g, f, e, d)
DOROW(3, d, c, b, a, h, g, f, e)
DOROW(4, e, d, c, b, a, h, g, f)
DOROW(5, f, e, d, c, b, a, h, g)
DOROW(6, g, f, e, d, c, b, a, h)
DOROW(7, h, g, f, e, d, c, b, a)
}
WASM_EXPORT
void Hash_Init() {
for (uint32_t i = 0; i < 64; i+=8) {
*(uint64_t*)(sctx.hash + i) = 0;
}
sctx.totalBytes = 0;
sctx.rem = 0;
}
inline uint32_t min(uint32_t a, uint32_t b) {
if (a < b) return a;
return b;
}
WASM_EXPORT
void Hash_Update(uint32_t len) {
sctx.totalBytes += len;
uint32_t read = 0;
if (sctx.rem > 0) {
uint32_t end = min(64, sctx.rem + len);
for (uint8_t z = sctx.rem; z < end; z++) {
sctx.buffer[z] = main_buffer[read++];
}
if (end == 64) {
whirlpool_compress(sctx.hash, sctx.buffer);
sctx.rem = 0;
} else {
sctx.rem = end;
}
}
while (len - read >= 64) {
whirlpool_compress(sctx.hash, &main_buffer[read]);
read += 64;
}
if (len - read > 0) {
sctx.rem = len - read;
for (uint8_t z = 0; z < sctx.rem; z++) {
sctx.buffer[z] = main_buffer[read + z];
}
}
}
WASM_EXPORT
void Hash_Final() {
const int LENGTH_SIZE = 32;
uint8_t temp[64] = {0};
for (uint8_t i = 0; i < sctx.rem; i++) {
temp[i] = sctx.buffer[i];
}
temp[sctx.rem] = 0x80;
sctx.rem++;
if (BLOCK_LEN - sctx.rem < LENGTH_SIZE) {
whirlpool_compress(sctx.hash, temp);
for (uint32_t i = 0; i < 32; i+=8) {
*(uint64_t*)(temp + i) = 0;
}
}
temp[BLOCK_LEN - 1] = (uint8_t)((sctx.totalBytes & 0x1FU) << 3);
sctx.totalBytes >>= 5;
for (int i = 1; i < LENGTH_SIZE; i++, sctx.totalBytes >>= 8) {
temp[BLOCK_LEN - 1 - i] = (uint8_t)(sctx.totalBytes & 0xFFU);
}
whirlpool_compress(sctx.hash, temp);
for (uint32_t i = 0; i < 64; i+=8) {
*(uint64_t*)(main_buffer + i) = *(uint64_t*)(sctx.hash + i);
}
}
WASM_EXPORT
const uint32_t STATE_SIZE = sizeof(sctx);
WASM_EXPORT
uint8_t* Hash_GetState() {
return (uint8_t*) &sctx;
}
WASM_EXPORT
void Hash_Calculate(uint32_t length) {
Hash_Init();
Hash_Update(length);
Hash_Final();
}
@@ -0,0 +1,12 @@
import { type IHasher } from "./WASMInterface";
import type { IDataType } from "./util";
/**
* Calculates SM3 hash
* @param data Input data (string, Buffer or TypedArray)
* @returns Computed hash as a hexadecimal string
*/
export declare function sm3(data: IDataType): Promise<string>;
/**
* Creates a new SM3 hash instance
*/
export declare function createSM3(): Promise<IHasher>;

Some files were not shown because too many files have changed in this diff Show More