├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── biome.json ├── jest.config.js ├── lib ├── WASMInterface.ts ├── adler32.ts ├── argon2.ts ├── bcrypt.ts ├── blake2b.ts ├── blake2s.ts ├── blake3.ts ├── crc32.ts ├── crc64.ts ├── hmac.ts ├── index.ts ├── keccak.ts ├── lockedCreate.ts ├── md4.ts ├── md5.ts ├── mutex.ts ├── pbkdf2.ts ├── ripemd160.ts ├── scrypt.ts ├── sha1.ts ├── sha224.ts ├── sha256.ts ├── sha3.ts ├── sha384.ts ├── sha512.ts ├── sm3.ts ├── util.ts ├── whirlpool.ts ├── xxhash128.ts ├── xxhash3.ts ├── xxhash32.ts └── xxhash64.ts ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── scripts ├── Dockerfile ├── Makefile-clang ├── build.sh ├── make_json.js └── optimize.js ├── src ├── adler32.c ├── argon2.c ├── bcrypt.c ├── blake2b.c ├── blake2s.c ├── blake3.c ├── crc32.c ├── crc64.c ├── hash-wasm.h ├── md4.c ├── md5.c ├── ripemd160.c ├── scrypt.c ├── sha1.c ├── sha256.c ├── sha3.c ├── sha512.c ├── sm3.c ├── whirlpool.c ├── xxhash128.c ├── xxhash3.c ├── xxhash32.c └── xxhash64.c ├── test ├── adler32.test.ts ├── api.test.ts ├── argon2.test.ts ├── async.test.ts ├── base.test.ts ├── base64.test.ts ├── bcrypt.test.ts ├── blake2b-128.test.ts ├── blake2b-256.test.ts ├── blake2b-384.test.ts ├── blake2b-512.test.ts ├── blake2b-keys.test.ts ├── blake2b.test.ts ├── blake2s-128.test.ts ├── blake2s-256.test.ts ├── blake2s-keys.test.ts ├── blake2s.test.ts ├── blake3-256.test.ts ├── blake3-512.test.ts ├── blake3-keys.test.ts ├── blake3.test.ts ├── browser.test.ts ├── crc32.test.ts ├── crc32c.test.ts ├── crc64.test.ts ├── hmac.test.ts ├── keccak-224.test.ts ├── keccak-256.test.ts ├── keccak-384.test.ts ├── keccak-512.test.ts ├── keccak.test.ts ├── md4.test.ts ├── md5.test.ts ├── node │ └── index.js ├── pbkdf2.test.ts ├── ripemd160.test.ts ├── scrypt.test.ts ├── sha1.test.ts ├── sha224.test.ts ├── sha256.test.ts ├── sha3-224.test.ts ├── sha3-256.test.ts ├── sha3-384.test.ts ├── sha3-512.test.ts ├── sha3.test.ts ├── sha384.test.ts ├── sha512.test.ts ├── sm3.test.ts ├── throw.test.ts ├── utf8.txt ├── util.ts ├── whirlpool.test.ts ├── xxhash128.test.ts ├── xxhash3.test.ts ├── xxhash32.test.ts └── xxhash64.test.ts └── tsconfig.json /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build & publish 5 | on: 6 | push: 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [22.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build 26 | - run: npm test && bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }} 27 | - run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc 28 | - run: npm publish 29 | env: 30 | CI: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Coverage directory used by tools like istanbul 7 | coverage 8 | 9 | # Dependency directories 10 | node_modules/ 11 | 12 | # Optional npm cache directory 13 | .npm 14 | 15 | # Optional eslint cache 16 | .eslintcache 17 | 18 | # Output of 'npm pack' 19 | *.tgz 20 | 21 | # dotenv environment variables file 22 | .env 23 | 24 | dist 25 | 26 | .jest-cache 27 | 28 | wasm 29 | .bench 30 | 31 | docs 32 | benchmark*.js 33 | 34 | test.html 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Coverage directory used by tools like istanbul 7 | coverage 8 | 9 | # Dependency directories 10 | node_modules/ 11 | 12 | # Optional npm cache directory 13 | .npm 14 | 15 | # Optional eslint cache 16 | .eslintcache 17 | 18 | # Output of 'npm pack' 19 | *.tgz 20 | 21 | # dotenv environment variables file 22 | .env 23 | 24 | dist 25 | 26 | .jest-cache 27 | 28 | wasm 29 | 30 | benchmark 31 | test 32 | scripts 33 | .github 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dani Biró 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Embedded C implementations might use other, similarly permissive licenses. 24 | Check the beginning of the files from the /src directory. 25 | 26 | Special thank you to the authors of original C algorithms: 27 | - Alexander Peslyak 28 | - Aleksey Kravchenko 29 | - Colin Percival 30 | - Stephan Brumme 31 | - Steve Reid 32 | - Samuel Neves 33 | - Solar Designer 34 | - Project Nayuki 35 | - ARM Limited 36 | - Yanbo Li dreamfly281@gmail.com, goldboar@163.comYanbo Li 37 | - Mark Adler 38 | - Yann Collet 39 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true 23 | } 24 | }, 25 | "javascript": { 26 | "formatter": { 27 | "quoteStyle": "double" 28 | } 29 | }, 30 | "overrides": [ 31 | { 32 | "include": ["test/**"], 33 | "linter": { 34 | "rules": { 35 | "suspicious": { 36 | "noExplicitAny": "off" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [ 3 | '/test', 4 | ], 5 | transform: { 6 | '^.+\\.ts$': 'ts-jest', 7 | }, 8 | cacheDirectory: '/.jest-cache', 9 | }; 10 | -------------------------------------------------------------------------------- /lib/adler32.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/adler32.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates Adler-32 hash. The resulting 32-bit hash is stored in 16 | * network byte order (big-endian). 17 | * 18 | * @param data Input data (string, Buffer or TypedArray) 19 | * @returns Computed hash as a hexadecimal string 20 | */ 21 | export function adler32(data: IDataType): Promise { 22 | if (wasmCache === null) { 23 | return lockedCreate(mutex, wasmJson, 4).then((wasm) => { 24 | wasmCache = wasm; 25 | return wasmCache.calculate(data); 26 | }); 27 | } 28 | 29 | try { 30 | const hash = wasmCache.calculate(data); 31 | return Promise.resolve(hash); 32 | } catch (err) { 33 | return Promise.reject(err); 34 | } 35 | } 36 | 37 | /** 38 | * Creates a new Adler-32 hash instance 39 | */ 40 | export function createAdler32(): Promise { 41 | return WASMInterface(wasmJson, 4).then((wasm) => { 42 | wasm.init(); 43 | const obj: IHasher = { 44 | init: () => { 45 | wasm.init(); 46 | return obj; 47 | }, 48 | update: (data) => { 49 | wasm.update(data); 50 | return obj; 51 | }, 52 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 53 | digest: (outputType) => wasm.digest(outputType) as any, 54 | save: () => wasm.save(), 55 | load: (data) => { 56 | wasm.load(data); 57 | return obj; 58 | }, 59 | blockSize: 4, 60 | digestSize: 4, 61 | }; 62 | return obj; 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /lib/bcrypt.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/bcrypt.wasm.json"; 2 | import { WASMInterface } from "./WASMInterface"; 3 | import { 4 | type IDataType, 5 | getDigestHex, 6 | getUInt8Buffer, 7 | intArrayToString, 8 | } from "./util"; 9 | 10 | export interface BcryptOptions { 11 | /** 12 | * Password to be hashed 13 | */ 14 | password: IDataType; 15 | /** 16 | * Salt (16 bytes long - usually containing random bytes) 17 | */ 18 | salt: IDataType; 19 | /** 20 | * Number of iterations to perform (4 - 31) 21 | */ 22 | costFactor: number; 23 | /** 24 | * Desired output type. Defaults to 'encoded' 25 | */ 26 | outputType?: "hex" | "binary" | "encoded"; 27 | } 28 | 29 | async function bcryptInternal( 30 | options: BcryptOptions, 31 | ): Promise { 32 | const { costFactor, password, salt } = options; 33 | 34 | const bcryptInterface = await WASMInterface(wasmJson, 0); 35 | bcryptInterface.writeMemory(getUInt8Buffer(salt), 0); 36 | const passwordBuffer = getUInt8Buffer(password); 37 | bcryptInterface.writeMemory(passwordBuffer, 16); 38 | const shouldEncode = options.outputType === "encoded" ? 1 : 0; 39 | bcryptInterface 40 | .getExports() 41 | .bcrypt(passwordBuffer.length, costFactor, shouldEncode); 42 | const memory = bcryptInterface.getMemory(); 43 | 44 | if (options.outputType === "encoded") { 45 | return intArrayToString(memory, 60); 46 | } 47 | 48 | if (options.outputType === "hex") { 49 | const digestChars = new Uint8Array(24 * 2); 50 | return getDigestHex(digestChars, memory, 24); 51 | } 52 | 53 | // return binary format 54 | // the data is copied to allow GC of the original memory buffer 55 | return memory.slice(0, 24); 56 | } 57 | 58 | const validateOptions = (options: BcryptOptions) => { 59 | if (!options || typeof options !== "object") { 60 | throw new Error("Invalid options parameter. It requires an object."); 61 | } 62 | 63 | if ( 64 | !Number.isInteger(options.costFactor) || 65 | options.costFactor < 4 || 66 | options.costFactor > 31 67 | ) { 68 | throw new Error("Cost factor should be a number between 4 and 31"); 69 | } 70 | 71 | options.password = getUInt8Buffer(options.password); 72 | if (options.password.length < 1) { 73 | throw new Error("Password should be at least 1 byte long"); 74 | } 75 | 76 | if (options.password.length > 72) { 77 | throw new Error("Password should be at most 72 bytes long"); 78 | } 79 | 80 | options.salt = getUInt8Buffer(options.salt); 81 | if (options.salt.length !== 16) { 82 | throw new Error("Salt should be 16 bytes long"); 83 | } 84 | 85 | if (options.outputType === undefined) { 86 | options.outputType = "encoded"; 87 | } 88 | 89 | if (!["hex", "binary", "encoded"].includes(options.outputType)) { 90 | throw new Error( 91 | `Insupported output type ${options.outputType}. Valid values: ['hex', 'binary', 'encoded']`, 92 | ); 93 | } 94 | }; 95 | 96 | interface IBcryptOptionsBinary { 97 | outputType: "binary"; 98 | } 99 | 100 | type BcryptReturnType = T extends IBcryptOptionsBinary ? Uint8Array : string; 101 | 102 | /** 103 | * Calculates hash using the bcrypt password-hashing function 104 | * @returns Computed hash 105 | */ 106 | export async function bcrypt( 107 | options: T, 108 | ): Promise> { 109 | validateOptions(options); 110 | 111 | return bcryptInternal(options) as Promise>; 112 | } 113 | 114 | export interface BcryptVerifyOptions { 115 | /** 116 | * Password to be verified 117 | */ 118 | password: IDataType; 119 | /** 120 | * A previously generated bcrypt hash in the 'encoded' output format 121 | */ 122 | hash: string; 123 | } 124 | 125 | const validateHashCharacters = (hash: string): boolean => { 126 | if (!/^\$2[axyb]\$[0-3][0-9]\$[./A-Za-z0-9]{53}$/.test(hash)) { 127 | return false; 128 | } 129 | 130 | if (hash[4] === "0" && Number(hash[5]) < 4) { 131 | return false; 132 | } 133 | 134 | if (hash[4] === "3" && Number(hash[5]) > 1) { 135 | return false; 136 | } 137 | 138 | return true; 139 | }; 140 | 141 | const validateVerifyOptions = (options: BcryptVerifyOptions) => { 142 | if (!options || typeof options !== "object") { 143 | throw new Error("Invalid options parameter. It requires an object."); 144 | } 145 | 146 | if (options.hash === undefined || typeof options.hash !== "string") { 147 | throw new Error("Hash should be specified"); 148 | } 149 | 150 | if (options.hash.length !== 60) { 151 | throw new Error("Hash should be 60 bytes long"); 152 | } 153 | 154 | if (!validateHashCharacters(options.hash)) { 155 | throw new Error("Invalid hash"); 156 | } 157 | 158 | options.password = getUInt8Buffer(options.password); 159 | if (options.password.length < 1) { 160 | throw new Error("Password should be at least 1 byte long"); 161 | } 162 | 163 | if (options.password.length > 72) { 164 | throw new Error("Password should be at most 72 bytes long"); 165 | } 166 | }; 167 | 168 | /** 169 | * Verifies password using bcrypt password-hashing function 170 | * @returns True if the encoded hash matches the password 171 | */ 172 | export async function bcryptVerify( 173 | options: BcryptVerifyOptions, 174 | ): Promise { 175 | validateVerifyOptions(options); 176 | 177 | const { hash, password } = options; 178 | 179 | const bcryptInterface = await WASMInterface(wasmJson, 0); 180 | bcryptInterface.writeMemory(getUInt8Buffer(hash), 0); 181 | 182 | const passwordBuffer = getUInt8Buffer(password); 183 | bcryptInterface.writeMemory(passwordBuffer, 60); 184 | 185 | return !!bcryptInterface.getExports().bcrypt_verify(passwordBuffer.length); 186 | } 187 | -------------------------------------------------------------------------------- /lib/blake2b.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/blake2b.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import { type IDataType, getUInt8Buffer } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | function validateBits(bits: number) { 15 | if (!Number.isInteger(bits) || bits < 8 || bits > 512 || bits % 8 !== 0) { 16 | return new Error("Invalid variant! Valid values: 8, 16, ..., 512"); 17 | } 18 | return null; 19 | } 20 | 21 | function getInitParam(outputBits, keyBits) { 22 | return outputBits | (keyBits << 16); 23 | } 24 | 25 | /** 26 | * Calculates BLAKE2b hash 27 | * @param data Input data (string, Buffer or TypedArray) 28 | * @param bits Number of output bits, which has to be a number 29 | * divisible by 8, between 8 and 512. Defaults to 512. 30 | * @param key Optional key (string, Buffer or TypedArray). Maximum length is 64 bytes. 31 | * @returns Computed hash as a hexadecimal string 32 | */ 33 | export function blake2b( 34 | data: IDataType, 35 | bits = 512, 36 | key: IDataType = null, 37 | ): Promise { 38 | if (validateBits(bits)) { 39 | return Promise.reject(validateBits(bits)); 40 | } 41 | 42 | let keyBuffer = null; 43 | let initParam = bits; 44 | if (key !== null) { 45 | keyBuffer = getUInt8Buffer(key); 46 | if (keyBuffer.length > 64) { 47 | return Promise.reject(new Error("Max key length is 64 bytes")); 48 | } 49 | initParam = getInitParam(bits, keyBuffer.length); 50 | } 51 | 52 | const hashLength = bits / 8; 53 | 54 | if (wasmCache === null || wasmCache.hashLength !== hashLength) { 55 | return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { 56 | wasmCache = wasm; 57 | if (initParam > 512) { 58 | wasmCache.writeMemory(keyBuffer); 59 | } 60 | return wasmCache.calculate(data, initParam); 61 | }); 62 | } 63 | 64 | try { 65 | if (initParam > 512) { 66 | wasmCache.writeMemory(keyBuffer); 67 | } 68 | const hash = wasmCache.calculate(data, initParam); 69 | return Promise.resolve(hash); 70 | } catch (err) { 71 | return Promise.reject(err); 72 | } 73 | } 74 | 75 | /** 76 | * Creates a new BLAKE2b hash instance 77 | * @param bits Number of output bits, which has to be a number 78 | * divisible by 8, between 8 and 512. Defaults to 512. 79 | * @param key Optional key (string, Buffer or TypedArray). Maximum length is 64 bytes. 80 | */ 81 | export function createBLAKE2b( 82 | bits = 512, 83 | key: IDataType = null, 84 | ): Promise { 85 | if (validateBits(bits)) { 86 | return Promise.reject(validateBits(bits)); 87 | } 88 | 89 | let keyBuffer = null; 90 | let initParam = bits; 91 | if (key !== null) { 92 | keyBuffer = getUInt8Buffer(key); 93 | if (keyBuffer.length > 64) { 94 | return Promise.reject(new Error("Max key length is 64 bytes")); 95 | } 96 | initParam = getInitParam(bits, keyBuffer.length); 97 | } 98 | 99 | const outputSize = bits / 8; 100 | 101 | return WASMInterface(wasmJson, outputSize).then((wasm) => { 102 | if (initParam > 512) { 103 | wasm.writeMemory(keyBuffer); 104 | } 105 | wasm.init(initParam); 106 | 107 | const obj: IHasher = { 108 | init: 109 | initParam > 512 110 | ? () => { 111 | wasm.writeMemory(keyBuffer); 112 | wasm.init(initParam); 113 | return obj; 114 | } 115 | : () => { 116 | wasm.init(initParam); 117 | return obj; 118 | }, 119 | update: (data) => { 120 | wasm.update(data); 121 | return obj; 122 | }, 123 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 124 | digest: (outputType) => wasm.digest(outputType) as any, 125 | save: () => wasm.save(), 126 | load: (data) => { 127 | wasm.load(data); 128 | return obj; 129 | }, 130 | blockSize: 128, 131 | digestSize: outputSize, 132 | }; 133 | return obj; 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /lib/blake2s.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/blake2s.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import { type IDataType, getUInt8Buffer } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | function validateBits(bits: number) { 15 | if (!Number.isInteger(bits) || bits < 8 || bits > 256 || bits % 8 !== 0) { 16 | return new Error("Invalid variant! Valid values: 8, 16, ..., 256"); 17 | } 18 | return null; 19 | } 20 | 21 | function getInitParam(outputBits, keyBits) { 22 | return outputBits | (keyBits << 16); 23 | } 24 | 25 | /** 26 | * Calculates BLAKE2s hash 27 | * @param data Input data (string, Buffer or TypedArray) 28 | * @param bits Number of output bits, which has to be a number 29 | * divisible by 8, between 8 and 256. Defaults to 256. 30 | * @param key Optional key (string, Buffer or TypedArray). Maximum length is 32 bytes. 31 | * @returns Computed hash as a hexadecimal string 32 | */ 33 | export function blake2s( 34 | data: IDataType, 35 | bits = 256, 36 | key: IDataType = null, 37 | ): Promise { 38 | if (validateBits(bits)) { 39 | return Promise.reject(validateBits(bits)); 40 | } 41 | 42 | let keyBuffer = null; 43 | let initParam = bits; 44 | if (key !== null) { 45 | keyBuffer = getUInt8Buffer(key); 46 | if (keyBuffer.length > 32) { 47 | return Promise.reject(new Error("Max key length is 32 bytes")); 48 | } 49 | initParam = getInitParam(bits, keyBuffer.length); 50 | } 51 | 52 | const hashLength = bits / 8; 53 | 54 | if (wasmCache === null || wasmCache.hashLength !== hashLength) { 55 | return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { 56 | wasmCache = wasm; 57 | if (initParam > 512) { 58 | wasmCache.writeMemory(keyBuffer); 59 | } 60 | return wasmCache.calculate(data, initParam); 61 | }); 62 | } 63 | 64 | try { 65 | if (initParam > 512) { 66 | wasmCache.writeMemory(keyBuffer); 67 | } 68 | const hash = wasmCache.calculate(data, initParam); 69 | return Promise.resolve(hash); 70 | } catch (err) { 71 | return Promise.reject(err); 72 | } 73 | } 74 | 75 | /** 76 | * Creates a new BLAKE2s hash instance 77 | * @param bits Number of output bits, which has to be a number 78 | * divisible by 8, between 8 and 256. Defaults to 256. 79 | * @param key Optional key (string, Buffer or TypedArray). Maximum length is 32 bytes. 80 | */ 81 | export function createBLAKE2s( 82 | bits = 256, 83 | key: IDataType = null, 84 | ): Promise { 85 | if (validateBits(bits)) { 86 | return Promise.reject(validateBits(bits)); 87 | } 88 | 89 | let keyBuffer = null; 90 | let initParam = bits; 91 | if (key !== null) { 92 | keyBuffer = getUInt8Buffer(key); 93 | if (keyBuffer.length > 32) { 94 | return Promise.reject(new Error("Max key length is 32 bytes")); 95 | } 96 | initParam = getInitParam(bits, keyBuffer.length); 97 | } 98 | 99 | const outputSize = bits / 8; 100 | 101 | return WASMInterface(wasmJson, outputSize).then((wasm) => { 102 | if (initParam > 512) { 103 | wasm.writeMemory(keyBuffer); 104 | } 105 | wasm.init(initParam); 106 | 107 | const obj: IHasher = { 108 | init: 109 | initParam > 512 110 | ? () => { 111 | wasm.writeMemory(keyBuffer); 112 | wasm.init(initParam); 113 | return obj; 114 | } 115 | : () => { 116 | wasm.init(initParam); 117 | return obj; 118 | }, 119 | update: (data) => { 120 | wasm.update(data); 121 | return obj; 122 | }, 123 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 124 | digest: (outputType) => wasm.digest(outputType) as any, 125 | save: () => wasm.save(), 126 | load: (data) => { 127 | wasm.load(data); 128 | return obj; 129 | }, 130 | blockSize: 64, 131 | digestSize: outputSize, 132 | }; 133 | return obj; 134 | }); 135 | } 136 | -------------------------------------------------------------------------------- /lib/blake3.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/blake3.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import { type IDataType, getUInt8Buffer } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | function validateBits(bits: number) { 15 | if (!Number.isInteger(bits) || bits < 8 || bits % 8 !== 0) { 16 | return new Error("Invalid variant! Valid values: 8, 16, ..."); 17 | } 18 | return null; 19 | } 20 | 21 | /** 22 | * Calculates BLAKE3 hash 23 | * @param data Input data (string, Buffer or TypedArray) 24 | * @param bits Number of output bits, which has to be a number 25 | * divisible by 8. Defaults to 256. 26 | * @param key Optional key (string, Buffer or TypedArray). Length should be 32 bytes. 27 | * @returns Computed hash as a hexadecimal string 28 | */ 29 | export function blake3( 30 | data: IDataType, 31 | bits = 256, 32 | key: IDataType = null, 33 | ): Promise { 34 | if (validateBits(bits)) { 35 | return Promise.reject(validateBits(bits)); 36 | } 37 | 38 | let keyBuffer = null; 39 | let initParam = 0; // key is empty by default 40 | if (key !== null) { 41 | keyBuffer = getUInt8Buffer(key); 42 | if (keyBuffer.length !== 32) { 43 | return Promise.reject(new Error("Key length must be exactly 32 bytes")); 44 | } 45 | initParam = 32; 46 | } 47 | 48 | const hashLength = bits / 8; 49 | const digestParam = hashLength; 50 | 51 | if (wasmCache === null || wasmCache.hashLength !== hashLength) { 52 | return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { 53 | wasmCache = wasm; 54 | if (initParam === 32) { 55 | wasmCache.writeMemory(keyBuffer); 56 | } 57 | return wasmCache.calculate(data, initParam, digestParam); 58 | }); 59 | } 60 | 61 | try { 62 | if (initParam === 32) { 63 | wasmCache.writeMemory(keyBuffer); 64 | } 65 | const hash = wasmCache.calculate(data, initParam, digestParam); 66 | return Promise.resolve(hash); 67 | } catch (err) { 68 | return Promise.reject(err); 69 | } 70 | } 71 | 72 | /** 73 | * Creates a new BLAKE3 hash instance 74 | * @param bits Number of output bits, which has to be a number 75 | * divisible by 8. Defaults to 256. 76 | * @param key Optional key (string, Buffer or TypedArray). Length should be 32 bytes. 77 | */ 78 | export function createBLAKE3( 79 | bits = 256, 80 | key: IDataType = null, 81 | ): Promise { 82 | if (validateBits(bits)) { 83 | return Promise.reject(validateBits(bits)); 84 | } 85 | 86 | let keyBuffer = null; 87 | let initParam = 0; // key is empty by default 88 | if (key !== null) { 89 | keyBuffer = getUInt8Buffer(key); 90 | if (keyBuffer.length !== 32) { 91 | return Promise.reject(new Error("Key length must be exactly 32 bytes")); 92 | } 93 | initParam = 32; 94 | } 95 | 96 | const outputSize = bits / 8; 97 | const digestParam = outputSize; 98 | 99 | return WASMInterface(wasmJson, outputSize).then((wasm) => { 100 | if (initParam === 32) { 101 | wasm.writeMemory(keyBuffer); 102 | } 103 | wasm.init(initParam); 104 | 105 | const obj: IHasher = { 106 | init: 107 | initParam === 32 108 | ? () => { 109 | wasm.writeMemory(keyBuffer); 110 | wasm.init(initParam); 111 | return obj; 112 | } 113 | : () => { 114 | wasm.init(initParam); 115 | return obj; 116 | }, 117 | update: (data) => { 118 | wasm.update(data); 119 | return obj; 120 | }, 121 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 122 | digest: (outputType) => wasm.digest(outputType, digestParam) as any, 123 | save: () => wasm.save(), 124 | load: (data) => { 125 | wasm.load(data); 126 | return obj; 127 | }, 128 | blockSize: 64, 129 | digestSize: outputSize, 130 | }; 131 | return obj; 132 | }); 133 | } 134 | -------------------------------------------------------------------------------- /lib/crc32.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/crc32.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | function validatePoly(poly: number) { 15 | if (!Number.isInteger(poly) || poly < 0 || poly > 0xffffffff) { 16 | return new Error("Polynomial must be a valid 32-bit long unsigned integer"); 17 | } 18 | return null; 19 | } 20 | 21 | /** 22 | * Calculates CRC-32 hash 23 | * @param data Input data (string, Buffer or TypedArray) 24 | * @param polynomial Input polynomial (defaults to 0xedb88320, for CRC32C use 0x82f63b78) 25 | * @returns Computed hash as a hexadecimal string 26 | */ 27 | export function crc32( 28 | data: IDataType, 29 | polynomial = 0xedb88320, 30 | ): Promise { 31 | if (validatePoly(polynomial)) { 32 | return Promise.reject(validatePoly(polynomial)); 33 | } 34 | 35 | if (wasmCache === null) { 36 | return lockedCreate(mutex, wasmJson, 4).then((wasm) => { 37 | wasmCache = wasm; 38 | return wasmCache.calculate(data, polynomial); 39 | }); 40 | } 41 | 42 | try { 43 | const hash = wasmCache.calculate(data, polynomial); 44 | return Promise.resolve(hash); 45 | } catch (err) { 46 | return Promise.reject(err); 47 | } 48 | } 49 | 50 | /** 51 | * Creates a new CRC-32 hash instance 52 | * @param polynomial Input polynomial (defaults to 0xedb88320, for CRC32C use 0x82f63b78) 53 | */ 54 | export function createCRC32(polynomial = 0xedb88320): Promise { 55 | if (validatePoly(polynomial)) { 56 | return Promise.reject(validatePoly(polynomial)); 57 | } 58 | 59 | return WASMInterface(wasmJson, 4).then((wasm) => { 60 | wasm.init(polynomial); 61 | const obj: IHasher = { 62 | init: () => { 63 | wasm.init(polynomial); 64 | return obj; 65 | }, 66 | update: (data) => { 67 | wasm.update(data); 68 | return obj; 69 | }, 70 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 71 | digest: (outputType) => wasm.digest(outputType) as any, 72 | save: () => wasm.save(), 73 | load: (data) => { 74 | wasm.load(data); 75 | return obj; 76 | }, 77 | blockSize: 4, 78 | digestSize: 4, 79 | }; 80 | return obj; 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /lib/crc64.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/crc64.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | const polyBuffer = new Uint8Array(8); 14 | 15 | function parsePoly(poly: string) { 16 | const errText = "Polynomial must be provided as a 16 char long hex string"; 17 | 18 | if (typeof poly !== "string" || poly.length !== 16) { 19 | return { hi: 0, lo: 0, err: new Error(errText) }; 20 | } 21 | 22 | const hi = Number(`0x${poly.slice(0, 8)}`); 23 | const lo = Number(`0x${poly.slice(8)}`); 24 | 25 | if (Number.isNaN(hi) || Number.isNaN(lo)) { 26 | return { hi, lo, err: new Error(errText) }; 27 | } 28 | 29 | return { hi, lo, err: null }; 30 | } 31 | 32 | function writePoly(arr: ArrayBuffer, lo: number, hi: number) { 33 | // write in little-endian format 34 | const buffer = new DataView(arr); 35 | buffer.setUint32(0, lo, true); 36 | buffer.setUint32(4, hi, true); 37 | } 38 | 39 | /** 40 | * Calculates CRC-64 hash 41 | * @param data Input data (string, Buffer or TypedArray) 42 | * @param polynomial Input polynomial (defaults to 'c96c5795d7870f42' - ECMA) 43 | * @returns Computed hash as a hexadecimal string 44 | */ 45 | export function crc64( 46 | data: IDataType, 47 | polynomial = "c96c5795d7870f42", 48 | ): Promise { 49 | const { hi, lo, err } = parsePoly(polynomial); 50 | if (err !== null) { 51 | return Promise.reject(err); 52 | } 53 | 54 | if (wasmCache === null) { 55 | return lockedCreate(mutex, wasmJson, 8).then((wasm) => { 56 | wasmCache = wasm; 57 | writePoly(polyBuffer.buffer, lo, hi); 58 | wasmCache.writeMemory(polyBuffer); 59 | return wasmCache.calculate(data); 60 | }); 61 | } 62 | 63 | try { 64 | writePoly(polyBuffer.buffer, lo, hi); 65 | wasmCache.writeMemory(polyBuffer); 66 | const hash = wasmCache.calculate(data); 67 | return Promise.resolve(hash); 68 | } catch (err) { 69 | return Promise.reject(err); 70 | } 71 | } 72 | 73 | /** 74 | * Creates a new CRC-64 hash instance 75 | * @param polynomial Input polynomial (defaults to 'c96c5795d7870f42' - ECMA) 76 | */ 77 | export function createCRC64(polynomial = "c96c5795d7870f42"): Promise { 78 | const { hi, lo, err } = parsePoly(polynomial); 79 | if (err !== null) { 80 | return Promise.reject(err); 81 | } 82 | 83 | return WASMInterface(wasmJson, 8).then((wasm) => { 84 | const instanceBuffer = new Uint8Array(8); 85 | writePoly(instanceBuffer.buffer, lo, hi); 86 | wasm.writeMemory(instanceBuffer); 87 | wasm.init(); 88 | const obj: IHasher = { 89 | init: () => { 90 | wasm.writeMemory(instanceBuffer); 91 | wasm.init(); 92 | return obj; 93 | }, 94 | update: (data) => { 95 | wasm.update(data); 96 | return obj; 97 | }, 98 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 99 | digest: (outputType) => wasm.digest(outputType) as any, 100 | save: () => wasm.save(), 101 | load: (data) => { 102 | wasm.load(data); 103 | return obj; 104 | }, 105 | blockSize: 8, 106 | digestSize: 8, 107 | }; 108 | return obj; 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /lib/hmac.ts: -------------------------------------------------------------------------------- 1 | import type { IHasher } from "./WASMInterface"; 2 | import { type IDataType, getUInt8Buffer } from "./util"; 3 | 4 | function calculateKeyBuffer(hasher: IHasher, key: IDataType): Uint8Array { 5 | const { blockSize } = hasher; 6 | 7 | const buf = getUInt8Buffer(key); 8 | 9 | if (buf.length > blockSize) { 10 | hasher.update(buf); 11 | const uintArr = hasher.digest("binary"); 12 | hasher.init(); 13 | return uintArr; 14 | } 15 | 16 | return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); 17 | } 18 | 19 | function calculateHmac(hasher: IHasher, key: IDataType): IHasher { 20 | hasher.init(); 21 | 22 | const { blockSize } = hasher; 23 | const keyBuf = calculateKeyBuffer(hasher, key); 24 | const keyBuffer = new Uint8Array(blockSize); 25 | keyBuffer.set(keyBuf); 26 | 27 | const opad = new Uint8Array(blockSize); 28 | 29 | for (let i = 0; i < blockSize; i++) { 30 | const v = keyBuffer[i]; 31 | opad[i] = v ^ 0x5c; 32 | keyBuffer[i] = v ^ 0x36; 33 | } 34 | 35 | hasher.update(keyBuffer); 36 | 37 | const obj: IHasher = { 38 | init: () => { 39 | hasher.init(); 40 | hasher.update(keyBuffer); 41 | return obj; 42 | }, 43 | 44 | update: (data: IDataType) => { 45 | hasher.update(data); 46 | return obj; 47 | }, 48 | 49 | digest: ((outputType) => { 50 | const uintArr = hasher.digest("binary"); 51 | hasher.init(); 52 | hasher.update(opad); 53 | hasher.update(uintArr); 54 | return hasher.digest(outputType); 55 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 56 | }) as any, 57 | save: () => { 58 | throw new Error("save() not supported"); 59 | }, 60 | load: () => { 61 | throw new Error("load() not supported"); 62 | }, 63 | 64 | blockSize: hasher.blockSize, 65 | digestSize: hasher.digestSize, 66 | }; 67 | return obj; 68 | } 69 | 70 | /** 71 | * Calculates HMAC hash 72 | * @param hash Hash algorithm to use. It has to be the return value of a function like createSHA1() 73 | * @param key Key (string, Buffer or TypedArray) 74 | */ 75 | export function createHMAC( 76 | hash: Promise, 77 | key: IDataType, 78 | ): Promise { 79 | if (!hash || !hash.then) { 80 | throw new Error( 81 | 'Invalid hash function is provided! Usage: createHMAC(createMD5(), "key").', 82 | ); 83 | } 84 | 85 | return hash.then((hasher) => calculateHmac(hasher, key)); 86 | } 87 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./adler32"; 2 | export * from "./argon2"; 3 | export * from "./blake2b"; 4 | export * from "./blake2s"; 5 | export * from "./blake3"; 6 | export * from "./crc32"; 7 | export * from "./crc64"; 8 | export * from "./md4"; 9 | export * from "./md5"; 10 | export * from "./sha1"; 11 | export * from "./sha3"; 12 | export * from "./keccak"; 13 | export * from "./sha224"; 14 | export * from "./sha256"; 15 | export * from "./sha384"; 16 | export * from "./sha512"; 17 | export * from "./xxhash32"; 18 | export * from "./xxhash64"; 19 | export * from "./xxhash3"; 20 | export * from "./xxhash128"; 21 | export * from "./ripemd160"; 22 | export * from "./hmac"; 23 | export * from "./pbkdf2"; 24 | export * from "./scrypt"; 25 | export * from "./bcrypt"; 26 | export * from "./whirlpool"; 27 | export * from "./sm3"; 28 | 29 | export type { IDataType } from "./util"; 30 | export type { IHasher } from "./WASMInterface"; 31 | -------------------------------------------------------------------------------- /lib/keccak.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha3.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | type IValidBits = 224 | 256 | 384 | 512; 12 | const mutex = new Mutex(); 13 | let wasmCache: IWASMInterface = null; 14 | 15 | function validateBits(bits: IValidBits) { 16 | if (![224, 256, 384, 512].includes(bits)) { 17 | return new Error("Invalid variant! Valid values: 224, 256, 384, 512"); 18 | } 19 | 20 | return null; 21 | } 22 | 23 | /** 24 | * Calculates Keccak hash 25 | * @param data Input data (string, Buffer or TypedArray) 26 | * @param bits Number of output bits. Valid values: 224, 256, 384, 512 27 | * @returns Computed hash as a hexadecimal string 28 | */ 29 | export function keccak( 30 | data: IDataType, 31 | bits: IValidBits = 512, 32 | ): Promise { 33 | if (validateBits(bits)) { 34 | return Promise.reject(validateBits(bits)); 35 | } 36 | 37 | const hashLength = bits / 8; 38 | 39 | if (wasmCache === null || wasmCache.hashLength !== hashLength) { 40 | return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { 41 | wasmCache = wasm; 42 | return wasmCache.calculate(data, bits, 0x01); 43 | }); 44 | } 45 | 46 | try { 47 | const hash = wasmCache.calculate(data, bits, 0x01); 48 | return Promise.resolve(hash); 49 | } catch (err) { 50 | return Promise.reject(err); 51 | } 52 | } 53 | 54 | /** 55 | * Creates a new Keccak hash instance 56 | * @param bits Number of output bits. Valid values: 224, 256, 384, 512 57 | */ 58 | export function createKeccak(bits: IValidBits = 512): Promise { 59 | if (validateBits(bits)) { 60 | return Promise.reject(validateBits(bits)); 61 | } 62 | 63 | const outputSize = bits / 8; 64 | 65 | return WASMInterface(wasmJson, outputSize).then((wasm) => { 66 | wasm.init(bits); 67 | const obj: IHasher = { 68 | init: () => { 69 | wasm.init(bits); 70 | return obj; 71 | }, 72 | update: (data) => { 73 | wasm.update(data); 74 | return obj; 75 | }, 76 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 77 | digest: (outputType) => wasm.digest(outputType, 0x01) as any, 78 | save: () => wasm.save(), 79 | load: (data) => { 80 | wasm.load(data); 81 | return obj; 82 | }, 83 | blockSize: 200 - 2 * outputSize, 84 | digestSize: outputSize, 85 | }; 86 | return obj; 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /lib/lockedCreate.ts: -------------------------------------------------------------------------------- 1 | import { type IWASMInterface, WASMInterface } from "./WASMInterface"; 2 | import type Mutex from "./mutex"; 3 | import type { IEmbeddedWasm } from "./util"; 4 | 5 | export default async function lockedCreate( 6 | mutex: Mutex, 7 | binary: IEmbeddedWasm, 8 | hashLength: number, 9 | ): Promise { 10 | const unlock = await mutex.lock(); 11 | const wasm = await WASMInterface(binary, hashLength); 12 | unlock(); 13 | return wasm; 14 | } 15 | -------------------------------------------------------------------------------- /lib/md4.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/md4.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates MD4 hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function md4(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 16).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new MD4 hash instance 37 | */ 38 | export function createMD4(): Promise { 39 | return WASMInterface(wasmJson, 16).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 16, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/md5.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/md5.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates MD5 hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function md5(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 16).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new MD5 hash instance 37 | */ 38 | export function createMD5(): Promise { 39 | return WASMInterface(wasmJson, 16).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 16, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/mutex.ts: -------------------------------------------------------------------------------- 1 | class Mutex { 2 | private mutex = Promise.resolve(); 3 | 4 | lock(): PromiseLike<() => void> { 5 | let begin: (unlock: () => void) => void = () => {}; 6 | 7 | this.mutex = this.mutex.then(() => new Promise(begin)); 8 | 9 | return new Promise((res) => { 10 | begin = res; 11 | }); 12 | } 13 | 14 | async dispatch(fn: () => PromiseLike): Promise { 15 | const unlock = await this.lock(); 16 | try { 17 | return await Promise.resolve(fn()); 18 | } finally { 19 | unlock(); 20 | } 21 | } 22 | } 23 | 24 | export default Mutex; 25 | -------------------------------------------------------------------------------- /lib/pbkdf2.ts: -------------------------------------------------------------------------------- 1 | import type { IHasher } from "./WASMInterface"; 2 | import { createHMAC } from "./hmac"; 3 | import { type IDataType, getDigestHex, getUInt8Buffer } from "./util"; 4 | 5 | export interface IPBKDF2Options { 6 | /** 7 | * Password (or message) to be hashed 8 | */ 9 | password: IDataType; 10 | /** 11 | * Salt (usually containing random bytes) 12 | */ 13 | salt: IDataType; 14 | /** 15 | * Number of iterations to perform 16 | */ 17 | iterations: number; 18 | /** 19 | * Output size in bytes 20 | */ 21 | hashLength: number; 22 | /** 23 | * Hash algorithm to use. It has to be the return value of a function like createSHA1() 24 | */ 25 | hashFunction: Promise; 26 | /** 27 | * Desired output type. Defaults to 'hex' 28 | */ 29 | outputType?: "hex" | "binary"; 30 | } 31 | 32 | async function calculatePBKDF2( 33 | digest: IHasher, 34 | salt: IDataType, 35 | iterations: number, 36 | hashLength: number, 37 | outputType?: "hex" | "binary", 38 | ): Promise { 39 | const DK = new Uint8Array(hashLength); 40 | const block1 = new Uint8Array(salt.length + 4); 41 | const block1View = new DataView(block1.buffer); 42 | const saltBuffer = getUInt8Buffer(salt); 43 | const saltUIntBuffer = new Uint8Array( 44 | saltBuffer.buffer, 45 | saltBuffer.byteOffset, 46 | saltBuffer.length, 47 | ); 48 | block1.set(saltUIntBuffer); 49 | 50 | let destPos = 0; 51 | const hLen = digest.digestSize; 52 | const l = Math.ceil(hashLength / hLen); 53 | 54 | let T: Uint8Array = null; 55 | let U: Uint8Array = null; 56 | 57 | for (let i = 1; i <= l; i++) { 58 | block1View.setUint32(salt.length, i); 59 | 60 | digest.init(); 61 | digest.update(block1); 62 | T = digest.digest("binary"); 63 | U = T.slice(); 64 | 65 | for (let j = 1; j < iterations; j++) { 66 | digest.init(); 67 | digest.update(U); 68 | U = digest.digest("binary"); 69 | for (let k = 0; k < hLen; k++) { 70 | T[k] ^= U[k]; 71 | } 72 | } 73 | 74 | DK.set(T.subarray(0, hashLength - destPos), destPos); 75 | destPos += hLen; 76 | } 77 | 78 | if (outputType === "binary") { 79 | return DK; 80 | } 81 | 82 | const digestChars = new Uint8Array(hashLength * 2); 83 | return getDigestHex(digestChars, DK, hashLength); 84 | } 85 | 86 | const validateOptions = (options: IPBKDF2Options) => { 87 | if (!options || typeof options !== "object") { 88 | throw new Error("Invalid options parameter. It requires an object."); 89 | } 90 | 91 | if (!options.hashFunction || !options.hashFunction.then) { 92 | throw new Error( 93 | 'Invalid hash function is provided! Usage: pbkdf2("password", "salt", 1000, 32, createSHA1()).', 94 | ); 95 | } 96 | 97 | if (!Number.isInteger(options.iterations) || options.iterations < 1) { 98 | throw new Error("Iterations should be a positive number"); 99 | } 100 | 101 | if (!Number.isInteger(options.hashLength) || options.hashLength < 1) { 102 | throw new Error("Hash length should be a positive number"); 103 | } 104 | 105 | if (options.outputType === undefined) { 106 | options.outputType = "hex"; 107 | } 108 | 109 | if (!["hex", "binary"].includes(options.outputType)) { 110 | throw new Error( 111 | `Insupported output type ${options.outputType}. Valid values: ['hex', 'binary']`, 112 | ); 113 | } 114 | }; 115 | 116 | interface IPBKDF2OptionsBinary { 117 | outputType: "binary"; 118 | } 119 | 120 | type PBKDF2ReturnType = T extends IPBKDF2OptionsBinary ? Uint8Array : string; 121 | 122 | /** 123 | * Generates a new PBKDF2 hash for the supplied password 124 | */ 125 | export async function pbkdf2( 126 | options: T, 127 | ): Promise> { 128 | validateOptions(options); 129 | 130 | const hmac = await createHMAC(options.hashFunction, options.password); 131 | return calculatePBKDF2( 132 | hmac, 133 | options.salt, 134 | options.iterations, 135 | options.hashLength, 136 | options.outputType, 137 | ) as Promise>; 138 | } 139 | -------------------------------------------------------------------------------- /lib/ripemd160.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/ripemd160.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates RIPEMD-160 hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function ripemd160(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 20).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new RIPEMD-160 hash instance 37 | */ 38 | export function createRIPEMD160(): Promise { 39 | return WASMInterface(wasmJson, 20).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 20, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/scrypt.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/scrypt.wasm.json"; 2 | import { WASMInterface } from "./WASMInterface"; 3 | import { pbkdf2 } from "./pbkdf2"; 4 | import { createSHA256 } from "./sha256"; 5 | import { type IDataType, getDigestHex } from "./util"; 6 | 7 | export interface ScryptOptions { 8 | /** 9 | * Password (or message) to be hashed 10 | */ 11 | password: IDataType; 12 | /** 13 | * Salt (usually containing random bytes) 14 | */ 15 | salt: IDataType; 16 | /** 17 | * CPU / memory cost - must be a power of 2 (e.g. 1024) 18 | */ 19 | costFactor: number; 20 | /** 21 | * Block size (8 is commonly used) 22 | */ 23 | blockSize: number; 24 | /** 25 | * Degree of parallelism 26 | */ 27 | parallelism: number; 28 | /** 29 | * Output size in bytes 30 | */ 31 | hashLength: number; 32 | /** 33 | * Output data type. Defaults to hexadecimal string 34 | */ 35 | outputType?: "hex" | "binary"; 36 | } 37 | 38 | async function scryptInternal( 39 | options: ScryptOptions, 40 | ): Promise { 41 | const { costFactor, blockSize, parallelism, hashLength } = options; 42 | const SHA256Hasher = createSHA256(); 43 | 44 | const blockData = await pbkdf2({ 45 | password: options.password, 46 | salt: options.salt, 47 | iterations: 1, 48 | hashLength: 128 * blockSize * parallelism, 49 | hashFunction: SHA256Hasher, 50 | outputType: "binary", 51 | }); 52 | 53 | const scryptInterface = await WASMInterface(wasmJson, 0); 54 | 55 | // last block is for storing the temporary vectors 56 | const VSize = 128 * blockSize * costFactor; 57 | const XYSize = 256 * blockSize; 58 | scryptInterface.setMemorySize(blockData.length + VSize + XYSize); 59 | scryptInterface.writeMemory(blockData, 0); 60 | 61 | // mix blocks 62 | scryptInterface.getExports().scrypt(blockSize, costFactor, parallelism); 63 | 64 | const expensiveSalt = scryptInterface 65 | .getMemory() 66 | .subarray(0, 128 * blockSize * parallelism); 67 | 68 | const outputData = await pbkdf2({ 69 | password: options.password, 70 | salt: expensiveSalt, 71 | iterations: 1, 72 | hashLength, 73 | hashFunction: SHA256Hasher, 74 | outputType: "binary", 75 | }); 76 | 77 | if (options.outputType === "hex") { 78 | const digestChars = new Uint8Array(hashLength * 2); 79 | return getDigestHex(digestChars, outputData, hashLength); 80 | } 81 | 82 | // return binary format 83 | return outputData; 84 | } 85 | 86 | const isPowerOfTwo = (v: number): boolean => v && !(v & (v - 1)); 87 | 88 | const validateOptions = (options: ScryptOptions) => { 89 | if (!options || typeof options !== "object") { 90 | throw new Error("Invalid options parameter. It requires an object."); 91 | } 92 | 93 | if (!Number.isInteger(options.blockSize) || options.blockSize < 1) { 94 | throw new Error("Block size should be a positive number"); 95 | } 96 | 97 | if ( 98 | !Number.isInteger(options.costFactor) || 99 | options.costFactor < 2 || 100 | !isPowerOfTwo(options.costFactor) 101 | ) { 102 | throw new Error("Cost factor should be a power of 2, greater than 1"); 103 | } 104 | 105 | if (!Number.isInteger(options.parallelism) || options.parallelism < 1) { 106 | throw new Error("Parallelism should be a positive number"); 107 | } 108 | 109 | if (!Number.isInteger(options.hashLength) || options.hashLength < 1) { 110 | throw new Error("Hash length should be a positive number."); 111 | } 112 | 113 | if (options.outputType === undefined) { 114 | options.outputType = "hex"; 115 | } 116 | 117 | if (!["hex", "binary"].includes(options.outputType)) { 118 | throw new Error( 119 | `Insupported output type ${options.outputType}. Valid values: ['hex', 'binary']`, 120 | ); 121 | } 122 | }; 123 | 124 | interface IScryptOptionsBinary { 125 | outputType: "binary"; 126 | } 127 | 128 | type ScryptReturnType = T extends IScryptOptionsBinary ? Uint8Array : string; 129 | 130 | /** 131 | * Calculates hash using the scrypt password-based key derivation function 132 | * @returns Computed hash as a hexadecimal string or as 133 | * Uint8Array depending on the outputType option 134 | */ 135 | export async function scrypt( 136 | options: T, 137 | ): Promise> { 138 | validateOptions(options); 139 | 140 | return scryptInternal(options) as Promise>; 141 | } 142 | -------------------------------------------------------------------------------- /lib/sha1.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha1.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SHA-1 hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sha1(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 20).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SHA-1 hash instance 37 | */ 38 | export function createSHA1(): Promise { 39 | return WASMInterface(wasmJson, 20).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 20, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/sha224.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha256.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SHA-2 (SHA-224) hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sha224(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 28).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data, 224); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data, 224); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SHA-2 (SHA-224) hash instance 37 | */ 38 | export function createSHA224(): Promise { 39 | return WASMInterface(wasmJson, 28).then((wasm) => { 40 | wasm.init(224); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(224); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 28, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/sha256.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha256.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SHA-2 (SHA-256) hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sha256(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 32).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data, 256); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data, 256); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SHA-2 (SHA-256) hash instance 37 | */ 38 | export function createSHA256(): Promise { 39 | return WASMInterface(wasmJson, 32).then((wasm) => { 40 | wasm.init(256); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(256); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 32, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/sha3.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha3.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | type IValidBits = 224 | 256 | 384 | 512; 12 | const mutex = new Mutex(); 13 | let wasmCache: IWASMInterface = null; 14 | 15 | function validateBits(bits: IValidBits) { 16 | if (![224, 256, 384, 512].includes(bits)) { 17 | return new Error("Invalid variant! Valid values: 224, 256, 384, 512"); 18 | } 19 | return null; 20 | } 21 | 22 | /** 23 | * Calculates SHA-3 hash 24 | * @param data Input data (string, Buffer or TypedArray) 25 | * @param bits Number of output bits. Valid values: 224, 256, 384, 512 26 | * @returns Computed hash as a hexadecimal string 27 | */ 28 | export function sha3(data: IDataType, bits: IValidBits = 512): Promise { 29 | if (validateBits(bits)) { 30 | return Promise.reject(validateBits(bits)); 31 | } 32 | 33 | const hashLength = bits / 8; 34 | 35 | if (wasmCache === null || wasmCache.hashLength !== hashLength) { 36 | return lockedCreate(mutex, wasmJson, hashLength).then((wasm) => { 37 | wasmCache = wasm; 38 | return wasmCache.calculate(data, bits, 0x06); 39 | }); 40 | } 41 | 42 | try { 43 | const hash = wasmCache.calculate(data, bits, 0x06); 44 | return Promise.resolve(hash); 45 | } catch (err) { 46 | return Promise.reject(err); 47 | } 48 | } 49 | 50 | /** 51 | * Creates a new SHA-3 hash instance 52 | * @param bits Number of output bits. Valid values: 224, 256, 384, 512 53 | */ 54 | export function createSHA3(bits: IValidBits = 512): Promise { 55 | if (validateBits(bits)) { 56 | return Promise.reject(validateBits(bits)); 57 | } 58 | 59 | const outputSize = bits / 8; 60 | 61 | return WASMInterface(wasmJson, outputSize).then((wasm) => { 62 | wasm.init(bits); 63 | const obj: IHasher = { 64 | init: () => { 65 | wasm.init(bits); 66 | return obj; 67 | }, 68 | update: (data) => { 69 | wasm.update(data); 70 | return obj; 71 | }, 72 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 73 | digest: (outputType) => wasm.digest(outputType, 0x06) as any, 74 | save: () => wasm.save(), 75 | load: (data) => { 76 | wasm.load(data); 77 | return obj; 78 | }, 79 | blockSize: 200 - 2 * outputSize, 80 | digestSize: outputSize, 81 | }; 82 | return obj; 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /lib/sha384.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha512.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SHA-2 (SHA-384) hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sha384(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 48).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data, 384); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data, 384); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SHA-2 (SHA-384) hash instance 37 | */ 38 | export function createSHA384(): Promise { 39 | return WASMInterface(wasmJson, 48).then((wasm) => { 40 | wasm.init(384); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(384); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 128, 58 | digestSize: 48, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/sha512.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sha512.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SHA-2 (SHA-512) hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sha512(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 64).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data, 512); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data, 512); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SHA-2 (SHA-512) hash instance 37 | */ 38 | export function createSHA512(): Promise { 39 | return WASMInterface(wasmJson, 64).then((wasm) => { 40 | wasm.init(512); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(512); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 128, 58 | digestSize: 64, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/sm3.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/sm3.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates SM3 hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function sm3(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 32).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new SM3 hash instance 37 | */ 38 | export function createSM3(): Promise { 39 | return WASMInterface(wasmJson, 32).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 32, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/util.ts: -------------------------------------------------------------------------------- 1 | function getGlobal() { 2 | if (typeof globalThis !== "undefined") return globalThis; 3 | if (typeof self !== "undefined") return self; 4 | if (typeof window !== "undefined") return window; 5 | return global; 6 | } 7 | 8 | const globalObject = getGlobal(); 9 | const nodeBuffer = globalObject.Buffer ?? null; 10 | const textEncoder = globalObject.TextEncoder 11 | ? new globalObject.TextEncoder() 12 | : null; 13 | 14 | export type ITypedArray = Uint8Array | Uint16Array | Uint32Array; 15 | export type IDataType = string | Buffer | ITypedArray; 16 | export type IEmbeddedWasm = { name: string; data: string; hash: string }; 17 | 18 | export function intArrayToString(arr: Uint8Array, len: number): string { 19 | return String.fromCharCode(...arr.subarray(0, len)); 20 | } 21 | 22 | function hexCharCodesToInt(a: number, b: number): number { 23 | return ( 24 | (((a & 0xf) + ((a >> 6) | ((a >> 3) & 0x8))) << 4) | 25 | ((b & 0xf) + ((b >> 6) | ((b >> 3) & 0x8))) 26 | ); 27 | } 28 | 29 | export function writeHexToUInt8(buf: Uint8Array, str: string) { 30 | const size = str.length >> 1; 31 | for (let i = 0; i < size; i++) { 32 | const index = i << 1; 33 | buf[i] = hexCharCodesToInt( 34 | str.charCodeAt(index), 35 | str.charCodeAt(index + 1), 36 | ); 37 | } 38 | } 39 | 40 | export function hexStringEqualsUInt8(str: string, buf: Uint8Array): boolean { 41 | if (str.length !== buf.length * 2) { 42 | return false; 43 | } 44 | for (let i = 0; i < buf.length; i++) { 45 | const strIndex = i << 1; 46 | if ( 47 | buf[i] !== 48 | hexCharCodesToInt(str.charCodeAt(strIndex), str.charCodeAt(strIndex + 1)) 49 | ) { 50 | return false; 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | const alpha = "a".charCodeAt(0) - 10; 57 | const digit = "0".charCodeAt(0); 58 | export function getDigestHex( 59 | tmpBuffer: Uint8Array, 60 | input: Uint8Array, 61 | hashLength: number, 62 | ): string { 63 | let p = 0; 64 | for (let i = 0; i < hashLength; i++) { 65 | let nibble = input[i] >>> 4; 66 | tmpBuffer[p++] = nibble > 9 ? nibble + alpha : nibble + digit; 67 | nibble = input[i] & 0xf; 68 | tmpBuffer[p++] = nibble > 9 ? nibble + alpha : nibble + digit; 69 | } 70 | 71 | return String.fromCharCode.apply(null, tmpBuffer); 72 | } 73 | 74 | export const getUInt8Buffer = 75 | nodeBuffer !== null 76 | ? (data: IDataType): Uint8Array => { 77 | if (typeof data === "string") { 78 | const buf = nodeBuffer.from(data, "utf8"); 79 | return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); 80 | } 81 | 82 | if (nodeBuffer.isBuffer(data)) { 83 | return new Uint8Array(data.buffer, data.byteOffset, data.length); 84 | } 85 | 86 | if (ArrayBuffer.isView(data)) { 87 | return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 88 | } 89 | 90 | throw new Error("Invalid data type!"); 91 | } 92 | : (data: IDataType): Uint8Array => { 93 | if (typeof data === "string") { 94 | return textEncoder.encode(data); 95 | } 96 | 97 | if (ArrayBuffer.isView(data)) { 98 | return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); 99 | } 100 | 101 | throw new Error("Invalid data type!"); 102 | }; 103 | 104 | const base64Chars = 105 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 106 | const base64Lookup = new Uint8Array(256); 107 | for (let i = 0; i < base64Chars.length; i++) { 108 | base64Lookup[base64Chars.charCodeAt(i)] = i; 109 | } 110 | 111 | export function encodeBase64(data: Uint8Array, pad = true): string { 112 | const len = data.length; 113 | const extraBytes = len % 3; 114 | const parts = []; 115 | 116 | const len2 = len - extraBytes; 117 | for (let i = 0; i < len2; i += 3) { 118 | const tmp = 119 | ((data[i] << 16) & 0xff0000) + 120 | ((data[i + 1] << 8) & 0xff00) + 121 | (data[i + 2] & 0xff); 122 | 123 | const triplet = 124 | base64Chars.charAt((tmp >> 18) & 0x3f) + 125 | base64Chars.charAt((tmp >> 12) & 0x3f) + 126 | base64Chars.charAt((tmp >> 6) & 0x3f) + 127 | base64Chars.charAt(tmp & 0x3f); 128 | 129 | parts.push(triplet); 130 | } 131 | 132 | if (extraBytes === 1) { 133 | const tmp = data[len - 1]; 134 | const a = base64Chars.charAt(tmp >> 2); 135 | const b = base64Chars.charAt((tmp << 4) & 0x3f); 136 | 137 | parts.push(`${a}${b}`); 138 | if (pad) { 139 | parts.push("=="); 140 | } 141 | } else if (extraBytes === 2) { 142 | const tmp = (data[len - 2] << 8) + data[len - 1]; 143 | const a = base64Chars.charAt(tmp >> 10); 144 | const b = base64Chars.charAt((tmp >> 4) & 0x3f); 145 | const c = base64Chars.charAt((tmp << 2) & 0x3f); 146 | parts.push(`${a}${b}${c}`); 147 | if (pad) { 148 | parts.push("="); 149 | } 150 | } 151 | 152 | return parts.join(""); 153 | } 154 | 155 | export function getDecodeBase64Length(data: string): number { 156 | let bufferLength = Math.floor(data.length * 0.75); 157 | const len = data.length; 158 | 159 | if (data[len - 1] === "=") { 160 | bufferLength -= 1; 161 | if (data[len - 2] === "=") { 162 | bufferLength -= 1; 163 | } 164 | } 165 | 166 | return bufferLength; 167 | } 168 | 169 | export function decodeBase64(data: string): Uint8Array { 170 | const bufferLength = getDecodeBase64Length(data); 171 | const len = data.length; 172 | 173 | const bytes = new Uint8Array(bufferLength); 174 | 175 | let p = 0; 176 | for (let i = 0; i < len; i += 4) { 177 | const encoded1 = base64Lookup[data.charCodeAt(i)]; 178 | const encoded2 = base64Lookup[data.charCodeAt(i + 1)]; 179 | const encoded3 = base64Lookup[data.charCodeAt(i + 2)]; 180 | const encoded4 = base64Lookup[data.charCodeAt(i + 3)]; 181 | 182 | bytes[p] = (encoded1 << 2) | (encoded2 >> 4); 183 | p += 1; 184 | bytes[p] = ((encoded2 & 15) << 4) | (encoded3 >> 2); 185 | p += 1; 186 | bytes[p] = ((encoded3 & 3) << 6) | (encoded4 & 63); 187 | p += 1; 188 | } 189 | 190 | return bytes; 191 | } 192 | -------------------------------------------------------------------------------- /lib/whirlpool.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/whirlpool.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | /** 15 | * Calculates Whirlpool hash 16 | * @param data Input data (string, Buffer or TypedArray) 17 | * @returns Computed hash as a hexadecimal string 18 | */ 19 | export function whirlpool(data: IDataType): Promise { 20 | if (wasmCache === null) { 21 | return lockedCreate(mutex, wasmJson, 64).then((wasm) => { 22 | wasmCache = wasm; 23 | return wasmCache.calculate(data); 24 | }); 25 | } 26 | 27 | try { 28 | const hash = wasmCache.calculate(data); 29 | return Promise.resolve(hash); 30 | } catch (err) { 31 | return Promise.reject(err); 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new Whirlpool hash instance 37 | */ 38 | export function createWhirlpool(): Promise { 39 | return WASMInterface(wasmJson, 64).then((wasm) => { 40 | wasm.init(); 41 | const obj: IHasher = { 42 | init: () => { 43 | wasm.init(); 44 | return obj; 45 | }, 46 | update: (data) => { 47 | wasm.update(data); 48 | return obj; 49 | }, 50 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 51 | digest: (outputType) => wasm.digest(outputType) as any, 52 | save: () => wasm.save(), 53 | load: (data) => { 54 | wasm.load(data); 55 | return obj; 56 | }, 57 | blockSize: 64, 58 | digestSize: 64, 59 | }; 60 | return obj; 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /lib/xxhash128.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/xxhash128.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | const seedBuffer = new Uint8Array(8); 14 | 15 | function validateSeed(seed: number) { 16 | if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) { 17 | return new Error( 18 | "Seed must be given as two valid 32-bit long unsigned integers (lo + high).", 19 | ); 20 | } 21 | return null; 22 | } 23 | 24 | function writeSeed(arr: ArrayBuffer, low: number, high: number) { 25 | // write in little-endian format 26 | const buffer = new DataView(arr); 27 | buffer.setUint32(0, low, true); 28 | buffer.setUint32(4, high, true); 29 | } 30 | 31 | /** 32 | * Calculates xxHash128 hash 33 | * @param data Input data (string, Buffer or TypedArray) 34 | * @param seedLow Lower 32 bits of the number used to 35 | * initialize the internal state of the algorithm (defaults to 0) 36 | * @param seedHigh Higher 32 bits of the number used to 37 | * initialize the internal state of the algorithm (defaults to 0) 38 | * @returns Computed hash as a hexadecimal string 39 | */ 40 | export function xxhash128( 41 | data: IDataType, 42 | seedLow = 0, 43 | seedHigh = 0, 44 | ): Promise { 45 | if (validateSeed(seedLow)) { 46 | return Promise.reject(validateSeed(seedLow)); 47 | } 48 | 49 | if (validateSeed(seedHigh)) { 50 | return Promise.reject(validateSeed(seedHigh)); 51 | } 52 | 53 | if (wasmCache === null) { 54 | return lockedCreate(mutex, wasmJson, 16).then((wasm) => { 55 | wasmCache = wasm; 56 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 57 | wasmCache.writeMemory(seedBuffer); 58 | return wasmCache.calculate(data); 59 | }); 60 | } 61 | 62 | try { 63 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 64 | wasmCache.writeMemory(seedBuffer); 65 | const hash = wasmCache.calculate(data); 66 | return Promise.resolve(hash); 67 | } catch (err) { 68 | return Promise.reject(err); 69 | } 70 | } 71 | 72 | /** 73 | * Creates a new xxHash128 hash instance 74 | * @param seedLow Lower 32 bits of the number used to 75 | * initialize the internal state of the algorithm (defaults to 0) 76 | * @param seedHigh Higher 32 bits of the number used to 77 | * initialize the internal state of the algorithm (defaults to 0) 78 | */ 79 | export function createXXHash128(seedLow = 0, seedHigh = 0): Promise { 80 | if (validateSeed(seedLow)) { 81 | return Promise.reject(validateSeed(seedLow)); 82 | } 83 | 84 | if (validateSeed(seedHigh)) { 85 | return Promise.reject(validateSeed(seedHigh)); 86 | } 87 | 88 | return WASMInterface(wasmJson, 16).then((wasm) => { 89 | const instanceBuffer = new Uint8Array(8); 90 | writeSeed(instanceBuffer.buffer, seedLow, seedHigh); 91 | wasm.writeMemory(instanceBuffer); 92 | wasm.init(); 93 | const obj: IHasher = { 94 | init: () => { 95 | wasm.writeMemory(instanceBuffer); 96 | wasm.init(); 97 | return obj; 98 | }, 99 | update: (data) => { 100 | wasm.update(data); 101 | return obj; 102 | }, 103 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 104 | digest: (outputType) => wasm.digest(outputType) as any, 105 | save: () => wasm.save(), 106 | load: (data) => { 107 | wasm.load(data); 108 | return obj; 109 | }, 110 | blockSize: 512, 111 | digestSize: 16, 112 | }; 113 | return obj; 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /lib/xxhash3.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/xxhash3.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | const seedBuffer = new Uint8Array(8); 14 | 15 | function validateSeed(seed: number) { 16 | if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) { 17 | return new Error( 18 | "Seed must be given as two valid 32-bit long unsigned integers (lo + high).", 19 | ); 20 | } 21 | return null; 22 | } 23 | 24 | function writeSeed(arr: ArrayBuffer, low: number, high: number) { 25 | // write in little-endian format 26 | const buffer = new DataView(arr); 27 | buffer.setUint32(0, low, true); 28 | buffer.setUint32(4, high, true); 29 | } 30 | 31 | /** 32 | * Calculates xxHash3 hash 33 | * @param data Input data (string, Buffer or TypedArray) 34 | * @param seedLow Lower 32 bits of the number used to 35 | * initialize the internal state of the algorithm (defaults to 0) 36 | * @param seedHigh Higher 32 bits of the number used to 37 | * initialize the internal state of the algorithm (defaults to 0) 38 | * @returns Computed hash as a hexadecimal string 39 | */ 40 | export function xxhash3( 41 | data: IDataType, 42 | seedLow = 0, 43 | seedHigh = 0, 44 | ): Promise { 45 | if (validateSeed(seedLow)) { 46 | return Promise.reject(validateSeed(seedLow)); 47 | } 48 | 49 | if (validateSeed(seedHigh)) { 50 | return Promise.reject(validateSeed(seedHigh)); 51 | } 52 | 53 | if (wasmCache === null) { 54 | return lockedCreate(mutex, wasmJson, 8).then((wasm) => { 55 | wasmCache = wasm; 56 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 57 | wasmCache.writeMemory(seedBuffer); 58 | return wasmCache.calculate(data); 59 | }); 60 | } 61 | 62 | try { 63 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 64 | wasmCache.writeMemory(seedBuffer); 65 | const hash = wasmCache.calculate(data); 66 | return Promise.resolve(hash); 67 | } catch (err) { 68 | return Promise.reject(err); 69 | } 70 | } 71 | 72 | /** 73 | * Creates a new xxHash3 hash instance 74 | * @param seedLow Lower 32 bits of the number used to 75 | * initialize the internal state of the algorithm (defaults to 0) 76 | * @param seedHigh Higher 32 bits of the number used to 77 | * initialize the internal state of the algorithm (defaults to 0) 78 | */ 79 | export function createXXHash3(seedLow = 0, seedHigh = 0): Promise { 80 | if (validateSeed(seedLow)) { 81 | return Promise.reject(validateSeed(seedLow)); 82 | } 83 | 84 | if (validateSeed(seedHigh)) { 85 | return Promise.reject(validateSeed(seedHigh)); 86 | } 87 | 88 | return WASMInterface(wasmJson, 8).then((wasm) => { 89 | const instanceBuffer = new Uint8Array(8); 90 | writeSeed(instanceBuffer.buffer, seedLow, seedHigh); 91 | wasm.writeMemory(instanceBuffer); 92 | wasm.init(); 93 | const obj: IHasher = { 94 | init: () => { 95 | wasm.writeMemory(instanceBuffer); 96 | wasm.init(); 97 | return obj; 98 | }, 99 | update: (data) => { 100 | wasm.update(data); 101 | return obj; 102 | }, 103 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 104 | digest: (outputType) => wasm.digest(outputType) as any, 105 | save: () => wasm.save(), 106 | load: (data) => { 107 | wasm.load(data); 108 | return obj; 109 | }, 110 | blockSize: 512, 111 | digestSize: 8, 112 | }; 113 | return obj; 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /lib/xxhash32.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/xxhash32.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | 14 | function validateSeed(seed: number) { 15 | if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) { 16 | return new Error("Seed must be a valid 32-bit long unsigned integer."); 17 | } 18 | return null; 19 | } 20 | /** 21 | * Calculates xxHash32 hash 22 | * @param data Input data (string, Buffer or TypedArray) 23 | * @param seed Number used to initialize the internal state of the algorithm (defaults to 0) 24 | * @returns Computed hash as a hexadecimal string 25 | */ 26 | export function xxhash32(data: IDataType, seed = 0): Promise { 27 | if (validateSeed(seed)) { 28 | return Promise.reject(validateSeed(seed)); 29 | } 30 | 31 | if (wasmCache === null) { 32 | return lockedCreate(mutex, wasmJson, 4).then((wasm) => { 33 | wasmCache = wasm; 34 | return wasmCache.calculate(data, seed); 35 | }); 36 | } 37 | 38 | try { 39 | const hash = wasmCache.calculate(data, seed); 40 | return Promise.resolve(hash); 41 | } catch (err) { 42 | return Promise.reject(err); 43 | } 44 | } 45 | 46 | /** 47 | * Creates a new xxHash32 hash instance 48 | * @param data Input data (string, Buffer or TypedArray) 49 | * @param seed Number used to initialize the internal state of the algorithm (defaults to 0) 50 | */ 51 | export function createXXHash32(seed = 0): Promise { 52 | if (validateSeed(seed)) { 53 | return Promise.reject(validateSeed(seed)); 54 | } 55 | 56 | return WASMInterface(wasmJson, 4).then((wasm) => { 57 | wasm.init(seed); 58 | const obj: IHasher = { 59 | init: () => { 60 | wasm.init(seed); 61 | return obj; 62 | }, 63 | update: (data) => { 64 | wasm.update(data); 65 | return obj; 66 | }, 67 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 68 | digest: (outputType) => wasm.digest(outputType) as any, 69 | save: () => wasm.save(), 70 | load: (data) => { 71 | wasm.load(data); 72 | return obj; 73 | }, 74 | blockSize: 16, 75 | digestSize: 4, 76 | }; 77 | return obj; 78 | }); 79 | } 80 | -------------------------------------------------------------------------------- /lib/xxhash64.ts: -------------------------------------------------------------------------------- 1 | import wasmJson from "../wasm/xxhash64.wasm.json"; 2 | import { 3 | type IHasher, 4 | type IWASMInterface, 5 | WASMInterface, 6 | } from "./WASMInterface"; 7 | import lockedCreate from "./lockedCreate"; 8 | import Mutex from "./mutex"; 9 | import type { IDataType } from "./util"; 10 | 11 | const mutex = new Mutex(); 12 | let wasmCache: IWASMInterface = null; 13 | const seedBuffer = new Uint8Array(8); 14 | 15 | function validateSeed(seed: number) { 16 | if (!Number.isInteger(seed) || seed < 0 || seed > 0xffffffff) { 17 | return new Error( 18 | "Seed must be given as two valid 32-bit long unsigned integers (lo + high).", 19 | ); 20 | } 21 | return null; 22 | } 23 | 24 | function writeSeed(arr: ArrayBuffer, low: number, high: number) { 25 | // write in little-endian format 26 | const buffer = new DataView(arr); 27 | buffer.setUint32(0, low, true); 28 | buffer.setUint32(4, high, true); 29 | } 30 | 31 | /** 32 | * Calculates xxHash64 hash 33 | * @param data Input data (string, Buffer or TypedArray) 34 | * @param seedLow Lower 32 bits of the number used to 35 | * initialize the internal state of the algorithm (defaults to 0) 36 | * @param seedHigh Higher 32 bits of the number used to 37 | * initialize the internal state of the algorithm (defaults to 0) 38 | * @returns Computed hash as a hexadecimal string 39 | */ 40 | export function xxhash64( 41 | data: IDataType, 42 | seedLow = 0, 43 | seedHigh = 0, 44 | ): Promise { 45 | if (validateSeed(seedLow)) { 46 | return Promise.reject(validateSeed(seedLow)); 47 | } 48 | 49 | if (validateSeed(seedHigh)) { 50 | return Promise.reject(validateSeed(seedHigh)); 51 | } 52 | 53 | if (wasmCache === null) { 54 | return lockedCreate(mutex, wasmJson, 8).then((wasm) => { 55 | wasmCache = wasm; 56 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 57 | wasmCache.writeMemory(seedBuffer); 58 | return wasmCache.calculate(data); 59 | }); 60 | } 61 | 62 | try { 63 | writeSeed(seedBuffer.buffer, seedLow, seedHigh); 64 | wasmCache.writeMemory(seedBuffer); 65 | const hash = wasmCache.calculate(data); 66 | return Promise.resolve(hash); 67 | } catch (err) { 68 | return Promise.reject(err); 69 | } 70 | } 71 | 72 | /** 73 | * Creates a new xxHash64 hash instance 74 | * @param seedLow Lower 32 bits of the number used to 75 | * initialize the internal state of the algorithm (defaults to 0) 76 | * @param seedHigh Higher 32 bits of the number used to 77 | * initialize the internal state of the algorithm (defaults to 0) 78 | */ 79 | export function createXXHash64(seedLow = 0, seedHigh = 0): Promise { 80 | if (validateSeed(seedLow)) { 81 | return Promise.reject(validateSeed(seedLow)); 82 | } 83 | 84 | if (validateSeed(seedHigh)) { 85 | return Promise.reject(validateSeed(seedHigh)); 86 | } 87 | 88 | return WASMInterface(wasmJson, 8).then((wasm) => { 89 | const instanceBuffer = new Uint8Array(8); 90 | writeSeed(instanceBuffer.buffer, seedLow, seedHigh); 91 | wasm.writeMemory(instanceBuffer); 92 | wasm.init(); 93 | const obj: IHasher = { 94 | init: () => { 95 | wasm.writeMemory(instanceBuffer); 96 | wasm.init(); 97 | return obj; 98 | }, 99 | update: (data) => { 100 | wasm.update(data); 101 | return obj; 102 | }, 103 | // biome-ignore lint/suspicious/noExplicitAny: Conflict with IHasher type 104 | digest: (outputType) => wasm.digest(outputType) as any, 105 | save: () => wasm.save(), 106 | load: (data) => { 107 | wasm.load(data); 108 | return obj; 109 | }, 110 | blockSize: 32, 111 | digestSize: 8, 112 | }; 113 | return obj; 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hash-wasm", 3 | "version": "4.12.0", 4 | "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)", 5 | "main": "dist/index.umd.js", 6 | "module": "dist/index.esm.js", 7 | "types": "dist/lib/index.d.ts", 8 | "scripts": { 9 | "build": "sh -c ./scripts/build.sh", 10 | "lint": "npx @biomejs/biome check lib test", 11 | "prepublishOnly": "sh -c ./scripts/build.sh", 12 | "test": "node --expose-gc ./node_modules/.bin/jest --coverage --logHeapUsage", 13 | "webpack": "node webpack/webpack.js" 14 | }, 15 | "sideEffects": false, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/Daninet/hash-wasm.git" 19 | }, 20 | "keywords": [ 21 | "hash", 22 | "wasm", 23 | "webassembly", 24 | "md5", 25 | "adler-32", 26 | "crc32", 27 | "crc64", 28 | "sha-1", 29 | "sha-2", 30 | "sha-3", 31 | "xxhash", 32 | "keccak", 33 | "ripemd", 34 | "hmac", 35 | "pbkdf2", 36 | "blake2", 37 | "blake2b", 38 | "blake2s", 39 | "blake3", 40 | "fast", 41 | "hashing", 42 | "browser", 43 | "key", 44 | "derivation", 45 | "password", 46 | "md4", 47 | "sha", 48 | "sha1", 49 | "sha2", 50 | "sha224", 51 | "sha-224", 52 | "sha256", 53 | "sha-256", 54 | "sha384", 55 | "sha-384", 56 | "sha512", 57 | "sha-512", 58 | "sha3", 59 | "sha3-224", 60 | "sha3-256", 61 | "sha3-384", 62 | "sha3-512", 63 | "xxhash32", 64 | "xxhash64", 65 | "ripemd160", 66 | "argon2", 67 | "argon2i", 68 | "argon2d", 69 | "argon2id", 70 | "scrypt", 71 | "bcrypt", 72 | "sm3", 73 | "whirlpool" 74 | ], 75 | "bugs": { 76 | "url": "https://github.com/Daninet/hash-wasm/issues" 77 | }, 78 | "homepage": "https://github.com/Daninet/hash-wasm#readme", 79 | "author": "Dani Biró (https://danibiro.com)", 80 | "license": "MIT", 81 | "devDependencies": { 82 | "@biomejs/biome": "1.9.4", 83 | "@rollup/plugin-json": "^6.1.0", 84 | "@rollup/plugin-typescript": "^12.1.1", 85 | "@types/estree": "^1.0.6", 86 | "@types/jest": "^29.5.14", 87 | "@types/node": "^22.9.0", 88 | "binaryen": "^120.0.0", 89 | "jest": "^29.7.0", 90 | "rollup": "^4.27.3", 91 | "rollup-plugin-gzip": "^4.0.1", 92 | "rollup-plugin-license": "^3.5.3", 93 | "rollup-plugin-terser": "^7.0.2", 94 | "ts-jest": "^29.2.5", 95 | "ts-loader": "^9.5.1", 96 | "ts-node": "^10.9.2", 97 | "tslib": "^2.8.1", 98 | "typescript": "^5.6.3" 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | import json from "@rollup/plugin-json"; 3 | import { terser } from "rollup-plugin-terser"; 4 | // import gzipPlugin from 'rollup-plugin-gzip'; 5 | import license from "rollup-plugin-license"; 6 | 7 | const ALGORITHMS = [ 8 | "adler32", 9 | "argon2", 10 | "bcrypt", 11 | "blake2b", 12 | "blake2s", 13 | "blake3", 14 | "crc32", 15 | "crc64", 16 | "hmac", 17 | "keccak", 18 | "md4", 19 | "md5", 20 | "pbkdf2", 21 | "ripemd160", 22 | "scrypt", 23 | "sha1", 24 | "sha3", 25 | "sha224", 26 | "sha256", 27 | "sha384", 28 | "sha512", 29 | "sm3", 30 | "whirlpool", 31 | "xxhash32", 32 | "xxhash64", 33 | "xxhash3", 34 | "xxhash128", 35 | ]; 36 | 37 | const TERSER_CONFIG = { 38 | output: { 39 | comments: false, 40 | }, 41 | }; 42 | 43 | const LICENSE_CONFIG = { 44 | banner: { 45 | commentStyle: "ignored", 46 | content: `hash-wasm (https://www.npmjs.com/package/hash-wasm) 47 | (c) Dani Biro 48 | @license MIT`, 49 | }, 50 | }; 51 | 52 | const MAIN_BUNDLE_CONFIG = { 53 | input: "lib/index.ts", 54 | output: [ 55 | { 56 | file: "dist/index.umd.js", 57 | name: "hashwasm", 58 | format: "umd", 59 | }, 60 | { 61 | file: "dist/index.esm.js", 62 | format: "es", 63 | }, 64 | ], 65 | plugins: [json(), typescript(), license(LICENSE_CONFIG)], 66 | }; 67 | 68 | const MINIFIED_MAIN_BUNDLE_CONFIG = { 69 | input: "lib/index.ts", 70 | output: [ 71 | { 72 | file: "dist/index.umd.min.js", 73 | name: "hashwasm", 74 | format: "umd", 75 | }, 76 | { 77 | file: "dist/index.esm.min.js", 78 | format: "es", 79 | }, 80 | ], 81 | plugins: [ 82 | json(), 83 | typescript(), 84 | terser(TERSER_CONFIG), 85 | license(LICENSE_CONFIG), 86 | ], 87 | }; 88 | 89 | const INDIVIDUAL_BUNDLE_CONFIG = (algorithm) => ({ 90 | input: `lib/${algorithm}.ts`, 91 | output: [ 92 | { 93 | file: `dist/${algorithm}.umd.min.js`, 94 | name: "hashwasm", 95 | format: "umd", 96 | extend: true, 97 | }, 98 | ], 99 | plugins: [ 100 | json(), 101 | typescript(), 102 | terser(TERSER_CONFIG), 103 | license(LICENSE_CONFIG), 104 | // gzipPlugin(), 105 | ], 106 | }); 107 | 108 | export default [ 109 | MAIN_BUNDLE_CONFIG, 110 | MINIFIED_MAIN_BUNDLE_CONFIG, 111 | ...ALGORITHMS.map(INDIVIDUAL_BUNDLE_CONFIG), 112 | ]; 113 | -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.20.3 2 | RUN apk update && apk upgrade 3 | RUN apk add --no-cache clang clang-dev bash lld make 4 | -------------------------------------------------------------------------------- /scripts/Makefile-clang: -------------------------------------------------------------------------------- 1 | CFLAGS=-flto -O3 -nostdlib -fno-builtin -ffreestanding -mexec-model=reactor --target=wasm32 2 | LDFLAGS=-Wl,--strip-all -Wl,--initial-memory=131072 -Wl,--max-memory=131072 -Wl,--no-entry -Wl,--allow-undefined -Wl,--compress-relocations -Wl,--export-dynamic 3 | 4 | # -msimd128 -msign-ext -mmutable-globals -mmultivalue -mbulk-memory -mtail-call -munimplemented-simd128 5 | # -g -fdebug-prefix-map=/app/src=/C:/Projects/hash-wasm/src 6 | 7 | all : \ 8 | /app/wasm/adler32.wasm \ 9 | /app/wasm/argon2.wasm \ 10 | /app/wasm/bcrypt.wasm \ 11 | /app/wasm/blake2b.wasm \ 12 | /app/wasm/blake2s.wasm \ 13 | /app/wasm/blake3.wasm \ 14 | /app/wasm/crc32.wasm \ 15 | /app/wasm/crc64.wasm \ 16 | /app/wasm/md4.wasm \ 17 | /app/wasm/md5.wasm \ 18 | /app/wasm/ripemd160.wasm \ 19 | /app/wasm/scrypt.wasm \ 20 | /app/wasm/sha1.wasm \ 21 | /app/wasm/sha256.wasm \ 22 | /app/wasm/sha512.wasm \ 23 | /app/wasm/sha3.wasm \ 24 | /app/wasm/sm3.wasm \ 25 | /app/wasm/whirlpool.wasm \ 26 | /app/wasm/xxhash32.wasm \ 27 | /app/wasm/xxhash64.wasm \ 28 | /app/wasm/xxhash3.wasm \ 29 | /app/wasm/xxhash128.wasm 30 | clang --version 31 | wasm-ld --version 32 | 33 | # Generic targets: 34 | /app/wasm/%.wasm : /app/src/%.c 35 | clang $(CFLAGS) $(LDFLAGS) -o $@ $< 36 | sha1sum $@ 37 | stat -c "%n size: %s bytes" $@ 38 | 39 | # Targets that need special compile arguments: 40 | /app/wasm/argon2.wasm : /app/src/argon2.c 41 | clang $(CFLAGS) $(LDFLAGS) -Wl,--max-memory=2147483648 -o $@ $< 42 | sha1sum $@ 43 | stat -c "%n size: %s bytes" $@ 44 | 45 | /app/wasm/bcrypt.wasm : /app/src/bcrypt.c 46 | clang $(CFLAGS) $(LDFLAGS) -fno-strict-aliasing -o $@ $< 47 | sha1sum $@ 48 | stat -c "%n size: %s bytes" $@ 49 | 50 | /app/wasm/scrypt.wasm : /app/src/scrypt.c 51 | clang $(CFLAGS) $(LDFLAGS) -Wl,--max-memory=2147483648 -o $@ $< 52 | sha1sum $@ 53 | stat -c "%n size: %s bytes" $@ 54 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p dist 6 | mkdir -p wasm 7 | 8 | npm run lint 9 | 10 | if [[ "$(docker images -q clang:hash-wasm 2> /dev/null)" == "" ]]; then 11 | docker build -f scripts/Dockerfile -t clang:hash-wasm . 12 | fi 13 | 14 | # copy to docker volume 15 | docker rm hash-wasm-temp || true 16 | docker volume rm hash-wasm-volume || true 17 | docker container create --name hash-wasm-temp -v hash-wasm-volume:/app busybox 18 | docker cp . hash-wasm-temp:/app 19 | 20 | docker run \ 21 | --rm \ 22 | -v hash-wasm-volume:/app \ 23 | -u $(id -u):$(id -g) \ 24 | clang:hash-wasm \ 25 | make -f /app/scripts/Makefile-clang --silent --always-make --output-sync=target -j8 all 26 | 27 | # copy output back 28 | docker cp hash-wasm-temp:/app/wasm/ . 29 | docker rm hash-wasm-temp 30 | docker volume rm hash-wasm-volume 31 | 32 | # node scripts/optimize 33 | node scripts/make_json 34 | node --max-old-space-size=4096 ./node_modules/rollup/dist/bin/rollup -c 35 | npx tsc ./lib/index --outDir ./dist --downlevelIteration --emitDeclarationOnly --declaration --resolveJsonModule --allowSyntheticDefaultImports 36 | 37 | #-s ASSERTIONS=1 \ 38 | -------------------------------------------------------------------------------- /scripts/make_json.js: -------------------------------------------------------------------------------- 1 | const fs = require("node:fs"); 2 | const path = require("node:path"); 3 | const crypto = require("node:crypto"); 4 | 5 | const dir = path.resolve(__dirname, "..", "wasm"); 6 | const files = fs.readdirSync(dir).filter((file) => file.endsWith(".wasm")); 7 | 8 | for (const file of files) { 9 | const data = fs.readFileSync(path.join(dir, file)); 10 | const base64Data = data.toString("base64"); 11 | const parsedName = path.parse(file); 12 | 13 | const hash = crypto 14 | .createHash("sha1") 15 | .update(data) 16 | .digest("hex") 17 | .substring(0, 8); 18 | 19 | const json = JSON.stringify({ 20 | name: parsedName.name, 21 | data: base64Data, 22 | hash, 23 | }); 24 | 25 | fs.writeFileSync(path.join(dir, `${file}.json`), json); 26 | } 27 | -------------------------------------------------------------------------------- /scripts/optimize.js: -------------------------------------------------------------------------------- 1 | const fs = require("node:fs"); 2 | const binaryen = require("binaryen"); 3 | 4 | console.log("binaryen optimize start"); 5 | 6 | const mod = binaryen.readBinary(fs.readFileSync("./wasm/bcrypt.wasm")); 7 | mod.optimize(); 8 | 9 | const wasmData = mod.emitBinary(); 10 | fs.writeFileSync("./wasm/bcrypt.wasm", wasmData); 11 | 12 | console.log("binaryen optimize done"); 13 | -------------------------------------------------------------------------------- /src/adler32.c: -------------------------------------------------------------------------------- 1 | /* 2 | adler32.c -- compute the Adler-32 checksum of a data stream 3 | Copyright (C) 1995-2011, 2016 Mark Adler 4 | 5 | Licensed under the zlib license: 6 | 7 | Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler 8 | 9 | This software is provided 'as-is', without any express or implied 10 | warranty. In no event will the authors be held liable for any damages 11 | arising from the use of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, 14 | including commercial applications, and to alter it and redistribute it 15 | freely, subject to the following restrictions: 16 | 17 | 1. The origin of this software must not be misrepresented; you must not 18 | claim that you wrote the original software. If you use this software 19 | in a product, an acknowledgment in the product documentation would be 20 | appreciated but is not required. 21 | 2. Altered source versions must be plainly marked as such, and must not be 22 | misrepresented as being the original software. 23 | 3. This notice may not be removed or altered from any source distribution. 24 | 25 | Jean-loup Gailly Mark Adler 26 | jloup@gzip.org madler@alumni.caltech.edu 27 | 28 | Modified for hash-wasm by Nicholas Sherlock and Dani Biro, 2021 29 | */ 30 | 31 | #define WITH_BUFFER 32 | #include "hash-wasm.h" 33 | 34 | #define bswap_32(x) __builtin_bswap32(x) 35 | 36 | #define BASE 65521U /* largest prime smaller than 65536 */ 37 | #define NMAX 5552 38 | /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ 39 | 40 | #define DO1(b,i) adler += ((b) >> i) & 0xFF; sum2 += adler; 41 | #define DO4(buf,i) { uint32_t b = ((uint32_t*)buf)[i]; DO1(b,0); DO1(b,8); DO1(b,16); DO1(b,24); } 42 | #define DO16(buf) DO4(buf,0); DO4(buf,1); DO4(buf,2); DO4(buf,3); 43 | 44 | 45 | #define MOD(a) a %= BASE 46 | #define MOD28(a) a %= BASE 47 | #define MOD63(a) a %= BASE 48 | 49 | uint32_t previousAdler = 1; 50 | 51 | WASM_EXPORT 52 | void Hash_Init() { 53 | previousAdler = 1; 54 | } 55 | 56 | static uint32_t adler32(uint32_t adler, const uint8_t *buf, uint32_t len) { 57 | /* split Adler-32 into component sums */ 58 | uint32_t sum2 = (adler >> 16) & 0xffff; 59 | adler &= 0xffff; 60 | 61 | /* in case user likes doing a byte at a time, keep it fast */ 62 | if (len == 1) { 63 | adler += buf[0]; 64 | if (adler >= BASE) { 65 | adler -= BASE; 66 | } 67 | sum2 += adler; 68 | if (sum2 >= BASE) { 69 | sum2 -= BASE; 70 | } 71 | return adler | (sum2 << 16); 72 | } 73 | 74 | /* in case short lengths are provided, keep it somewhat fast */ 75 | if (len < 16) { 76 | while (len--) { 77 | adler += *buf++; 78 | sum2 += adler; 79 | } 80 | if (adler >= BASE) { 81 | adler -= BASE; 82 | } 83 | MOD28(sum2); /* only added so many BASE's */ 84 | return adler | (sum2 << 16); 85 | } 86 | 87 | /* do length NMAX blocks -- requires just one modulo operation */ 88 | while (len >= NMAX) { 89 | len -= NMAX; 90 | uint32_t n = NMAX / 16; /* NMAX is divisible by 16 */ 91 | do { 92 | DO16(buf); /* 16 sums unrolled */ 93 | buf += 16; 94 | } while (--n); 95 | MOD(adler); 96 | MOD(sum2); 97 | } 98 | 99 | /* do remaining bytes (less than NMAX, still just one modulo) */ 100 | if (len) { /* avoid modulos if none remaining */ 101 | while (len >= 16) { 102 | len -= 16; 103 | DO16(buf); 104 | buf += 16; 105 | } 106 | while (len--) { 107 | adler += *buf++; 108 | sum2 += adler; 109 | } 110 | MOD(adler); 111 | MOD(sum2); 112 | } 113 | 114 | /* return recombined sums */ 115 | return adler | (sum2 << 16); 116 | } 117 | 118 | WASM_EXPORT 119 | void Hash_Update(uint32_t len) { 120 | const uint8_t *buf = main_buffer; 121 | 122 | previousAdler = adler32(previousAdler, buf, len); 123 | } 124 | 125 | WASM_EXPORT 126 | void Hash_Final() { 127 | ((uint32_t*)main_buffer)[0] = bswap_32(previousAdler); 128 | } 129 | 130 | WASM_EXPORT 131 | const uint32_t STATE_SIZE = sizeof(previousAdler); 132 | 133 | WASM_EXPORT 134 | uint8_t* Hash_GetState() { 135 | return (uint8_t*) &previousAdler; 136 | } 137 | 138 | WASM_EXPORT 139 | void Hash_Calculate(uint32_t length) { 140 | Hash_Init(); 141 | Hash_Update(length); 142 | Hash_Final(); 143 | } 144 | -------------------------------------------------------------------------------- /src/crc32.c: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // Crc32.cpp 3 | // Copyright (c) 2011-2019 Stephan Brumme. All rights reserved. 4 | // Slicing-by-16 contributed by Bulat Ziganshin 5 | // Tableless bytewise CRC contributed by Hagai Gold 6 | // see http://create.stephan-brumme.com/disclaimer.html 7 | // 8 | // Modified for hash-wasm by Dani Biró 9 | // 10 | 11 | #define WITH_BUFFER 12 | #include "hash-wasm.h" 13 | 14 | #define bswap_32(x) __builtin_bswap32(x) 15 | 16 | alignas(128) static uint32_t crc32_lookup[8][256] = {0}; 17 | 18 | void init_lut(uint32_t polynomial) { 19 | for (int i = 0; i < 256; ++i) { 20 | uint32_t crc = i; 21 | for (int j = 0; j < 8; ++j) { 22 | crc = (crc >> 1) ^ (-(int32_t)(crc & 1) & polynomial); 23 | } 24 | crc32_lookup[0][i] = crc; 25 | } 26 | 27 | for (int i = 1; i < 256; ++i) { 28 | uint32_t lv = crc32_lookup[0][i]; 29 | for (int j = 1; j < 8; ++j) { 30 | lv = (lv >> 8) ^ crc32_lookup[0][lv & 255]; 31 | crc32_lookup[j][i] = lv; 32 | } 33 | } 34 | } 35 | 36 | uint32_t crc32_lut_initialized_to = 0; 37 | uint32_t previous_crc32 = 0; 38 | 39 | WASM_EXPORT 40 | void Hash_Init(uint32_t polynomial) { 41 | if (crc32_lut_initialized_to != polynomial) { 42 | init_lut(polynomial); 43 | crc32_lut_initialized_to = polynomial; 44 | } 45 | 46 | previous_crc32 = 0; 47 | } 48 | 49 | WASM_EXPORT 50 | void Hash_Update(uint32_t length) { 51 | const uint8_t *data = main_buffer; 52 | 53 | uint32_t crc = ~previous_crc32; // same as previous_crc32 ^ 0xFFFFFFFF 54 | const uint32_t *current = (const uint32_t *)data; 55 | 56 | // process eight bytes at once (Slicing-by-8) 57 | while (length >= 8) { 58 | uint32_t one = *current++ ^ crc; 59 | uint32_t two = *current++; 60 | crc = crc32_lookup[0][(two >> 24) & 0xFF] ^ 61 | crc32_lookup[1][(two >> 16) & 0xFF] ^ 62 | crc32_lookup[2][(two >> 8) & 0xFF] ^ crc32_lookup[3][two & 0xFF] ^ 63 | crc32_lookup[4][(one >> 24) & 0xFF] ^ 64 | crc32_lookup[5][(one >> 16) & 0xFF] ^ 65 | crc32_lookup[6][(one >> 8) & 0xFF] ^ crc32_lookup[7][one & 0xFF]; 66 | 67 | length -= 8; 68 | } 69 | 70 | const uint8_t *currentChar = (const uint8_t *)current; 71 | 72 | // remaining 1 to 7 bytes (standard algorithm) 73 | while (length-- != 0) { 74 | crc = (crc >> 8) ^ crc32_lookup[0][(crc & 0xFF) ^ *currentChar++]; 75 | } 76 | 77 | previous_crc32 = ~crc; // same as crc ^ 0xFFFFFFFF 78 | } 79 | 80 | WASM_EXPORT 81 | void Hash_Final() { ((uint32_t *)main_buffer)[0] = bswap_32(previous_crc32); } 82 | 83 | WASM_EXPORT 84 | const uint32_t STATE_SIZE = sizeof(previous_crc32); 85 | 86 | WASM_EXPORT 87 | uint8_t *Hash_GetState() { return (uint8_t *)&previous_crc32; } 88 | 89 | WASM_EXPORT 90 | void Hash_Calculate(uint32_t length, uint32_t initParam) { 91 | Hash_Init(initParam); 92 | Hash_Update(length); 93 | Hash_Final(); 94 | } 95 | -------------------------------------------------------------------------------- /src/crc64.c: -------------------------------------------------------------------------------- 1 | // Based on crc32.c implementation of Stephan Brumme 2 | // Modified for hash-wasm by Dani Biró 3 | 4 | #include 5 | #define WITH_BUFFER 6 | #include "hash-wasm.h" 7 | 8 | #define bswap_64(x) __builtin_bswap64(x) 9 | 10 | alignas(128) static uint64_t crc64_lookup[8][256] = {0}; 11 | 12 | void init_lut(uint64_t polynomial) { 13 | for (int i = 0; i < 256; ++i) { 14 | uint64_t crc = i; 15 | for (int j = 0; j < 8; ++j) { 16 | crc = (crc >> 1) ^ (-(int64_t)(crc & 1) & polynomial); 17 | } 18 | crc64_lookup[0][i] = crc; 19 | } 20 | 21 | for (int i = 1; i < 256; ++i) { 22 | uint64_t lv = crc64_lookup[0][i]; 23 | for (int j = 1; j < 8; ++j) { 24 | lv = (lv >> 8) ^ crc64_lookup[0][lv & 255]; 25 | crc64_lookup[j][i] = lv; 26 | } 27 | } 28 | } 29 | 30 | uint64_t crc64_lut_initialized_to = 0; 31 | uint64_t previous_crc64 = 0; 32 | 33 | WASM_EXPORT 34 | void Hash_Init() { 35 | // polynomial is at the memory object 36 | uint64_t polynomial = *((uint64_t *)main_buffer); 37 | 38 | if (crc64_lut_initialized_to != polynomial) { 39 | init_lut(polynomial); 40 | crc64_lut_initialized_to = polynomial; 41 | } 42 | 43 | previous_crc64 = 0; 44 | } 45 | 46 | WASM_EXPORT 47 | void Hash_Update(uint32_t length) { 48 | const uint8_t *data = main_buffer; 49 | 50 | uint64_t crc = ~previous_crc64; // same as previous_crc64 ^ 0xFFFFFFFF 51 | const uint64_t *current = (const uint64_t *)data; 52 | 53 | // process eight bytes at once (Slicing-by-8) 54 | while (length >= 8) { 55 | uint64_t val = *current++ ^ crc; 56 | crc = crc64_lookup[0][(val >> 56)] ^ crc64_lookup[1][(val >> 48) & 0xFF] ^ 57 | crc64_lookup[2][(val >> 40) & 0xFF] ^ 58 | crc64_lookup[3][(val >> 32) & 0xFF] ^ 59 | crc64_lookup[4][(val >> 24) & 0xFF] ^ 60 | crc64_lookup[5][(val >> 16) & 0xFF] ^ 61 | crc64_lookup[6][(val >> 8) & 0xFF] ^ crc64_lookup[7][val & 0xFF]; 62 | 63 | length -= 8; 64 | } 65 | 66 | const uint8_t *currentChar = (const uint8_t *)current; 67 | 68 | // remaining 1 to 7 bytes (standard algorithm) 69 | while (length-- != 0) { 70 | crc = (crc >> 8) ^ crc64_lookup[0][(crc & 0xFF) ^ *currentChar++]; 71 | } 72 | 73 | previous_crc64 = ~crc; 74 | } 75 | 76 | WASM_EXPORT 77 | void Hash_Final() { ((uint64_t *)main_buffer)[0] = bswap_64(previous_crc64); } 78 | 79 | WASM_EXPORT 80 | const uint32_t STATE_SIZE = sizeof(previous_crc64); 81 | 82 | WASM_EXPORT 83 | uint8_t *Hash_GetState() { return (uint8_t *)&previous_crc64; } 84 | 85 | WASM_EXPORT 86 | void Hash_Calculate() { return; } 87 | -------------------------------------------------------------------------------- /src/hash-wasm.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef NULL 5 | #define NULL 0 6 | #endif 7 | 8 | #ifdef _MSC_VER 9 | #define WASM_EXPORT 10 | #define __inline__ 11 | #else 12 | #define WASM_EXPORT __attribute__((visibility("default"))) 13 | #endif 14 | 15 | #ifdef WITH_BUFFER 16 | 17 | #define MAIN_BUFFER_SIZE 16 * 1024 18 | alignas(128) uint8_t main_buffer[MAIN_BUFFER_SIZE]; 19 | 20 | WASM_EXPORT 21 | uint8_t *Hash_GetBuffer() { 22 | return main_buffer; 23 | } 24 | 25 | #endif 26 | 27 | // Sometimes LLVM emits these functions during the optimization step 28 | // even with -nostdlib -fno-builtin flags 29 | static __inline__ void* memcpy(void* dst, const void* src, uint32_t cnt) { 30 | uint8_t *destination = dst; 31 | const uint8_t *source = src; 32 | while (cnt) { 33 | *(destination++)= *(source++); 34 | --cnt; 35 | } 36 | return dst; 37 | } 38 | 39 | static __inline__ void* memset(void* dst, const uint8_t value, uint32_t cnt) { 40 | uint8_t *p = dst; 41 | while (cnt--) { 42 | *p++ = value; 43 | } 44 | return dst; 45 | } 46 | 47 | static __inline__ void* memcpy2(void* dst, const void* src, uint32_t cnt) { 48 | uint64_t *destination64 = dst; 49 | const uint64_t *source64 = src; 50 | while (cnt >= 8) { 51 | *(destination64++)= *(source64++); 52 | cnt -= 8; 53 | } 54 | 55 | uint8_t *destination = (uint8_t*)destination64; 56 | const uint8_t *source = (uint8_t*)source64; 57 | while (cnt) { 58 | *(destination++)= *(source++); 59 | --cnt; 60 | } 61 | return dst; 62 | } 63 | 64 | static __inline__ void memcpy16(void* dst, const void* src) { 65 | uint64_t* dst64 = (uint64_t*)dst; 66 | uint64_t* src64 = (uint64_t*)src; 67 | 68 | dst64[0] = src64[0]; 69 | dst64[1] = src64[1]; 70 | } 71 | 72 | static __inline__ void memcpy32(void* dst, const void* src) { 73 | uint64_t* dst64 = (uint64_t*)dst; 74 | uint64_t* src64 = (uint64_t*)src; 75 | 76 | #pragma clang loop unroll(full) 77 | for (int i = 0; i < 4; i++) { 78 | dst64[i] = src64[i]; 79 | } 80 | } 81 | 82 | static __inline__ void memcpy64(void* dst, const void* src) { 83 | uint64_t* dst64 = (uint64_t*)dst; 84 | uint64_t* src64 = (uint64_t*)src; 85 | 86 | #pragma clang loop unroll(full) 87 | for (int i = 0; i < 8; i++) { 88 | dst64[i] = src64[i]; 89 | } 90 | } 91 | 92 | static __inline__ uint64_t widen8to64(const uint8_t value) { 93 | return value | (value << 8) | (value << 16) | (value << 24); 94 | } 95 | 96 | static __inline__ void memset16(void* dst, const uint8_t value) { 97 | uint64_t val = widen8to64(value); 98 | uint64_t* dst64 = (uint64_t*)dst; 99 | 100 | dst64[0] = val; 101 | dst64[1] = val; 102 | } 103 | 104 | static __inline__ void memset32(void* dst, const uint8_t value) { 105 | uint64_t val = widen8to64(value); 106 | uint64_t* dst64 = (uint64_t*)dst; 107 | 108 | #pragma clang loop unroll(full) 109 | for (int i = 0; i < 4; i++) { 110 | dst64[i] = val; 111 | } 112 | } 113 | 114 | static __inline__ void memset64(void* dst, const uint8_t value) { 115 | uint64_t val = widen8to64(value); 116 | uint64_t* dst64 = (uint64_t*)dst; 117 | 118 | #pragma clang loop unroll(full) 119 | for (int i = 0; i < 8; i++) { 120 | dst64[i] = val; 121 | } 122 | } 123 | 124 | static __inline__ void memset128(void* dst, const uint8_t value) { 125 | uint64_t val = widen8to64(value); 126 | uint64_t* dst64 = (uint64_t*)dst; 127 | 128 | #pragma clang loop unroll(full) 129 | for (int i = 0; i < 16; i++) { 130 | dst64[i] = val; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/sm3.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * SM3 function implementation 3 | * Copyright 2016 Yanbo Li dreamfly281@gmail.com, goldboar@163.com 4 | * MIT License 5 | * 6 | * Modified for hash-wasm by Dani Biró 7 | */ 8 | 9 | 10 | #define WITH_BUFFER 11 | #include "hash-wasm.h" 12 | 13 | #define SM3_DIGEST_LEN 32 14 | 15 | #define u8 uint8_t 16 | #define u32 uint32_t 17 | 18 | struct sm3_ctx { 19 | u32 total[2]; 20 | u32 state[8]; 21 | u8 buffer[64]; 22 | }; 23 | 24 | #define S(x,n) ((x << n) | (x >> (32 - n))) 25 | 26 | #define P0(x) (x ^ S(x, 9) ^ S(x,17)) 27 | #define P1(x) (x ^ S(x,15) ^ S(x,23)) 28 | 29 | #define PW(t) \ 30 | ( \ 31 | temp = W[t - 16] ^ W[t - 9] ^ (S(W[t - 3], 15)), \ 32 | P1(temp) ^ W[t - 6] ^ (S(W[t - 13], 7)) \ 33 | ) 34 | 35 | #define FF1(x,y,z) (x ^ y ^ z) 36 | #define FF2(x,y,z) ((x & y) | (x & z) | (y & z)) 37 | 38 | #define GG1(x,y,z) (x ^ y ^ z) 39 | #define GG2(x,y,z) ((x & y) | ((~x) & z)) 40 | 41 | #define T1 0x79cc4519 42 | #define T2 0x7a879d8a 43 | 44 | #define bswap_32(x) __builtin_bswap32(x) 45 | 46 | void sm3_init(struct sm3_ctx *ctx) { 47 | ctx->total[0] = 0; 48 | ctx->total[1] = 0; 49 | 50 | ctx->state[0] = 0x7380166f; 51 | ctx->state[1] = 0x4914b2b9; 52 | ctx->state[2] = 0x172442d7; 53 | ctx->state[3] = 0xda8a0600; 54 | ctx->state[4] = 0xa96f30bc; 55 | ctx->state[5] = 0x163138aa; 56 | ctx->state[6] = 0xe38dee4d; 57 | ctx->state[7] = 0xb0fb0e4e; 58 | } 59 | 60 | static void sm3_process(struct sm3_ctx *ctx, const u8 data[64]) { 61 | u32 temp, W[68], WP[64], A, B, C, D, E, F, G, H, SS1, SS2, TT1, TT2; 62 | int j, k; 63 | 64 | #pragma clang loop unroll(full) 65 | for (int i = 0; i < 16; i++) { 66 | W[i] = bswap_32(((u32 *) data)[i]); 67 | } 68 | 69 | W[16] = PW(16); 70 | W[17] = PW(17); 71 | W[18] = PW(18); 72 | W[19] = PW(19); 73 | 74 | A = ctx->state[0]; 75 | B = ctx->state[1]; 76 | C = ctx->state[2]; 77 | D = ctx->state[3]; 78 | E = ctx->state[4]; 79 | F = ctx->state[5]; 80 | G = ctx->state[6]; 81 | H = ctx->state[7]; 82 | 83 | // #pragma clang loop unroll(full) 84 | for (int i = 0; i < 16; i++) { 85 | WP[i] = W[i] ^ W[i+4]; 86 | 87 | SS1 = S(A, 12) + E + S(T1, i); 88 | SS1 = S(SS1, 7); 89 | SS2 = SS1 ^ S(A, 12); 90 | TT1 = FF1(A, B, C) + D + SS2 + WP[i]; 91 | TT2 = GG1(E, F, G) + H + SS1 + W[i]; 92 | D = C; 93 | C = S(B,9); 94 | B = A; 95 | A = TT1; 96 | H = G; 97 | G = S(F,19); 98 | F = E; 99 | E = P0(TT2); 100 | } 101 | 102 | // #pragma clang loop unroll(full) 103 | for (int i = 16; i < 64; i++) { 104 | k = i + 4; 105 | W[k] = PW(k); 106 | WP[i] = W[i] ^ W[i + 4]; 107 | 108 | j = i % 32; 109 | 110 | SS1 = S(A, 12) + E + S(T2, j); 111 | SS1 = S(SS1, 7); 112 | SS2 = SS1 ^ S(A, 12); 113 | TT1 = FF2(A, B, C) + D + SS2 + WP[i]; 114 | TT2 = GG2(E, F, G) + H + SS1 + W[i]; 115 | D = C; 116 | C = S(B, 9); 117 | B = A; 118 | A = TT1; 119 | H = G; 120 | G = S(F, 19); 121 | F = E; 122 | E = P0(TT2); 123 | } 124 | 125 | ctx->state[0] ^= A; 126 | ctx->state[1] ^= B; 127 | ctx->state[2] ^= C; 128 | ctx->state[3] ^= D; 129 | ctx->state[4] ^= E; 130 | ctx->state[5] ^= F; 131 | ctx->state[6] ^= G; 132 | ctx->state[7] ^= H; 133 | } 134 | 135 | static void sm3_update(struct sm3_ctx *ctx, const u8 *msg, u32 len) { 136 | u32 left, fill; 137 | 138 | if (!len) { 139 | return; 140 | } 141 | 142 | left = ctx->total[0] & 0x3F; 143 | fill = 64 - left; 144 | 145 | ctx->total[0] += len; 146 | ctx->total[0] &= 0xFFFFFFFF; 147 | 148 | if (ctx->total[0] < len) { 149 | ctx->total[1]++; 150 | } 151 | 152 | if (left && (len >= fill)) { 153 | memcpy((void *)(ctx->buffer + left), (void *)msg, fill); 154 | sm3_process(ctx, ctx->buffer); 155 | len -= fill; 156 | msg += fill; 157 | left = 0; 158 | } 159 | 160 | while (len >= 64) { 161 | sm3_process(ctx, msg); 162 | len -= 64; 163 | msg += 64; 164 | } 165 | 166 | if (len) { 167 | memcpy((void *)(ctx->buffer + left), (void *)msg, len); 168 | } 169 | } 170 | 171 | static u8 sm3_padding[64] = { 172 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 175 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 176 | }; 177 | 178 | static void sm3_finish(struct sm3_ctx *ctx, u8 digest[32]) { 179 | u32 last, padn; 180 | u32 high, low; 181 | u8 msglen[8]; 182 | 183 | high = (ctx->total[0] >> 29) 184 | | (ctx->total[1] << 3); 185 | low = (ctx->total[0] << 3); 186 | 187 | ((u32 *)msglen)[0] = bswap_32(high); 188 | ((u32 *)msglen)[1] = bswap_32(low); 189 | 190 | last = ctx->total[0] & 0x3F; 191 | padn = (last < 56 ) ? (56 - last) : (120 - last); 192 | 193 | sm3_update(ctx, sm3_padding, padn); 194 | sm3_update(ctx, msglen, 8); 195 | 196 | for (int i = 0; i < 8; i++) { 197 | ((u32 *)digest)[i] = bswap_32(ctx->state[i]); 198 | } 199 | } 200 | 201 | struct sm3_ctx ctx; 202 | 203 | WASM_EXPORT 204 | void Hash_Init() { 205 | sm3_init(&ctx); 206 | } 207 | 208 | WASM_EXPORT 209 | void Hash_Update(uint32_t size) { 210 | sm3_update(&ctx, main_buffer, size); 211 | } 212 | 213 | WASM_EXPORT 214 | void Hash_Final() { 215 | sm3_finish(&ctx, main_buffer); 216 | } 217 | 218 | WASM_EXPORT 219 | const uint32_t STATE_SIZE = sizeof(ctx); 220 | 221 | WASM_EXPORT 222 | uint8_t* Hash_GetState() { 223 | return (uint8_t*) &ctx; 224 | } 225 | 226 | WASM_EXPORT 227 | void Hash_Calculate(uint32_t length) { 228 | Hash_Init(); 229 | Hash_Update(length); 230 | Hash_Final(); 231 | } 232 | -------------------------------------------------------------------------------- /test/adler32.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { adler32, createAdler32 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await adler32("")).toBe("00000001"); 8 | expect(await adler32("a")).toBe("00620062"); 9 | expect(await adler32("1234567890")).toBe("0b2c020e"); 10 | expect(await adler32("a\x00")).toBe("00c40062"); 11 | expect(await adler32("abc")).toBe("024d0127"); 12 | expect(await adler32("message digest")).toBe("29750586"); 13 | expect(await adler32("abcdefghijklmnopqrstuvwxyz")).toBe("90860b20"); 14 | expect( 15 | await adler32( 16 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 17 | ), 18 | ).toBe("8adb150c"); 19 | expect( 20 | await adler32( 21 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 22 | ), 23 | ).toBe("97b61069"); 24 | }); 25 | 26 | test("unicode strings", async () => { 27 | expect(await adler32("😊")).toBe("075b02b2"); 28 | expect(await adler32("😊a😊")).toBe("1e1105c4"); 29 | const file = fs.readFileSync("./test/utf8.txt"); 30 | expect(await adler32(file)).toBe("dd7a5843"); 31 | expect(await adler32(file.toString())).toBe("dd7a5843"); 32 | }); 33 | 34 | test("Node.js buffers", async () => { 35 | expect(await adler32(Buffer.from([]))).toBe("00000001"); 36 | expect(await adler32(Buffer.from(["a".charCodeAt(0)]))).toBe("00620062"); 37 | expect(await adler32(Buffer.from([0]))).toBe("00010001"); 38 | expect(await adler32(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe("000f0004"); 39 | }); 40 | 41 | test("typed arrays", async () => { 42 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 43 | expect(await adler32(Buffer.from(arr))).toBe("0345020d"); 44 | const uint8 = new Uint8Array(arr); 45 | expect(await adler32(uint8)).toBe("0345020d"); 46 | expect(await adler32(new Uint16Array(uint8.buffer))).toBe("0345020d"); 47 | expect(await adler32(new Uint32Array(uint8.buffer))).toBe("0345020d"); 48 | }); 49 | 50 | test("long strings", async () => { 51 | const SIZE = 5 * 1024 * 1024; 52 | const chunk = "012345678\x09"; 53 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 54 | expect(await adler32(str)).toBe("de04df99"); 55 | }); 56 | 57 | test("long buffers", async () => { 58 | const SIZE = 5 * 1024 * 1024; 59 | const buf = Buffer.alloc(SIZE); 60 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 61 | expect(await adler32(buf)).toBe("1b87ca64"); 62 | }); 63 | 64 | test("chunked", async () => { 65 | const hash = await createAdler32(); 66 | expect(hash.digest()).toBe("00000001"); 67 | hash.init(); 68 | hash.update("a"); 69 | hash.update(new Uint8Array([0])); 70 | hash.update("bc"); 71 | hash.update(new Uint8Array([255, 254])); 72 | expect(hash.digest()).toBe("07f90324"); 73 | 74 | hash.init(); 75 | for (let i = 0; i < 1000; i++) { 76 | hash.update(new Uint8Array([i & 0xff])); 77 | } 78 | hash.update(Buffer.alloc(1000).fill(0xdf)); 79 | expect(hash.digest()).toBe("06904e90"); 80 | }); 81 | 82 | test("chunked increasing length", async () => { 83 | const hash = await createAdler32(); 84 | const test = async (maxLen: number) => { 85 | const chunks = getVariableLengthChunks(maxLen); 86 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 87 | const hashRef = await adler32(new Uint8Array(flatchunks)); 88 | hash.init(); 89 | for (const chunk of chunks) { 90 | hash.update(new Uint8Array(chunk)); 91 | } 92 | expect(hash.digest("hex")).toBe(hashRef); 93 | }; 94 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 95 | await Promise.all(maxLens.map((length) => test(length))); 96 | }); 97 | 98 | test("interlaced shorthand", async () => { 99 | const [hashA, hashB] = await Promise.all([adler32("a"), adler32("abc")]); 100 | expect(hashA).toBe("00620062"); 101 | expect(hashB).toBe("024d0127"); 102 | }); 103 | 104 | test("interlaced create", async () => { 105 | const hashA = await createAdler32(); 106 | hashA.update("a"); 107 | const hashB = await createAdler32(); 108 | hashB.update("abc"); 109 | expect(hashA.digest()).toBe("00620062"); 110 | expect(hashB.digest()).toBe("024d0127"); 111 | }); 112 | 113 | test("Invalid inputs throw", async () => { 114 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 115 | const hash = await createAdler32(); 116 | 117 | for (const input of invalidInputs) { 118 | await expect(adler32(input as any)).rejects.toThrow(); 119 | expect(() => hash.update(input as any)).toThrow(); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /test/api.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import * as api from "../lib"; 3 | import type { IHasher } from "../lib/WASMInterface"; 4 | 5 | async function createAllFunctions(includeHMAC): Promise { 6 | const keys = Object.keys(api).filter( 7 | (key) => key.startsWith("create") && (includeHMAC || key !== "createHMAC"), 8 | ); 9 | 10 | return Promise.all( 11 | keys.map((key) => { 12 | switch (key) { 13 | case "createHMAC": 14 | return api[key](api.createMD5(), "x"); 15 | default: 16 | return api[key](); 17 | } 18 | }), 19 | ); 20 | } 21 | 22 | test("IHasherApi", async () => { 23 | const functions: IHasher[] = await createAllFunctions(true); 24 | expect(functions.length).toBe(23); 25 | 26 | for (const fn of functions) { 27 | expect(fn.blockSize).toBeGreaterThan(0); 28 | expect(fn.digestSize).toBeGreaterThan(0); 29 | 30 | const startValueHex = fn.digest(); 31 | expect(typeof startValueHex).toBe("string"); 32 | fn.init(); 33 | expect(fn.digest()).toBe(startValueHex); 34 | fn.init(); 35 | expect(fn.digest("hex")).toBe(startValueHex); 36 | 37 | fn.init(); 38 | const startValueBinary = fn.digest("binary"); 39 | expect(ArrayBuffer.isView(startValueBinary)).toBe(true); 40 | expect(startValueBinary.BYTES_PER_ELEMENT).toBe(1); 41 | expect(startValueBinary.length).toBe(startValueHex.length / 2); 42 | fn.init(); 43 | expect(fn.digest("binary")).toStrictEqual(startValueBinary); 44 | 45 | const arr = new Array(2000).fill(0xff).map((i) => i % 256); 46 | const buf = Buffer.from(arr); 47 | fn.init(); 48 | fn.update(buf); 49 | const hash = fn.digest(); 50 | 51 | let chain = fn.init(); 52 | for (let i = 0; i < 2000; i++) { 53 | chain = chain.update(new Uint8Array([arr[i]])); 54 | } 55 | expect(chain.digest()).toBe(hash); 56 | 57 | expect(() => fn.digest()).toThrow(); 58 | expect(() => fn.update("a")).toThrow(); 59 | } 60 | }); 61 | 62 | test("saveAndLoad", async () => { 63 | const aHash: string[] = (await createAllFunctions(false)).map((fn) => { 64 | fn.init(); 65 | fn.update("a"); 66 | return fn.digest(); 67 | }); 68 | const abcHash: string[] = (await createAllFunctions(false)).map((fn) => { 69 | fn.init(); 70 | fn.update("abc"); 71 | return fn.digest(); 72 | }); 73 | 74 | const functions: IHasher[] = await createAllFunctions(false); 75 | 76 | expect(functions.length).toBe(22); 77 | 78 | functions.forEach((fn, index) => { 79 | fn.init(); 80 | fn.load(fn.save()); 81 | fn.update("a"); 82 | const saved = fn.save(); 83 | fn.update("bc"); 84 | expect(fn.digest()).toBe(abcHash[index]); 85 | fn.load(saved); 86 | expect(fn.digest()).toBe(aHash[index]); 87 | fn.load(saved); 88 | fn.update("bc"); 89 | expect(fn.digest()).toBe(abcHash[index]); 90 | // save() shoudn't work after digest() is called 91 | expect(() => fn.save()).toThrow(); 92 | }); 93 | }); 94 | 95 | test("saveAndLoad - load as init", async () => { 96 | // Verify that load() can be used instead of a call to init() and still give the same results 97 | // This checks that e.g. crc32's init_lut() gets called even if we don't call init() ourselves 98 | const helloWorldHashes = (await createAllFunctions(false)).map((fn) => { 99 | fn.init(); 100 | fn.update("Hello world"); 101 | return fn.digest(); 102 | }); 103 | expect(helloWorldHashes.length).toBe(22); 104 | const savedHasherStates = (await createAllFunctions(false)).map((fn) => { 105 | fn.update("Hello "); 106 | return fn.save(); 107 | }); 108 | (await createAllFunctions(false)).forEach((fn, index) => { 109 | fn.load(savedHasherStates[index]).update("world"); 110 | expect(fn.digest()).toBe(helloWorldHashes[index]); 111 | }); 112 | }); 113 | 114 | test("saveAndLoad - invalid parameters", async () => { 115 | const functions: IHasher[] = await createAllFunctions(false); 116 | 117 | // Detect changes in the function hash: 118 | for (const fn of functions) { 119 | fn.init(); 120 | expect(() => fn.load(0 as any)).toThrow(); 121 | expect(() => fn.load({} as any)).toThrow(); 122 | expect(() => fn.load("1234" as any)).toThrow(); 123 | expect(() => fn.load([] as any)).toThrow(); 124 | expect(() => fn.load(null as any)).toThrow(); 125 | expect(() => fn.load(undefined as any)).toThrow(); 126 | expect(() => fn.load(new ArrayBuffer(8) as any)).toThrow(); 127 | expect(() => fn.load(new Uint8ClampedArray(8) as any)).toThrow(); 128 | expect(() => fn.load(new Uint16Array(8) as any)).toThrow(); 129 | expect(() => fn.load(new Int8Array(8) as any)).toThrow(); 130 | } 131 | }); 132 | 133 | test("saveAndLoad - incompatible states", async () => { 134 | const functions: IHasher[] = await createAllFunctions(false); 135 | 136 | // Detect changes in the function hash: 137 | for (const fn of functions) { 138 | fn.init(); 139 | const state = fn.save(); 140 | // Check that every byte is verified: 141 | for (let i = 0; i < 4; i++) { 142 | state[i] = 255 - state[i]; 143 | expect(() => fn.load(state)).toThrow(); 144 | state[i] = 255 - state[i]; 145 | } 146 | } 147 | 148 | // Detect incompatible lengths: 149 | for (const fn of functions) { 150 | fn.init(); 151 | let state = fn.save(); 152 | state = state.subarray(0, state.length - 1); 153 | expect(() => fn.load(state)).toThrow(); 154 | } 155 | }); 156 | -------------------------------------------------------------------------------- /test/async.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import { 3 | blake2b, 4 | keccak, 5 | md4, 6 | md5, 7 | ripemd160, 8 | sha1, 9 | sha3, 10 | sha256, 11 | sha384, 12 | xxhash32, 13 | xxhash64, 14 | } from "../lib"; 15 | 16 | function getMemoryUsage() { 17 | const usage = process.memoryUsage().heapUsed; 18 | 19 | const i = ~~(Math.log2(usage) / 10); 20 | return `${(usage / 1024 ** i).toFixed(2)}${"KMGTPEZY"[i - 1] || ""}B`; 21 | } 22 | 23 | test("Async cycle multiple algorithms", async () => { 24 | console.log("Before", getMemoryUsage()); 25 | 26 | const promises = []; 27 | for (let i = 0; i < 250; i++) { 28 | promises.push(blake2b("a")); 29 | promises.push(md4("a")); 30 | promises.push(md5("a")); 31 | promises.push(sha1("a")); 32 | promises.push(sha256("a")); 33 | promises.push(sha384("a")); 34 | promises.push(sha3("a", 224)); 35 | promises.push(keccak("a", 224)); 36 | promises.push(ripemd160("a")); 37 | promises.push(xxhash32("a", 0x6789abcd)); 38 | promises.push(xxhash64("a")); 39 | } 40 | 41 | const res = await Promise.all(promises); 42 | for (let i = 0; i < 250 * 8; i += 11) { 43 | expect(res[i + 0]).toBe( 44 | "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c", 45 | ); 46 | expect(res[i + 1]).toBe("bde52cb31de33e46245e05fbdbd6fb24"); 47 | expect(res[i + 2]).toBe("0cc175b9c0f1b6a831c399e269772661"); 48 | expect(res[i + 3]).toBe("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); 49 | expect(res[i + 4]).toBe( 50 | "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", 51 | ); 52 | expect(res[i + 5]).toBe( 53 | "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31", 54 | ); 55 | expect(res[i + 6]).toBe( 56 | "9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", 57 | ); 58 | expect(res[i + 7]).toBe( 59 | "7cf87d912ee7088d30ec23f8e7100d9319bff090618b439d3fe91308", 60 | ); 61 | expect(res[i + 8]).toBe("0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); 62 | expect(res[i + 9]).toBe("88488ff7"); 63 | expect(res[i + 10]).toBe("d24ec4f1a98c6e5b"); 64 | } 65 | 66 | console.log("After", getMemoryUsage()); 67 | }); 68 | -------------------------------------------------------------------------------- /test/base.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import { 3 | adler32, 4 | blake2b, 5 | blake3, 6 | crc32, 7 | createMD4, 8 | keccak, 9 | md4, 10 | md5, 11 | ripemd160, 12 | sha1, 13 | sha3, 14 | sha256, 15 | sha384, 16 | xxhash32, 17 | xxhash64, 18 | } from "../lib"; 19 | import { MAX_HEAP } from "../lib/WASMInterface"; 20 | import { hexStringEqualsUInt8 } from "../lib/util"; 21 | 22 | test("Sync cycle multiple algorithms", async () => { 23 | for (let i = 0; i < 100; i++) { 24 | expect(await adler32("a")).toBe("00620062"); 25 | expect(await blake2b("a")).toBe( 26 | "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c", 27 | ); 28 | expect(await blake3("a")).toBe( 29 | "17762fddd969a453925d65717ac3eea21320b66b54342fde15128d6caf21215f", 30 | ); 31 | expect(await crc32("a")).toBe("e8b7be43"); 32 | expect(await md4("a")).toBe("bde52cb31de33e46245e05fbdbd6fb24"); 33 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 34 | expect(await ripemd160("a")).toBe( 35 | "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", 36 | ); 37 | expect(await sha1("a")).toBe("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); 38 | expect(await sha256("a")).toBe( 39 | "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", 40 | ); 41 | expect(await sha384("a")).toBe( 42 | "54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31", 43 | ); 44 | expect(await sha3("a", 224)).toBe( 45 | "9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", 46 | ); 47 | expect(await keccak("a", 224)).toBe( 48 | "7cf87d912ee7088d30ec23f8e7100d9319bff090618b439d3fe91308", 49 | ); 50 | expect(await xxhash32("a", 0x6789abcd)).toBe("88488ff7"); 51 | expect(await xxhash64("a")).toBe("d24ec4f1a98c6e5b"); 52 | } 53 | }); 54 | 55 | test("converting slices from typedarrays", async () => { 56 | const buffer = new ArrayBuffer(256); 57 | const intBuf = new Uint8Array(buffer); 58 | for (let i = 0; i < intBuf.length; i++) { 59 | intBuf[i] = i; 60 | } 61 | 62 | const int32Slice = new Uint32Array(buffer, 16, 4); 63 | const int16Slice = new Uint16Array(buffer, 16, 4 * 2); 64 | const int8Slice = new Uint8Array(buffer, 16, 4 * 4); 65 | 66 | expect(await md4(int32Slice)).toBe(await md4(int16Slice)); 67 | expect(await md4(int32Slice)).toBe(await md4(int8Slice)); 68 | }); 69 | 70 | test("unicode string length handling", async () => { 71 | const utf8 = ["a", "ѱ", "彁", "𠜎"]; 72 | const md4Instance = await createMD4(); 73 | 74 | for (let j = 0; j < 4; j++) { 75 | let str = ""; 76 | for (let i = 0; i < MAX_HEAP + 10; i++) { 77 | str += utf8[j]; 78 | const ok = await md4(Buffer.from(str)); 79 | expect(await md4(str)).toBe(ok); 80 | md4Instance.init(); 81 | md4Instance.update(str); 82 | expect(md4Instance.digest()).toBe(ok); 83 | } 84 | } 85 | }); 86 | 87 | test("hexStringEqualsUInt8()", async () => { 88 | expect(hexStringEqualsUInt8("", new Uint8Array([]))).toBe(true); 89 | expect(hexStringEqualsUInt8("", new Uint8Array([0]))).toBe(false); 90 | expect(hexStringEqualsUInt8("AB", new Uint8Array([0xab]))).toBe(true); 91 | expect(hexStringEqualsUInt8("ABCD", new Uint8Array([0xab, 0xcd]))).toBe(true); 92 | expect(hexStringEqualsUInt8("ABCD", new Uint8Array([0xab]))).toBe(false); 93 | expect(hexStringEqualsUInt8("ABCD", new Uint8Array([0x1b, 0xcd]))).toBe( 94 | false, 95 | ); 96 | expect(hexStringEqualsUInt8("ABCD", new Uint8Array([0xab, 0x0d]))).toBe( 97 | false, 98 | ); 99 | expect(hexStringEqualsUInt8("ABCD", new Uint8Array([0xab, 0xc0]))).toBe( 100 | false, 101 | ); 102 | expect( 103 | hexStringEqualsUInt8("123456", new Uint8Array([0x12, 0x34, 0x56])), 104 | ).toBe(true); 105 | expect( 106 | hexStringEqualsUInt8("123456", new Uint8Array([0x12, 0x34, 0x66])), 107 | ).toBe(false); 108 | }); 109 | -------------------------------------------------------------------------------- /test/base64.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import { decodeBase64, encodeBase64, getDecodeBase64Length } from "../lib/util"; 3 | 4 | test("encodes basic base64 strings", () => { 5 | expect(encodeBase64(Buffer.from(""))).toBe(""); 6 | expect(encodeBase64(Buffer.from("f"))).toBe("Zg=="); 7 | expect(encodeBase64(Buffer.from("fo"))).toBe("Zm8="); 8 | expect(encodeBase64(Buffer.from("foo"))).toBe("Zm9v"); 9 | expect(encodeBase64(Buffer.from("foob"))).toBe("Zm9vYg=="); 10 | expect(encodeBase64(Buffer.from("fooba"))).toBe("Zm9vYmE="); 11 | expect(encodeBase64(Buffer.from("foobar"))).toBe("Zm9vYmFy"); 12 | 13 | expect(encodeBase64(Buffer.from(""), false)).toBe(""); 14 | expect(encodeBase64(Buffer.from("f"), false)).toBe("Zg"); 15 | expect(encodeBase64(Buffer.from("fo"), false)).toBe("Zm8"); 16 | expect(encodeBase64(Buffer.from("foo"), false)).toBe("Zm9v"); 17 | expect(encodeBase64(Buffer.from("foob"), false)).toBe("Zm9vYg"); 18 | expect(encodeBase64(Buffer.from("fooba"), false)).toBe("Zm9vYmE"); 19 | expect(encodeBase64(Buffer.from("foobar"), false)).toBe("Zm9vYmFy"); 20 | }); 21 | 22 | test("encodes binary base64", () => { 23 | for (let i = 800; i < 1050; i++) { 24 | let buf = Buffer.alloc(i, 0xff); 25 | expect(encodeBase64(buf)).toBe(buf.toString("base64")); 26 | buf = Buffer.alloc(i, 0x00); 27 | expect(encodeBase64(buf)).toBe(buf.toString("base64")); 28 | buf = Buffer.alloc(i, i % 256); 29 | expect(encodeBase64(buf)).toBe(buf.toString("base64")); 30 | buf = Buffer.alloc(i); 31 | for (let j = 0; j < i; j++) { 32 | buf[j] = j % 256; 33 | } 34 | expect(encodeBase64(buf)).toBe(buf.toString("base64")); 35 | } 36 | }); 37 | 38 | const toHex = (a: Uint8Array): string => Buffer.from(a).toString("hex"); 39 | 40 | test("decodes basic base64 strings", () => { 41 | expect(toHex(decodeBase64(""))).toBe(""); 42 | expect(toHex(decodeBase64("Zg=="))).toBe(Buffer.from("f").toString("hex")); 43 | expect(toHex(decodeBase64("Zg"))).toBe(Buffer.from("f").toString("hex")); 44 | expect(toHex(decodeBase64("Zm8="))).toBe(Buffer.from("fo").toString("hex")); 45 | expect(toHex(decodeBase64("Zm8"))).toBe(Buffer.from("fo").toString("hex")); 46 | expect(toHex(decodeBase64("Zm9v"))).toBe(Buffer.from("foo").toString("hex")); 47 | expect(toHex(decodeBase64("Zm9vYg=="))).toBe( 48 | Buffer.from("foob").toString("hex"), 49 | ); 50 | expect(toHex(decodeBase64("Zm9vYg"))).toBe( 51 | Buffer.from("foob").toString("hex"), 52 | ); 53 | expect(toHex(decodeBase64("Zm9vYmE="))).toBe( 54 | Buffer.from("fooba").toString("hex"), 55 | ); 56 | expect(toHex(decodeBase64("Zm9vYmE"))).toBe( 57 | Buffer.from("fooba").toString("hex"), 58 | ); 59 | expect(toHex(decodeBase64("Zm9vYmFy"))).toBe( 60 | Buffer.from("foobar").toString("hex"), 61 | ); 62 | }); 63 | 64 | test("decodes binary base64", () => { 65 | for (let i = 800; i < 1050; i++) { 66 | let buf = Buffer.alloc(i, 0xff); 67 | expect(toHex(decodeBase64(buf.toString("base64")))).toStrictEqual( 68 | buf.toString("hex"), 69 | ); 70 | buf = Buffer.alloc(i, 0x00); 71 | expect(toHex(decodeBase64(buf.toString("base64")))).toStrictEqual( 72 | buf.toString("hex"), 73 | ); 74 | buf = Buffer.alloc(i, i % 256); 75 | expect(toHex(decodeBase64(buf.toString("base64")))).toStrictEqual( 76 | buf.toString("hex"), 77 | ); 78 | buf = Buffer.alloc(i); 79 | for (let j = 0; j < i; j++) { 80 | buf[j] = j % 256; 81 | } 82 | expect(toHex(decodeBase64(buf.toString("base64")))).toStrictEqual( 83 | buf.toString("hex"), 84 | ); 85 | } 86 | }); 87 | 88 | test("encode-decode", () => { 89 | for (let i = 800; i < 1050; i++) { 90 | let buf = Buffer.alloc(i, 0xff); 91 | expect(toHex(decodeBase64(encodeBase64(buf, false)))).toStrictEqual( 92 | buf.toString("hex"), 93 | ); 94 | expect(toHex(decodeBase64(encodeBase64(buf, true)))).toStrictEqual( 95 | buf.toString("hex"), 96 | ); 97 | buf = Buffer.alloc(i, 0x00); 98 | expect(toHex(decodeBase64(encodeBase64(buf, false)))).toStrictEqual( 99 | buf.toString("hex"), 100 | ); 101 | expect(toHex(decodeBase64(encodeBase64(buf, true)))).toStrictEqual( 102 | buf.toString("hex"), 103 | ); 104 | buf = Buffer.alloc(i, i % 256); 105 | expect(toHex(decodeBase64(encodeBase64(buf, false)))).toStrictEqual( 106 | buf.toString("hex"), 107 | ); 108 | expect(toHex(decodeBase64(encodeBase64(buf, true)))).toStrictEqual( 109 | buf.toString("hex"), 110 | ); 111 | buf = Buffer.alloc(i); 112 | for (let j = 0; j < i; j++) { 113 | buf[j] = j % 256; 114 | } 115 | expect(toHex(decodeBase64(encodeBase64(buf, false)))).toStrictEqual( 116 | buf.toString("hex"), 117 | ); 118 | expect(toHex(decodeBase64(encodeBase64(buf, true)))).toStrictEqual( 119 | buf.toString("hex"), 120 | ); 121 | } 122 | }); 123 | 124 | test("decode length", () => { 125 | expect(getDecodeBase64Length("")).toBe(0); 126 | expect(getDecodeBase64Length("Zg==")).toBe(1); 127 | expect(getDecodeBase64Length("Zg")).toBe(1); 128 | expect(getDecodeBase64Length("Zm8=")).toBe(2); 129 | expect(getDecodeBase64Length("Zm8")).toBe(2); 130 | expect(getDecodeBase64Length("Zm9v")).toBe(3); 131 | expect(getDecodeBase64Length("Zm9vYg==")).toBe(4); 132 | expect(getDecodeBase64Length("Zm9vYg")).toBe(4); 133 | expect(getDecodeBase64Length("Zm9vYmE=")).toBe(5); 134 | expect(getDecodeBase64Length("Zm9vYmE")).toBe(5); 135 | expect(getDecodeBase64Length("Zm9vYmFy")).toBe(6); 136 | }); 137 | -------------------------------------------------------------------------------- /test/blake2b-128.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { blake2b, createBLAKE2b } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await blake2b("", 128)).toBe("cae66941d9efbd404e4d88758ea67670"); 8 | expect(await blake2b("a", 128)).toBe("27c35e6e9373877f29e562464e46497e"); 9 | expect(await blake2b("a\x00", 128)).toBe("396660e76c84bb7786f979f10b58fa79"); 10 | expect(await blake2b("abc", 128)).toBe("cf4ab791c62b8d2b2109c90275287816"); 11 | expect(await blake2b("message digest", 128)).toBe( 12 | "a235c121347fdd24feffe048dbe68ccc", 13 | ); 14 | expect(await blake2b("abcdefghijklmnopqrstuvwxyz", 128)).toBe( 15 | "82a82a043c4946fa81b9a598a3e8d35b", 16 | ); 17 | expect( 18 | await blake2b( 19 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 20 | 128, 21 | ), 22 | ).toBe("38d187cd913684abd1d08f63337bdde1"); 23 | expect( 24 | await blake2b( 25 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 26 | 128, 27 | ), 28 | ).toBe("3242cc3901ffad79cb164104a9486881"); 29 | }); 30 | 31 | test("unicode strings", async () => { 32 | expect(await blake2b("😊", 128)).toBe("ad99432ec7a06269504c35cb14d308b6"); 33 | expect(await blake2b("😊a😊", 128)).toBe("9f3f9baef29c626fa205069b1729c46d"); 34 | const file = fs.readFileSync("./test/utf8.txt"); 35 | expect(await blake2b(file, 128)).toBe("bfa7e10f39f0120c640b0c09122a13b2"); 36 | expect(await blake2b(file.toString(), 128)).toBe( 37 | "bfa7e10f39f0120c640b0c09122a13b2", 38 | ); 39 | }); 40 | 41 | test("Node.js buffers", async () => { 42 | expect(await blake2b(Buffer.from([]), 128)).toBe( 43 | "cae66941d9efbd404e4d88758ea67670", 44 | ); 45 | expect(await blake2b(Buffer.from(["a".charCodeAt(0)]), 128)).toBe( 46 | "27c35e6e9373877f29e562464e46497e", 47 | ); 48 | expect(await blake2b(Buffer.from([0]), 128)).toBe( 49 | "7025e075d5e2f6cde3cc051a31f07660", 50 | ); 51 | expect(await blake2b(Buffer.from([0, 1, 0, 0, 2, 0]), 128)).toBe( 52 | "05b74966098218dd7a4e538981ac2984", 53 | ); 54 | }); 55 | 56 | test("typed arrays", async () => { 57 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 58 | expect(await blake2b(Buffer.from(arr), 128)).toBe( 59 | "9533446a2d7e4ebd36f196ab18ff5040", 60 | ); 61 | const uint8 = new Uint8Array(arr); 62 | expect(await blake2b(uint8, 128)).toBe("9533446a2d7e4ebd36f196ab18ff5040"); 63 | expect(await blake2b(new Uint16Array(uint8.buffer), 128)).toBe( 64 | "9533446a2d7e4ebd36f196ab18ff5040", 65 | ); 66 | expect(await blake2b(new Uint32Array(uint8.buffer), 128)).toBe( 67 | "9533446a2d7e4ebd36f196ab18ff5040", 68 | ); 69 | }); 70 | 71 | test("long strings", async () => { 72 | const SIZE = 5 * 1024 * 1024; 73 | const chunk = "012345678\x09"; 74 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 75 | expect(await blake2b(str, 128)).toBe("b403f117a7480ac5d6e2c95a36f297c6"); 76 | }); 77 | 78 | test("long buffers", async () => { 79 | const SIZE = 5 * 1024 * 1024; 80 | const buf = Buffer.alloc(SIZE); 81 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 82 | expect(await blake2b(buf, 128)).toBe("2ac5ecb59ea724a158e3fc27ea995d9a"); 83 | }); 84 | 85 | test("chunked", async () => { 86 | const hash = await createBLAKE2b(128); 87 | expect(hash.digest()).toBe("cae66941d9efbd404e4d88758ea67670"); 88 | hash.init(); 89 | hash.update("a"); 90 | hash.update(new Uint8Array([0])); 91 | hash.update("bc"); 92 | hash.update(new Uint8Array([255, 254])); 93 | expect(hash.digest()).toBe("2c11b76032e1a69a2fbb51f97a0cd38b"); 94 | 95 | hash.init(); 96 | for (let i = 0; i < 1000; i++) { 97 | hash.update(new Uint8Array([i & 0xff])); 98 | } 99 | hash.update(Buffer.alloc(1000).fill(0xdf)); 100 | expect(hash.digest()).toBe("7bf7eefed052e81f351589df83c8cde2"); 101 | }); 102 | 103 | test("chunked increasing length", async () => { 104 | const hash = await createBLAKE2b(128); 105 | const test = async (maxLen: number) => { 106 | const chunks = getVariableLengthChunks(maxLen); 107 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 108 | const hashRef = await blake2b(new Uint8Array(flatchunks), 128); 109 | hash.init(); 110 | for (const chunk of chunks) { 111 | hash.update(new Uint8Array(chunk)); 112 | } 113 | expect(hash.digest("hex")).toBe(hashRef); 114 | }; 115 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 116 | await Promise.all(maxLens.map((length) => test(length))); 117 | }); 118 | 119 | test("interlaced shorthand", async () => { 120 | const [hashA, hashB] = await Promise.all([ 121 | blake2b("a", 128), 122 | blake2b("abc", 128), 123 | ]); 124 | expect(hashA).toBe("27c35e6e9373877f29e562464e46497e"); 125 | expect(hashB).toBe("cf4ab791c62b8d2b2109c90275287816"); 126 | }); 127 | 128 | test("interlaced create", async () => { 129 | const hashA = await createBLAKE2b(128); 130 | hashA.update("a"); 131 | const hashB = await createBLAKE2b(128); 132 | hashB.update("abc"); 133 | expect(hashA.digest()).toBe("27c35e6e9373877f29e562464e46497e"); 134 | expect(hashB.digest()).toBe("cf4ab791c62b8d2b2109c90275287816"); 135 | }); 136 | 137 | test("Invalid inputs throw", async () => { 138 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 139 | const hash = await createBLAKE2b(128); 140 | 141 | for (const input of invalidInputs) { 142 | await expect(blake2b(input as any, 128)).rejects.toThrow(); 143 | expect(() => hash.update(input as any)).toThrow(); 144 | } 145 | }); 146 | -------------------------------------------------------------------------------- /test/blake2b-keys.test.ts: -------------------------------------------------------------------------------- 1 | import { blake2b, createBLAKE2b } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("simple keys", async () => { 5 | expect(await blake2b("", 256, "")).toBe( 6 | "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8", 7 | ); 8 | expect(await blake2b("", 256, "this is the password")).toBe( 9 | "386435aaf3ba280ea9b3ebd7b42d9c5bbf63f23dbae9ab91553332a588fe337c", 10 | ); 11 | expect(await blake2b("", 256, "this is the password123")).toBe( 12 | "27e55781cb30cd6b8f81c7eade5939d1d8752a79aa3dd63d16a186023a5b9252", 13 | ); 14 | expect(await blake2b("a", 256, "this is the password")).toBe( 15 | "1e4287cf1a916ec4d54a4847774bb50371b20d867083881f8a7ce3f3b8b2c74a", 16 | ); 17 | expect(await blake2b("abc", 256, "this is the password")).toBe( 18 | "5652f81b0858c71cae415dd2749b6a52a614e3207328cbfa806f40a7a36cf561", 19 | ); 20 | 21 | const hash = await createBLAKE2b(256, "this is the password"); 22 | hash.update("a"); 23 | expect(hash.digest()).toBe( 24 | "1e4287cf1a916ec4d54a4847774bb50371b20d867083881f8a7ce3f3b8b2c74a", 25 | ); 26 | }); 27 | 28 | test("unicode keys", async () => { 29 | expect(await blake2b("a", 256, "😊")).toBe( 30 | "43a791ea899560fc25e3a18eaa11f85b640a6802e7c94b08ac51f0efe39806c9", 31 | ); 32 | expect(await blake2b("😊a😊", 256, "😊a😊")).toBe( 33 | "0dcc9cd4672ad13d447a3f64399d20d5ac8b2c835746b570c1e3ff4d735dfc96", 34 | ); 35 | expect(await blake2b("a", 512, "😊")).toBe( 36 | "662f46f559501f4fb780bba0a410c8a5bb58f646a02b1361aeb9f0c9c29dd3faf99db0abc6132d302e5e07cec147cc0359cf0c4568e6bfc6455fe12e3eed6d5b", 37 | ); 38 | expect(await blake2b("😊a😊", 512, "😊a😊")).toBe( 39 | "5e644d389195eb2fa99227fbcde278f290d8a02c50202fa59b71a53d5d0810b03cfaff7e609b57daba9fecb8ab9beabf75f42ce263dfaaf0bd6bb84255dec16a", 40 | ); 41 | }); 42 | 43 | test("Node.js buffers", async () => { 44 | expect(await blake2b("a", 256, Buffer.from([]))).toBe( 45 | "8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4", 46 | ); 47 | expect(await blake2b("a", 256, Buffer.from(["a".charCodeAt(0)]))).toBe( 48 | "f5ae00102c0fc6fd2ca53a0c9b6a7f7ccddec83de24473609ac0c30af6a4b5f2", 49 | ); 50 | const key = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]); 51 | expect(await blake2b("a", 256, key)).toBe( 52 | "0f94022d7654f36fb4182dcba4fb893045ebdbbc0ffa55f1ed2c201d3ee1106e", 53 | ); 54 | }); 55 | 56 | test("typed arrays", async () => { 57 | const key = Buffer.from([ 58 | 0, 1, 2, 3, 4, 255, 254, 7, 0, 1, 254, 3, 4, 255, 0, 7, 59 | ]); 60 | expect(await blake2b("a", 256, Buffer.from(key))).toBe( 61 | "3584ad664f76aaa768424a8a125d3052a9ea5bc90c18c06ffba60e980f2d9f3a", 62 | ); 63 | const uint8 = new Uint8Array(key); 64 | expect(await blake2b("a", 256, uint8)).toBe( 65 | "3584ad664f76aaa768424a8a125d3052a9ea5bc90c18c06ffba60e980f2d9f3a", 66 | ); 67 | expect(await blake2b("a", 256, new Uint16Array(uint8.buffer))).toBe( 68 | "3584ad664f76aaa768424a8a125d3052a9ea5bc90c18c06ffba60e980f2d9f3a", 69 | ); 70 | expect(await blake2b("a", 256, new Uint32Array(uint8.buffer))).toBe( 71 | "3584ad664f76aaa768424a8a125d3052a9ea5bc90c18c06ffba60e980f2d9f3a", 72 | ); 73 | }); 74 | 75 | test("long syntax", async () => { 76 | const hash = await createBLAKE2b(256, "this is the password"); 77 | hash.update("a"); 78 | expect(hash.digest()).toBe( 79 | "1e4287cf1a916ec4d54a4847774bb50371b20d867083881f8a7ce3f3b8b2c74a", 80 | ); 81 | hash.init(); 82 | expect(hash.digest()).toBe( 83 | "386435aaf3ba280ea9b3ebd7b42d9c5bbf63f23dbae9ab91553332a588fe337c", 84 | ); 85 | hash.init(); 86 | hash.update("a"); 87 | hash.update("b"); 88 | hash.update("c"); 89 | expect(hash.digest()).toBe( 90 | "5652f81b0858c71cae415dd2749b6a52a614e3207328cbfa806f40a7a36cf561", 91 | ); 92 | }); 93 | 94 | test("Invalid keys throw", async () => { 95 | const invalidKeys = [0, 1, Number(1), {}, []]; 96 | 97 | for (const key of invalidKeys) { 98 | await expect(() => blake2b("a", 256, key as any)).toThrow(); 99 | await expect(() => createBLAKE2b(256, key as any)).toThrow(); 100 | } 101 | }); 102 | 103 | test("Too long keys reject", async () => { 104 | const invalidKeys = [ 105 | new Array(65).fill("x").join(""), 106 | Buffer.alloc(65), 107 | new Uint8Array(65), 108 | new Uint32Array(17), 109 | new Array(17).fill("😊").join(""), 110 | ]; 111 | 112 | for (const key of invalidKeys) { 113 | await expect(() => blake2b("a", 256, key as any)).rejects.toThrow(); 114 | await expect(() => createBLAKE2b(256, key as any)).rejects.toThrow(); 115 | } 116 | }); 117 | 118 | test("small digest size", async () => { 119 | expect(await blake2b("abc", 8, "123")).toBe("f1"); 120 | expect(await blake2b("abc", 16, "1")).toBe("872f"); 121 | expect(await blake2b("abc", 24, "123")).toBe("ee2d74"); 122 | expect(await blake2b("", 32, "123")).toBe("2c4839fc"); 123 | }); 124 | -------------------------------------------------------------------------------- /test/blake2b.test.ts: -------------------------------------------------------------------------------- 1 | import { blake2b, createBLAKE2b } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("invalid parameters", async () => { 5 | await expect(blake2b("", -1 as any)).rejects.toThrow(); 6 | await expect(blake2b("", "a" as any)).rejects.toThrow(); 7 | await expect(blake2b("", 223 as any)).rejects.toThrow(); 8 | await expect(blake2b("", 0 as any)).rejects.toThrow(); 9 | await expect(blake2b("", 127 as any)).rejects.toThrow(); 10 | await expect(blake2b("", 513 as any)).rejects.toThrow(); 11 | await expect(blake2b("", null as any)).rejects.toThrow(); 12 | await expect(blake2b("", 1024 as any)).rejects.toThrow(); 13 | 14 | await expect(createBLAKE2b(-1 as any)).rejects.toThrow(); 15 | await expect(createBLAKE2b("a" as any)).rejects.toThrow(); 16 | await expect(createBLAKE2b(223 as any)).rejects.toThrow(); 17 | await expect(createBLAKE2b(0 as any)).rejects.toThrow(); 18 | await expect(createBLAKE2b(127 as any)).rejects.toThrow(); 19 | await expect(createBLAKE2b(513 as any)).rejects.toThrow(); 20 | await expect(createBLAKE2b(null as any)).rejects.toThrow(); 21 | await expect(createBLAKE2b(1024 as any)).rejects.toThrow(); 22 | }); 23 | 24 | test("default value for create constructor", async () => { 25 | const hash = await blake2b("a", 512); 26 | const hasher = await createBLAKE2b(); 27 | hasher.init(); 28 | hasher.update("a"); 29 | expect(hasher.digest()).toBe(hash); 30 | }); 31 | 32 | test("it invalidates the cache when changing parameters", async () => { 33 | expect(await blake2b("a", 128)).toBe("27c35e6e9373877f29e562464e46497e"); 34 | expect(await blake2b("a", 256)).toBe( 35 | "8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4", 36 | ); 37 | expect(await blake2b("a", 384)).toBe( 38 | "7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7c94c29399c50de86983af1ec61d5dcef0", 39 | ); 40 | expect(await blake2b("a")).toBe( 41 | "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c", 42 | ); 43 | 44 | let hash = await createBLAKE2b(128); 45 | hash.update("a"); 46 | expect(hash.digest()).toBe("27c35e6e9373877f29e562464e46497e"); 47 | 48 | hash = await createBLAKE2b(256); 49 | hash.update("a"); 50 | expect(hash.digest()).toBe( 51 | "8928aae63c84d87ea098564d1e03ad813f107add474e56aedd286349c0c03ea4", 52 | ); 53 | 54 | hash = await createBLAKE2b(384); 55 | hash.update("a"); 56 | expect(hash.digest()).toBe( 57 | "7d40de16ff771d4595bf70cbda0c4ea0a066a6046fa73d34471cd4d93d827d7c94c29399c50de86983af1ec61d5dcef0", 58 | ); 59 | 60 | hash = await createBLAKE2b(); 61 | hash.update("a"); 62 | expect(hash.digest()).toBe( 63 | "333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c", 64 | ); 65 | }); 66 | 67 | test("small digest size", async () => { 68 | expect(await blake2b("abc", 8)).toBe("6b"); 69 | expect(await blake2b("abc", 16)).toBe("ae1e"); 70 | expect(await blake2b("abc", 24)).toBe("8c45ed"); 71 | expect(await blake2b("", 32)).toBe("1271cf25"); 72 | 73 | let hash = await createBLAKE2b(8); 74 | hash.update("abc"); 75 | expect(hash.digest()).toBe("6b"); 76 | 77 | hash = await createBLAKE2b(16); 78 | hash.update("abc"); 79 | expect(hash.digest()).toBe("ae1e"); 80 | 81 | hash = await createBLAKE2b(24); 82 | hash.update("abc"); 83 | expect(hash.digest()).toBe("8c45ed"); 84 | 85 | hash = await createBLAKE2b(32); 86 | hash.update(""); 87 | expect(hash.digest()).toBe("1271cf25"); 88 | }); 89 | -------------------------------------------------------------------------------- /test/blake2s-128.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { blake2s, createBLAKE2s } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await blake2s("", 128)).toBe("64550d6ffe2c0a01a14aba1eade0200c"); 8 | expect(await blake2s("a", 128)).toBe("854b9e9ba49bfd9457d4c3bf96e42523"); 9 | expect(await blake2s("a\x00", 128)).toBe("eeba73053b247274c536072c4b46d8ce"); 10 | expect(await blake2s("abc", 128)).toBe("aa4938119b1dc7b87cbad0ffd200d0ae"); 11 | expect(await blake2s("message digest", 128)).toBe( 12 | "a120dbd782f5e524252ba9e77e69301b", 13 | ); 14 | expect(await blake2s("abcdefghijklmnopqrstuvwxyz", 128)).toBe( 15 | "6b5da6a19a600add9fada4c0b95bf6c9", 16 | ); 17 | expect( 18 | await blake2s( 19 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 20 | 128, 21 | ), 22 | ).toBe("ae8812ea7e3507014d764e3d1f57387e"); 23 | expect( 24 | await blake2s( 25 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 26 | 128, 27 | ), 28 | ).toBe("d0b88b4a58efa805a1f7642865edd050"); 29 | }); 30 | 31 | test("unicode strings", async () => { 32 | expect(await blake2s("😊", 128)).toBe("e019bd2d04270ca2357a5c0047ad8a46"); 33 | expect(await blake2s("😊a😊", 128)).toBe("90557312f688a5d9e463c5ef4a86ef1f"); 34 | const file = fs.readFileSync("./test/utf8.txt"); 35 | expect(await blake2s(file, 128)).toBe("2c5f7941cd083e25fa0b6e0eab371f40"); 36 | expect(await blake2s(file.toString(), 128)).toBe( 37 | "2c5f7941cd083e25fa0b6e0eab371f40", 38 | ); 39 | }); 40 | 41 | test("Node.js buffers", async () => { 42 | expect(await blake2s(Buffer.from([]), 128)).toBe( 43 | "64550d6ffe2c0a01a14aba1eade0200c", 44 | ); 45 | expect(await blake2s(Buffer.from(["a".charCodeAt(0)]), 128)).toBe( 46 | "854b9e9ba49bfd9457d4c3bf96e42523", 47 | ); 48 | expect(await blake2s(Buffer.from([0]), 128)).toBe( 49 | "9f31f3ec588c6064a8e1f9051aeab90a", 50 | ); 51 | expect(await blake2s(Buffer.from([0, 1, 0, 0, 2, 0]), 128)).toBe( 52 | "7c357b1444b9b7f89c7cd4e72ff10c36", 53 | ); 54 | }); 55 | 56 | test("typed arrays", async () => { 57 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 58 | expect(await blake2s(Buffer.from(arr), 128)).toBe( 59 | "2c64964b70aad235398456062b3984fb", 60 | ); 61 | const uint8 = new Uint8Array(arr); 62 | expect(await blake2s(uint8, 128)).toBe("2c64964b70aad235398456062b3984fb"); 63 | expect(await blake2s(new Uint16Array(uint8.buffer), 128)).toBe( 64 | "2c64964b70aad235398456062b3984fb", 65 | ); 66 | expect(await blake2s(new Uint32Array(uint8.buffer), 128)).toBe( 67 | "2c64964b70aad235398456062b3984fb", 68 | ); 69 | }); 70 | 71 | test("long strings", async () => { 72 | const SIZE = 5 * 1024 * 1024; 73 | const chunk = "012345678\x09"; 74 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 75 | expect(await blake2s(str, 128)).toBe("c072ca2a3b3031c02ff82f15598849c7"); 76 | }); 77 | 78 | test("long buffers", async () => { 79 | const SIZE = 5 * 1024 * 1024; 80 | const buf = Buffer.alloc(SIZE); 81 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 82 | expect(await blake2s(buf, 128)).toBe("6088971a9c00f0b2861a2bce1d5da4c1"); 83 | }); 84 | 85 | test("chunked", async () => { 86 | const hash = await createBLAKE2s(128); 87 | expect(hash.digest()).toBe("64550d6ffe2c0a01a14aba1eade0200c"); 88 | hash.init(); 89 | hash.update("a"); 90 | hash.update(new Uint8Array([0])); 91 | hash.update("bc"); 92 | hash.update(new Uint8Array([255, 254])); 93 | expect(hash.digest()).toBe("4776ba03571cc119d15418f216233a4f"); 94 | 95 | hash.init(); 96 | for (let i = 0; i < 1000; i++) { 97 | hash.update(new Uint8Array([i & 0xff])); 98 | } 99 | hash.update(Buffer.alloc(1000).fill(0xdf)); 100 | expect(hash.digest()).toBe("aca313375b9de5bb799054ffbad14a16"); 101 | }); 102 | 103 | test("chunked increasing length", async () => { 104 | const hash = await createBLAKE2s(128); 105 | const test = async (maxLen: number) => { 106 | const chunks = getVariableLengthChunks(maxLen); 107 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 108 | const hashRef = await blake2s(new Uint8Array(flatchunks), 128); 109 | hash.init(); 110 | for (const chunk of chunks) { 111 | hash.update(new Uint8Array(chunk)); 112 | } 113 | expect(hash.digest("hex")).toBe(hashRef); 114 | }; 115 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 116 | await Promise.all(maxLens.map((length) => test(length))); 117 | }); 118 | 119 | test("interlaced shorthand", async () => { 120 | const [hashA, hashB] = await Promise.all([ 121 | blake2s("a", 128), 122 | blake2s("abc", 128), 123 | ]); 124 | expect(hashA).toBe("854b9e9ba49bfd9457d4c3bf96e42523"); 125 | expect(hashB).toBe("aa4938119b1dc7b87cbad0ffd200d0ae"); 126 | }); 127 | 128 | test("interlaced create", async () => { 129 | const hashA = await createBLAKE2s(128); 130 | hashA.update("a"); 131 | const hashB = await createBLAKE2s(128); 132 | hashB.update("abc"); 133 | expect(hashA.digest()).toBe("854b9e9ba49bfd9457d4c3bf96e42523"); 134 | expect(hashB.digest()).toBe("aa4938119b1dc7b87cbad0ffd200d0ae"); 135 | }); 136 | 137 | test("Invalid inputs throw", async () => { 138 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 139 | const hash = await createBLAKE2s(128); 140 | 141 | for (const input of invalidInputs) { 142 | await expect(blake2s(input as any, 128)).rejects.toThrow(); 143 | expect(() => hash.update(input as any)).toThrow(); 144 | } 145 | }); 146 | -------------------------------------------------------------------------------- /test/blake2s-keys.test.ts: -------------------------------------------------------------------------------- 1 | import { blake2s, createBLAKE2s } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("simple keys", async () => { 5 | expect(await blake2s("", 256, "")).toBe( 6 | "69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9", 7 | ); 8 | expect(await blake2s("", 256, "this is the password")).toBe( 9 | "39bc6c308f16fdc2cba393f0a50b63182fd60af27df426b5810aa847a8f08654", 10 | ); 11 | expect(await blake2s("", 256, "this is the password123")).toBe( 12 | "4f9ed939a9581ae3cca3b853475b40614b77d63c8149d0e2740adb15a65ea8a3", 13 | ); 14 | expect(await blake2s("a", 256, "this is the password")).toBe( 15 | "1f0c1c3b738785fc10fcc5923c8db5e7a055d148c217117c538f228444d771b1", 16 | ); 17 | expect(await blake2s("abc", 256, "this is the password")).toBe( 18 | "78c88dbc7a1bb3e8916a4ed8c6414dfe93080fcb7d6a51f0f9349c6bbe691228", 19 | ); 20 | 21 | const hash = await createBLAKE2s(256, "this is the password"); 22 | hash.update("a"); 23 | expect(hash.digest()).toBe( 24 | "1f0c1c3b738785fc10fcc5923c8db5e7a055d148c217117c538f228444d771b1", 25 | ); 26 | }); 27 | 28 | test("unicode keys", async () => { 29 | expect(await blake2s("a", 128, "😊")).toBe( 30 | "6ff855c10085ac5f0079816a24129ce3", 31 | ); 32 | expect(await blake2s("😊a😊", 128, "😊a😊")).toBe( 33 | "3802680002ebd909710d426144751b89", 34 | ); 35 | expect(await blake2s("a", 256, "😊")).toBe( 36 | "31de2ca01f34bbd658c860a872ed18aaebf060487699251f7a6b3cf1c848a0b9", 37 | ); 38 | expect(await blake2s("😊a😊", 256, "😊a😊")).toBe( 39 | "e0725514192fdd314e45938880050210e043121dd0f40c776402be41dea298c7", 40 | ); 41 | }); 42 | 43 | test("Node.js buffers", async () => { 44 | expect(await blake2s("a", 256, Buffer.from([]))).toBe( 45 | "4a0d129873403037c2cd9b9048203687f6233fb6738956e0349bd4320fec3e90", 46 | ); 47 | expect(await blake2s("a", 256, Buffer.from(["a".charCodeAt(0)]))).toBe( 48 | "54b9a8e6f5e77ea2f0311c9779a573fc29bcb87c1d1d7baabf1db1759276af28", 49 | ); 50 | const key = Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7]); 51 | expect(await blake2s("a", 256, key)).toBe( 52 | "4e3fff325a9a7e2d485a7eb7caa51e503053e7b3d1a66acb2b01b30fb4141ccc", 53 | ); 54 | }); 55 | 56 | test("typed arrays", async () => { 57 | const key = Buffer.from([ 58 | 0, 1, 2, 3, 4, 255, 254, 7, 0, 1, 254, 3, 4, 255, 0, 7, 59 | ]); 60 | expect(await blake2s("a", 256, Buffer.from(key))).toBe( 61 | "374b348674927f04919c6b4db23e3f1a85066298cea171dec91584f6474ddeff", 62 | ); 63 | const uint8 = new Uint8Array(key); 64 | expect(await blake2s("a", 256, uint8)).toBe( 65 | "374b348674927f04919c6b4db23e3f1a85066298cea171dec91584f6474ddeff", 66 | ); 67 | expect(await blake2s("a", 256, new Uint16Array(uint8.buffer))).toBe( 68 | "374b348674927f04919c6b4db23e3f1a85066298cea171dec91584f6474ddeff", 69 | ); 70 | expect(await blake2s("a", 256, new Uint32Array(uint8.buffer))).toBe( 71 | "374b348674927f04919c6b4db23e3f1a85066298cea171dec91584f6474ddeff", 72 | ); 73 | }); 74 | 75 | test("long syntax", async () => { 76 | const hash = await createBLAKE2s(256, "this is the password"); 77 | hash.update("a"); 78 | expect(hash.digest()).toBe( 79 | "1f0c1c3b738785fc10fcc5923c8db5e7a055d148c217117c538f228444d771b1", 80 | ); 81 | hash.init(); 82 | expect(hash.digest()).toBe( 83 | "39bc6c308f16fdc2cba393f0a50b63182fd60af27df426b5810aa847a8f08654", 84 | ); 85 | hash.init(); 86 | hash.update("a"); 87 | hash.update("b"); 88 | hash.update("c"); 89 | expect(hash.digest()).toBe( 90 | "78c88dbc7a1bb3e8916a4ed8c6414dfe93080fcb7d6a51f0f9349c6bbe691228", 91 | ); 92 | }); 93 | 94 | test("Invalid keys throw", async () => { 95 | const invalidKeys = [0, 1, Number(1), {}, []]; 96 | 97 | for (const key of invalidKeys) { 98 | await expect(() => blake2s("a", 256, key as any)).toThrow(); 99 | await expect(() => createBLAKE2s(256, key as any)).toThrow(); 100 | } 101 | }); 102 | 103 | test("Too long keys reject", async () => { 104 | const invalidKeys = [ 105 | new Array(33).fill("x").join(""), 106 | Buffer.alloc(33), 107 | new Uint8Array(33), 108 | new Uint32Array(17), 109 | new Array(17).fill("😊").join(""), 110 | ]; 111 | 112 | for (const key of invalidKeys) { 113 | await expect(() => blake2s("a", 256, key as any)).rejects.toThrow(); 114 | await expect(() => createBLAKE2s(256, key as any)).rejects.toThrow(); 115 | } 116 | }); 117 | 118 | test("small digest size", async () => { 119 | expect(await blake2s("abc", 8, "123")).toBe("a0"); 120 | expect(await blake2s("abc", 16, "1")).toBe("ae47"); 121 | expect(await blake2s("abc", 24, "123")).toBe("de6c36"); 122 | expect(await blake2s("", 32, "123")).toBe("5073461e"); 123 | }); 124 | -------------------------------------------------------------------------------- /test/blake2s.test.ts: -------------------------------------------------------------------------------- 1 | import { blake2s, createBLAKE2s } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("invalid parameters", async () => { 5 | await expect(blake2s("", -1 as any)).rejects.toThrow(); 6 | await expect(blake2s("", "a" as any)).rejects.toThrow(); 7 | await expect(blake2s("", 223 as any)).rejects.toThrow(); 8 | await expect(blake2s("", 0 as any)).rejects.toThrow(); 9 | await expect(blake2s("", 127 as any)).rejects.toThrow(); 10 | await expect(blake2s("", 257 as any)).rejects.toThrow(); 11 | await expect(blake2s("", null as any)).rejects.toThrow(); 12 | await expect(blake2s("", 512 as any)).rejects.toThrow(); 13 | 14 | await expect(createBLAKE2s(-1 as any)).rejects.toThrow(); 15 | await expect(createBLAKE2s("a" as any)).rejects.toThrow(); 16 | await expect(createBLAKE2s(223 as any)).rejects.toThrow(); 17 | await expect(createBLAKE2s(0 as any)).rejects.toThrow(); 18 | await expect(createBLAKE2s(127 as any)).rejects.toThrow(); 19 | await expect(createBLAKE2s(257 as any)).rejects.toThrow(); 20 | await expect(createBLAKE2s(null as any)).rejects.toThrow(); 21 | await expect(createBLAKE2s(512 as any)).rejects.toThrow(); 22 | }); 23 | 24 | test("default value for create constructor", async () => { 25 | const hash = await blake2s("a", 256); 26 | const hasher = await createBLAKE2s(); 27 | hasher.init(); 28 | hasher.update("a"); 29 | expect(hasher.digest()).toBe(hash); 30 | }); 31 | 32 | test("it invalidates the cache when changing parameters", async () => { 33 | expect(await blake2s("a", 64)).toBe("3746ba2ac42c6821"); 34 | expect(await blake2s("a", 128)).toBe("854b9e9ba49bfd9457d4c3bf96e42523"); 35 | expect(await blake2s("a")).toBe( 36 | "4a0d129873403037c2cd9b9048203687f6233fb6738956e0349bd4320fec3e90", 37 | ); 38 | 39 | let hash = await createBLAKE2s(64); 40 | hash.update("a"); 41 | expect(hash.digest()).toBe("3746ba2ac42c6821"); 42 | 43 | hash = await createBLAKE2s(128); 44 | hash.update("a"); 45 | expect(hash.digest()).toBe("854b9e9ba49bfd9457d4c3bf96e42523"); 46 | 47 | hash = await createBLAKE2s(); 48 | hash.update("a"); 49 | expect(hash.digest()).toBe( 50 | "4a0d129873403037c2cd9b9048203687f6233fb6738956e0349bd4320fec3e90", 51 | ); 52 | }); 53 | 54 | test("small digest size", async () => { 55 | expect(await blake2s("abc", 8)).toBe("0d"); 56 | expect(await blake2s("abc", 16)).toBe("d8ce"); 57 | expect(await blake2s("abc", 24)).toBe("9d3283"); 58 | expect(await blake2s("", 32)).toBe("36e9d246"); 59 | 60 | let hash = await createBLAKE2s(8); 61 | hash.update("abc"); 62 | expect(hash.digest()).toBe("0d"); 63 | 64 | hash = await createBLAKE2s(16); 65 | hash.update("abc"); 66 | expect(hash.digest()).toBe("d8ce"); 67 | 68 | hash = await createBLAKE2s(24); 69 | hash.update("abc"); 70 | expect(hash.digest()).toBe("9d3283"); 71 | 72 | hash = await createBLAKE2s(32); 73 | hash.update(""); 74 | expect(hash.digest()).toBe("36e9d246"); 75 | }); 76 | -------------------------------------------------------------------------------- /test/blake3-keys.test.ts: -------------------------------------------------------------------------------- 1 | import { blake3, createBLAKE3 } from "../lib"; 2 | /* global test, expect */ 3 | 4 | const KEY = Buffer.alloc(32); 5 | for (let i = 0; i < 32; i++) { 6 | KEY[i] = i; 7 | } 8 | const KEY_ZERO = Buffer.alloc(32); 9 | 10 | test("simple keys", async () => { 11 | expect(await blake3("", 256, KEY)).toBe( 12 | "73492b19995d71cdb1e9d74decc09809eb732f1b00bc95c27cb15f9dd4d6478f", 13 | ); 14 | expect(await blake3("", 256, KEY_ZERO)).toBe( 15 | "a7f91ced0533c12cd59706f2dc38c2a8c39c007ae89ab6492698778c8684c483", 16 | ); 17 | expect(await blake3("a", 256, KEY)).toBe( 18 | "538b8f2a2b1a04a5d427e97de10674bb33364a790dc6452700a72cdcd695299d", 19 | ); 20 | expect(await blake3("abc", 256, KEY)).toBe( 21 | "6da54495d8152f2bcba87bd7282df70901cdb66b4448ed5f4c7bd2852b8b5532", 22 | ); 23 | 24 | const hash = await createBLAKE3(256, KEY); 25 | hash.update("a"); 26 | expect(hash.digest()).toBe( 27 | "538b8f2a2b1a04a5d427e97de10674bb33364a790dc6452700a72cdcd695299d", 28 | ); 29 | }); 30 | 31 | test("unicode keys", async () => { 32 | expect(await blake3("a", 128, "01234567890123456789012345678912")).toBe( 33 | "d67ca39b701518f364e498ba58767017", 34 | ); 35 | expect(await blake3("😊a😊", 128, "01234567890123456789012345678912")).toBe( 36 | "5ab945d5dc0fea9e51f19c859ef2c5ec", 37 | ); 38 | expect(await blake3("a", 256, "01234567890123456789012345678912")).toBe( 39 | "d67ca39b701518f364e498ba58767017bc76802077ec51ec59c816ee4eac035f", 40 | ); 41 | expect(await blake3("😊a😊", 256, "01234567890123456789012345678912")).toBe( 42 | "5ab945d5dc0fea9e51f19c859ef2c5ec51f18e0463fe18486a27be63fc800d33", 43 | ); 44 | }); 45 | 46 | test("Node.js buffers", async () => { 47 | const key = Buffer.alloc(32); 48 | key.fill(0x61); 49 | expect(await blake3("a", 256, key)).toBe( 50 | "bc131823d32e505633c1707a18caf789f4a2f39cc86f339d2f7b19e92a14bcf3", 51 | ); 52 | }); 53 | 54 | test("typed arrays", async () => { 55 | const key = Buffer.alloc(32); 56 | key.fill(0x61); 57 | const uint8 = new Uint8Array(key); 58 | expect(await blake3("a", 256, uint8)).toBe( 59 | "bc131823d32e505633c1707a18caf789f4a2f39cc86f339d2f7b19e92a14bcf3", 60 | ); 61 | expect(await blake3("a", 256, new Uint16Array(uint8.buffer))).toBe( 62 | "bc131823d32e505633c1707a18caf789f4a2f39cc86f339d2f7b19e92a14bcf3", 63 | ); 64 | expect(await blake3("a", 256, new Uint32Array(uint8.buffer))).toBe( 65 | "bc131823d32e505633c1707a18caf789f4a2f39cc86f339d2f7b19e92a14bcf3", 66 | ); 67 | }); 68 | 69 | test("long syntax", async () => { 70 | const hash = await createBLAKE3(256, KEY); 71 | hash.update("a"); 72 | expect(hash.digest()).toBe( 73 | "538b8f2a2b1a04a5d427e97de10674bb33364a790dc6452700a72cdcd695299d", 74 | ); 75 | hash.init(); 76 | expect(hash.digest()).toBe( 77 | "73492b19995d71cdb1e9d74decc09809eb732f1b00bc95c27cb15f9dd4d6478f", 78 | ); 79 | hash.init(); 80 | hash.update("a"); 81 | hash.update("b"); 82 | hash.update("c"); 83 | expect(hash.digest()).toBe( 84 | "6da54495d8152f2bcba87bd7282df70901cdb66b4448ed5f4c7bd2852b8b5532", 85 | ); 86 | }); 87 | 88 | test("Invalid keys throw", async () => { 89 | const invalidKeys = [0, 1, Number(1), {}, []]; 90 | 91 | for (const key of invalidKeys) { 92 | await expect(() => blake3("a", 256, key as any)).toThrow(); 93 | await expect(() => createBLAKE3(256, key as any)).toThrow(); 94 | } 95 | }); 96 | 97 | test("Key size mismatch", async () => { 98 | const invalidKeys = [ 99 | Buffer.alloc(0), 100 | Buffer.alloc(16), 101 | Buffer.alloc(31), 102 | Buffer.alloc(33), 103 | Buffer.alloc(64), 104 | ]; 105 | 106 | for (const key of invalidKeys) { 107 | await expect(() => blake3("a", 256, key as any)).rejects.toThrow(); 108 | await expect(() => createBLAKE3(256, key as any)).rejects.toThrow(); 109 | } 110 | }); 111 | 112 | test("small digest size", async () => { 113 | expect(await blake3("abc", 8, KEY)).toBe("6d"); 114 | expect(await blake3("abc", 16, KEY)).toBe("6da5"); 115 | expect(await blake3("abc", 24, KEY)).toBe("6da544"); 116 | expect(await blake3("", 32, KEY)).toBe("73492b19"); 117 | }); 118 | -------------------------------------------------------------------------------- /test/blake3.test.ts: -------------------------------------------------------------------------------- 1 | import { blake3, createBLAKE3 } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("invalid parameters", async () => { 5 | await expect(blake3("", -1 as any)).rejects.toThrow(); 6 | await expect(blake3("", "a" as any)).rejects.toThrow(); 7 | await expect(blake3("", 223 as any)).rejects.toThrow(); 8 | await expect(blake3("", 0 as any)).rejects.toThrow(); 9 | await expect(blake3("", 127 as any)).rejects.toThrow(); 10 | await expect(blake3("", 257 as any)).rejects.toThrow(); 11 | await expect(blake3("", null as any)).rejects.toThrow(); 12 | 13 | await expect(createBLAKE3(-1 as any)).rejects.toThrow(); 14 | await expect(createBLAKE3("a" as any)).rejects.toThrow(); 15 | await expect(createBLAKE3(223 as any)).rejects.toThrow(); 16 | await expect(createBLAKE3(0 as any)).rejects.toThrow(); 17 | await expect(createBLAKE3(127 as any)).rejects.toThrow(); 18 | await expect(createBLAKE3(257 as any)).rejects.toThrow(); 19 | await expect(createBLAKE3(null as any)).rejects.toThrow(); 20 | }); 21 | 22 | test("default value for create constructor", async () => { 23 | const hash = await blake3("a", 256); 24 | const hasher = await createBLAKE3(); 25 | hasher.init(); 26 | hasher.update("a"); 27 | expect(hasher.digest()).toBe(hash); 28 | }); 29 | 30 | test("it invalidates the cache when changing parameters", async () => { 31 | expect(await blake3("a", 64)).toBe("17762fddd969a453"); 32 | expect(await blake3("a", 128)).toBe("17762fddd969a453925d65717ac3eea2"); 33 | expect(await blake3("a")).toBe( 34 | "17762fddd969a453925d65717ac3eea21320b66b54342fde15128d6caf21215f", 35 | ); 36 | 37 | let hash = await createBLAKE3(64); 38 | hash.update("a"); 39 | expect(hash.digest()).toBe("17762fddd969a453"); 40 | 41 | hash = await createBLAKE3(128); 42 | hash.update("a"); 43 | expect(hash.digest()).toBe("17762fddd969a453925d65717ac3eea2"); 44 | 45 | hash = await createBLAKE3(); 46 | hash.update("a"); 47 | expect(hash.digest()).toBe( 48 | "17762fddd969a453925d65717ac3eea21320b66b54342fde15128d6caf21215f", 49 | ); 50 | }); 51 | 52 | test("small digest size", async () => { 53 | expect(await blake3("abc", 8)).toBe("64"); 54 | expect(await blake3("abc", 16)).toBe("6437"); 55 | expect(await blake3("abc", 24)).toBe("6437b3"); 56 | expect(await blake3("", 32)).toBe("af1349b9"); 57 | 58 | let hash = await createBLAKE3(8); 59 | hash.update("abc"); 60 | expect(hash.digest()).toBe("64"); 61 | 62 | hash = await createBLAKE3(16); 63 | hash.update("abc"); 64 | expect(hash.digest()).toBe("6437"); 65 | 66 | hash = await createBLAKE3(24); 67 | hash.update("abc"); 68 | expect(hash.digest()).toBe("6437b3"); 69 | 70 | hash = await createBLAKE3(32); 71 | hash.update(""); 72 | expect(hash.digest()).toBe("af1349b9"); 73 | }); 74 | -------------------------------------------------------------------------------- /test/browser.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | 3 | beforeEach(() => { 4 | jest.resetModules(); 5 | }); 6 | 7 | afterEach(() => { 8 | jest.restoreAllMocks(); 9 | }); 10 | 11 | test("Throws when WebAssembly is unavailable", async () => { 12 | const { md5 } = jest.requireActual("../lib"); 13 | 14 | const WASM = globalThis.WebAssembly; 15 | (globalThis.WebAssembly as any) = undefined; 16 | 17 | await expect(() => md5("a")).rejects.toThrow(); 18 | globalThis.WebAssembly = WASM; 19 | }); 20 | 21 | const NodeBuffer = (globalThis as any).Buffer; 22 | 23 | class TextEncoderMock { 24 | encode(str) { 25 | const buf = NodeBuffer.from(str); 26 | return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); 27 | } 28 | } 29 | 30 | test("Simulate browsers", async () => { 31 | const originalBuffer = globalThis.Buffer; 32 | ((globalThis as any).Buffer as any) = undefined; 33 | const originalTextEncoder = globalThis.TextEncoder; 34 | ((globalThis as any).TextEncoder as any) = TextEncoderMock; 35 | 36 | const { md5 } = jest.requireActual("../lib"); 37 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 38 | expect(await md5(new Uint8Array([0]))).toBe( 39 | "93b885adfe0da089cdf634904fd59f71", 40 | ); 41 | expect(() => md5(1)).rejects.toThrow(); 42 | 43 | globalThis.TextEncoder = originalTextEncoder; 44 | globalThis.Buffer = originalBuffer; 45 | }); 46 | 47 | test("Use global self", async () => { 48 | const global = globalThis; 49 | (globalThis as any).self = global; 50 | 51 | const { md5 } = jest.requireActual("../lib"); 52 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 53 | }); 54 | 55 | test("Delete global self", async () => { 56 | const originalSelf = globalThis.self; 57 | (globalThis.self as any) = undefined; 58 | 59 | const { md5 } = jest.requireActual("../lib"); 60 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 61 | 62 | globalThis.self = originalSelf; 63 | }); 64 | 65 | test("Use global window", async () => { 66 | const originalWindow = globalThis.window; 67 | (globalThis.window as any) = undefined; 68 | 69 | const { md5 } = jest.requireActual("../lib"); 70 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 71 | 72 | globalThis.window = originalWindow; 73 | }); 74 | 75 | test("Delete global self + window", async () => { 76 | const originalWindow = globalThis.window; 77 | (globalThis.window as any) = undefined; 78 | 79 | const { md5 } = jest.requireActual("../lib"); 80 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 81 | 82 | globalThis.window = originalWindow; 83 | }); 84 | -------------------------------------------------------------------------------- /test/crc32.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { crc32, createCRC32 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await crc32("")).toBe("00000000"); 8 | expect(await crc32("a")).toBe("e8b7be43"); 9 | expect(await crc32("1234567890")).toBe("261daee5"); 10 | expect(await crc32("a\x00")).toBe("3d3f4819"); 11 | expect(await crc32("abc")).toBe("352441c2"); 12 | expect(await crc32("message digest")).toBe("20159d7f"); 13 | expect(await crc32("abcdefghijklmnopqrstuvwxyz")).toBe("4c2750bd"); 14 | expect( 15 | await crc32( 16 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 17 | ), 18 | ).toBe("1fc2e6d2"); 19 | expect( 20 | await crc32( 21 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 22 | ), 23 | ).toBe("7ca94a72"); 24 | }); 25 | 26 | test("unicode strings", async () => { 27 | expect(await crc32("😊")).toBe("e5985c5a"); 28 | expect(await crc32("😊a😊")).toBe("85dda337"); 29 | const file = fs.readFileSync("./test/utf8.txt"); 30 | expect(await crc32(file)).toBe("15694f02"); 31 | expect(await crc32(file.toString())).toBe("15694f02"); 32 | }); 33 | 34 | test("Node.js buffers", async () => { 35 | expect(await crc32(Buffer.from([]))).toBe("00000000"); 36 | expect(await crc32(Buffer.from(["a".charCodeAt(0)]))).toBe("e8b7be43"); 37 | expect(await crc32(Buffer.from([0]))).toBe("d202ef8d"); 38 | expect(await crc32(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe("be94ea91"); 39 | }); 40 | 41 | test("typed arrays", async () => { 42 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 43 | expect(await crc32(Buffer.from(arr))).toBe("89b578d3"); 44 | const uint8 = new Uint8Array(arr); 45 | expect(await crc32(uint8)).toBe("89b578d3"); 46 | expect(await crc32(new Uint16Array(uint8.buffer))).toBe("89b578d3"); 47 | expect(await crc32(new Uint32Array(uint8.buffer))).toBe("89b578d3"); 48 | }); 49 | 50 | test("long strings", async () => { 51 | const SIZE = 5 * 1024 * 1024; 52 | const chunk = "012345678\x09"; 53 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 54 | expect(await crc32(str)).toBe("5d7c1b96"); 55 | }); 56 | 57 | test("long buffers", async () => { 58 | const SIZE = 5 * 1024 * 1024; 59 | const buf = Buffer.alloc(SIZE); 60 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 61 | expect(await crc32(buf)).toBe("8717a175"); 62 | }); 63 | 64 | test("chunked", async () => { 65 | const hash = await createCRC32(); 66 | expect(hash.digest()).toBe("00000000"); 67 | hash.init(); 68 | hash.update("a"); 69 | hash.update(new Uint8Array([0])); 70 | hash.update("bc"); 71 | hash.update(new Uint8Array([255, 254])); 72 | expect(hash.digest()).toBe("60f515c4"); 73 | 74 | hash.init(); 75 | for (let i = 0; i < 1000; i++) { 76 | hash.update(new Uint8Array([i & 0xff])); 77 | } 78 | hash.update(Buffer.alloc(1000).fill(0xdf)); 79 | expect(hash.digest()).toBe("f683f7e3"); 80 | }); 81 | 82 | test("chunked increasing length", async () => { 83 | const hash = await createCRC32(); 84 | const test = async (maxLen: number) => { 85 | const chunks = getVariableLengthChunks(maxLen); 86 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 87 | const hashRef = await crc32(new Uint8Array(flatchunks)); 88 | hash.init(); 89 | for (const chunk of chunks) { 90 | hash.update(new Uint8Array(chunk)); 91 | } 92 | expect(hash.digest("hex")).toBe(hashRef); 93 | }; 94 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 95 | await Promise.all(maxLens.map((length) => test(length))); 96 | }); 97 | 98 | test("interlaced shorthand", async () => { 99 | const [hashA, hashB] = await Promise.all([crc32("a"), crc32("abc")]); 100 | expect(hashA).toBe("e8b7be43"); 101 | expect(hashB).toBe("352441c2"); 102 | }); 103 | 104 | test("interlaced create", async () => { 105 | const hashA = await createCRC32(); 106 | hashA.update("a"); 107 | const hashB = await createCRC32(); 108 | hashB.update("abc"); 109 | expect(hashA.digest()).toBe("e8b7be43"); 110 | expect(hashB.digest()).toBe("352441c2"); 111 | }); 112 | 113 | test("Invalid inputs throw", async () => { 114 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 115 | const hash = await createCRC32(); 116 | 117 | for (const input of invalidInputs) { 118 | await expect(crc32(input as any)).rejects.toThrow(); 119 | expect(() => hash.update(input as any)).toThrow(); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /test/crc32c.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { crc32, createCRC32 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | const POLY = 0x82f63b78; 7 | 8 | test("simple strings", async () => { 9 | expect(await crc32("", POLY)).toBe("00000000"); 10 | expect(await crc32("a", POLY)).toBe("c1d04330"); 11 | expect(await crc32("1234567890", POLY)).toBe("f3dbd4fe"); 12 | expect(await crc32("a\x00", POLY)).toBe("625fcaa3"); 13 | expect(await crc32("abc", POLY)).toBe("364b3fb7"); 14 | expect(await crc32("message digest", POLY)).toBe("02bd79d0"); 15 | expect(await crc32("abcdefghijklmnopqrstuvwxyz", POLY)).toBe("9ee6ef25"); 16 | expect( 17 | await crc32( 18 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 19 | POLY, 20 | ), 21 | ).toBe("a245d57d"); 22 | expect( 23 | await crc32( 24 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 25 | POLY, 26 | ), 27 | ).toBe("477a6781"); 28 | }); 29 | 30 | test("unicode strings", async () => { 31 | expect(await crc32("😊", POLY)).toBe("8802f34c"); 32 | expect(await crc32("😊a😊", POLY)).toBe("0f51707f"); 33 | const file = fs.readFileSync("./test/utf8.txt"); 34 | expect(await crc32(file, POLY)).toBe("96665a41"); 35 | expect(await crc32(file.toString(), POLY)).toBe("96665a41"); 36 | }); 37 | 38 | test("Node.js buffers", async () => { 39 | expect(await crc32(Buffer.from([]), POLY)).toBe("00000000"); 40 | expect(await crc32(Buffer.from(["a".charCodeAt(0)]), POLY)).toBe("c1d04330"); 41 | expect(await crc32(Buffer.from([0]), POLY)).toBe("527d5351"); 42 | expect(await crc32(Buffer.from([0, 1, 0, 0, 2, 0]), POLY)).toBe("487e23c8"); 43 | }); 44 | 45 | test("typed arrays", async () => { 46 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 47 | expect(await crc32(Buffer.from(arr), POLY)).toBe("cbdc7d33"); 48 | const uint8 = new Uint8Array(arr); 49 | expect(await crc32(uint8, POLY)).toBe("cbdc7d33"); 50 | expect(await crc32(new Uint16Array(uint8.buffer), POLY)).toBe("cbdc7d33"); 51 | expect(await crc32(new Uint32Array(uint8.buffer), POLY)).toBe("cbdc7d33"); 52 | }); 53 | 54 | test("long strings", async () => { 55 | const SIZE = 5 * 1024 * 1024; 56 | const chunk = "012345678\x09"; 57 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 58 | expect(await crc32(str, POLY)).toBe("8546928c"); 59 | }); 60 | 61 | test("long buffers", async () => { 62 | const SIZE = 5 * 1024 * 1024; 63 | const buf = Buffer.alloc(SIZE); 64 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 65 | expect(await crc32(buf, POLY)).toBe("1acced17"); 66 | }); 67 | 68 | test("chunked", async () => { 69 | const hash = await createCRC32(POLY); 70 | expect(hash.digest()).toBe("00000000"); 71 | hash.init(); 72 | hash.update("a"); 73 | hash.update(new Uint8Array([0])); 74 | hash.update("bc"); 75 | hash.update(new Uint8Array([255, 254])); 76 | expect(hash.digest()).toBe("c0f47d77"); 77 | 78 | hash.init(); 79 | for (let i = 0; i < 1000; i++) { 80 | hash.update(new Uint8Array([i & 0xff])); 81 | } 82 | hash.update(Buffer.alloc(1000).fill(0xdf)); 83 | expect(hash.digest()).toBe("b9c3c58a"); 84 | }); 85 | 86 | test("chunked increasing length", async () => { 87 | const hash = await createCRC32(POLY); 88 | const test = async (maxLen: number) => { 89 | const chunks = getVariableLengthChunks(maxLen); 90 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 91 | const hashRef = await crc32(new Uint8Array(flatchunks), POLY); 92 | hash.init(); 93 | for (const chunk of chunks) { 94 | hash.update(new Uint8Array(chunk)); 95 | } 96 | expect(hash.digest("hex")).toBe(hashRef); 97 | }; 98 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 99 | await Promise.all(maxLens.map((length) => test(length))); 100 | }); 101 | 102 | test("interlaced shorthand", async () => { 103 | const [hashA, hashB] = await Promise.all([ 104 | crc32("a", POLY), 105 | crc32("abc", POLY), 106 | ]); 107 | expect(hashA).toBe("c1d04330"); 108 | expect(hashB).toBe("364b3fb7"); 109 | }); 110 | 111 | test("interlaced create", async () => { 112 | const hashA = await createCRC32(POLY); 113 | hashA.update("a"); 114 | const hashB = await createCRC32(POLY); 115 | hashB.update("abc"); 116 | expect(hashA.digest()).toBe("c1d04330"); 117 | expect(hashB.digest()).toBe("364b3fb7"); 118 | }); 119 | 120 | test("Invalid inputs throw", async () => { 121 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 122 | const hash = await createCRC32(POLY); 123 | 124 | for (const input of invalidInputs) { 125 | await expect(crc32(input as any, POLY)).rejects.toThrow(); 126 | expect(() => hash.update(input as any)).toThrow(); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /test/crc64.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { crc64, createCRC64 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await crc64("")).toBe("0000000000000000"); 8 | expect(await crc64("a")).toBe("330284772e652b05"); 9 | expect(await crc64("1234567890")).toBe("b1cb31bbb4a2b2be"); 10 | expect(await crc64("a\x00")).toBe("d7602ccdef615b2e"); 11 | expect(await crc64("abc")).toBe("2cd8094a1a277627"); 12 | expect(await crc64("message digest")).toBe("5dbcc956318a9b6f"); 13 | expect(await crc64("abcdefghijklmnopqrstuvwxyz")).toBe("26967875751b122f"); 14 | expect( 15 | await crc64( 16 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 17 | ), 18 | ).toBe("0305bfe116b75626"); 19 | expect( 20 | await crc64( 21 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 22 | ), 23 | ).toBe("ae220a5d76b73ebd"); 24 | }); 25 | 26 | test("unicode strings", async () => { 27 | expect(await crc64("😊")).toBe("df73c949825718c3"); 28 | expect(await crc64("😊a😊")).toBe("3faa139c31f8ef94"); 29 | const file = fs.readFileSync("./test/utf8.txt"); 30 | expect(await crc64(file)).toBe("411adb4dceac4778"); 31 | expect(await crc64(file.toString())).toBe("411adb4dceac4778"); 32 | }); 33 | 34 | test("Node.js buffers", async () => { 35 | expect(await crc64(Buffer.from([]))).toBe("0000000000000000"); 36 | expect(await crc64(Buffer.from(["a".charCodeAt(0)]))).toBe( 37 | "330284772e652b05", 38 | ); 39 | expect(await crc64(Buffer.from([0]))).toBe("1fada17364673f59"); 40 | expect(await crc64(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe("a4cbad973e91157a"); 41 | }); 42 | 43 | test("typed arrays", async () => { 44 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 45 | expect(await crc64(Buffer.from(arr))).toBe("09a40113c88f593d"); 46 | const uint8 = new Uint8Array(arr); 47 | expect(await crc64(uint8)).toBe("09a40113c88f593d"); 48 | expect(await crc64(new Uint16Array(uint8.buffer))).toBe("09a40113c88f593d"); 49 | expect(await crc64(new Uint32Array(uint8.buffer))).toBe("09a40113c88f593d"); 50 | }); 51 | 52 | test("long strings", async () => { 53 | const SIZE = 5 * 1024 * 1024; 54 | const chunk = "012345678\x09"; 55 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 56 | expect(await crc64(str)).toBe("78d1a62ea3dcc899"); 57 | }); 58 | 59 | test("long buffers", async () => { 60 | const SIZE = 5 * 1024 * 1024; 61 | const buf = Buffer.alloc(SIZE); 62 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 63 | expect(await crc64(buf)).toBe("902cccb9945d81c8"); 64 | }); 65 | 66 | test("chunked", async () => { 67 | const hash = await createCRC64(); 68 | expect(hash.digest()).toBe("0000000000000000"); 69 | hash.init(); 70 | hash.update("a"); 71 | hash.update(new Uint8Array([0])); 72 | hash.update("bc"); 73 | hash.update(new Uint8Array([255, 254])); 74 | expect(hash.digest()).toBe("4fbaccdf43bc3840"); 75 | 76 | hash.init(); 77 | for (let i = 0; i < 1000; i++) { 78 | hash.update(new Uint8Array([i & 0xff])); 79 | } 80 | hash.update(Buffer.alloc(1000).fill(0xdf)); 81 | expect(hash.digest()).toBe("ca91869e58acdeee"); 82 | }); 83 | 84 | test("chunked increasing length", async () => { 85 | const hash = await createCRC64(); 86 | const test = async (maxLen: number) => { 87 | const chunks = getVariableLengthChunks(maxLen); 88 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 89 | const hashRef = await crc64(new Uint8Array(flatchunks)); 90 | hash.init(); 91 | for (const chunk of chunks) { 92 | hash.update(new Uint8Array(chunk)); 93 | } 94 | expect(hash.digest("hex")).toBe(hashRef); 95 | }; 96 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 97 | await Promise.all(maxLens.map((length) => test(length))); 98 | }); 99 | 100 | test("interlaced shorthand", async () => { 101 | const [hashA, hashB] = await Promise.all([crc64("a"), crc64("abc")]); 102 | expect(hashA).toBe("330284772e652b05"); 103 | expect(hashB).toBe("2cd8094a1a277627"); 104 | }); 105 | 106 | test("interlaced create", async () => { 107 | const hashA = await createCRC64(); 108 | hashA.update("a"); 109 | const hashB = await createCRC64(); 110 | hashB.update("abc"); 111 | expect(hashA.digest()).toBe("330284772e652b05"); 112 | expect(hashB.digest()).toBe("2cd8094a1a277627"); 113 | }); 114 | 115 | test("Invalid inputs throw", async () => { 116 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 117 | const hash = await createCRC64(); 118 | 119 | for (const input of invalidInputs) { 120 | await expect(crc64(input as any)).rejects.toThrow(); 121 | expect(() => hash.update(input as any)).toThrow(); 122 | } 123 | }); 124 | 125 | const ISO_POLY = "D800000000000000"; 126 | 127 | test("with iso poly", async () => { 128 | expect(await crc64("", ISO_POLY)).toBe("0000000000000000"); 129 | expect(await crc64("abc", ISO_POLY)).toBe("3776c42000000000"); 130 | expect(await crc64("1234567890", ISO_POLY)).toBe("43990956c775a410"); 131 | 132 | const hash = await createCRC64(ISO_POLY); 133 | hash.update("abc"); 134 | expect(hash.digest()).toBe("3776c42000000000"); 135 | hash.init(); 136 | hash.update("123"); 137 | hash.update("4567890"); 138 | expect(hash.digest()).toBe("43990956c775a410"); 139 | }); 140 | -------------------------------------------------------------------------------- /test/keccak.test.ts: -------------------------------------------------------------------------------- 1 | import { createKeccak, keccak } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("invalid parameters", async () => { 5 | await expect(keccak("", -1 as any)).rejects.toThrow(); 6 | await expect(keccak("", "a" as any)).rejects.toThrow(); 7 | await expect(keccak("", 223 as any)).rejects.toThrow(); 8 | await expect(keccak("", 0 as any)).rejects.toThrow(); 9 | await expect(keccak("", null as any)).rejects.toThrow(); 10 | 11 | await expect(createKeccak(-1 as any)).rejects.toThrow(); 12 | await expect(createKeccak("a" as any)).rejects.toThrow(); 13 | await expect(createKeccak(223 as any)).rejects.toThrow(); 14 | await expect(createKeccak(0 as any)).rejects.toThrow(); 15 | await expect(createKeccak(null as any)).rejects.toThrow(); 16 | }); 17 | 18 | test("default value for create constructor", async () => { 19 | const hash = await keccak("a", 512); 20 | const hasher = await createKeccak(); 21 | hasher.init(); 22 | hasher.update("a"); 23 | expect(hasher.digest()).toBe(hash); 24 | }); 25 | 26 | test("it invalidates the cache when changing parameters", async () => { 27 | expect(await keccak("a", 224)).toBe( 28 | "7cf87d912ee7088d30ec23f8e7100d9319bff090618b439d3fe91308", 29 | ); 30 | expect(await keccak("a", 256)).toBe( 31 | "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb", 32 | ); 33 | expect(await keccak("a", 384)).toBe( 34 | "85e964c0843a7ee32e6b5889d50e130e6485cffc826a30167d1dc2b3a0cc79cba303501a1eeaba39915f13baab5abacf", 35 | ); 36 | expect(await keccak("a")).toBe( 37 | "9c46dbec5d03f74352cc4a4da354b4e9796887eeb66ac292617692e765dbe400352559b16229f97b27614b51dbfbbb14613f2c10350435a8feaf53f73ba01c7c", 38 | ); 39 | 40 | let hash = await createKeccak(224); 41 | hash.update("a"); 42 | expect(hash.digest()).toBe( 43 | "7cf87d912ee7088d30ec23f8e7100d9319bff090618b439d3fe91308", 44 | ); 45 | 46 | hash = await createKeccak(256); 47 | hash.update("a"); 48 | expect(hash.digest()).toBe( 49 | "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb", 50 | ); 51 | 52 | hash = await createKeccak(384); 53 | hash.update("a"); 54 | expect(hash.digest()).toBe( 55 | "85e964c0843a7ee32e6b5889d50e130e6485cffc826a30167d1dc2b3a0cc79cba303501a1eeaba39915f13baab5abacf", 56 | ); 57 | 58 | hash = await createKeccak(); 59 | hash.update("a"); 60 | expect(hash.digest()).toBe( 61 | "9c46dbec5d03f74352cc4a4da354b4e9796887eeb66ac292617692e765dbe400352559b16229f97b27614b51dbfbbb14613f2c10350435a8feaf53f73ba01c7c", 62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /test/md4.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { createMD4, md4 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await md4("")).toBe("31d6cfe0d16ae931b73c59d7e0c089c0"); 8 | expect(await md4("a")).toBe("bde52cb31de33e46245e05fbdbd6fb24"); 9 | expect(await md4("a\x00")).toBe("186cb09181e2c2ecaac768c47c729904"); 10 | expect(await md4("abc")).toBe("a448017aaf21d8525fc10ae87aa6729d"); 11 | expect(await md4("message digest")).toBe("d9130a8164549fe818874806e1c7014b"); 12 | expect(await md4("abcdefghijklmnopqrstuvwxyz")).toBe( 13 | "d79e1c308aa5bbcdeea8ed63df412da9", 14 | ); 15 | expect( 16 | await md4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 17 | ).toBe("043f8582f241db351ce627e153e7f0e4"); 18 | expect( 19 | await md4( 20 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 21 | ), 22 | ).toBe("e33b4ddc9c38f2199c3e7b164fcc0536"); 23 | }); 24 | 25 | test("unicode strings", async () => { 26 | expect(await md4("😊")).toBe("69948427129e5504f67e51ce22b4486b"); 27 | expect(await md4("😊a😊")).toBe("6b9f614e8987bc2ed0c2015007408e2c"); 28 | const file = fs.readFileSync("./test/utf8.txt"); 29 | expect(await md4(file)).toBe("fa160f6eaa81606f3b7ed273ee9123e3"); 30 | expect(await md4(file.toString())).toBe("fa160f6eaa81606f3b7ed273ee9123e3"); 31 | }); 32 | 33 | test("Node.js buffers", async () => { 34 | expect(await md4(Buffer.from([]))).toBe("31d6cfe0d16ae931b73c59d7e0c089c0"); 35 | expect(await md4(Buffer.from(["a".charCodeAt(0)]))).toBe( 36 | "bde52cb31de33e46245e05fbdbd6fb24", 37 | ); 38 | expect(await md4(Buffer.from([0]))).toBe("47c61a0fa8738ba77308a8a600f88e4b"); 39 | expect(await md4(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe( 40 | "a322bcc3d130ed59fcc5d445f3db6074", 41 | ); 42 | }); 43 | 44 | test("typed arrays", async () => { 45 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 46 | expect(await md4(Buffer.from(arr))).toBe("e2a641653e013acd2da864e6ed8e816e"); 47 | const uint8 = new Uint8Array(arr); 48 | expect(await md4(uint8)).toBe("e2a641653e013acd2da864e6ed8e816e"); 49 | expect(await md4(new Uint16Array(uint8.buffer))).toBe( 50 | "e2a641653e013acd2da864e6ed8e816e", 51 | ); 52 | expect(await md4(new Uint32Array(uint8.buffer))).toBe( 53 | "e2a641653e013acd2da864e6ed8e816e", 54 | ); 55 | }); 56 | 57 | test("long strings", async () => { 58 | const SIZE = 5 * 1024 * 1024; 59 | const chunk = "012345678\x09"; 60 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 61 | expect(await md4(str)).toBe("8a91d073c046b98a0e7c4a45fc617be8"); 62 | }); 63 | 64 | test("long buffers", async () => { 65 | const SIZE = 5 * 1024 * 1024; 66 | const buf = Buffer.alloc(SIZE); 67 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 68 | expect(await md4(buf)).toBe("249f0de16d9c9498cb6810a51489d8e0"); 69 | }); 70 | 71 | test("chunked", async () => { 72 | const hash = await createMD4(); 73 | expect(hash.digest()).toBe("31d6cfe0d16ae931b73c59d7e0c089c0"); 74 | hash.init(); 75 | hash.update("a"); 76 | hash.update(new Uint8Array([0])); 77 | hash.update("bc"); 78 | hash.update(new Uint8Array([255, 254])); 79 | expect(hash.digest()).toBe("d37af7dede3e6e7f97aea9285a53a02f"); 80 | 81 | hash.init(); 82 | for (let i = 0; i < 1000; i++) { 83 | hash.update(new Uint8Array([i & 0xff])); 84 | } 85 | hash.update(Buffer.alloc(1000).fill(0xdf)); 86 | expect(hash.digest()).toBe("c85d05258be1a9accac6723b300b2618"); 87 | }); 88 | 89 | test("chunked increasing length", async () => { 90 | const hash = await createMD4(); 91 | const test = async (maxLen: number) => { 92 | const chunks = getVariableLengthChunks(maxLen); 93 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 94 | const hashRef = await md4(new Uint8Array(flatchunks)); 95 | hash.init(); 96 | for (const chunk of chunks) { 97 | hash.update(new Uint8Array(chunk)); 98 | } 99 | expect(hash.digest("hex")).toBe(hashRef); 100 | }; 101 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 102 | await Promise.all(maxLens.map((length) => test(length))); 103 | }); 104 | 105 | test("interlaced shorthand", async () => { 106 | const [hashA, hashB] = await Promise.all([md4("a"), md4("abc")]); 107 | expect(hashA).toBe("bde52cb31de33e46245e05fbdbd6fb24"); 108 | expect(hashB).toBe("a448017aaf21d8525fc10ae87aa6729d"); 109 | }); 110 | 111 | test("interlaced create", async () => { 112 | const hashA = await createMD4(); 113 | hashA.update("a"); 114 | const hashB = await createMD4(); 115 | hashB.update("abc"); 116 | expect(hashA.digest()).toBe("bde52cb31de33e46245e05fbdbd6fb24"); 117 | expect(hashB.digest()).toBe("a448017aaf21d8525fc10ae87aa6729d"); 118 | }); 119 | 120 | test("Invalid inputs throw", async () => { 121 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 122 | const hash = await createMD4(); 123 | 124 | for (const input of invalidInputs) { 125 | await expect(md4(input as any)).rejects.toThrow(); 126 | expect(() => hash.update(input as any)).toThrow(); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /test/md5.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { createMD5, md5 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await md5("")).toBe("d41d8cd98f00b204e9800998ecf8427e"); 8 | expect(await md5("a")).toBe("0cc175b9c0f1b6a831c399e269772661"); 9 | expect(await md5("a\x00")).toBe("4144e195f46de78a3623da7364d04f11"); 10 | expect(await md5("abc")).toBe("900150983cd24fb0d6963f7d28e17f72"); 11 | expect(await md5("message digest")).toBe("f96b697d7cb7938d525a2f31aaf161d0"); 12 | expect(await md5("abcdefghijklmnopqrstuvwxyz")).toBe( 13 | "c3fcd3d76192e4007dfb496cca67e13b", 14 | ); 15 | expect( 16 | await md5("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), 17 | ).toBe("d174ab98d277d9f5a5611c2c9f419d9f"); 18 | expect( 19 | await md5( 20 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 21 | ), 22 | ).toBe("57edf4a22be3c955ac49da2e2107b67a"); 23 | }); 24 | 25 | test("unicode strings", async () => { 26 | expect(await md5("😊")).toBe("5deda34cd95f304948d2bc1b4a62c11e"); 27 | expect(await md5("😊a😊")).toBe("c7f73db036e1509bc9eb0daa04881195"); 28 | const file = fs.readFileSync("./test/utf8.txt"); 29 | expect(await md5(file)).toBe("24dcee50e16fe589d9c6a20e43d76ec8"); 30 | expect(await md5(file.toString())).toBe("24dcee50e16fe589d9c6a20e43d76ec8"); 31 | }); 32 | 33 | test("Node.js buffers", async () => { 34 | expect(await md5(Buffer.from([]))).toBe("d41d8cd98f00b204e9800998ecf8427e"); 35 | expect(await md5(Buffer.from(["a".charCodeAt(0)]))).toBe( 36 | "0cc175b9c0f1b6a831c399e269772661", 37 | ); 38 | expect(await md5(Buffer.from([0]))).toBe("93b885adfe0da089cdf634904fd59f71"); 39 | expect(await md5(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe( 40 | "8dd6f66d8ae62c8c777d9b62fe7ae1af", 41 | ); 42 | }); 43 | 44 | test("typed arrays", async () => { 45 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 46 | expect(await md5(Buffer.from(arr))).toBe("f29787c936b2acf6bca41764fc0376ec"); 47 | const uint8 = new Uint8Array(arr); 48 | expect(await md5(uint8)).toBe("f29787c936b2acf6bca41764fc0376ec"); 49 | expect(await md5(new Uint16Array(uint8.buffer))).toBe( 50 | "f29787c936b2acf6bca41764fc0376ec", 51 | ); 52 | expect(await md5(new Uint32Array(uint8.buffer))).toBe( 53 | "f29787c936b2acf6bca41764fc0376ec", 54 | ); 55 | }); 56 | 57 | test("long strings", async () => { 58 | const SIZE = 5 * 1024 * 1024; 59 | const chunk = "012345678\x09"; 60 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 61 | expect(await md5(str)).toBe("8bcd28f120e3d90da1cb831bca925ca7"); 62 | }); 63 | 64 | test("long buffers", async () => { 65 | const SIZE = 5 * 1024 * 1024; 66 | const buf = Buffer.alloc(SIZE); 67 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 68 | expect(await md5(buf)).toBe("f195aef51a25af5d29ca871eb3780c06"); 69 | }); 70 | 71 | test("chunked", async () => { 72 | const hash = await createMD5(); 73 | expect(hash.digest()).toBe("d41d8cd98f00b204e9800998ecf8427e"); 74 | hash.init(); 75 | hash.update("a"); 76 | hash.update(new Uint8Array([0])); 77 | hash.update("bc"); 78 | hash.update(new Uint8Array([255, 254])); 79 | expect(hash.digest()).toBe("cb8bd330c93f032f1efcf189023eab77"); 80 | 81 | hash.init(); 82 | for (let i = 0; i < 1000; i++) { 83 | hash.update(new Uint8Array([i & 0xff])); 84 | } 85 | hash.update(Buffer.alloc(1000).fill(0xdf)); 86 | expect(hash.digest()).toBe("63a7c37748d0c5afcf75c3560c2382de"); 87 | }); 88 | 89 | test("chunked increasing length", async () => { 90 | const hash = await createMD5(); 91 | const test = async (maxLen: number) => { 92 | const chunks = getVariableLengthChunks(maxLen); 93 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 94 | const hashRef = await md5(new Uint8Array(flatchunks)); 95 | hash.init(); 96 | for (const chunk of chunks) { 97 | hash.update(new Uint8Array(chunk)); 98 | } 99 | expect(hash.digest("hex")).toBe(hashRef); 100 | }; 101 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 102 | await Promise.all(maxLens.map((length) => test(length))); 103 | }); 104 | 105 | test("interlaced shorthand", async () => { 106 | const [hashA, hashB] = await Promise.all([md5("a"), md5("abc")]); 107 | expect(hashA).toBe("0cc175b9c0f1b6a831c399e269772661"); 108 | expect(hashB).toBe("900150983cd24fb0d6963f7d28e17f72"); 109 | }); 110 | 111 | test("interlaced create", async () => { 112 | const hashA = await createMD5(); 113 | hashA.update("a"); 114 | const hashB = await createMD5(); 115 | hashB.update("abc"); 116 | expect(hashA.digest()).toBe("0cc175b9c0f1b6a831c399e269772661"); 117 | expect(hashB.digest()).toBe("900150983cd24fb0d6963f7d28e17f72"); 118 | }); 119 | 120 | test("Invalid inputs throw", async () => { 121 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 122 | const hash = await createMD5(); 123 | 124 | for (const input of invalidInputs) { 125 | await expect(md5(input as any)).rejects.toThrow(); 126 | expect(() => hash.update(input as any)).toThrow(); 127 | } 128 | }); 129 | -------------------------------------------------------------------------------- /test/node/index.js: -------------------------------------------------------------------------------- 1 | const { md5 } = require("../../dist/index.umd"); 2 | 3 | async function run() { 4 | console.log("Result: ", await md5("a")); 5 | } 6 | 7 | run(); 8 | -------------------------------------------------------------------------------- /test/ripemd160.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { createRIPEMD160, ripemd160 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await ripemd160("")).toBe("9c1185a5c5e9fc54612808977ee8f548b2258d31"); 8 | expect(await ripemd160("a")).toBe("0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); 9 | expect(await ripemd160("a\x00")).toBe( 10 | "3213d398bb951aa09625539093524fa528848bd0", 11 | ); 12 | expect(await ripemd160("abc")).toBe( 13 | "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", 14 | ); 15 | expect(await ripemd160("message digest")).toBe( 16 | "5d0689ef49d2fae572b881b123a85ffa21595f36", 17 | ); 18 | expect(await ripemd160("abcdefghijklmnopqrstuvwxyz")).toBe( 19 | "f71c27109c692c1b56bbdceb5b9d2865b3708dbc", 20 | ); 21 | expect( 22 | await ripemd160( 23 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 24 | ), 25 | ).toBe("b0e20b6e3116640286ed3a87a5713079b21f5189"); 26 | expect( 27 | await ripemd160( 28 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 29 | ), 30 | ).toBe("9b752e45573d4b39f4dbd3323cab82bf63326bfb"); 31 | }); 32 | 33 | test("unicode strings", async () => { 34 | expect(await ripemd160("😊")).toBe( 35 | "2527e5519a42891531856a0ca959402cd6b335c1", 36 | ); 37 | expect(await ripemd160("😊a😊")).toBe( 38 | "11debd04fd7fb2a81b0065994fac4e030c023a34", 39 | ); 40 | const file = fs.readFileSync("./test/utf8.txt"); 41 | expect(await ripemd160(file)).toBe( 42 | "d6d1dda80b44f7cd6424c8af9a4224c480b06013", 43 | ); 44 | expect(await ripemd160(file.toString())).toBe( 45 | "d6d1dda80b44f7cd6424c8af9a4224c480b06013", 46 | ); 47 | }); 48 | 49 | test("Node.js buffers", async () => { 50 | expect(await ripemd160(Buffer.from([]))).toBe( 51 | "9c1185a5c5e9fc54612808977ee8f548b2258d31", 52 | ); 53 | expect(await ripemd160(Buffer.from(["a".charCodeAt(0)]))).toBe( 54 | "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", 55 | ); 56 | expect(await ripemd160(Buffer.from([0]))).toBe( 57 | "c81b94933420221a7ac004a90242d8b1d3e5070d", 58 | ); 59 | expect(await ripemd160(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe( 60 | "502c8fdabcdf8e11d4a32a0fb67d5b71c9a3acf1", 61 | ); 62 | }); 63 | 64 | test("typed arrays", async () => { 65 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 66 | expect(await ripemd160(Buffer.from(arr))).toBe( 67 | "4f058de8c4a6315224ebf855e6ebc19bcdab12ba", 68 | ); 69 | const uint8 = new Uint8Array(arr); 70 | expect(await ripemd160(uint8)).toBe( 71 | "4f058de8c4a6315224ebf855e6ebc19bcdab12ba", 72 | ); 73 | expect(await ripemd160(new Uint16Array(uint8.buffer))).toBe( 74 | "4f058de8c4a6315224ebf855e6ebc19bcdab12ba", 75 | ); 76 | expect(await ripemd160(new Uint32Array(uint8.buffer))).toBe( 77 | "4f058de8c4a6315224ebf855e6ebc19bcdab12ba", 78 | ); 79 | }); 80 | 81 | test("long strings", async () => { 82 | const SIZE = 5 * 1024 * 1024; 83 | const chunk = "012345678\x09"; 84 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 85 | expect(await ripemd160(str)).toBe("a9697dca54c98d6c33eb9bbd310c0aff9fee74a1"); 86 | }); 87 | 88 | test("long buffers", async () => { 89 | const SIZE = 5 * 1024 * 1024; 90 | const buf = Buffer.alloc(SIZE); 91 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 92 | expect(await ripemd160(buf)).toBe("5a78d021a2bde7933ae0e0f236105ee72e38199c"); 93 | }); 94 | 95 | test("chunked", async () => { 96 | const hash = await createRIPEMD160(); 97 | expect(hash.digest()).toBe("9c1185a5c5e9fc54612808977ee8f548b2258d31"); 98 | hash.init(); 99 | hash.update("a"); 100 | hash.update(new Uint8Array([0])); 101 | hash.update("bc"); 102 | hash.update(new Uint8Array([255, 254])); 103 | expect(hash.digest()).toBe("f39cee3567f8e52d2ecb0e79b19f23c64d7b57f2"); 104 | 105 | hash.init(); 106 | for (let i = 0; i < 1000; i++) { 107 | hash.update(new Uint8Array([i & 0xff])); 108 | } 109 | hash.update(Buffer.alloc(1000).fill(0xdf)); 110 | expect(hash.digest()).toBe("0c3f2d7884f90ee147afabc89fed293a3928ff7d"); 111 | }); 112 | 113 | test("chunked increasing length", async () => { 114 | const hash = await createRIPEMD160(); 115 | const test = async (maxLen: number) => { 116 | const chunks = getVariableLengthChunks(maxLen); 117 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 118 | const hashRef = await ripemd160(new Uint8Array(flatchunks)); 119 | hash.init(); 120 | for (const chunk of chunks) { 121 | hash.update(new Uint8Array(chunk)); 122 | } 123 | expect(hash.digest("hex")).toBe(hashRef); 124 | }; 125 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 126 | await Promise.all(maxLens.map((length) => test(length))); 127 | }); 128 | 129 | test("interlaced shorthand", async () => { 130 | const [hashA, hashB] = await Promise.all([ripemd160("a"), ripemd160("abc")]); 131 | expect(hashA).toBe("0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); 132 | expect(hashB).toBe("8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); 133 | }); 134 | 135 | test("interlaced create", async () => { 136 | const hashA = await createRIPEMD160(); 137 | hashA.update("a"); 138 | const hashB = await createRIPEMD160(); 139 | hashB.update("abc"); 140 | expect(hashA.digest()).toBe("0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"); 141 | expect(hashB.digest()).toBe("8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"); 142 | }); 143 | 144 | test("Invalid inputs throw", async () => { 145 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 146 | const hash = await createRIPEMD160(); 147 | 148 | for (const input of invalidInputs) { 149 | await expect(ripemd160(input as any)).rejects.toThrow(); 150 | expect(() => hash.update(input as any)).toThrow(); 151 | } 152 | }); 153 | -------------------------------------------------------------------------------- /test/scrypt.test.ts: -------------------------------------------------------------------------------- 1 | import { scrypt } from "../lib"; 2 | /* global test, expect */ 3 | 4 | const hash = async ( 5 | password, 6 | salt, 7 | costFactor, 8 | blockSize, 9 | parallelism, 10 | hashLength, 11 | outputType, 12 | ) => 13 | scrypt({ 14 | password, 15 | salt, 16 | costFactor, 17 | blockSize, 18 | parallelism, 19 | hashLength, 20 | outputType, 21 | }); 22 | 23 | test("scrypt", async () => { 24 | expect(await hash("", "", 2, 1, 1, 16, "hex")).toBe( 25 | "fa76e020d54d9e8aa24023c6baecdd46", 26 | ); 27 | 28 | expect(await hash("", "", 2, 1, 1, 16, undefined)).toBe( 29 | "fa76e020d54d9e8aa24023c6baecdd46", 30 | ); 31 | 32 | expect(await hash("", "", 2, 1, 1, 16, "binary")).toMatchObject( 33 | new Uint8Array([ 34 | 0xfa, 0x76, 0xe0, 0x20, 0xd5, 0x4d, 0x9e, 0x8a, 0xa2, 0x40, 0x23, 0xc6, 35 | 0xba, 0xec, 0xdd, 0x46, 36 | ]), 37 | ); 38 | 39 | expect(await hash("a", "abcdefgh", 128, 1, 1, 16, "hex")).toBe( 40 | "7a386084f8c60a04238de836c2d5dff1", 41 | ); 42 | 43 | expect(await hash("text demo", "123456789", 8, 17, 15, 32, "hex")).toBe( 44 | "bcae071d6bb1389b462fe3135a0b6bbaf980028d0d035f688ce36a8b0b53c391", 45 | ); 46 | 47 | expect(await hash("text demo", "123456789", 2, 1, 32, 32, "hex")).toBe( 48 | "3ab33fa8bdee86ecf66ce568ae19a57ca1339a2a41d92ca244f183b759c57f30", 49 | ); 50 | 51 | expect(await hash("abc", "1234567812345678", 4, 3, 177, 4, "hex")).toBe( 52 | "f31520d2", 53 | ); 54 | 55 | expect(await hash("abc", "12345678", 64, 27, 67, 17, "hex")).toBe( 56 | "6e018c6f21b5647b3ce0c1c65ea14961f4", 57 | ); 58 | }); 59 | 60 | test("scrypt official test vectors", async () => { 61 | expect(await hash("", "", 16, 1, 1, 64, "hex")).toBe( 62 | "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906", 63 | ); 64 | 65 | expect(await hash("password", "NaCl", 1024, 8, 16, 64, "hex")).toBe( 66 | "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640", 67 | ); 68 | 69 | expect( 70 | await hash("pleaseletmein", "SodiumChloride", 16384, 8, 1, 64, "hex"), 71 | ).toBe( 72 | "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887", 73 | ); 74 | 75 | expect( 76 | await hash("pleaseletmein", "SodiumChloride", 1048576, 8, 1, 64, "hex"), 77 | ).toBe( 78 | "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4", 79 | ); 80 | }); 81 | 82 | test("scrypt binary", async () => { 83 | expect(await hash("\0", "\u0001", 16, 1, 1, 16, "hex")).toBe( 84 | "20e4dedae031ba96c1b1fe3e21b236fc", 85 | ); 86 | 87 | expect( 88 | await hash(Buffer.from("\0"), Buffer.from("\u0001"), 16, 1, 1, 16, "hex"), 89 | ).toBe("20e4dedae031ba96c1b1fe3e21b236fc"); 90 | 91 | expect(await hash("\0\0\0\0\0", "\0\0\0\u0001", 16, 1, 1, 16, "hex")).toBe( 92 | "6fac8ee2ffdce78632fee3a5935d4f53", 93 | ); 94 | 95 | expect( 96 | await hash( 97 | Buffer.from("\0\0\0\0\0"), 98 | Buffer.from("\0\0\0\u0001"), 99 | 16, 100 | 1, 101 | 1, 102 | 16, 103 | "hex", 104 | ), 105 | ).toBe("6fac8ee2ffdce78632fee3a5935d4f53"); 106 | }); 107 | 108 | test("invalid parameters", async () => { 109 | await expect(() => hash("", "", "", 1, 1, 16, "hex")).rejects.toThrow(); 110 | await expect(() => hash("", "", 1, 1, 1, 16, "hex")).rejects.toThrow(); 111 | await expect(() => hash("", "", 3, 1, 1, 16, "hex")).rejects.toThrow(); 112 | await expect(() => hash("", "", 9, 1, 1, 16, "hex")).rejects.toThrow(); 113 | await expect(() => hash("", "", 63, 1, 1, 16, "hex")).rejects.toThrow(); 114 | await expect(() => hash("", "", 65, 1, 1, 16, "hex")).rejects.toThrow(); 115 | await expect(() => hash("", "", 2, "", 1, 16, "hex")).rejects.toThrow(); 116 | await expect(() => hash("", "", 2, 0, 1, 16, "hex")).rejects.toThrow(); 117 | await expect(() => hash("", "", 2, 1, "", 16, "hex")).rejects.toThrow(); 118 | await expect(() => hash("", "", 2, 1, 0, 16, "hex")).rejects.toThrow(); 119 | await expect(() => hash("", "", 2, 1, 1, "", "hex")).rejects.toThrow(); 120 | await expect(() => hash("", "", 2, 1, 1, 0, "hex")).rejects.toThrow(); 121 | await expect(() => hash("", "", 2, 1, 1, 16, "binaryx")).rejects.toThrow(); 122 | await expect(() => hash("", "", 2, 1, 1, 16, "")).rejects.toThrow(); 123 | await expect(() => hash("", 1, 2, 1, 1, 16, "hex")).rejects.toThrow(); 124 | await expect(() => hash(1, "", 2, 1, 1, 16, "hex")).rejects.toThrow(); 125 | 126 | await expect(() => (scrypt as any)()).rejects.toThrow(); 127 | await expect(() => (scrypt as any)([])).rejects.toThrow(); 128 | await expect(() => (scrypt as any)({})).rejects.toThrow(); 129 | await expect(() => (scrypt as any)(1)).rejects.toThrow(); 130 | }); 131 | -------------------------------------------------------------------------------- /test/sha1.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { createSHA1, sha1 } from "../lib"; 3 | import { getVariableLengthChunks } from "./util"; 4 | /* global test, expect */ 5 | 6 | test("simple strings", async () => { 7 | expect(await sha1("")).toBe("da39a3ee5e6b4b0d3255bfef95601890afd80709"); 8 | expect(await sha1("a")).toBe("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); 9 | expect(await sha1("a\x00")).toBe("0a04b971b03da607ce6c455184037b660ca89f78"); 10 | expect(await sha1("abc")).toBe("a9993e364706816aba3e25717850c26c9cd0d89d"); 11 | expect(await sha1("message digest")).toBe( 12 | "c12252ceda8be8994d5fa0290a47231c1d16aae3", 13 | ); 14 | expect(await sha1("abcdefghijklmnopqrstuvwxyz")).toBe( 15 | "32d10c7b8cf96570ca04ce37f2a19d84240d3a89", 16 | ); 17 | expect( 18 | await sha1( 19 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 20 | ), 21 | ).toBe("761c457bf73b14d27e9e9265c46f4b4dda11f940"); 22 | expect( 23 | await sha1( 24 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 25 | ), 26 | ).toBe("50abf5706a150990a08b2c5ea40fa0e585554732"); 27 | }); 28 | 29 | test("unicode strings", async () => { 30 | expect(await sha1("😊")).toBe("8a7a73f2b2e726f0f8b86120e69d147b7f598b1c"); 31 | expect(await sha1("😊a😊")).toBe("f96d1f1782d2eed29b67ea3e9f5c44168d310983"); 32 | const file = fs.readFileSync("./test/utf8.txt"); 33 | expect(await sha1(file)).toBe("5e07b0786796c0c0fcece4150042bce68e91856d"); 34 | expect(await sha1(file.toString())).toBe( 35 | "5e07b0786796c0c0fcece4150042bce68e91856d", 36 | ); 37 | }); 38 | 39 | test("Node.js buffers", async () => { 40 | expect(await sha1(Buffer.from([]))).toBe( 41 | "da39a3ee5e6b4b0d3255bfef95601890afd80709", 42 | ); 43 | expect(await sha1(Buffer.from(["a".charCodeAt(0)]))).toBe( 44 | "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", 45 | ); 46 | expect(await sha1(Buffer.from([0]))).toBe( 47 | "5ba93c9db0cff93f52b521d7420e43f6eda2784f", 48 | ); 49 | expect(await sha1(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe( 50 | "26693987b06935cd70b41982061b761dd64d08a0", 51 | ); 52 | }); 53 | 54 | test("typed arrays", async () => { 55 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 56 | expect(await sha1(Buffer.from(arr))).toBe( 57 | "ef473cc975f136d3eefe22d51cbfcadb31369beb", 58 | ); 59 | const uint8 = new Uint8Array(arr); 60 | expect(await sha1(uint8)).toBe("ef473cc975f136d3eefe22d51cbfcadb31369beb"); 61 | expect(await sha1(new Uint16Array(uint8.buffer))).toBe( 62 | "ef473cc975f136d3eefe22d51cbfcadb31369beb", 63 | ); 64 | expect(await sha1(new Uint32Array(uint8.buffer))).toBe( 65 | "ef473cc975f136d3eefe22d51cbfcadb31369beb", 66 | ); 67 | }); 68 | 69 | test("long strings", async () => { 70 | const SIZE = 5 * 1024 * 1024; 71 | const chunk = "012345678\x09"; 72 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 73 | expect(await sha1(str)).toBe("1a86b6d5cebe7d1ff044842ceddf6c5e69a01f81"); 74 | }); 75 | 76 | test("long buffers", async () => { 77 | const SIZE = 5 * 1024 * 1024; 78 | const buf = Buffer.alloc(SIZE); 79 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 80 | expect(await sha1(buf)).toBe("6677c7445dd30888d61009461afe79df58d7721b"); 81 | }); 82 | 83 | test("chunked", async () => { 84 | const hash = await createSHA1(); 85 | expect(hash.digest()).toBe("da39a3ee5e6b4b0d3255bfef95601890afd80709"); 86 | hash.init(); 87 | hash.update("a"); 88 | hash.update(new Uint8Array([0])); 89 | hash.update("bc"); 90 | hash.update(new Uint8Array([255, 254])); 91 | expect(hash.digest()).toBe("610988cf8e3bbec72ee7c5293d5226ee89bea856"); 92 | 93 | hash.init(); 94 | for (let i = 0; i < 1000; i++) { 95 | hash.update(new Uint8Array([i & 0xff])); 96 | } 97 | hash.update(Buffer.alloc(1000).fill(0xdf)); 98 | expect(hash.digest()).toBe("ca117730e3b614b8a30f4ec55fc812ba498c4e28"); 99 | }); 100 | 101 | test("chunked increasing length", async () => { 102 | const hash = await createSHA1(); 103 | const test = async (maxLen: number) => { 104 | const chunks = getVariableLengthChunks(maxLen); 105 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 106 | const hashRef = await sha1(new Uint8Array(flatchunks)); 107 | hash.init(); 108 | for (const chunk of chunks) { 109 | hash.update(new Uint8Array(chunk)); 110 | } 111 | expect(hash.digest("hex")).toBe(hashRef); 112 | }; 113 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 114 | await Promise.all(maxLens.map((length) => test(length))); 115 | }); 116 | 117 | test("interlaced shorthand", async () => { 118 | const [hashA, hashB] = await Promise.all([sha1("a"), sha1("abc")]); 119 | expect(hashA).toBe("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); 120 | expect(hashB).toBe("a9993e364706816aba3e25717850c26c9cd0d89d"); 121 | }); 122 | 123 | test("interlaced create", async () => { 124 | const hashA = await createSHA1(); 125 | hashA.update("a"); 126 | const hashB = await createSHA1(); 127 | hashB.update("abc"); 128 | expect(hashA.digest()).toBe("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8"); 129 | expect(hashB.digest()).toBe("a9993e364706816aba3e25717850c26c9cd0d89d"); 130 | }); 131 | 132 | test("Invalid inputs throw", async () => { 133 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 134 | const hash = await createSHA1(); 135 | 136 | for (const input of invalidInputs) { 137 | await expect(sha1(input as any)).rejects.toThrow(); 138 | expect(() => hash.update(input as any)).toThrow(); 139 | } 140 | }); 141 | -------------------------------------------------------------------------------- /test/sha3.test.ts: -------------------------------------------------------------------------------- 1 | import { createSHA3, sha3 } from "../lib"; 2 | /* global test, expect */ 3 | 4 | test("invalid parameters", async () => { 5 | await expect(sha3("", -1 as any)).rejects.toThrow(); 6 | await expect(sha3("", "a" as any)).rejects.toThrow(); 7 | await expect(sha3("", 223 as any)).rejects.toThrow(); 8 | await expect(sha3("", 0 as any)).rejects.toThrow(); 9 | await expect(sha3("", null as any)).rejects.toThrow(); 10 | 11 | await expect(createSHA3(-1 as any)).rejects.toThrow(); 12 | await expect(createSHA3("a" as any)).rejects.toThrow(); 13 | await expect(createSHA3(223 as any)).rejects.toThrow(); 14 | await expect(createSHA3(0 as any)).rejects.toThrow(); 15 | await expect(createSHA3(null as any)).rejects.toThrow(); 16 | }); 17 | 18 | test("default value for create constructor", async () => { 19 | const hash = await sha3("a", 512); 20 | const hasher = await createSHA3(); 21 | hasher.init(); 22 | hasher.update("a"); 23 | expect(hasher.digest()).toBe(hash); 24 | }); 25 | 26 | test("it invalidates the cache when changing parameters", async () => { 27 | expect(await sha3("a", 224)).toBe( 28 | "9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", 29 | ); 30 | expect(await sha3("a", 256)).toBe( 31 | "80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b", 32 | ); 33 | expect(await sha3("a", 384)).toBe( 34 | "1815f774f320491b48569efec794d249eeb59aae46d22bf77dafe25c5edc28d7ea44f93ee1234aa88f61c91912a4ccd9", 35 | ); 36 | expect(await sha3("a")).toBe( 37 | "697f2d856172cb8309d6b8b97dac4de344b549d4dee61edfb4962d8698b7fa803f4f93ff24393586e28b5b957ac3d1d369420ce53332712f997bd336d09ab02a", 38 | ); 39 | 40 | let hash = await createSHA3(224); 41 | hash.update("a"); 42 | expect(hash.digest()).toBe( 43 | "9e86ff69557ca95f405f081269685b38e3a819b309ee942f482b6a8b", 44 | ); 45 | 46 | hash = await createSHA3(256); 47 | hash.update("a"); 48 | expect(hash.digest()).toBe( 49 | "80084bf2fba02475726feb2cab2d8215eab14bc6bdd8bfb2c8151257032ecd8b", 50 | ); 51 | 52 | hash = await createSHA3(384); 53 | hash.update("a"); 54 | expect(hash.digest()).toBe( 55 | "1815f774f320491b48569efec794d249eeb59aae46d22bf77dafe25c5edc28d7ea44f93ee1234aa88f61c91912a4ccd9", 56 | ); 57 | 58 | hash = await createSHA3(); 59 | hash.update("a"); 60 | expect(hash.digest()).toBe( 61 | "697f2d856172cb8309d6b8b97dac4de344b549d4dee61edfb4962d8698b7fa803f4f93ff24393586e28b5b957ac3d1d369420ce53332712f997bd336d09ab02a", 62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /test/throw.test.ts: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import { 3 | adler32, 4 | crc32, 5 | keccak, 6 | md4, 7 | md5, 8 | sha1, 9 | sha3, 10 | sha224, 11 | sha256, 12 | sha384, 13 | sha512, 14 | xxhash32, 15 | xxhash64, 16 | } from "../lib"; 17 | 18 | test("Invalid inputs throw after", async () => { 19 | await expect(adler32(0 as any)).rejects.toThrow(); 20 | await expect(md4(0 as any)).rejects.toThrow(); 21 | await expect(md5(0 as any)).rejects.toThrow(); 22 | await expect(crc32(0 as any)).rejects.toThrow(); 23 | await expect(sha1(0 as any)).rejects.toThrow(); 24 | await expect(sha224(0 as any)).rejects.toThrow(); 25 | await expect(sha256(0 as any)).rejects.toThrow(); 26 | await expect(sha384(0 as any)).rejects.toThrow(); 27 | await expect(sha512(0 as any)).rejects.toThrow(); 28 | await expect(sha3(0 as any)).rejects.toThrow(); 29 | await expect(keccak(0 as any)).rejects.toThrow(); 30 | await expect(xxhash32(0 as any)).rejects.toThrow(); 31 | await expect(xxhash64(0 as any)).rejects.toThrow(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/util.ts: -------------------------------------------------------------------------------- 1 | export const getVariableLengthChunks = (maxLen: number): number[][] => { 2 | const chunks = []; 3 | let x = 0; 4 | for (let len = 0; len <= maxLen; len++) { 5 | const chunk: number[] = []; 6 | for (let i = 0; i < len; i++) { 7 | chunk.push(x); 8 | x = (x + 1) % 256; 9 | } 10 | chunks.push(chunk); 11 | } 12 | 13 | for (let len = maxLen; len >= 0; len--) { 14 | const chunk: number[] = []; 15 | for (let i = 0; i < len; i++) { 16 | chunk.push(x); 17 | x = (x + 1) % 256; 18 | } 19 | chunks.push(chunk); 20 | } 21 | 22 | return chunks; 23 | }; 24 | -------------------------------------------------------------------------------- /test/xxhash32.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import { createXXHash32, xxhash32 as origXXHash32 } from "../lib"; 3 | import type { IDataType } from "../lib/util"; 4 | import { getVariableLengthChunks } from "./util"; 5 | /* global test, expect */ 6 | 7 | const xxhash32 = async (data: IDataType) => origXXHash32(data, 0x6789abcd); 8 | 9 | test("simple strings with 0 seed", async () => { 10 | expect(await origXXHash32("")).toBe("02cc5d05"); 11 | expect(await origXXHash32("a")).toBe("550d7456"); 12 | expect(await origXXHash32("a\x00")).toBe("19832f52"); 13 | expect(await origXXHash32("abc")).toBe("32d153ff"); 14 | expect(await origXXHash32("1234567890")).toBe("e8412d73"); 15 | }); 16 | 17 | test("simple strings", async () => { 18 | expect(await xxhash32("")).toBe("51c917a3"); 19 | expect(await xxhash32("a")).toBe("88488ff7"); 20 | expect(await xxhash32("1234567890")).toBe("e488df66"); 21 | expect(await xxhash32("a\x00")).toBe("0e7c1075"); 22 | expect(await xxhash32("abc")).toBe("344def81"); 23 | expect(await xxhash32("message digest")).toBe("072766cd"); 24 | expect(await xxhash32("abcdefghijklmnopqrstuvwxyz")).toBe("d4ea111e"); 25 | expect( 26 | await xxhash32( 27 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 28 | ), 29 | ).toBe("7768b3a0"); 30 | expect( 31 | await xxhash32( 32 | "12345678901234567890123456789012345678901234567890123456789012345678901234567890", 33 | ), 34 | ).toBe("6273ef9a"); 35 | }); 36 | 37 | test("unicode strings", async () => { 38 | expect(await xxhash32("😊")).toBe("6dcaa4fe"); 39 | expect(await xxhash32("😊a😊")).toBe("421b3b97"); 40 | const file = fs.readFileSync("./test/utf8.txt"); 41 | expect(await xxhash32(file)).toBe("1807f963"); 42 | expect(await xxhash32(file.toString())).toBe("1807f963"); 43 | }); 44 | 45 | test("Node.js buffers", async () => { 46 | expect(await xxhash32(Buffer.from([]))).toBe("51c917a3"); 47 | expect(await xxhash32(Buffer.from(["a".charCodeAt(0)]))).toBe("88488ff7"); 48 | expect(await xxhash32(Buffer.from([0]))).toBe("666de50d"); 49 | expect(await xxhash32(Buffer.from([0, 1, 0, 0, 2, 0]))).toBe("5fd527bb"); 50 | }); 51 | 52 | test("typed arrays", async () => { 53 | const arr = [0, 1, 2, 3, 4, 5, 255, 254]; 54 | expect(await xxhash32(Buffer.from(arr))).toBe("7eebdfd4"); 55 | const uint8 = new Uint8Array(arr); 56 | expect(await xxhash32(uint8)).toBe("7eebdfd4"); 57 | expect(await xxhash32(new Uint16Array(uint8.buffer))).toBe("7eebdfd4"); 58 | expect(await xxhash32(new Uint32Array(uint8.buffer))).toBe("7eebdfd4"); 59 | }); 60 | 61 | test("long strings", async () => { 62 | const SIZE = 5 * 1024 * 1024; 63 | const chunk = "012345678\x09"; 64 | const str = new Array(Math.floor(SIZE / chunk.length)).fill(chunk).join(""); 65 | expect(await xxhash32(str)).toBe("ee6e5c97"); 66 | }); 67 | 68 | test("long buffers", async () => { 69 | const SIZE = 5 * 1024 * 1024; 70 | const buf = Buffer.alloc(SIZE); 71 | buf.fill("\x00\x01\x02\x03\x04\x05\x06\x07\x08\xFF"); 72 | expect(await xxhash32(buf)).toBe("d8416dcc"); 73 | }); 74 | 75 | test("chunked", async () => { 76 | const hash = await createXXHash32(); 77 | expect(hash.digest()).toBe("02cc5d05"); 78 | hash.init(); 79 | hash.update("a"); 80 | hash.update(new Uint8Array([0])); 81 | hash.update("bc"); 82 | hash.update(new Uint8Array([255, 254])); 83 | expect(hash.digest()).toBe("a0155f6d"); 84 | 85 | hash.init(); 86 | for (let i = 0; i < 1000; i++) { 87 | hash.update(new Uint8Array([i & 0xff])); 88 | } 89 | hash.update(Buffer.alloc(1000).fill(0xdf)); 90 | expect(hash.digest()).toBe("7bba26d1"); 91 | }); 92 | 93 | test("chunked increasing length", async () => { 94 | const hash = await createXXHash32(0x6789abcd); 95 | const test = async (maxLen: number) => { 96 | const chunks = getVariableLengthChunks(maxLen); 97 | const flatchunks = chunks.reduce((acc, val) => acc.concat(val), []); 98 | const hashRef = await xxhash32(new Uint8Array(flatchunks)); 99 | hash.init(); 100 | for (const chunk of chunks) { 101 | hash.update(new Uint8Array(chunk)); 102 | } 103 | expect(hash.digest("hex")).toBe(hashRef); 104 | }; 105 | const maxLens = [1, 3, 27, 50, 57, 64, 91, 127, 256, 300]; 106 | await Promise.all(maxLens.map((length) => test(length))); 107 | }); 108 | 109 | test("interlaced shorthand", async () => { 110 | const [hashA, hashB] = await Promise.all([ 111 | origXXHash32("a"), 112 | origXXHash32("abc"), 113 | ]); 114 | expect(hashA).toBe("550d7456"); 115 | expect(hashB).toBe("32d153ff"); 116 | }); 117 | 118 | test("interlaced create", async () => { 119 | const hashA = await createXXHash32(); 120 | hashA.update("a"); 121 | const hashB = await createXXHash32(); 122 | hashB.update("abc"); 123 | expect(hashA.digest()).toBe("550d7456"); 124 | expect(hashB.digest()).toBe("32d153ff"); 125 | }); 126 | 127 | test("invalid parameters", async () => { 128 | await expect(origXXHash32("", -1)).rejects.toThrow(); 129 | await expect(origXXHash32("", "a" as any)).rejects.toThrow(); 130 | await expect(origXXHash32("", 0xffffffff + 1)).rejects.toThrow(); 131 | await expect(origXXHash32("", 0.1)).rejects.toThrow(); 132 | await expect(origXXHash32("", Number.NaN)).rejects.toThrow(); 133 | 134 | await expect(createXXHash32(-1 as any)).rejects.toThrow(); 135 | await expect(createXXHash32("a" as any)).rejects.toThrow(); 136 | await expect(createXXHash32((0xffffffff + 1) as any)).rejects.toThrow(); 137 | await expect(createXXHash32(0.1 as any)).rejects.toThrow(); 138 | await expect(createXXHash32(Number.NaN as any)).rejects.toThrow(); 139 | }); 140 | 141 | test("Invalid inputs throw", async () => { 142 | const invalidInputs = [0, 1, Number(1), {}, [], null, undefined]; 143 | const hash = await createXXHash32(); 144 | 145 | for (const input of invalidInputs) { 146 | await expect(origXXHash32(input as any)).rejects.toThrow(); 147 | expect(() => hash.update(input as any)).toThrow(); 148 | } 149 | }); 150 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ES6", 5 | "lib": ["es2017", "es7", "es6", "dom"], 6 | "baseUrl": ".", 7 | "strict": false, 8 | "esModuleInterop": true, 9 | "downlevelIteration": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true 12 | }, 13 | "include": [ 14 | "lib", 15 | ] 16 | } --------------------------------------------------------------------------------